Blog · October 25, 2026 · 9 min read · by Vilva Athiban P B

Claude Agent SDK + MCP Servers: mcpServers, allowedTools, Status Checks and Auth

Searching for “claude code sdk mcp” lands you on two different worlds. The Claude Code CLI has claude mcp add, interactive approval prompts and a /mcp screen. The Claude Agent SDK, the library you use to run Claude Code inside your own program, has none of that: there is no terminal to click through, so every MCP server, every permission and every credential has to be written down in code or in a config file before the run starts. This post is the SDK side of the story: the mcpServers option in TypeScript and Python, the allowedTools rule that makes MCP tools callable at all, how to read the server status from the init message, how long the first turn waits for servers, how authentication works without a browser, and what to check when a server fails.

Everything here is from the Agent SDK MCP documentation as of late October 2026. For the CLI side, which covers scopes, the claude mcp command and monorepo config, see our Claude Code MCP servers guide and the claude mcp command reference.

Why MCP in the SDK is a different job from MCP in the CLI

In an interactive session Claude Code asks you to approve each MCP tool the first time, offers a sign-in for servers that need OAuth, and remembers what you chose. An SDK program runs headless. The packages are @anthropic-ai/claude-agent-sdk for TypeScript, where you call query({ prompt, options }), and claude_agent_sdk for Python, where you call query(prompt=..., options=ClaudeAgentOptions(...)). Both take the same three MCP-related inputs: a map of servers, a list of allowed tools, and optionally a .mcp.json file that the SDK picks up from the project. Nothing is approved on your behalf unless you say so.

The three server shapes

Servers go in options.mcpServers (TypeScript) or mcp_servers= (Python), keyed by the name you choose. The key matters later, because it becomes part of every tool name. There are three shapes. If the server’s docs give you a command to run, it is a stdio server; if they give you a URL, it is HTTP or SSE; if you are writing the tools yourself, it is an in-process SDK server.

stdio: a local process

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "List files in my project",
  options: {
    mcpServers: {
      filesystem: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"],
      },
    },
    allowedTools: ["mcp__filesystem__*"],
  },
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

The Python version is the same shape with snake_case names:

from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

options = ClaudeAgentOptions(
    mcp_servers={
        "filesystem": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"],
        }
    },
    allowed_tools=["mcp__filesystem__*"],
)

async for message in query(prompt="List files in my project", options=options):
    if isinstance(message, ResultMessage) and message.subtype == "success":
        print(message.result)

HTTP and SSE: a remote server

Remote servers take type: "http" for the streamable HTTP transport or type: "sse" for SSE, a url, and optional headers. The docs site itself exposes one you can test against:

mcpServers: {
  "claude-code-docs": {
    type: "http",
    url: "https://code.claude.com/docs/mcp",
  },
},
allowedTools: ["mcp__claude-code-docs__*"],

One detail trips people up: in .mcp.json and other JSON config files, "streamable-http" is accepted as an alias for "http", but the SDK’s McpHttpServerConfig type only declares "http". Use "http" for anything you pass in code.

SDK servers: tools defined in your own process

If the tool is a function you already have, you do not need a separate process. The TypeScript SDK’s createSdkMcpServer() wraps your functions as an in-process MCP server; it takes a timeout option for how long a tool call may run. One thing to know: a tool whose input schema cannot be converted to JSON Schema is silently left out of the server’s tool list, with a Node process warning whose code is CLAUDE_SDK_MCP_TOOL_SCHEMA_UNCONVERTIBLE (from TypeScript SDK v0.3.286; before that, one bad schema broke the whole listing, so none of the server’s tools reached Claude).

From .mcp.json instead of code

A .mcp.json at the project root is loaded when the project setting source is enabled, which it is with the default query() options. If you set settingSources explicitly, you must include "project" or the file is ignored. The ${API_KEY} syntax inside it expands environment variables at runtime. How .mcp.json behaves in subfolders and monorepos is covered in our .mcp.json project-scope guide.

allowedTools: without it, Claude sees the tools and cannot use them

This is the step most first SDK programs miss. MCP tools require explicit permission; without it Claude will notice the tools exist and then fail to call them. Tool names follow mcp__<server>__<tool>, where the server part is your key in mcpServers with dots and spaces replaced by underscores, so a key of my.server gives mcp__my_server__list_issues. Wildcards cover a whole server:

allowedTools: [
  "mcp__github__*",          // every tool from the github server
  "mcp__db__query",          // only query from the db server
  "mcp__slack__send_message" // only send_message from slack
]

Prefer this over permission modes. permissionMode: "acceptEdits" does not auto-approve MCP tools at all; it only covers file edits and filesystem Bash commands. bypassPermissions does approve them, but it also switches off most other safety prompts, which is far broader than granting one server. A wildcard in allowedTools grants exactly the server you want and nothing else. Listing a tool here also “names” the server, which affects startup timing below.

Reading the init message: connected, pending, failed, needs-auth

Every query begins with a system message of subtype init. It carries a tools array, where MCP tools are the names starting with mcp__, and an mcp_servers array with a status per server: pending, connected, failed, needs-auth or disabled. The two to act on are failed and needs-auth:

for await (const message of query({ prompt, options })) {
  if (message.type === "system" && message.subtype === "init") {
    const mcpTools = message.tools.filter((n) => n.startsWith("mcp__"));
    console.log("MCP tools:", mcpTools);

    const unavailable = message.mcp_servers.filter(
      (s) => s.status === "failed" || s.status === "needs-auth"
    );
    if (unavailable.length > 0) console.warn("Unavailable:", unavailable);
  }
  if (message.type === "result" && message.subtype === "error_during_execution") {
    console.error("Execution failed");
  }
}

Do not treat pending as a failure on its own. It can mean the server has not connected yet, that its tool list was served from the discovery cache and it will connect on first use, or that the connection deadline expired. A server that fails to connect does not throw; the run simply continues without its tools, and Claude may fall back to built-in tools, so check the status before trusting a result. Later in a session, mcpServerStatus() on the TypeScript query object or ClaudeSDKClient.get_mcp_status() in Python gives the current picture.

How long the first turn waits

Servers start connecting when the session starts, and the init message is sent once the first-turn wait ends. The wait depends on where the server was configured:

ServerDelays the first turn?Limit
stdio, or HTTP/SSE with no cached tool listYes, until it connectsMCP_TIMEOUT, 30 s by default
Remote server with a cached tool listNo; connects on its first tool callOwn deferred-connect timeout
In-process SDK serverYes, until it lists its toolsMCP_TIMEOUT per attempt
Servers from .mcp.json or pluginsOften still pending at initUp to 2 s, and only for named or alwaysLoad servers when tool search is on

Tool search is on by default and withholds tool definitions until a turn needs them, so settings-file servers are mostly deferred. If you need control, set CLAUDE_CODE_MCP_STARTUP_WAIT_MS in the env option (Claude Code v2.1.274 or later): the first turn waits up to that many milliseconds, and 0 skips the wait for every server except a permissionPromptToolName server, in-process SDK servers and still-connecting named servers, which keep at least their 2 seconds. Setting MCP_CONNECTION_NONBLOCKING=0 instead makes startup itself wait for the whole batch, capped at 5 s unless MCP_CONNECT_TIMEOUT_MS says otherwise. Running tool calls have their own limit, MCP_TOOL_TIMEOUT, which we cover in the MCP timeout post.

Authentication without a browser

For stdio servers, pass credentials through the env field; for HTTP and SSE, put them in headers. The documented GitHub example uses the remote GitHub MCP server with a personal access token and allows a single tool:

mcpServers: {
  github: {
    type: "http",
    url: "https://api.githubcopilot.com/mcp/",
    headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
  },
},
allowedTools: ["mcp__github__list_issues"],

The SDK does not open a browser or run an interactive OAuth flow. If a server answers with an authorization challenge and Claude Code has no token to send, the server reports needs-auth, meaning no usable sign-in was found: pass a token in headers, or sign in once from an interactive Claude Code session on the same machine before the run. It reports failed with an error beginning “Couldn’t read the stored credentials” when the credential store itself could not be read (before v2.1.307 a locked Mac login keychain showed as needs-auth instead). For a real OAuth integration, complete the flow in your own app and pass the resulting access token in headers.

Troubleshooting

  • Status is failed. Usual causes: a missing environment variable the server expects, an npx package that is not installed or Node not on the PATH, a bad database connection string, or a remote URL blocked by a firewall. Raise MCP_TIMEOUT (milliseconds) for servers that are slow to start, or pre-warm them.
  • Status stays pending. See the timing table; a settings-file server with tool search on is expected to show pending at init. Poll mcpServerStatus() or get_mcp_status() after the first turn.
  • Connected, then pending again. A remote connection that drops mid-session goes back to pending while Claude Code reconnects. After five failed attempts it reports failed (or needs-auth if it needs authorizing again) but keeps retrying in the background; reconnectMcpServer() in TypeScript or reconnect_mcp_server() in Python forces a retry.
  • Tools visible but never called. Almost always a missing allowedTools entry. Check the exact server key and the underscore substitution.
  • Tool output replaced by an error naming a file. A successful result over 25,000 tokens (with no image content) is written to a file and the tool result becomes an error with the path, so the agent can read it back in pieces; tune with MAX_MCP_OUTPUT_TOKENS. Text results over 50,000 characters are saved to a file regardless, unless the tool declares anthropic/maxResultSizeChars.

SDK versus CLI in one table

ConcernClaude Code CLIAgent SDK
Adding a serverclaude mcp add, scopes local/project/usermcpServers option, or .mcp.json via the project setting source
Approving toolsInteractive prompt, rememberedallowedTools patterns, nothing remembered
OAuthBrowser sign-in from the sessionToken in headers; needs-auth otherwise
Seeing status/mcp and claude mcp listinit message, mcpServerStatus()

If your SDK program is really a one-off script, claude -p with the same .mcp.json may be the shorter route; our headless mode guide covers that. The SDK earns its keep when you need the server map, the tool allowlist and the status checks to live in code you can test.

Keep reading

Desk Exercises for Programmers: 15 Moves to Do While Your AI Agent Codes

7 min read

Exercise While Coding: Turn Claude Code Wait Time Into Micro-Workouts

7 min read

Claude Code Notch Notifications on Mac: See When Claude Is Done Without Watching the Terminal

7 min read

Claude Code Tips and Tricks: 12 Ways to Use Claude Code Effectively

8 min read

Claude Code Multiple Sessions: How to Run Agents in Parallel Without Losing Track

6 min read

A Claude Code Workflow That Doesn't Involve Watching the Terminal

5 min read

Claude Code Hooks: A Practical Guide to Automating Your Agent Workflow

7 min read

Claude Code Auto Mode: Fewer Permission Prompts Without Living Dangerously

7 min read

Claude Code Subagents: How to Delegate Work to Specialized Agents

7 min read

Claude Code Context Management: Treat the Context Window Like a Budget

6 min read

Per-Subagent Model Selection: Route the Grunt Work Down, Keep the Judgment Up Top

7 min read

Skills and Plugins: How to Teach Claude Code Your Way of Working

7 min read

Claude Code Background Tasks: Run Long Commands Without Blocking Your Session

6 min read

Codex CLI Notifications: How to Get a Ding When Codex Is Done or Needs Input

7 min read

Cursor Notification When Done: Every Way to Get Notified When Cursor Finishes

6 min read

Claude Code Notifications: How to Get Notified When Claude Code Finishes or Needs Your Input

6 min read

Want to Be Notified When Claude Responds? How Claude Notifications Work on Web, Desktop, and Mobile

5 min read

Claude Code Notification Scripts: Copy-Paste Recipes for Every Platform

6 min read

Get a Ding the Moment Codex Needs Your Response

6 min read

Get Notified the Moment Claude Code Is Waiting for Your Input

6 min read

“Notifications Are Turned Off for Claude” — Here Is the Fix

6 min read

Codex Sound When Done: Make Codex CLI Play a Sound When It Finishes

6 min read

terminal-notifier + Claude Code: Native macOS Alerts When Your Agent Finishes

6 min read

Gemini CLI Notifications: How to Get a Sound or Alert When Gemini Finishes

6 min read

Get Claude Code Notifications on Your Phone

6 min read

Claude Code Remote Control, Explained

7 min read

preferredNotifChannel: Claude Code's Built-In Notification Setting

6 min read

Claude Code Notifications in tmux and Over SSH

7 min read

Claude Code Effort Levels: Why Your Setting Keeps Getting Ignored

8 min read

Codex vs Claude Code Notifications: How Each One Tells You It Is Done

8 min read

Claude Code Notifications Not Working: A Diagnostic Checklist

10 min read

Claude Code Notifications Inside Your Editor's Terminal

7 min read

CLAUDE_CODE_DISABLE_BACKGROUND_TASKS Explained

7 min read

CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP Explained

8 min read

Cursor Alerts Explained: Every Alert Cursor Raises, and How to Control Them

7 min read

Claude Code Environment Variables: The Groups That Actually Matter

10 min read

Claude Code Notifications on WSL and Windows

9 min read

Claude Code Checkpoints and /rewind

8 min read

Claude Remote Control: How It Works, Turning It Off, and Auto Mode

7 min read

Claude Code /loop: Poll a Deploy, Babysit a PR, and Get Told When It's Done

8 min read

Setting Up NotchFit With OpenAI Codex CLI: Hooks, $notchfit-plan, and the Daily Flow

7 min read

Claude Mods Explained: What the New Plugin Layer Can Do, and How to Turn On 'You Should Know'

9 min read

NotchFit vs Mac Break Reminder Apps: A Clock or a Signal From Your Coding Agent?

8 min read

Claude Code Headless Mode: Running claude -p in Scripts and CI, and the Three Signals That It Finished

10 min read

Building a Movement Habit at a Desk: Use the Agent Run as the Cue, One Set as the Unit, and Streaks Carefully

8 min read

Claude Code MCP Servers Explained: Adding Servers, Choosing a Scope, Handling Secrets, and Why Long Tool Calls Go to the Background

10 min read

/notchfit:plan Explained: How Claude Code or Codex Writes a Weekly Workout Plan the Notch Can Actually Run

8 min read

Claude Code Spinner Verbs Explained: What “Pondering” and “Brewing” Mean, How to Change Them, and Why You Should Stop Watching the Spinner

7 min read

Why NotchFit's Done and Permission Alerts Wait Until Your Set Ends, When That Costs You, and How to Turn the Workout Off

7 min read

How to Resume a Claude Code Session: --continue vs --resume, Naming Sessions, the Session Picker, Branching, and What Comes Back

9 min read

NotchFit on a Mac Without a Notch: How Pill Mode Works on Mac mini, iMac, Mac Studio and a MacBook With the Lid Closed

6 min read

How to Run a Claude Code Session in the Background: /background, claude --bg, Agent View, and Getting Back to It

9 min read

What a Notch App Can See: NotchFit's Local-First Design, What the Claude Code and Codex Hooks Send, and What Stays on Your Mac

7 min read

Why Codex CLI Asks for Permission Every Time, and How to Tune the Sandbox and Approval Policy Without Going Full Access

8 min read

What to Do While Claude Code or Codex Works: A Simple Protocol for the Two-to-Twenty-Minute Gap in Every Agent Run

7 min read

Is It Possible to Get a Notification When Claude Code or Codex Hits a Reset? Yes for Claude Code: the quota_auto_resume Hooks, Explained

8 min read

What a MacBook Notch App Can Actually Do: The Five Kinds of Notch Utility, Their Trade-offs, and Where NotchFit Fits

7 min read

How to Make MCP Servers Not Take Up as Much Context in Claude Code: Tool Search, alwaysLoad, Scopes and Output Caps

8 min read

The notchfit Command Line: Drive Your Workout, Read Status as JSON, Validate Plans and Test the Notch Without Leaving the Terminal

7 min read

How to Audit MCP Tool Calls Made by Claude Code: A Five-Minute Hook Audit Log, OpenTelemetry Tool Events, and the Transcript for Investigations

9 min read

Exercise Snacks: What the Research Actually Says About One-Minute Vigorous Breaks, What It Does Not, and How NotchFit's Agent-Run Sets Compare

8 min read

How Long Until a Claude Code MCP Tool Call Times Out? The Four Timers (Startup, Two-Minute Backgrounding, Idle, Wall-Clock), Their Defaults, and Which One to Change

8 min read

Sitting Too Long as a Developer: The Posture Problems Coding Agents Made Worse, What the Research Says About Breaking Up Sitting, and How NotchFit Turns an Agent Run Into the Break

8 min read

Claude Code .mcp.json Explained: Where Project-Scoped MCP Servers Are Read From, What Happens in Subfolders and Monorepos, and Why a Committed Server Shows Pending Approval

8 min read

Standing Desk Exercises for Developers: Why Standing Still Is Not the Goal, Eight Movements That Fit a Thirty-Second Agent Run, and How NotchFit Cues Them From the Notch

8 min read

The claude mcp Command Reference: add Syntax by Transport, What --scope local, project and user Actually Mean, add-json and add-from-claude-desktop, list, get, remove, login and serve

9 min read

Your First Week With NotchFit: Install and Simulate, Run Real Tasks, Write a Smaller Plan Than You Want, Tune the Alerts, Survive the First Skipped Day, and Decide What Stays

8 min read

How to Stop a Running Claude Code Task: Esc vs Ctrl+C, Background Tasks, Subagents, and Disconnecting Remote Control

8 min read

Pomodoro vs Agent-Driven Breaks: Timing Movement to Claude Code and Codex Runs Instead of a Timer

7 min read

Claude Desktop MCP Configuration: claude_desktop_config.json, Logs, Extensions, and the Difference From Claude Code

9 min read

The 20-20-20 Rule for Developers: Eye Strain, the Evidence, and Agent Runs as the Cue

8 min read

Discreet Desk Exercises for an Open-Plan Office While Claude Code Runs

7 min read