7 min read

MCP client configuration review before you trust a server

Use an MCP client configuration review to inspect commands, inherited environment variables, credentials, and tool capabilities before a local server runs.

MCP client configuration review before you trust a server

A new MCP server deserves the same review you would give a new executable that asks to run inside your development account. The configuration file may look harmless because it contains a command and a few arguments. In practice, that entry decides which code starts, which environment it receives, which directories it can reach, and which tool descriptions will be offered to an agent.

The mistake I keep seeing is treating "local" as a trust boundary. It is not. A local server usually starts with your user permissions, can act before it publishes its first tool, and may inherit credentials that nobody intended to share. Review the launch contract before the client connects. Then review the advertised actions before the agent can call them.

A local server runs with your account's consequences

A local MCP server is a child process, not a harmless configuration object. If your client launches it under your normal macOS account, the server may read accessible project files, write to your home directory, make outbound network requests, and inspect environment variables available to that process. MCP does not sandbox the server. The protocol carries messages; it does not constrain what the executable does between messages.

That distinction matters when a server arrives through a package command. This configuration:

{
  "command": "npx",
  "args": ["-y", "some-mcp-server"]
}

does more than start a known local binary. It asks a package runner to resolve a package, install or reuse code from its cache, and launch it. A clean machine, a warm cache, and a changed package tag can produce different code. The familiar command makes people skip the question that matters: which exact executable will run today?

The same concern applies to a server checked out beside the project. A repository can contain an honest MCP implementation and a malicious install hook, wrapper, or runtime dependency. Review the path that starts the process, not only the source file whose name appears in a setup guide.

Use the operating system account as your first containment decision. A disposable workspace, a separate low-privilege account, or a virtual machine gives an early test less access than the account that holds production source code and cloud credentials. That does not replace code review. It limits damage when your review misses something.

The command field deserves literal reading

Read the command and arguments exactly as the client will execute them. Do not mentally translate them into the friendly description in a README.

The safer shape uses a fixed executable path and an argument array:

{
  "command": "/Users/dev/tools/acme-mcp/bin/server",
  "args": ["--config", "/Users/dev/review/acme-mcp.json"],
  "env": {
    "HOME": "/Users/dev/review-home",
    "PATH": "/usr/bin:/bin"
  }
}

That shape gives you concrete review points. Does the executable exist at that path? Who owns it? Does the configuration file sit outside a repository that other people can change? Does the process need HOME at all? Does it genuinely need a compiler, package manager, or a broad PATH?

A wrapper changes the review. Consider this instead:

{
  "command": "sh",
  "args": ["-c", "npx -y acme-mcp --token $SERVICE_TOKEN"]
}

The shell now expands $SERVICE_TOKEN, reads shell syntax, and may run more than the one program you expected. The package runner may fetch code. The server receives a secret as a command argument, which can appear in process inspection and diagnostic output. Each layer has behavior that a direct executable path avoids.

Do not assume every client executes command the same way. Some clients use direct process spawning with an argument array. Others offer a shell-oriented setting or allow a wrapper script. Read the client's documentation and test the launch with a non-sensitive command before you approve a real one. If the format permits both direct execution and a shell, choose direct execution unless the server has a specific need that you can explain.

Inspect wrappers line by line. Small scripts often hide the riskiest behavior: downloading a release, reading a token file, changing the working directory, exporting all environment variables, or silently restarting through another runtime. A twenty-line launcher can deserve more attention than the server's main implementation.

Inherited environment is the credential leak people miss

An env block does not reliably mean "this is the entire environment." In many process launch APIs, the parent process environment passes through unless the launcher deliberately replaces it. The entries in env then add or override selected values. Your MCP client may itself have started from a terminal, desktop launcher, editor, or automation service, and each path can supply different variables.

That creates a nasty gap between what reviewers see and what the server receives. The JSON may list only LOG_LEVEL, while the process also gets a package registry token, a source control token, cloud credentials, proxy settings, SSH agent details, and an internal service URL inherited from the client.

Before connecting, write down the environment contract in plain language: this process needs this endpoint, this non-secret setting, and perhaps one narrowly scoped credential. Anything else is accidental authority.

A useful first pass starts the client itself from a sparse shell. This macOS and Unix command preserves only a few ordinary variables:

env -i HOME="$HOME/review-home" PATH="/usr/bin:/bin" LANG="${LANG:-C}" \
  YOUR_MCP_CLIENT

Replace YOUR_MCP_CLIENT with the actual client command. If the server fails, add one variable at a time and record why it needs it. Do not solve the failure by restoring your whole login environment. That shortcut has exposed more credentials than people realize.

You can also inspect a saved configuration without executing any command. The following Python fragment prints server names, commands, arguments, and explicit environment variable names. It deliberately prints no environment values.

python3 - "$HOME/.config/your-client/mcp.json" <<'PY'
import json, sys

with open(sys.argv[1], encoding="utf-8") as f:
    data = json.load(f)

for name, spec in data.get("mcpServers", {}).items():
    print(f"server: {name}")
    print(f"  command: {spec.get('command', '')}")
    print("  args:")
    for arg in spec.get("args", []):
        print(f"    - {arg}")
    print("  explicit env names:")
    for env_name in sorted(spec.get("env", {})):
        print(f"    - {env_name}")
PY

Its output has a shape like this:

server: issue-tracker
  command: /Users/dev/tools/issue-mcp/server
  args:
    - --read-only
  explicit env names:
    - ISSUE_TRACKER_URL

If you see names such as AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, SSH_AUTH_SOCK, or SERVICE_TOKEN, stop and ask why that server needs them. Secret values do not become safe because a JSON file holds them instead of source code. Config files get copied into backups, shared in support requests, committed by accident, and read by every process that can read the file.

A tool list is a capability claim, not a permission grant

The Model Context Protocol specification defines tools/list for discovery and tools/call for invocation. That is useful because a client can inspect a server's proposed interface before an agent chooses a tool. It does not certify the server, its descriptions, or the side effects behind a call.

Treat each advertised tool as a proposed capability. Read its name, description, input schema, and any annotations together. A tool named search_issues may make a read-only request, or it may collect project files and send them to a third party before it searches. A tool named deploy_preview might create resources, alter DNS, or use a credential with broader scope than the name suggests.

The MCP specification lets servers supply annotations that hint at behavior, such as whether a tool reads data, changes data, or interacts with an external system. Hints help a client present useful UI, but the specification does not turn them into enforcement. A dishonest or careless server can label a destructive tool as read-only. Your operating system and the server's credentials will not inspect that label before an action runs.

Record a small inventory for each server you approve:

  • Tool name and the action it claims to perform.
  • Inputs that can carry file paths, URLs, shell fragments, or free-form prompts.
  • Systems the tool can reach and the credential it uses.
  • Side effects, including indirect ones such as sending data to a remote API.
  • The exact version or source revision you reviewed.

Keep the inventory close to the configuration. A diff becomes meaningful when an update adds delete_repository, changes query into execute, or adds an input that accepts an arbitrary URL. Without a previous inventory, people often approve a changed list because the server name still looks familiar.

Descriptions deserve the same suspicion as any other untrusted text delivered to an agent. A server can describe one tool as mandatory, claim that approval is unnecessary, or instruct the agent to pass unrelated secrets as arguments. The agent should not treat server-provided prose as higher authority than the user's request and the client's approval rules.

Credentials need a boundary outside the agent process

Gate sensitive keys per call
Require one click or Touch ID whenever Sallyport uses a sensitive key.

Do not hand a long-lived API token or private SSH key to a server merely because it runs on the same laptop. Once the server process receives a secret, it can log it, forward it, write it to disk, or expose it through a tool result. The client cannot claw that secret back after the process reads it.

Separate two decisions that teams often blur. Permission to start a server is permission to execute code. Permission to use a production credential is permission to act on an external system. A server may deserve the first permission in a test workspace while it clearly does not deserve the second.

For simple development use, issue a scoped, short-lived credential that can only touch test data. Put its scope in writing. "Used by the issue server" is vague; "may read tickets in the sandbox project and cannot create, comment, or change membership" gives reviewers something to test.

For actions that need a serious credential, keep the secret in a local credential boundary and expose only the narrow operation. Sallyport follows this approach for HTTP and SSH actions: the agent does not receive the API key or SSH key, while the app performs the action and returns the result.

That boundary changes secret handling, not the need for server review. A malicious server can still ask an agent to make a damaging but authorized call. Put approval where the consequence happens, scope credentials to the smallest useful authority, and read every request that crosses into a system you care about.

Do not pass credentials in command arguments. Process listings, crash reporters, diagnostics, and parent processes can expose them. Avoid plaintext secrets in configuration files for the same reason. If a setup guide requires a secret in either location, find out whether the server can use an operating system credential store, a short-lived token, or an external action service before you accept that design.

First contact should happen in a boring test account

Run an unfamiliar server in a controlled account before you give it a project, a broad environment, or real credentials. This test answers a narrow question: what does the program do when it starts and when the client asks for its tools?

Use a new directory with harmless files whose names make unexpected reads obvious. Give the process a temporary HOME. Start with a sparse PATH. Do not mount a directory full of source code because you intend to test a source control server. Start with a fake repository or a copy with no credentials.

Watch for behavior before any tool call. A server that opens a network connection, scans your home directory, reads browser data, or creates persistence files at startup has already exceeded what most MCP use cases require. Some servers legitimately check an endpoint or load local configuration. They should make that behavior easy to explain and easy to disable.

Then ask for the tool inventory, inspect it, and make one harmless call with known input. Capture the request, result, process output, and files changed in the test directory. If the tool returns content from another system, make the test data unmistakable so you can tell whether it accessed the correct place.

A basic review sequence looks like this:

  1. Verify the executable path, package version, checksum or source revision, and every launcher script.
  2. Launch under a sparse environment in a test account or isolated workspace.
  3. Capture the first tools/list result and compare each tool with the intended job.
  4. Call a harmless read operation with fake data and observe files, child processes, and network destinations.
  5. Add only the credential and directory access that the confirmed behavior requires.

Do not confuse a successful response with a safe server. A server can return the expected answer while also copying files or using an inherited token elsewhere. The test gives you evidence, not proof. It catches the careless designs and obvious surprises before they reach production access.

Package convenience creates an update path you must own

Inject HTTP credentials locally
Route bearer, basic, and custom-header credentials through Sallyport instead of the agent.

Package managers and runtime launchers make MCP setup pleasantly short. They also create an update path. A version range, a floating package tag, or a bare package name can change the server that launches next week without a configuration diff.

Pin a version when your ecosystem supports it, and record the package source alongside the tool inventory. Better still, use a reviewed local artifact or a lockfile that your team already checks. The practical goal is simple: a later launch should resolve to code you can identify.

Do not let the agent install its own MCP server as part of a task. That folds software acquisition, execution, and tool authorization into one conversational request. A human should add the server configuration after reviewing the source and its launch contract. If a development workflow needs many servers, maintain a reviewed catalog rather than accepting setup snippets from issue comments or tool output.

Updates deserve a short repeat review. Compare the executable or dependency lockfile, the command and arguments, explicit environment names, and the tools/list inventory. A tool addition may be harmless. It may also introduce write access that the prior review never considered. The same applies when a server changes its credentials, endpoint, or authentication library.

The popular recommendation to "always use the latest package for security fixes" is incomplete for MCP servers. You should take security fixes promptly, but an unreviewed automatic change can alter the code that runs beside your credentials. Use a controlled update process that lets you inspect the change and roll it back if the new server behaves differently.

Approval should follow the action, not the server's name

Put SSH behind Sallyport
Send SSH commands through Sallyport using the bundled stateless sp-ssh helper.

A one-time approval for a server process answers only one question: may this executable participate in this session? It cannot safely answer every later question about deleting a remote branch, sending customer data, or opening an SSH command on a production host.

Split ordinary discovery from consequential actions. Listing available projects, reading a public issue, and fetching a local schema often need less scrutiny than mutating records or sending data outside the machine. Your client or credential boundary should ask for fresh human approval when a call crosses that line. If every read call demands attention, people will click through. If a single startup click grants unlimited production access, people will eventually regret it.

The process identity matters too. An approval prompt should tell you which signed process requested access, not merely display a server-selected label. A label such as database-helper can be copied by any program. The executable path, signing identity where available, and launch arguments make a better review surface.

Keep a record at two levels: the agent session that requested work and the individual action that used a credential or reached an external service. You need the session record when you investigate why an agent had authority. You need the action record when you investigate what actually changed. One log cannot answer both questions cleanly if it omits either the caller or the exact request.

A tamper-evident record helps when the server, client, or operator later disputes what happened. It does not make a dangerous action safe at the moment of approval. Read the target, method, command, and meaningful parameters before you authorize an action that cannot be easily undone.

A rejected server needs cleanup, not just removal

If you decide a server is untrustworthy after connecting it, remove its client entry immediately, but do not stop there. The process may have written files, altered shell configuration, created scheduled work, modified a repository hook, or copied credentials while it ran.

Preserve the evidence before you delete everything. Save the configuration, package version or source revision, process output, and action logs. Then inspect the test HOME, the server's working directory, package cache, shell startup files, launch agents, repository hooks, and any directories the server could write. Check active processes and recent network connections while the evidence remains available.

Rotate every secret the process could have read, not only the secret you intentionally supplied. That includes inherited tokens, SSH agent access, package credentials, and browser-backed development sessions where relevant. Removing a line from a configuration file does not revoke a copied token.

Then tighten the review process that allowed the connection. If the failure came from an inherited environment, use a sparse launcher. If it came from an unexpected package update, pin and review artifacts. If it came from a misleading tool description, require a captured inventory before approval. The useful outcome is a changed control, not a vague promise to be more careful next time.

A new MCP server gets access at its most dangerous moment, before it has earned any trust. Make its command literal, its environment small, its tool list inspected, and its credentials separate from the agent. That is the point where you still control the outcome.

FAQ

Is a local MCP server safe by default?

No. A local MCP server starts as a process under your user account, so it can read whatever that account can read unless the operating system or launcher restricts it. Local delivery removes one network hop; it does not turn downloaded code into trusted code.

What should I check before adding an MCP server to my client?

Inspect the executable path, every argument, the working directory, the environment passed to it, its network behavior, and the tools it advertises. Then run it first with a deliberately empty environment and a low-privilege test account or disposable workspace. A tool list is only a claim until you observe its calls.

Can an MCP server read my shell environment variables?

Assume it will inherit them unless the client documentation says it starts with a clean environment. Many clients merge an env object into their own process environment rather than replacing it. That can expose tokens for package registries, source control, cloud accounts, and internal services without a line in the MCP configuration mentioning them.

Are MCP command arguments safer than a shell command string?

A raw argument array avoids shell parsing in clients that use direct process execution. The danger returns when the configuration calls sh -c, bash -lc, a package runner, or a wrapper script, because those layers can expand variables, alter paths, and run extra commands. Prefer an absolute executable path and explicit arguments.

Can I trust an MCP server's tool descriptions?

Treat tool descriptions as untrusted input from a program you have not approved yet. They can accurately describe the tool, but they can also pressure an agent to disclose data, bypass review, or call unrelated tools. Compare the advertised verbs and inputs with the repository and with the job you intend the server to do.

Should I put API tokens in my MCP config file?

Do not put long-lived secrets in the client configuration if you can avoid it. Use a credential store, a short-lived token, or an action gateway that keeps the secret outside the agent and server process. If a token already reached a configuration file, shell history, or repository, rotate it rather than merely deleting the line.

How do I review MCP server updates safely?

A server can add, remove, or redefine tools after an update, and a package tag can resolve to different code later. Pin a reviewed version or immutable artifact where your package system supports it, record the tool inventory, and repeat the review after every change. Auto-updates and agent permissions make a bad combination.

What do tools/list and tools/call mean in MCP?

The client asks the server for tools/list, then invokes a selected tool with tools/call. That protocol flow does not grant trust to the server or to the tool result. The Model Context Protocol specification defines the message exchange, while you still need to decide which executable can start and which actions may use credentials.

Do I need a credential gateway for MCP servers?

Yes, if the server needs a credential you would not hand to an unreviewed local process. A gateway can hold the credential and perform a narrow HTTP or SSH action after human approval, returning only the result. That reduces secret exposure, but you must still review the server because it can request harmful actions or misuse data it can see.

What should I do if I already connected an untrusted MCP server?

Revoke or remove the server entry, rotate any exposed credentials, and preserve the configuration and logs before you clean up. Check the server's working directory, package cache, shell startup files, scheduled jobs, source control credentials, and recent network connections. Do not assume deleting the MCP entry removes changes the process already made.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov