Dashboard|

Remote MCP Servers

Register your own MCP servers as agent tools — with HMAC-signed end-user identity headers on every call.

Register a server

Any standard remote MCP server speaks Streamable HTTP — stateless with a legacy-handshake fallback. Register it, and Cencori discovers its tools:

curl -X POST https://cencori.com/v1/mcp/servers \
  -H "CENCORI_API_KEY: sk_secret_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"docs-search","url":"https://mcp.example.com/mcp"}'

The response is a discovery result: the server object plus its freshly discovered tools (everything counts as added on first registration). Two things happen here:

  • If discovery succeeds, the server is active and its tools are snapshotted.
  • The response carries identity_secret exactly once. Store it securely — it is never returned again.

If the server requires an OAuth credential or API key, first create a tool connection (see Actions and Approvals) and pass auth_connection_id; Cencori decrypts it only at call time and injects it as an Authorization: Bearer header.

Use the tools

Grant tools in an agent manifest (mcp.servers[].tools), or reference them directly in a run's tools array. Each grant is exposed to the model under a deterministic hosted name:

mcp__<server-id-prefix>__<tool-name>

The model never sees your server id or URL — only hosted names — so a prompt cannot steer it to a different MCP endpoint. On dispatch, Cencori routes the hosted name back to the registered server, enforces the agent's network policy (default-deny egress allowlist) before calling, and reuses the discovery transport.

Write and destructive tools follow the normal approval ladder: they create an action with mcp_server_id in its approval policy, resolved via POST /v1/actions/:id/approve; the approved dispatch calls the same server with the same identity headers.

Signed end-user identity headers

Every tools/call and approved dispatch sends three headers so your server knows which tenant and end user the call is for:

HeaderMeaning
X-Cencori-User-ContextBase64url-encoded JSON claims
X-Cencori-User-TimestampUnix seconds when signed (iat)
X-Cencori-User-Signaturesha256=<hex> HMAC-SHA256 over <timestamp>.<claims> keyed with your identity_secret

Claims:

{
  "project_id": "...",
  "tool": "mcp__mcpex1234__search",
  "tenant_id": "acme-corp",          // your external tenant id (when tenant-scoped)
  "external_user_id": "u_42",        // your end-user id
  "installation_id": "ins_...",      // null outside installed agents
  "run_id": "run_...",               // null for approved actions
  "session_id": null,
  "turn_number": null,
  "iat": 1760000000
}

Verify in Node (same envelope as Cencori webhook signatures):

import { createHmac, timingSafeEqual } from 'crypto';
 
const claims = JSON.parse(Buffer.from(headers['x-cencori-user-context'], 'base64url').toString());
const expected = `sha256=${createHmac('sha256', IDENTITY_SECRET)
  .update(`${headers['x-cencori-user-timestamp']}.${headers['x-cencori-user-context']}`)
  .digest('hex')}`;
 
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(headers['x-cencori-user-signature']));
// Then check Math.abs(now - claims.iat) <= 300 before honoring the context.

Treat an unsigned or invalid call as a protocol violation — reject it and do not fall back to unauthenticated behavior.

Spinoffs, snapshots, and hygiene

  • POST /v1/mcp/servers/:id/refresh-tools re-discovers and reports added/removed tools; unhealthy servers surface last_error.
  • Manifests snapshot granted tools at publish (and delegate runs pin the effective version), so a tool disappearing mid-run is rejected, not guessed.
  • Lost the secret? Re-register the server (a fresh identity_secret is minted). Server ids use the mcp_ prefix; all Embedded Agents ids are returned and accepted with their API prefix.