diff --git a/content/docs/ai/connect-mcp.mdx b/content/docs/ai/connect-mcp.mdx index 3a970420d3..876d4502ec 100644 --- a/content/docs/ai/connect-mcp.mdx +++ b/content/docs/ai/connect-mcp.mdx @@ -29,14 +29,31 @@ claude mcp add --transport http my-app http://localhost:3000/api/v1/mcp claude mcp add --transport http my-app https://your-deployment.example.com/api/v1/mcp ``` -For headless use (CI, containers) skip OAuth and attach an -[API key](#headless-api-keys) instead: +Then **open a new Claude Code session** and run `/mcp` — pick the server and +choose *Authenticate* to complete the browser login. Two things to expect on +this track: + +- Until that login has happened, `claude mcp list` / `claude mcp get my-app` + report the server as **✘ Failed to connect**. That is the health probe being + answered with a 401, not a broken setup — the status flips once the session + is authenticated. +- A Claude Code session that was already running when you ran `claude mcp add` + does not pick the server up; start a fresh one. + +For headless use (CI, containers), or whenever you would rather not go through +the browser login, skip OAuth and attach an [API key](#headless-api-keys) +instead — with a key the server reports **✔ Connected** immediately, no login +step: ```bash claude mcp add --transport http my-app https://your-deployment.example.com/api/v1/mcp \ --header "x-api-key: osk_..." ``` +`claude mcp add` registers the server at **local** scope by default — visible +only from the directory you ran it in. Add `-s user` to make it available from +every project. + ## Claude Desktop and claude.ai **Settings → Connectors → Add custom connector**, then paste the MCP URL @@ -61,8 +78,16 @@ Clients that read an `mcpServers` map connect the same way. With an API key: ## Headless: API keys -Mint a key from **Setup → Connect an Agent** in the Console (which also shows -copy-paste-ready connect snippets per client), or over REST: +Mint a key from **Setup → Connect an Agent** in the Console: the page lives at +`/_console/apps/com.objectstack.setup/page/connect_agent` (a link in the Setup +sidebar takes you there), and the **Create key** button sits at the bottom +under *API keys*. The new key is shown **once**, together with a ready-to-paste +`x-api-key: osk_...` header line — copy it before you dismiss the panel. The +same page carries copy-paste connect snippets for Claude Code, Claude Desktop, +Cursor, VS Code, and Codex, and a download link for the app's `SKILL.md`. +Existing keys are listed (prefix only) and revoked under **Setup → API keys**. + +Or mint one over REST: ```bash curl -b cookies.txt -X POST https://your-deployment.example.com/api/v1/keys @@ -217,7 +242,43 @@ drive this MCP server. Same word, different layer. ## Verify the connection -Ask the agent something only the live schema can answer: +Before involving a client, prove the server itself is answering — the anonymous +probe below must return `401`, which is the surface saying "present, needs a +credential" (a `WWW-Authenticate` header naming the OAuth resource metadata +means the browser-login track is live too). Anything else is a server-side +issue; see [Troubleshooting](#troubleshooting). + +```bash +curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3000/api/v1/mcp \ + -H 'content-type: application/json' -d '{}' +# → 401 +``` + +With an API key you can drive the whole protocol from the shell — a handshake, +the tool list, and one real read: + +```bash +MCP=http://localhost:3000/api/v1/mcp +KEY=osk_... +call() { curl -s -X POST "$MCP" -H "x-api-key: $KEY" \ + -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d "$1"; } + +call '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' +# → {"result":{"serverInfo":{"name":"objectstack",...},"capabilities":{"tools":{...},"prompts":{}}},...} + +call '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | grep -o '"name":"[a-z_]*"' +# → the eleven tools, list_objects … run_action + +call '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"query_records","arguments":{"objectName":"crm_opportunity","limit":3}}}' +# → {"result":{"content":[{"type":"text","text":"{ \"object\": \"crm_opportunity\", \"records\": [ ... ], \"total\": 23, \"hasMore\": true }"}]}} +``` + +Two details that trip up a first attempt: the object argument is +**`objectName`**, and its value is the object's API name as returned by +`list_objects` (`crm_opportunity`, not `opportunity`) — a wrong name comes back +as `Object 'opportunity' not found`. + +Then, in the client, ask the agent something only the live schema can answer: > "What objects does this app have, and what fields does the main one carry?" @@ -235,6 +296,10 @@ skill and a guided `/objectstack:connect` command. | Symptom | Cause → fix | |:---|:---| +| `claude mcp list` says **✘ Failed to connect** right after `claude mcp add` (OAuth track, no `--header`) | Expected until you authenticate — open a new session, run `/mcp`, pick the server, *Authenticate*. Or attach an API key, which connects without a login step | +| **✘ Failed to connect** / `ConnectionRefused` on a server that used to work | The app is not running. Start it — the client reconnects on its own, no need to re-register | +| The server is missing from `/mcp` in a running session | Sessions read MCP config at start — open a new one. If it is still missing, you registered at **local** scope from another directory; re-add with `-s user` | +| `Object 'x' not found` on `query_records` / `get_record` | Wrong object name — pass the API name from `list_objects` in **`objectName`** (`crm_opportunity`, not `opportunity`) | | `404` on `/api/v1/mcp` | The HTTP surface is disabled — unset `OS_MCP_SERVER_ENABLED` (default is on) | | `501 Not Implemented` | The MCP plugin isn't part of this build — check your stack's plugins | | `mcp` missing from `GET /api/v1/discovery` and no Connect-an-Agent card, but `OS_MCP_SERVER_ENABLED` is on | The same cause as the `501` above, seen from the other side: the surface is *enabled* but not *serveable*, so discovery declines to advertise a route that would 501 rather than over-promising it (`declared === enforced`). Load the MCP plugin — `os serve` / `os dev` do it for you; a host that embeds `@objectstack/rest` directly must add `@objectstack/mcp` itself |