Appearance
MatterWave as an MCP server
Claude Desktop, Claude Code, Cursor and any other Model Context Protocol client can read and continue your MatterWave conversations. The server is part of every deployment at a single endpoint:
https://<your-matterwave>/mcpIt speaks the Streamable HTTP transport, authenticates with a MatterWave API key sent as a bearer token, and acts strictly as the person that key belongs to: their conversations, their scopes, their organisation's plan and budget. Nothing a client can do here reaches past what that key can do on the ordinary API.
Get a key
Create one under Settings → API keys in the app (or POST /api/keys). The key is shown once. A key for reading only (chat:read) can list, search and read conversations; chat:write adds creating conversations and sending messages. The default key has both. The key's other settings apply as everywhere: expiry, allowed networks and its own rate limit.
A terminal or editor that signs in with a device code gets the same kind of key; see Signing in a device.
Connect a client
Replace <your-matterwave> with the host the app is served from and <your-key> with the key. Never commit a key; every client below can read it from an environment variable instead.
Claude Code
bash
claude mcp add --transport http --scope user matterwave https://<your-matterwave>/mcp \
--header "Authorization: Bearer <your-key>"--scope user keeps the key in your own configuration rather than the project's. Claude Code echoes the header it stored, so run this where the terminal is yours. Then /mcp inside Claude Code shows matterwave connected and its six tools.
Claude Desktop
Claude Desktop connects to a remote server through Settings → Connectors → Add custom connector: enter https://<your-matterwave>/mcp, open Request headers and add Authorization with the value Bearer <your-key> (the Bearer prefix included).
Where that header field is not offered, the mcp-remote bridge presents the key for you. In claude_desktop_config.json (Settings → Developer → Edit Config):
json
{
"mcpServers": {
"matterwave": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://<your-matterwave>/mcp",
"--header", "Authorization: Bearer ${MATTERWAVE_API_KEY}"
],
"env": { "MATTERWAVE_API_KEY": "<your-key>" }
}
}
}Restart Claude Desktop; the MatterWave tools appear under the tools menu of a new chat.
Cursor
In .cursor/mcp.json (the project's, or ~/.cursor/mcp.json for every project):
json
{
"mcpServers": {
"matterwave": {
"url": "https://<your-matterwave>/mcp",
"headers": { "Authorization": "Bearer ${env:MATTERWAVE_API_KEY}" }
}
}
}Set MATTERWAVE_API_KEY in the environment Cursor starts from, or write the key in place of ${env:MATTERWAVE_API_KEY} in a file you do not commit.
ChatGPT
ChatGPT's custom connectors authenticate with OAuth or not at all; a static bearer header cannot be configured there today. MatterWave's MCP endpoint is key-authenticated on purpose (see Authentication), so it is not reachable from ChatGPT until OAuth lands. A connector dialog that does offer an API-key or bearer field takes <your-key> as the token.
Any other client
Point it at https://<your-matterwave>/mcp with the header Authorization: Bearer <your-key>. The protocol is the whole interface; nothing MatterWave-specific is needed on the client.
What a client gets
Tools
| Tool | Needs | Does |
|---|---|---|
list_conversations | chat:read | One page of your conversations, most recent first. filter: default (unarchived, pinned first), all, pinned, archived; limit 1–200; cursor from the previous page's next_cursor. |
get_conversation | chat:read | A conversation and its transcript (the active branch, oldest first). Long transcripts are bounded; messages_left_out says how many of the oldest were dropped. |
search_conversations | chat:read | Full-text search over your own messages; each hit carries a snippet of where it matched. query up to 200 characters, limit 1–50. |
create_conversation | chat:write | A new, empty conversation (title optional). |
send_message | chat:write | Appends your message and returns MatterWave's whole answer, generated by the conversation's model (or model). Plan, budget and moderation apply exactly as in the app. |
list_models | chat:read | The models this deployment serves, which your plan includes, and the default. |
Every tool returns both prose (content) and JSON (structuredContent). The read tools are annotated readOnlyHint: true; the two writes are not, and nothing is destructiveHint, since no tool deletes. A key that lacks chat:write is not shown the write tools at all.
Resources
conversation://<id> is a conversation's transcript as Markdown (text/markdown). resources/list is your most recent conversations; resources/templates/list names the URI shape. Reading needs chat:read.
Transcripts, snippets and previews are your stored data arriving in another model's context. They are framed as such (a heading says what the text is, every turn is quoted under its role), but a client that renders them should treat them as information, not as instructions.
Prompts
None yet; prompts/list answers an empty list.
Protocol
The server implements the 2026-07-28 revision: every request is self-contained, carrying io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta and mirroring them into the MCP-Protocol-Version, Mcp-Method and Mcp-Name headers, which the server checks against the body (-32020 HeaderMismatch on disagreement, 400). There is no handshake and no session, so any replica answers any request. Results carry resultType, the server's io.modelcontextprotocol/serverInfo, and a caching verdict where the protocol defines one (cacheScope, ttlMs; transcripts and listings of your data say ttlMs: 0).
Clients on the earlier revisions (2025-03-26, 2025-06-18, 2025-11-25) are served too: they open with initialize, which is answered without an Mcp-Session-Id, so the connection stays stateless for them as well. A client that negotiates (the official SDKs do) discovers the modern path with server/discover and uses it.
Every reply is a single JSON object (application/json); the server never opens an SSE stream. GET and DELETE on the endpoint are 405. JSON-RPC batches are refused.
A raw call, for the curious:
bash
curl -s https://<your-matterwave>/mcp \
-H "Authorization: Bearer <your-key>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: search_conversations" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "search_conversations", "arguments": {"query": "pumpkin soup"},
"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}}}}'Authentication
The bearer is a MatterWave API key. OAuth, the protocol's standard authorization flow, is not offered yet: the identity provider cannot yet bind a token to this resource the way the MCP authorization specification requires, and a half-implemented OAuth is worse than a clear key-only story. The key route is the one the SDKs, the terminal client and the editor extension already use.
A request without a key, with a revoked or expired key, or from an address the key does not allow is answered at the HTTP level: 401 (with WWW-Authenticate: Bearer) or 403, and the body is a JSON-RPC error whose code is that status, so a client sees both. The same shape carries a 429 when the window below is full.
Limits, refusals and the audit trail
Rate limit. 60 requests a minute per key, the same ceiling as /v1, reported in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on every reply and refused with 429 and Retry-After. A key minted with its own rpm_limit is held to that too.
Refusals inside a tool come back as the protocol's tool error -- isError: true, the reason as text, and structuredContent.error with a machine-readable code:
code | Meaning |
|---|---|
budget, member_budget_exhausted | The organisation's (or your own) monthly allowance is used up; the message is kept, no answer is bought. |
upgrade_required | The plan does not include this (a model, more conversations); feature, current_plan and upgrade_to say what would. |
moderation | MatterWave would not send this message. |
not_found | No such conversation for this key: another organisation's conversation looks exactly like one that does not exist. |
forbidden | The key lacks the scope, or the organisation switched the model off. |
invalid_argument | A value the API refused (a bad cursor, say). |
upstream_error | The model gateway failed; partial_answer carries what arrived before it did. |
An unknown tool or malformed arguments are protocol errors (-32602), not tool errors.
Audit. Every tool call a key makes is a row in the organisation's audit trail (Settings → Organisation → Audit), action key.mcp_call, naming the key, the tool and whether it was refused -- never the arguments or the answer. The key's last_used_at moves as on any request.
Try it from Python
The Python SDK's examples carry a small client that connects, lists the tools and runs a search with the official mcp package:
bash
MATTERWAVE_BASE_URL=https://<your-matterwave> MATTERWAVE_API_KEY=<your-key> \
uv run sdk/python/examples/mcp_client.py "pumpkin soup"