Skip to content

MCP Server & Client

.md

Dewee speaks MCP in both directions, and there are four distinct things called “MCP” in this codebase. Read this table first; conflating them is the most common integration mistake.

| # | Meaning | Endpoint | Who calls whom | Auth | |---|---|---|---|---| | 1 | Dewee consumes an external MCP server | /v1/mcp/servers/* | Dewee is the MCP client | Gateway token / API key with operator scopes | | 2 | An external AI client consumes Dewee | POST /mcp/server | Dewee is the MCP server | Dewee API key with an mcp.* scope, or the server token | | 3 | Internal Claude CLI bridge | /mcp/bridge | Dewee is the MCP server, for the local Claude CLI only | Gateway token + bridge HMAC headers | | 4 | MCP server configuration backup | /v1/mcp/export, /v1/mcp/import | Operator backup/restore of saved MCP server configs | Gateway token / admin API key |

POST /mcp/server (meaning 2) and /v1/mcp/* (meanings 1 and 4) share a leading path segment. They are unrelated surfaces: /v1/mcp/* manages external servers, while /mcp/server serves Dewee’s own tools outward.

Dewee connects to external MCP servers and registers their tools into its own registry.

Supported transports: stdio, sse, streamable-http (OAuth 2.1 + PKCE supported for the HTTP transports).

Terminal window
dewee mcp list
dewee mcp tools filesystem
dewee mcp oauth config github-tools --registration-mode pre_registered --client-id <id>
dewee mcp oauth start github-tools
dewee mcp oauth status github-tools --json

Env for stdio servers. A stdio server’s env (server config, per-user credentials, channel context credentials and test connections) cannot set keys that load code or redirect the runtime, such as NODE_OPTIONS, LD_PRELOAD, PYTHONPATH, PATH, JAVA_TOOL_OPTIONS, proxy or TLS-CA overrides, or UV_/PIP_/GIT_ package-manager settings; such a key is refused with 400. Keys already stored on a server are not refused again when the server is edited, but they are dropped when the process is spawned. Servers defined in the gateway config file are not filtered. A test connection (POST /v1/mcp/servers/test) runs the same command allowlist and URL checks as creating a server.

Full CRUD, grants, and OAuth lifecycle: see the API reference. Tools coming from an external MCP server are named mcp_<server>__<tool> and are never re-exported through the public endpoint (meaning 2).

2. Connecting an AI client to Dewee (public MCP)

Section titled “2. Connecting an AI client to Dewee (public MCP)”

This is how Claude Code, Cursor, VS Code, Gemini CLI and other MCP clients call Dewee.

POST /mcp/server
Authorization: Bearer <credential>
X-Dewee-Agent: <optional agent selector>
Content-Type: application/json

Disabled by default. Nothing is served on /mcp/server until you enable it:

{
"tools": {
"mcp": {
"public": {
"enabled": true
}
}
}
}

or GOCLAW_MCP_PUBLIC_ENABLED=1.

/mcp/server is the canonical endpoint, and every snippet Dewee generates points there. Clients configured with the original bare /mcp keep working: MCP requests there (every POST, a GET asking for text/event-stream, anything carrying a bearer credential or an MCP header) are served by the same handler, with the same auth and limits, plus a Deprecation: @1791676800 header (an RFC 9745 date, 2026-10-11T00:00:00Z, when /mcp was deprecated) and Link: </mcp/server>; rel="successor-version". A plain browser page load of /mcp gets the dashboard’s MCP page instead, never the MCP handler. Move clients to /mcp/server; the legacy path will be removed in a future release.

Two credential forms are accepted, and only these two:

  1. A Dewee API key carrying an mcp.* scope. The key must be owner-bound; an MCP-scoped key without an owner is rejected, because its user identity would otherwise come from a request header.
  2. The instance-level server token (GOCLAW_MCP_SERVER_TOKEN), for self-hosted and operator use. It is env-only, must be at least 32 characters, must be paired with GOCLAW_MCP_SERVER_TENANT_ID, receives a read-only export set, and never crosses tenants.

Rules that matter in practice:

  • The gateway token is never accepted on the public MCP endpoint.
  • operator.read / operator.write keys do not gain MCP access; an MCP scope is required.
  • An MCP-only key is rejected on every other route, including the tenant’s read APIs. It works on the public MCP endpoint and on key management (to rotate itself), nothing else.
  • Tenant, user and workspace identity always come from the credential. Request headers such as X-GoClaw-Tenant-Id, X-GoClaw-User-Id, and X-GoClaw-Sender-Id carry no authority here.
  • Revocation and expiry take effect on the next request; no cache window applies.
  • A revoked or expired key gets the same 401 as a value that was never a key, but each attempt is recorded as a refused mcp_public trace in the key’s workspace (reason credential_revoked or credential_expired, with the key prefix, never the key). If you revoked a key because it leaked, check Traces, or dewee traces list --channel mcp_public, to see whether it is still being used.

| Scope | Grants | |---|---| | mcp.tools.read | Exported read-only tools: list and call | | mcp.tools.write | The above, plus the explicitly exported mutation actions |

Exposure is explicit and default-deny. A tool is invisible unless it was annotated for export, and tools/list and tools/call are governed by the same rule, so a hidden tool cannot be invoked by naming it directly. Dangerous tools (exec, file mutation, browser, spawn, cron, package, provider, admin and credential operations) are not exported.

The initial exported set is four read tools plus one narrowly gated mutation:

  • memory_search, memory_get, skill_search
  • web_search
  • workflow with action restricted per action: draft and update_draft require mcp.tools.write; run, request_publish, publish and dry_run are never available through public MCP.

The session read tools (sessions_list, session_status, sessions_history) are deliberately withheld. They are tenant-scoped but not user-scoped, while the equivalent HTTP routes are user-scoped, so exporting them would expose every user’s sessions for the agents the credential can reach.

Resolution order, all inside the authenticated tenant:

  1. X-Dewee-Agent header
  2. ?agent= query parameter (for clients that cannot set headers)
  3. the tenant-local agent key default

A selector that does not resolve — missing, archived, or belonging to another tenant — returns one identical 404 {"error":"agent not found"} so the endpoint cannot be used to enumerate tenants. When a selector is supplied, the default fallback is not applied.

| Setting | Env var | Default | |---|---|---| | Request body cap | GOCLAW_MCP_PUBLIC_MAX_BODY_BYTES | 1 MiB (1048576) | | Requests per minute per credential | GOCLAW_MCP_PUBLIC_REQUESTS_PER_MINUTE | 120 (burst 20) | | Concurrent tool calls per credential | GOCLAW_MCP_PUBLIC_MAX_CONCURRENT_TOOL_CALLS | 4 | | Tool-call deadline | GOCLAW_MCP_PUBLIC_TOOL_CALL_TIMEOUT_SECONDS | 60 | | Authentication failures per minute per presented credential | GOCLAW_MCP_PUBLIC_FAILURES_PER_MINUTE | 60 | | Authentication failures tracked per minute in total | GOCLAW_MCP_PUBLIC_GLOBAL_FAILURES_PER_MINUTE | 600 |

Every per-credential table is capped (4096 entries, idle entries swept after 10 minutes) regardless of configuration, so raising a limit cannot remove the bound.

/mcp/bridge exposes a fixed allowlist of Dewee tools to the local Claude CLI. It is gateway-token protected and relies on HMAC-signed context headers, and it is not an external integration point. Do not expose it to the internet, and do not reuse it for third-party clients — use POST /mcp/server instead.

The bridge is a hard precondition for tool-enabled Claude CLI turns: when the CLI reports the dewee-bridge server as failed, needs-auth or disabled at startup, Dewee stops the turn with a mcp_bridge_unavailable error rather than letting the CLI run without its managed tools. A Claude CLI turn that exits with an error after streaming some text is reported as a failed turn, with the partial text kept on the trace span; authentication, usage-limit and rate-limit failures are classified like the same failures from HTTP providers, so model fallback and cooldown apply.

The public endpoint implements MCP 2026-07-28 (stateless protocol core, server/discover, and Mcp-Method / Mcp-Name routing headers) and negotiates down to supported 2025-era clients. Requests are stateless: no protocol session store and no sticky routing are required.

Render a ready-to-paste config for Claude Code, Cursor, VS Code, Gemini CLI or a generic HTTP client with dewee mcp connect-config --client <client>. The key is referenced through the DEWEE_MCP_TOKEN environment variable, never embedded in the snippet.