Claude Code¶
There are two ways to connect Claude Code to a MEHO backplane:
- The MEHO plugin (recommended) — one command installs the MCP wiring and MEHO's operating discipline as skills. Start here.
- A manual
.mcp.json— native HTTP MCP you configure yourself. Use it for non-plugin clients, CI bots, or when you want the wiring in the repo.
Either way, Claude Code must run on a machine on the internal network / VPN — MEHO is internal-only — and nothing is exposed to the internet.
The one-command path: the MEHO plugin¶
MEHO ships an in-repo Claude Code plugin marketplace. Add it and install the plugin:
claude plugin marketplace add evoila/meho
/plugin install meho@meho
The plugin carries the MCP server (over the bundled mcp-remote stdio
shim, so no per-project .mcp.json editing) and loads the MEHO-first
operating discipline as skills. Point it at your backplane by setting
the endpoint once — either an environment variable or a small config
file the plugin reads:
# ~/.config/meho/plugin.env
MEHO_MCP_URL="https://meho.example.com/mcp"
# MEHO_CA_CERT="/path/to/internal-ca.pem" # only on internal-CA deploys
MEHO_CA_CERT is only needed on an internal-CA deploy (the plugin's
shim runs on Node, which does not read the OS trust store). To opt a
session into the operator planes, set MEHO_MCP_SCOPES to add
mcp:admin — see
MCP surface and scopes.
The realm still needs the public meho-mcp client and its loopback
redirect URI, exactly as the manual path below (see
Keycloak realm setup).
The manual path: configure .mcp.json¶
If you are not using the plugin, Claude Code also speaks MCP over
native HTTP — no shim. You point a project-scoped .mcp.json at the
backplane's /mcp route and pin the pre-registered meho-mcp public
client; Claude Code runs the OAuth 2.1 authorization-code + PKCE flow
itself, listening on a loopback port for the callback. This is the
pattern both MEHO dogfood repos run daily.
Add a meho server to the workspace .mcp.json. Claude Code reads it
at session start; restart the session after edits:
{
"mcpServers": {
"meho": {
"type": "http",
"url": "https://meho.example.com/mcp",
"oauth": {
"clientId": "meho-mcp",
"callbackPort": 8456,
"scopes": "mcp:read mcp:execute"
}
}
}
}
clientId: meho-mcppins the pre-registered public client, so Claude Code skips Dynamic Client Registration — which Keycloak's Trusted Hosts policy blocks on any real realm (Wall W1 on the troubleshooting page).callbackPortfixes the loopback port the authorization-code callback returns to. It must match a registered redirect URI on themeho-mcpclient (next section).
One-time realm redirect URI¶
Claude Code's callback path is /callback. With the callbackPort
above, register this loopback redirect URI on the meho-mcp client:
http://localhost:8456/callback
(This is distinct from the /oauth/callback path the
Claude Desktop shim
uses — a client used for both needs both.) Claude Code's flow also
requests offline_access to obtain a refresh token, so the meho-mcp
client must carry offline_access as an optional scope, or the
authorization request fails with invalid_scope (Wall W7). Both are
covered in Keycloak realm setup.
Internal-CA trust¶
Claude Code runs on Node, which trusts only public CAs by default and does not read the OS trust store. On an internal-CA deploy, point Node at the CA bundle before starting Claude Code:
export NODE_EXTRA_CA_CERTS=/path/to/internal-ca.pem
Without it the OAuth discovery and token requests fail with a
TLS/unable to verify error even though curl and meho login
(which use the OS store) succeed from the same machine. Skip this on a
publicly-trusted deploy.
Verify¶
After restarting the session, Claude Code lists the meho server's
tools. Confirm the connection with two calls:
meho_status— returns operator identity, Vault, and DB state.list_targets— returns your registered targets.
If the server shows as failed, or the browser never opens for the OAuth
step, the troubleshooting page maps the symptom —
a DCR 403 Host not trusted means the clientId was dropped from
.mcp.json; an invalid_scope means the offline_access optional
scope is missing.