| Transport | Where it lives | Auth | Tools exposed |
|---|---|---|---|
stdio (toolshed serve) | CLI command, local-only | Local session token | run, search_tools, read_tool, resume |
HTTP (POST /api/mcp/:token) | Remote connector for Claude Desktop, web agents | MCP token in URL | search_tools, read_tool, call_tool |
- stdio path lets the agent compose multiple calls inside a TypeScript sandbox (
run) and pause for user approval mid-execution (resume). - HTTP path (used by Claude Desktop’s “Add custom connector”) executes one tool per call via
call_tool. There’s no sandbox; approval happens upstream in the host UI.
HTTP transport details
The remote endpoint atPOST /api/mcp/:token uses Streamable HTTP transport (@hono/mcp’s StreamableHTTPTransport). Sessions are per-MCP-token and survive across requests:
- First request:
initializeJSON-RPC message. Server generates a session id and returns it in themcp-session-idresponse header. - Subsequent requests: client must echo the session id in the
mcp-session-idrequest header. - Each session caches a per-user
McpServerinstance withavailableToolsalready filtered (see below).
tool_connections rows, Better Auth account rows for OAuth, and env-backed API keys for built-ins). search_tools and read_tool only operate over tools whose effective authProvider is in that set, so the agent never sees a tool it can’t actually call.
Token resolution. When call_tool runs a plugin handler, the plugin calls ctx.auth.getToken("<provider>"). The server’s resolveToken() checks three sources in order: per-user tool_connections row → Better Auth’s stored OAuth token (with auto-refresh) → process.env["${UPPER}_API_KEY"] for built-in tools.
stdio transport details
The CLI’stoolshed serve command runs an MCP server over stdio via @toolshed/mcp-host. It registers the four tools listed above against the runtime (local or Vercel) and the full tool catalog:
Recommended agent workflow
Same pattern on both transports:- Discover — call
search_toolswith a keyword to find relevant tools. - Inspect — call
read_toolto see the full input/output schema. - Execute —
call_tool(HTTP) orrun(stdio) to invoke the chosen tool. - Approve — only on stdio: if the result includes a
pausedfield, callresume.