Skip to content

The /mcp Endpoint

The /mcp endpoint turns NimbleBrain into a Streamable HTTP MCP server. It is NimbleBrain’s public programmatic surface: external MCP clients (Claude Desktop, Claude Code, Cursor, another NimbleBrain instance, or any MCP-compatible host) connect here and access the tools installed across your workspaces.

The runtime also serves a /v1/* REST API, but that is a trusted same-origin surface for the bundled web client — not a supported external integration point. For programmatic access from outside the platform, use /mcp.

This makes NimbleBrain both an MCP client (connecting to installed bundles) and an MCP server (exposing composed tools to external hosts). The composition layer in between, which handles tool aggregation, feature gating, role filtering, and workspace routing, is what external clients benefit from without needing to know about individual bundles.

For end-user client setup walkthroughs, see Connecting External Clients.

Path/mcp
MethodsPOST, DELETE (GET returns 405)
ProtocolStreamable HTTP (MCP transport)
AuthOAuth 2.0 via WorkOS AuthKit (JWT bearer), discovered via RFC 9728
ScopeSessions are bound to your identity. Tool calls route to a workspace by the namespaced tool name.

The methods correspond to the Streamable HTTP session lifecycle:

  • POST — initialize a session or send subsequent JSON-RPC messages.
  • DELETE — terminate a session (with a valid Mcp-Session-Id).
  • GET — returns 405 Method Not Allowed with Allow: POST, DELETE. NimbleBrain does not offer the optional GET-style server-to-client SSE channel; progress and task updates flow over the POST that started them. Spec-compliant clients fall back to POST-only automatically.

Authentication is OAuth-based. There is no static API key.

The first time a client hits /mcp without a valid JWT, it receives a 401 with a WWW-Authenticate header:

Bearer error="unauthorized",
error_description="Authorization required",
resource_metadata="https://your-instance/.well-known/oauth-protected-resource"

Clients that implement RFC 9728 (Protected Resource Metadata) and RFC 8414 (Authorization Server Metadata) — Claude Code, Claude Desktop, and Cursor all do — follow the discovery chain automatically, open a browser for user login against AuthKit, and attach the returned JWT on subsequent requests.

Adapter configuration (dev, oidc, or workos) lives in instance.json. See auth adapters for the full setup, including the workos adapter and authkitDomain required to enable MCP OAuth.

NimbleBrain serves two unauthenticated discovery endpoints that MCP clients fetch during the OAuth flow. Both return 404 MCP OAuth not configured unless the workos adapter has an authkitDomain set.

RFC 9728 Protected Resource Metadata. Clients fetch this after the 401’s WWW-Authenticate header points them here. It tells them which authorization server to use.

{
"resource": "https://your-instance",
"authorization_servers": ["https://your-subdomain.authkit.app"],
"bearer_methods_supported": ["header"]
}

GET /.well-known/oauth-authorization-server

Section titled “GET /.well-known/oauth-authorization-server”

RFC 8414 Authorization Server Metadata, proxied from AuthKit for older MCP clients that look for this endpoint instead of Protected Resource Metadata.

  1. Client POSTs to /mcp with no token and gets 401 + WWW-Authenticate carrying the resource_metadata URL.
  2. Client fetches /.well-known/oauth-protected-resource to learn the authorization server (AuthKit).
  3. Client runs the OAuth authorization-code flow against AuthKit, opening a browser for user login.
  4. AuthKit issues a JWT; the client attaches it as Authorization: Bearer <jwt> on every /mcp request thereafter.

NimbleBrain verifies each access token against AuthKit’s JWKS endpoint (RS256, issuer validation). When organizationId is set on the adapter, only members of that WorkOS organization can authenticate.

Each initialize creates a new MCP session tracked server-side and addressed by the Mcp-Session-Id response header. Include that header on every subsequent request in the session.

Sessions are closed on DELETE /mcp (with a valid Mcp-Session-Id), transport close, idle-TTL eviction, or server shutdown.

When the server can’t serve a request because the session isn’t available, the 404 response includes error.data.reason:

{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Session not found",
"data": { "reason": "not_found" }
},
"id": null
}
ReasonMeaning
not_foundThe session registry has no record. Most often idle-TTL eviction, or the session never existed.
unavailableThe session exists in the registry, but the live transport isn’t on this process. Possible causes: process restart since initialize, or a sticky-routing miss.

Spec-compliant clients re-initialize on either reason.

The OAuth resource URL advertised on /.well-known/oauth-protected-resource (and the one in the WWW-Authenticate header) must match the scheme the client connected on. If you terminate TLS at an upstream proxy, the pod sees an internal http:// connection while the client used https://, which makes OAuth resource validation fail.

NimbleBrain honors X-Forwarded-Proto to derive the advertised scheme. Configure your proxy (ALB, nginx, Caddy) to set it based on the actual client connection. The Host header (url.host) is used verbatim; X-Forwarded-Host is deliberately not honored.

For the request’s validated X-Workspace-Id workspace, the endpoint exposes:

  • Workspace tools<source>__<tool>, from the bundles installed and running in that workspace. The workspace is not in the name: it comes from the X-Workspace-Id you sent, so the same call means a different workspace’s tool under a different header.
  • Identity tools — your conversations, files, and automations (bare <source>__<tool>).
  • Tool executiontools/call runs a tool in that workspace or an identity tool. A name cannot address a different workspace; the retired ws_<id>- form is refused with -32602 invalid_tool_name. With no X-Workspace-Id, only identity tools are exposed and a workspace tool is refused with -32602 workspace_access_denied. A bare name resolves against that workspace’s sources; your personal connectors are reachable only under their my_-marked names. A bare name naming neither is refused with -32602 unknown_tool_source.

Two filters apply to both tools/list and tools/call:

  • Feature flags — tools whose controlling flag is false are excluded (for example, the skills__* authoring tools when skillManagement is false). See Feature Flags.
  • Role — admin-only tools (such as nb__manage_users, nb__manage_workspaces) are excluded when the caller’s role is not admin or owner.

In dev mode (no auth), you can hit the endpoint with curl:

Terminal window
# Initialize a session
curl -i -X POST http://localhost:27247/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "curl", "version": "1.0" }
}
}'

Extract the Mcp-Session-Id response header and use it on subsequent calls:

Terminal window
curl -X POST http://localhost:27247/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: <session-id>" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":2}'

With auth enabled, include Authorization: Bearer <jwt> on every request.