# Agents - MCP Server Onboarding and Credentials
MCP failures hide behind green connection status and silent result-shape mismatches. Exercise the server process directly before and after you wire it into a client. Keep credentials in env entries that a secret runner or the OS keychain resolves, never in command argv.
## Key points
### Onboarding and smoke tests
- A server that shows connected but registers zero tools crashed during lifespan setup. The process never reaches tool registration.
- Test before you commit the config: run the package directly and read stderr for the crash reason.
- `npx @modelcontextprotocol/
[email protected]` config-file mode smoke-tests every server with `tools/list` plus read-only `tools/call`.
- Scope servers by least privilege: global, project, and user-project scopes exist. A data-warehouse server should not pollute every session's tool namespace.
- Chrome DevTools MCP attaches to an existing browser session and keeps cookies and auth state. Playwright MCP launches a fresh headless instance.
- For Python servers, `uv tool install -e .` gives an isolated venv and a console script on PATH. Register it with `claude mcp add --scope user <name> -- <console-script>`.
- MCP proxy middleware requires the upstream server command as its first positional argument. Zero args exit immediately as a JSON-RPC error. Expensive tool descriptions are the proxying use case.
- Version-pin remote wrappers with open auth regressions (`mcp-remote` at `0.1.38`). Prefer official hosted OAuth endpoints over deprecated community servers.
### Credentials and OAuth
- Serve credentials in `.mcp.json` as `env` entries with secret-manager references resolved via `op run`. Never use inline substitution.
- Debug resolution first: `op read` of the exact reference must succeed, or the server fails on init with a JSON-RPC error.
- A bearer token in process argv (an `sh -c exec` line with a `--header` flag) is visible to any process listing.
- Keychain pattern: add a generic password once, load it with `-w`, and verify an entry by omitting `-w` and checking the exit code.
- One adapter keys OAuth token stores by a hash of the server name. The URL is validated but never part of the key, so same-URL entries with different names get isolated stores.
- Slack MCP OAuth differs per host: one client expects `callbackUrl`, another `redirectUri` without an auth field. Two hosts mean two token stores, so one can be authed while the other still prompts.
- Check where a built-in client writes tokens before you abandon an adapter. One built-in writes plaintext tokens to disk.
- A risk-free OAuth probe: point the OAuth dir at a temp directory and launch from a scratch directory with a project-level MCP config.
- Account auth can succeed while every site read returns 403 "app is not installed". An empty accessible-resources list means the token binds no site resources. The fix is a fresh consent grant, not more retries.
### Silent result-shape failures
- A Slack payload nests results in `content[].text`. A regex against escaped JSON reads as no results.
- On Slack MCP searches, pass `include_context=false`, a concise format, and a small limit. Unrestricted results blow the context window.
- Tool discovery needs server scope: a server field in the search, or an explicit connect first.
- An adapter that skips re-prefixing tools already starting with the server name makes allowlist entries like `slack_slack_*` dead. Imported servers expose only namespace proxies unless you set `directTools`.
- A headless print-mode session (`pi -p`) re-reads the MCP config. Intersect its tool list with the allowlist to verify entries without an interactive session.
- When a result overflows to a one-line JSON file, page it: grep with a pattern like `.{1,300}` plus offset/limit.
- Tool-schema `pattern` fields must be RE2-compatible: lookaround makes OpenAI's validator reject the whole request, and grammar-constrained backends reject it too. Compile them with Go's `regexp`, which is literally RE2.
- A file-reading tool can silently regress to sample rows instead of full data. A CSV export restored the full dataset and removed a duplicated sheet.
- Image-search servers can use tag-based matching, not semantic search. Abstract queries match unrelated tagged images, so use concrete noun phrases.
### Sharp edges in the mcp-go library
- Tools registered with `WithInputSchema[T]()` carry their schema only in the raw JSON field. The structured fields stay zero-valued, so middleware that grafts onto them sees nothing. Unmarshal raw into structured before injection, then clear the raw field so serializers cannot double-encode.
- mcp-go decodes the JSON-RPC request ID into `any` without type validation. An object or array ID becomes a map or slice, which is unhashable, so a sync-map store panics. Recovery middleware covers only tool and resource handlers. The SSE transport runs each message in a goroutine without recover, so one crafted `initialize` stops the process.
## How to apply
1. Run the server package directly and read stderr before you commit the config.
2. Smoke-test every server with `npx @modelcontextprotocol/
[email protected]` config-file mode.
3. Serve credentials in `env` entries. Verify each reference first with `op read`.
4. Keep secrets out of argv. On macOS, use the Keychain: `security add-generic-password -a <name> -s <service> -w`.
5. Compile every tool-schema `pattern` with Go's `regexp` before shipping.
## Observations
- [failure-mode] a connected server with zero registered tools crashed during lifespan setup #mcp (source: agent sessions, 2026-03)
- [practice] secret-manager env entries keep MCP credentials out of process argv #security (source: agent sessions, 2026-08)
- [security] lookaround in a tool pattern passed local tests and failed a hosted RE2 validator #tools (source: agent sessions, 2026-09)
- [security] mcp-go panics on an unhashable JSON-RPC request ID, and one crafted initialize stops the process #security (source: agent sessions, 2026-10)
## Relations
- relates_to [[resources/documentation/Agents/Agents - Guards, Hooks, and Security Boundaries]] (credential exposure in argv versus env entries)
- relates_to [[resources/documentation/Agents/Agents - Gmail, Slack, and Google Docs Quirks]] (Slack response shapes and search limits)
- relates_to [[resources/concepts/MCP Tool Optimization Framework]] (server scoping and tool namespace hygiene)
- relates_to [[resources/documentation/Agents/pi - Extension Development]] (the pi-mcp-adapter that keys token stores by server name)
## Sources
- Personal agent-session notes, March 2026 to October 2026