Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 70 additions & 5 deletions content/docs/ai/connect-mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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?"

Expand All @@ -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 |
Expand Down
Loading