[email protected]
BelgiumFranceSwitzerlandUnited Arab Emirates
LinkedInFacebook
Kube IT Consulting
My coursesContact us

Configuring MCP servers correctly — the parts that bite

The two-part declaration people get half right, why an API key is not an MCP credential, and where secrets should live so a prompt injection cannot read them.

Facts checked 7 August 2026 against the vendor's own documentation. Exam codes, curricula and product versions change — verify against the official page before you book anything.

Model Context Protocol is how you give a model access to a real system: your issue tracker, your repository host, your monitoring. Most of the failures we see aren’t conceptual. They’re four specific configuration mistakes, and all four produce symptoms that look like something else. APIs move, so check the current Anthropic documentation before copying any of the shapes below into production.

Where each piece of configuration lives
Agent definition
  • mcp_servers[]
  • tools: mcp_toolset

Both halves required. Declaring a server without referencing it grants nothing.

Session
  • agent id
  • environment_id
  • vault_ids
Egress
  • Credential injected here

Anthropic adds the token after the request leaves the container, so sandbox code never sees it.

MCP server
  • Hosted endpoint
  • OAuth bearer token

Three of the four mistakes below are a piece of configuration sitting in the wrong band.

1. The declaration has two halves, and half of it is silently useless

The most common failure. You declare the server:

{
  "mcp_servers": [
    { "type": "url", "name": "linear", "url": "https://mcp.linear.app/mcp" }
  ]
}

…and nothing happens. No tools appear.

Declaring a server doesn’t grant access to its tools. You also have to reference it from the tool list:

{
  "mcp_servers": [
    { "type": "url", "name": "linear", "url": "https://mcp.linear.app/mcp" }
  ],
  "tools": [
    { "type": "mcp_toolset", "mcp_server_name": "linear" }
  ]
}

The mcp_server_name has to match the name in mcp_servers exactly. Every declared server needs exactly one toolset referencing it; omitting it is a validation error rather than a silent no-op, which at least gives you a clear failure.

Think of it as two separate statements: this server exists, and this agent may use it. Both are required.

2. An API key is usually not an MCP credential

This one costs people an afternoon.

Hosted MCP servers typically authenticate with OAuth bearer tokens rather than the service’s own API keys. A Notion integration token starting ntn_ authenticates perfectly against Notion’s REST API and will not work as an MCP credential for Notion’s MCP server. They’re different auth systems that happen to belong to the same vendor.

The symptom is misleading. The connection appears to establish, then tool calls fail with an authentication error from the far side. It reads as a broken server when what you have is a wrong credential type.

Check the MCP server’s own documentation for how to obtain a token. Don’t assume your existing API key transfers.

3. Secrets belong in a vault, not in the agent definition

The agent’s mcp_servers array declares type, name and url. There’s no auth field, and that’s deliberate.

Credentials live in a vault and attach to a session:

session = client.beta.sessions.create(
    agent=agent.id,
    environment_id=environment.id,
    vault_ids=[vault.id],
)

Two things follow from this design, and both matter more than the ergonomics.

Agent definitions stay shareable. The agent is a reusable, versioned object. Keeping secrets out of it means you can check it into version control and hand it between environments without a scrubbing step.

The credential never enters the sandbox. This is the part worth internalising. Anthropic’s side injects the credential into the outbound request after it leaves the container. Code running in the sandbox, including code the model itself wrote, can’t read the token even under prompt injection. Credentials are matched to servers by URL, and OAuth tokens refresh automatically when you supply a refresh block.

Contrast that with the tempting shortcut of putting the key in the system prompt. Prompts and messages are stored in the session’s event history, returned by the events API, and included in compaction summaries. A secret placed there is durably persisted and readable for the life of the session. Don’t do it.

4. Restricted networking silently breaks MCP

If your environment uses limited networking, the container can’t reach your MCP servers unless you say so:

{
  "networking": {
    "type": "limited",
    "allow_mcp_servers": true
  }
}

Either set allow_mcp_servers: true, or list each MCP server’s domain in allowed_hosts. Miss this and the tools fail quietly, which reads as “the MCP integration doesn’t work” rather than “the firewall is doing its job”.

There’s a related trap for non-MCP secrets. Vault environment-variable credentials have their own allowed_hosts, separate from the environment’s. That list controls which hosts the secret may be substituted for; the environment’s list controls which hosts are reachable at all. A domain missing from either layer fails, and the two failures look identical.

An invalid credential does not stop a session

Worth knowing for debugging: if a vault credential is invalid for a declared MCP server, the session still creates successfully. You get a session.error event describing the auth failure, and auth is retried on the next idle-to-running transition.

So “the session started” tells you nothing about whether your credentials work. Watch the event stream.

Tool descriptions are configuration too

One thing that doesn’t appear in any setup guide but changes results measurably: write tool descriptions that say when to call a tool, not just what it does.

Recent models reach for tools more conservatively than their predecessors. A description reading “Searches the issue tracker” underperforms one reading “Call this when the user asks about open issues, sprint status, or anything assigned to a person.” Prescriptive trigger conditions give real lift.

The same applies in reverse. Aggressive phrasing written for older models — CRITICAL: You MUST always use this tool — now causes over-triggering. Say what you mean at normal volume.

A checklist

Before you debug anything else:

  1. Is there an mcp_toolset entry referencing every declared server by exact name?
  2. Is the credential an OAuth token rather than a REST API key?
  3. Is the credential in a vault, attached via vault_ids, and not in the system prompt?
  4. If networking is limited, is allow_mcp_servers true or the domain in allowed_hosts?
  5. Are you watching the event stream for session.error?

Four of those five are configuration errors that present as “MCP is broken”.

Next steps

Get help

Running this in production?

We operate Kubernetes and OpenShift for clients across the EU and the Gulf, and train the teams who inherit them. Platform assessments, migrations and hands-on enablement.

Talk to us