Skip to main content
Toolshed ships two MCP server surfaces, with different tool sets tuned for the different transports. Both surfaces front the same plugin catalog. They differ in how the agent executes tools:
  • 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 at POST /api/mcp/:token uses Streamable HTTP transport (@hono/mcp’s StreamableHTTPTransport). Sessions are per-MCP-token and survive across requests:
  1. First request: initialize JSON-RPC message. Server generates a session id and returns it in the mcp-session-id response header.
  2. Subsequent requests: client must echo the session id in the mcp-session-id request header.
  3. Each session caches a per-user McpServer instance with availableTools already filtered (see below).
Per-user tool filtering. When a session is created, the server computes which auth providers the user has connected (union of 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’s toolshed 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:
The stdio surface does not filter by connected providers — the CLI flow assumes the local user has whatever credentials they need. Same pattern on both transports:
  1. Discover — call search_tools with a keyword to find relevant tools.
  2. Inspect — call read_tool to see the full input/output schema.
  3. Executecall_tool (HTTP) or run (stdio) to invoke the chosen tool.
  4. Approve — only on stdio: if the result includes a paused field, call resume.