Skip to content

Diagnostics

dynmcp ships two non-proxy subcommands to help you inspect your configuration and verify upstream MCPs are reachable: dynmcp ls and dynmcp test. Both are config-file-mode only — single-MCP (--) mode has nothing to list and only one upstream to test.

Prints an aligned table of every upstream MCP in the resolved config, including its transport, mode (eager / lazy), endpoint, and auth status. Pure config + keychain read — no network calls.

Terminal window
dynmcp ls
dynmcp ls --config ./mcp.json
dynmcp ls --json
NAME TRANSPORT MODE ENDPOINT AUTH
chrome-devtools stdio lazy npx -y chrome-devtools-mcp@latest n/a
aws-knowledge streamable-http eager https://knowledge-mcp.global.api.aws n/a
github streamable-http eager https://api.githubcopilot.com/mcp oauth: logged in (expires in 47m)
remote-dcr streamable-http lazy https://example.com/mcp oauth: not logged in
remote-bearer streamable-http eager https://api.example.com/mcp header
ColumnSource
NAMEThe map key under mcp.<name> in the config.
TRANSPORTstdio / streamable-http / sse.
MODElazy if the entry has a description field, otherwise eager.
ENDPOINTcommand + args joined with spaces for stdio; url for http/sse. Truncated with ... if longer than 48 characters.
AUTHSee auth status values below.
StatusMeaning
n/astdio transport — OAuth doesn’t apply.
headerA static Authorization header is set in the config and no OAuth token is cached.
oauth: not logged inhttp/sse with no static header and no cached keychain entry. Run dynmcp login <name> to authenticate.
oauth: logged in (expires in <duration>)A cached keychain entry exists; the duration is humanized from the stored expires_at. Negative durations render as expired.
oauth: logged in (expires in <duration>) (header also set)Both a cached OAuth token and a static Authorization header exist. The OAuth token is what the proxy actually attaches.
FlagDescription
--config <path> / -cPath to config file.
--env <path> / -ePath to a custom .env file.
--jsonEmit a JSON array of entry objects instead of the text table.
[
{
"name": "github",
"transport": "streamable-http",
"mode": "eager",
"endpoint": "https://api.githubcopilot.com/mcp",
"auth": {
"kind": "oauth",
"status": "logged_in",
"expiresInSeconds": 2820,
"expiresAt": 1700000000
}
}
]
  • The first time dynmcp ls reads from the keychain on macOS, the OS may prompt for access. This is standard Keychain behavior, not something dynmcp can suppress.
  • Exit code is 0 if the config loads successfully, non-zero on a config error.

Probes one or all configured upstreams. For a single upstream, prints a step-by-step pass/fail log followed by the full discovered tool / resource / prompt catalog. For all upstreams, prints a one-line summary per MCP and a final totals line.

Terminal window
dynmcp test # test every configured MCP
dynmcp test github # test just one
dynmcp test github --json
dynmcp test --timeout 30000 # raise the per-MCP timeout
Testing "github" (streamable-http, https://api.githubcopilot.com/mcp)
[ok] OAuth token present (expires in 47m)
[ok] Connected and initialized
[ok] Capabilities: tools(listChanged), resources(subscribe,listChanged), prompts
[ok] tools/list returned 23 tools
[ok] resources/list returned 5 resources, 2 templates
[ok] prompts/list returned 3 prompts
Tools (23):
- create_issue: Create a new issue in a repository
- list_issues: List issues in a repository with optional filtering
- get_pull_request: Read a pull request's metadata and review state
- ...
Resources (5):
- github://repos/{owner}: List repositories owned by a user or org
- github://user: The authenticated user's profile
- ...
Resource templates (2):
- github://repo/{owner}/{name}/issues/{number}: Read a single issue
- github://repo/{owner}/{name}/pulls/{number}: Read a single pull request
Prompts (3):
- write-pr-description: Generate a PR description from issue context
- ...
Result: PASS

Empty surface sections (count of zero) are omitted entirely — no Resources (0): header on MCPs without resources.

Tool, resource, and prompt descriptions are truncated to ~100 characters with ... if longer. Entries within each section are sorted by name (or URI / template) for stable output.

Testing all configured upstreams (5)...
[1/5] chrome-devtools (stdio) ... PASS (54 tools, 0 resources, 0 prompts)
[2/5] aws-knowledge (streamable-http) ... PASS (8 tools, 0 resources, 0 prompts)
[3/5] github (streamable-http) ... FAIL (auth required: run `dynmcp login github`)
[4/5] remote-dcr (streamable-http) ... PASS (45 tools, 0 resources, 3 prompts)
[5/5] remote-bearer (streamable-http) ... PASS (3 tools, 0 resources, 0 prompts)
Summary: 4 passed, 1 failed

For each MCP being tested, the following steps run in order:

  1. Resolve the config entry.
  2. Report auth status (for http/sse only).
  3. Open the transport and complete the MCP initialize handshake.
  4. Print the negotiated server capabilities.
  5. Call tools/list.
  6. Call resources/list + resources/templates/list if the server advertised the resources capability.
  7. Call prompts/list if the server advertised the prompts capability.
  8. Disconnect.

A failure within resources/list doesn’t abort prompts/list — each capability is probed independently. The overall result is PASS only if every step (other than informational status) succeeded.

FlagDescription
--config <path> / -cPath to config file.
--env <path> / -ePath to a custom .env file.
--jsonEmit a JSON result object (single-MCP) or { summary, results } (all-MCP) instead of the formatted output.
--timeout <ms>Per-MCP timeout, in milliseconds. Default 15000. Covers transport open + initialize + every catalog query.
InvocationExit code
dynmcp test <name>0 if PASS, 1 if FAIL.
dynmcp test (no name)0 if every MCP passed, 1 if any failed.
  • Sequential, not parallel. All-mode tests run one upstream at a time to keep output readable and avoid pile-ups on stdio MCPs that spawn child processes.
  • Continues past failures. In all-mode, one failing MCP does not skip the rest.
  • Lazy upstreams. Tested by connecting transiently and disconnecting after. They are not permanently loaded; their lazy registration is independent of any in-process proxy.
  • Auth-required failures. Reported as clean FAILs with the actionable Run \dynmcp login `message. The retry-budget logic that applies toload_mcpis not in effect here —test` is a one-shot diagnostic.
  • OAuth tokens. A test may trigger a silent token refresh on an http/sse upstream with a near-expiry cached token. The refreshed token is written back to the keychain atomically, just as it would be under the live proxy.
{
"name": "github",
"result": "PASS",
"transport": "streamable-http",
"endpoint": "https://api.githubcopilot.com/mcp",
"auth": { "kind": "oauth", "status": "valid", "expiresInSeconds": 2820 },
"capabilities": { "tools": { "listChanged": true }, "resources": {} },
"tools": [{ "name": "create_issue", "description": "..." }],
"resources": [{ "uri": "github://user", "name": "user" }],
"resource_templates": [
{ "uriTemplate": "github://repo/{owner}/{name}/issues/{number}", "name": "issue" }
],
"prompts": [{ "name": "write-pr-description", "description": "..." }],
"steps": [
{ "label": "OAuth token present (expires in 47m)", "status": "ok" },
{ "label": "Connected and initialized", "status": "ok" }
]
}
{
"summary": { "passed": 4, "failed": 1 },
"results": [
{ "name": "chrome-devtools", "result": "PASS", "..." },
{ "name": "github", "result": "FAIL", "fail_reason": "auth required: run `dynmcp login github`", "..." }
]
}