Connecting a client¶
Claude Desktop¶
Where the configuration lives¶
Open it from the app: Claude menu → Settings… → Developer → Edit Config. That creates the file if it does not exist yet.
| Path | |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
Quit Claude Desktop completely and reopen it after every change. On macOS, closing the window does not quit the app — the process keeps the old configuration, and a connector that shows up in the list only means "it started".
The stdio server (macOS, Linux)¶
The simplest arrangement: no port, no bridge, one token in a file.
{
"mcpServers": {
"inveniordm": {
"command": "python3",
"args": ["/Users/you/invenio-mcp/stdio/server.py"],
"env": {
"INVENIO_API": "https://invenio.example.org/api",
"INVENIO_CA_BUNDLE": "/Users/you/invenio-mcp/stdio/ca.crt",
"MCP_LANG": "en"
}
}
}
}
Use absolute paths. Claude Desktop does not launch the server from your project
directory, and a relative path resolves somewhere you did not intend. The token comes
from INVENIO_TOKEN or from .token beside server.py — see Running the stdio
server.
The HTTP server, via mcp-remote (macOS, Linux)¶
{
"mcpServers": {
"invenio-mcp": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.example.org/mcp",
"--header", "Authorization:${AUTH_HEADER}",
"--transport", "http-only"],
"env": {"AUTH_HEADER": "Bearer <PAT>"}
}
}
}
Node.js has to be installed, because mcp-remote runs through npx.
The HTTP server, via mcp-remote (Windows)¶
Do not put spaces in the arguments. On Windows, Claude Desktop launches through
cmd without quoting them. Keep the space in Bearer inside the environment variable
instead.
{
"mcpServers": {
"invenio-mcp": {
"command": "cmd",
"args": ["/c", "npx", "-y", "mcp-remote",
"https://mcp.example.org/mcp",
"--header", "Authorization:${AUTH_HEADER}",
"--transport", "http-only"],
"env": {
"AUTH_HEADER": "Bearer <PAT>",
"MCP_LANG": "en",
"NODE_EXTRA_CA_CERTS": "C:\\certs\\ca.crt"
}
}
}
}
- Do not write a full path in
command.C:\Program Files\...breaks at the space. - With a self-signed certificate you need
NODE_EXTRA_CA_CERTS. Node does not read the Windows certificate store, socertutil -addstorealone is not enough — and conversely the browser does not readNODE_EXTRA_CA_CERTS. You generally need both. - Add
--allow-httpfor plaintext HTTP. The token then travels in the clear, so only over a trusted path.
In keycloak mode there is no --header; a browser login happens instead, and
--auth-timeout 300 is required because the 30-second default is too short.
Custom connectors (remote MCP, no local process)¶
Claude can also reach the HTTP server directly, with no mcp-remote and nothing running
on your machine: Settings → Connectors → Add custom connector, then the server's URL.
Team and Enterprise organisations add these under Organization settings instead, and
only an owner can. OAuth is supported, including a client id and secret under Advanced
settings — which is what keycloak mode is for.
The connection comes from Anthropic's servers, not your machine
A custom connector is brokered through your Claude account, so the URL has to be
reachable from the public internet. An MCP server on 127.0.0.1, inside a campus
network, or behind a VPN cannot be used this way. Those need mcp-remote, which
connects from your own machine.
If you do expose it, use keycloak mode. A personal access token pasted into a header would be held outside your institution.
When it does not work¶
The logs say why. MCP connection failures land in mcp.log, and everything a stdio
server writes to stderr lands in mcp-server-<name>.log.
| Path | |
|---|---|
| macOS | ~/Library/Logs/Claude/ |
| Windows | %APPDATA%\Claude\logs\ |
Run the same command by hand with the same environment to see what it prints — a
missing locales/ directory or an unreadable .token stops the server at startup with
a message that only reaches these files otherwise.
More in Troubleshooting.
Claude Code¶
# the HTTP server
claude mcp add --transport http invenio-mcp https://<mcp>/mcp \
--header "Authorization: Bearer $PAT"
# the stdio server
claude mcp add inveniordm python3 /path/to/invenio-mcp/stdio/server.py \
--env INVENIO_API=https://invenio.example.org/api \
--env INVENIO_CA_BUNDLE=/path/to/ca.crt
Any other MCP client¶
Nothing here is Claude-specific. A client needs one of two things:
- the stdio server — a command to run (
python3 .../stdio/server.py) and the environment to run it with, or - the HTTP server — a URL, and either an
Authorization: Bearerheader (PAT mode) or OAuth 2.1 support (keycloak mode)
Tool names are the same either way.
When a client only authorizes after a 401¶
Some clients build their OAuth callback listener solely from the failure path, so a
server that lets them connect anonymously never triggers it. Point those at
/mcp-auth instead of /mcp — same tools, but 401 from the first request. The
mechanics are in Authorization.
Talking to it with curl¶
Every call is JSON-RPC over one POST. Both Content-Type and an Accept covering
application/json and text/event-stream are required — the MCP Streamable HTTP
transport rejects the request without them.
curl -s -X POST https://<mcp>/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $PAT" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"my_records","arguments":{"size":5}}}'
conformance/curl-tour.sh walks the whole authorization flow this way, as a
walkthrough.
Which language the client sees¶
The language is fixed per server process — MCP has no locale negotiation, so
initialize carries no locale field. For the stdio server, MCP_LANG goes in the
client's env block, because the client is what launches it. For the HTTP server it is
set where the container is configured, and every client of that instance sees the same
language. See Languages.