Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Connect to external tools with MCP changedagent-sdk/mcp

Nearest release: v2.1.302, published 8 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 17 Oct 2026 01:27 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.

Upstream edited
Recorded here
Lines+629added
Lines−665removed
From line 6 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits20to this page, all time

The whole hunk

from line 6, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 6
66 
77MCP servers can run as local processes, connect over HTTP, or execute directly within your SDK application.
88 
9<Note>
10 This page covers MCP configuration for the Agent SDK. To add MCP servers to the Claude Code CLI so they load in every project, see [MCP installation scopes](/docs/en/mcp#mcp-installation-scopes).
11</Note>
9> [!NOTE]
10>
11> This page covers MCP configuration for the Agent SDK. To add MCP servers to the Claude Code CLI so they load in every project, see [MCP installation scopes](/docs/en/mcp#mcp-installation-scopes).
1212 
1313## Quickstart
1414 
1515This example connects to the [Claude Code documentation](https://code.claude.com/docs) MCP server using [HTTP transport](#http%2Fsse-servers) and uses [`allowedTools`](#allow-mcp-tools) with a wildcard to permit all tools from the server.
1616 
17<CodeGroup>
18 ```typescript TypeScript theme={null}
19 import { query } from "@anthropic-ai/claude-agent-sdk";
20 
21 for await (const message of query({
22 prompt: "Use the docs MCP server to explain what hooks are in Claude Code",
23 options: {
24 mcpServers: {
25 "claude-code-docs": {
26 type: "http",
27 url: "https://code.claude.com/docs/mcp"
28 }
29 },
30 allowedTools: ["mcp__claude-code-docs__*"]
31 }
32 })) {
33 if (message.type === "result" && message.subtype === "success") {
34 console.log(message.result);
35 }
36 }
37 ```
38 
39 ```python Python theme={null}
40 import asyncio
41 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
42 
43 
44 async def main():
45 options = ClaudeAgentOptions(
46 mcp_servers={
47 "claude-code-docs": {
48 "type": "http",
49 "url": "https://code.claude.com/docs/mcp",
50 }
51 },
52 allowed_tools=["mcp__claude-code-docs__*"],
53 )
54 
55 async for message in query(
56 prompt="Use the docs MCP server to explain what hooks are in Claude Code",
57 options=options,
58 ):
59 if isinstance(message, ResultMessage) and message.subtype == "success":
60 print(message.result)
61 
62 
63 asyncio.run(main())
64 ```
65</CodeGroup>
17```typescript TypeScript theme={null}
18import { query } from "@anthropic-ai/claude-agent-sdk";
19 
20for await (const message of query({
21 prompt: "Use the docs MCP server to explain what hooks are in Claude Code",
22 options: {
23 mcpServers: {
24 "claude-code-docs": {
25 type: "http",
26 url: "https://code.claude.com/docs/mcp"
27 }
28 },
29 allowedTools: ["mcp__claude-code-docs__*"]
30 }
31})) {
32 if (message.type === "result" && message.subtype === "success") {
33 console.log(message.result);
34 }
35}
36```
37 
38```python Python theme={null}
39import asyncio
40from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
41 
42 
43async def main():
44 options = ClaudeAgentOptions(
45 mcp_servers={
46 "claude-code-docs": {
47 "type": "http",
48 "url": "https://code.claude.com/docs/mcp",
49 }
50 },
51 allowed_tools=["mcp__claude-code-docs__*"],
52 )
53 
54 async for message in query(
55 prompt="Use the docs MCP server to explain what hooks are in Claude Code",
56 options=options,
57 ):
58 if isinstance(message, ResultMessage) and message.subtype == "success":
59 print(message.result)
60 
61 
62asyncio.run(main())
63```
6664 
6765The agent connects to the documentation server, searches for information about hooks, and returns the results.
6866 
from line 68
7068 
7169You can configure MCP servers in code when calling `query()`, or in a `.mcp.json` file loaded via [`settingSources`](#from-a-config-file).
7270 
73<span id="in-code" />
74 
7571### Add a server in code
7672 
7773Pass MCP servers directly in the `mcpServers` option. This example starts a local filesystem MCP server for `/Users/me/projects`. Replace that path with a directory on your machine:
7874 
79<CodeGroup>
80 ```typescript TypeScript theme={null}
81 import { query } from "@anthropic-ai/claude-agent-sdk";
82 
83 for await (const message of query({
84 prompt: "List files in my project",
85 options: {
86 mcpServers: {
87 filesystem: {
88 command: "npx",
89 args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
90 }
91 },
92 allowedTools: ["mcp__filesystem__*"]
93 }
94 })) {
95 if (message.type === "result" && message.subtype === "success") {
96 console.log(message.result);
97 }
98 }
99 ```
100 
101 ```python Python theme={null}
102 import asyncio
103 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
104 
105 
106 async def main():
107 options = ClaudeAgentOptions(
108 mcp_servers={
109 "filesystem": {
110 "command": "npx",
111 "args": [
112 "-y",
113 "@modelcontextprotocol/server-filesystem",
114 "/Users/me/projects",
115 ],
116 }
117 },
118 allowed_tools=["mcp__filesystem__*"],
119 )
120 
121 async for message in query(prompt="List files in my project", options=options):
122 if isinstance(message, ResultMessage) and message.subtype == "success":
123 print(message.result)
124 
125 
126 asyncio.run(main())
127 ```
128</CodeGroup>
129 
130<span id="from-a-config-file" />
75```typescript TypeScript theme={null}
76import { query } from "@anthropic-ai/claude-agent-sdk";
77 
78for await (const message of query({
79 prompt: "List files in my project",
80 options: {
81 mcpServers: {
82 filesystem: {
83 command: "npx",
84 args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
85 }
86 },
87 allowedTools: ["mcp__filesystem__*"]
88 }
89})) {
90 if (message.type === "result" && message.subtype === "success") {
91 console.log(message.result);
92 }
93}
94```
95 
96```python Python theme={null}
97import asyncio
98from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
99 
100 
101async def main():
102 options = ClaudeAgentOptions(
103 mcp_servers={
104 "filesystem": {
105 "command": "npx",
106 "args": [
107 "-y",
108 "@modelcontextprotocol/server-filesystem",
109 "/Users/me/projects",
110 ],
111 }
112 },
113 allowed_tools=["mcp__filesystem__*"],
114 )
115 
116 async for message in query(prompt="List files in my project", options=options):
117 if isinstance(message, ResultMessage) and message.subtype == "success":
118 print(message.result)
119 
120 
121asyncio.run(main())
122```
131123 
132124### Add a server from a config file
133125 
from line 185
193185 
194186Use `allowedTools` to auto-approve specific MCP tools so Claude can use them without a permission prompt:
195187 
196<CodeGroup>
197 ```typescript TypeScript hidelines={1,-1} theme={null}
198 const _ = {
199 options: {
200 mcpServers: {
201 // your servers
202 },
203 allowedTools: [
204 "mcp__github__*", // All tools from the github server
205 "mcp__db__query", // Only the query tool from db server
206 "mcp__slack__send_message" // Only send_message from slack server
207 ]
208 }
209 };
210 ```
211 
212 ```python Python theme={null}
213 options = ClaudeAgentOptions(
214 mcp_servers={
215 # your servers
216 },
217 allowed_tools=[
218 "mcp__github__*", # All tools from the github server
219 "mcp__db__query", # Only the query tool from db server
220 "mcp__slack__send_message", # Only send_message from slack server
221 ],
222 )
223 ```
224</CodeGroup>
188```typescript TypeScript hidelines={1,-1} theme={null}
189const _ = {
190 options: {
191 mcpServers: {
192 // your servers
193 },
194 allowedTools: [
195 "mcp__github__*", // All tools from the github server
196 "mcp__db__query", // Only the query tool from db server
197 "mcp__slack__send_message" // Only send_message from slack server
198 ]
199 }
200};
201```
202 
203```python Python theme={null}
204options = ClaudeAgentOptions(
205 mcp_servers={
206 # your servers
207 },
208 allowed_tools=[
209 "mcp__github__*", # All tools from the github server
210 "mcp__db__query", # Only the query tool from db server
211 "mcp__slack__send_message", # Only send_message from slack server
212 ],
213)
214```
225215 
226216Wildcards (`*`) let you allow all tools from a server without listing each one individually.
227217 
228<Note>
229 **Prefer `allowedTools` over permission modes for MCP access.** `permissionMode: "acceptEdits"` does not auto-approve MCP tools (only file edits and filesystem Bash commands). `permissionMode: "bypassPermissions"` does auto-approve MCP tools but also disables most other safety prompts, which is broader than necessary; see [How permissions are evaluated](/docs/en/agent-sdk/permissions#how-permissions-are-evaluated) for the prompts that remain. A wildcard in `allowedTools` grants exactly the MCP server you want and nothing more. See [Permission modes](/docs/en/agent-sdk/permissions#permission-modes) for a full comparison.
230</Note>
218> [!NOTE]
219>
220> **Prefer `allowedTools` over permission modes for MCP access.** `permissionMode: "acceptEdits"` does not auto-approve MCP tools (only file edits and filesystem Bash commands). `permissionMode: "bypassPermissions"` does auto-approve MCP tools but also disables most other safety prompts, which is broader than necessary; see [How permissions are evaluated](/docs/en/agent-sdk/permissions#how-permissions-are-evaluated) for the prompts that remain. A wildcard in `allowedTools` grants exactly the MCP server you want and nothing more. See [Permission modes](/docs/en/agent-sdk/permissions#permission-modes) for a full comparison.
231221 
232222### Discover available tools
233223 
from line 227
237227 
238228This filter prints the MCP tool names:
239229 
240<CodeGroup>
241 ```typescript TypeScript theme={null}
242 import { query } from "@anthropic-ai/claude-agent-sdk";
243 
244 const options = {
245 mcpServers: {
246 // your servers
247 },
248 };
249 
250 for await (const message of query({ prompt: "...", options })) {
251 if (message.type === "system" && message.subtype === "init") {
252 const mcpTools = message.tools.filter((name) => name.startsWith("mcp__"));
253 console.log("Available MCP tools:", mcpTools);
254 }
255 }
256 ```
257 
258 ```python Python theme={null}
259 import asyncio
260 from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
261 
262 
263 async def main():
264 options = ClaudeAgentOptions(
265 mcp_servers={
266 # your servers
267 },
268 )
269 async for message in query(prompt="...", options=options):
270 if isinstance(message, SystemMessage) and message.subtype == "init":
271 mcp_tools = [t for t in message.data.get("tools", []) if t.startswith("mcp__")]
272 print("Available MCP tools:", mcp_tools)
273 
274 
275 asyncio.run(main())
276 ```
277</CodeGroup>
230```typescript TypeScript theme={null}
231import { query } from "@anthropic-ai/claude-agent-sdk";
232 
233const options = {
234 mcpServers: {
235 // your servers
236 },
237};
238 
239for await (const message of query({ prompt: "...", options })) {
240 if (message.type === "system" && message.subtype === "init") {
241 const mcpTools = message.tools.filter((name) => name.startsWith("mcp__"));
242 console.log("Available MCP tools:", mcpTools);
243 }
244}
245```
246 
247```python Python theme={null}
248import asyncio
249from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
250 
251 
252async def main():
253 options = ClaudeAgentOptions(
254 mcp_servers={
255 # your servers
256 },
257 )
258 async for message in query(prompt="...", options=options):
259 if isinstance(message, SystemMessage) and message.subtype == "init":
260 mcp_tools = [t for t in message.data.get("tools", []) if t.startswith("mcp__")]
261 print("Available MCP tools:", mcp_tools)
262 
263 
264asyncio.run(main())
265```
278266 
279267You can also ask Claude to list the tools available from a server.
280268 
from line 278
290278 
291279Local processes that communicate via stdin/stdout. Use this for MCP servers you run on the same machine. For the `.mcp.json` form, use the same fields shown at [Add a server from a config file](#from-a-config-file). In code, pass the command and its arguments. Replace `/Users/me/projects` with a directory on your machine:
292280 
293<CodeGroup>
294 ```typescript TypeScript hidelines={1,-1} theme={null}
295 const _ = {
296 options: {
297 mcpServers: {
298 filesystem: {
299 command: "npx",
300 args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
281```typescript TypeScript hidelines={1,-1} theme={null}
282const _ = {
283 options: {
284 mcpServers: {
285 filesystem: {
286 command: "npx",
287 args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
288 }
289 },
290 allowedTools: ["mcp__filesystem__read_file", "mcp__filesystem__list_directory"]
291 }
292};
293```
294 
295```python Python theme={null}
296options = ClaudeAgentOptions(
297 mcp_servers={
298 "filesystem": {
299 "command": "npx",
300 "args": [
301 "-y",
302 "@modelcontextprotocol/server-filesystem",
303 "/Users/me/projects",
304 ],
301305 }
302 },
303 allowedTools: ["mcp__filesystem__read_file", "mcp__filesystem__list_directory"]
306 },
307 allowed_tools=["mcp__filesystem__read_file", "mcp__filesystem__list_directory"],
308)
309```
310 
311### HTTP/SSE servers
312 
313Use HTTP or SSE for cloud-hosted MCP servers and remote APIs. For the `.mcp.json` form, use the same fields as the example at [HTTP headers for remote servers](#http-headers-for-remote-servers), with `"type": "sse"` for an SSE server. In code, pass the server's URL:
314 
315```typescript TypeScript hidelines={1,-1} theme={null}
316const _ = {
317 options: {
318 mcpServers: {
319 "remote-api": {
320 type: "sse",
321 url: "https://api.example.com/mcp/sse",
322 headers: {
323 Authorization: `Bearer ${process.env.API_TOKEN}`
324 }
325 }
326 },
327 allowedTools: ["mcp__remote-api__*"]
328 }
329};
330```
331 
332```python Python theme={null}
333options = ClaudeAgentOptions(
334 mcp_servers={
335 "remote-api": {
336 "type": "sse",
337 "url": "https://api.example.com/mcp/sse",
338 "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
339 }
340 },
341 allowed_tools=["mcp__remote-api__*"],
342)
343```
344 
345For the streamable HTTP transport, use `"type": "http"` instead. In `.mcp.json` and other JSON config files, `"streamable-http"` is accepted as an alias for `"http"`. The SDKs' `McpHttpServerConfig` type declares only `"http"`, so use `"http"` for servers you pass in code.
346 
347### SDK MCP servers
348 
349Define custom tools directly in your application code instead of running a separate server process. See the [custom tools guide](/docs/en/agent-sdk/custom-tools) for implementation details.
350 
351An SDK MCP server registered by an [`initialize` control request](/docs/en/agent-sdk/typescript#sdkcontrolinitializeresponse) begins connecting as soon as Claude Code processes the request.
352 
353## MCP tool search
354 
355When you have many MCP tools configured, tool definitions can consume a significant portion of your context window. Tool search solves this by withholding tool definitions from context and loading only the ones Claude needs for each turn.
356 
357Tool search is enabled by default. See [Tool search](/docs/en/agent-sdk/tool-search) for configuration options, best practices, and using tool search with custom SDK tools.
358 
359## Authentication
360 
361Most MCP servers require authentication to access external services. Pass credentials through environment variables in the server configuration.
362 
363### Pass credentials via environment variables
364 
365Use the `env` field to pass API keys, tokens, and other credentials to the MCP server:
366 
367**In code**
368 
369```typescript TypeScript hidelines={1,-1} theme={null}
370const _ = {
371 options: {
372 mcpServers: {
373 "api-server": {
374 command: "npx",
375 args: ["-y", "@your-org/api-mcp-server"],
376 env: {
377 API_KEY: process.env.API_KEY
378 }
379 }
380 },
381 allowedTools: ["mcp__api-server__*"]
382 }
383};
384```
385 
386```python Python theme={null}
387options = ClaudeAgentOptions(
388 mcp_servers={
389 "api-server": {
390 "command": "npx",
391 "args": ["-y", "@your-org/api-mcp-server"],
392 "env": {"API_KEY": os.environ["API_KEY"]},
393 }
394 },
395 allowed_tools=["mcp__api-server__*"],
396)
397```
398 
399**.mcp.json**
400 
401```json theme={null}
402{
403 "mcpServers": {
404 "api-server": {
405 "command": "npx",
406 "args": ["-y", "@your-org/api-mcp-server"],
407 "env": {
408 "API_KEY": "${API_KEY}"
409 }
304410 }
305 };
306 ```
307 
308 ```python Python theme={null}
309 options = ClaudeAgentOptions(
310 mcp_servers={
311 "filesystem": {
312 "command": "npx",
313 "args": [
314 "-y",
315 "@modelcontextprotocol/server-filesystem",
316 "/Users/me/projects",
317 ],
318 }
319 },
320 allowed_tools=["mcp__filesystem__read_file", "mcp__filesystem__list_directory"],
321 )
322 ```
323</CodeGroup>
324 
325### HTTP/SSE servers
326 
327Use HTTP or SSE for cloud-hosted MCP servers and remote APIs. For the `.mcp.json` form, use the same fields as the example at [HTTP headers for remote servers](#http-headers-for-remote-servers), with `"type": "sse"` for an SSE server. In code, pass the server's URL:
328 
329<CodeGroup>
330 ```typescript TypeScript hidelines={1,-1} theme={null}
331 const _ = {
332 options: {
333 mcpServers: {
334 "remote-api": {
335 type: "sse",
336 url: "https://api.example.com/mcp/sse",
337 headers: {
338 Authorization: `Bearer ${process.env.API_TOKEN}`
339 }
340 }
341 },
342 allowedTools: ["mcp__remote-api__*"]
343 }
344 };
345 ```
346 
347 ```python Python theme={null}
348 options = ClaudeAgentOptions(
349 mcp_servers={
350 "remote-api": {
351 "type": "sse",
352 "url": "https://api.example.com/mcp/sse",
353 "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
354 }
355 },
356 allowed_tools=["mcp__remote-api__*"],
357 )
358 ```
359</CodeGroup>
360 
361For the streamable HTTP transport, use `"type": "http"` instead. In `.mcp.json` and other JSON config files, `"streamable-http"` is accepted as an alias for `"http"`. The SDKs' `McpHttpServerConfig` type declares only `"http"`, so use `"http"` for servers you pass in code.
362 
363### SDK MCP servers
364 
365Define custom tools directly in your application code instead of running a separate server process. See the [custom tools guide](/docs/en/agent-sdk/custom-tools) for implementation details.
366 
367An SDK MCP server registered by an [`initialize` control request](/docs/en/agent-sdk/typescript#sdkcontrolinitializeresponse) begins connecting as soon as Claude Code processes the request.
368 
369## MCP tool search
370 
371When you have many MCP tools configured, tool definitions can consume a significant portion of your context window. Tool search solves this by withholding tool definitions from context and loading only the ones Claude needs for each turn.
372 
373Tool search is enabled by default. See [Tool search](/docs/en/agent-sdk/tool-search) for configuration options, best practices, and using tool search with custom SDK tools.
374 
375## Authentication
376 
377Most MCP servers require authentication to access external services. Pass credentials through environment variables in the server configuration.
378 
379### Pass credentials via environment variables
380 
381Use the `env` field to pass API keys, tokens, and other credentials to the MCP server:
382 
383<Tabs>
384 <Tab title="In code">
385 <CodeGroup>
386 ```typescript TypeScript hidelines={1,-1} theme={null}
387 const _ = {
388 options: {
389 mcpServers: {
390 "api-server": {
391 command: "npx",
392 args: ["-y", "@your-org/api-mcp-server"],
393 env: {
394 API_KEY: process.env.API_KEY
395 }
396 }
397 },
398 allowedTools: ["mcp__api-server__*"]
399 }
400 };
401 ```
402 
403 ```python Python theme={null}
404 options = ClaudeAgentOptions(
405 mcp_servers={
406 "api-server": {
407 "command": "npx",
408 "args": ["-y", "@your-org/api-mcp-server"],
409 "env": {"API_KEY": os.environ["API_KEY"]},
410 }
411 },
412 allowed_tools=["mcp__api-server__*"],
413 )
414 ```
415 </CodeGroup>
416 </Tab>
417 
418 <Tab title=".mcp.json">
419 ```json theme={null}
420 {
421 "mcpServers": {
422 "api-server": {
423 "command": "npx",
424 "args": ["-y", "@your-org/api-mcp-server"],
425 "env": {
426 "API_KEY": "${API_KEY}"
427 }
428 }
429 }
430 }
431 ```
432 
433 The `${API_KEY}` syntax expands environment variables at runtime.
434 </Tab>
435</Tabs>
411 }
412}
413```
414 
415The `${API_KEY}` syntax expands environment variables at runtime.
436416 
437417### HTTP headers for remote servers
438418 
439419For HTTP and SSE servers, pass authentication headers directly in the server configuration:
440420 
441<Tabs>
442 <Tab title="In code">
443 <CodeGroup>
444 ```typescript TypeScript hidelines={1,-1} theme={null}
445 const _ = {
446 options: {
447 mcpServers: {
448 "secure-api": {
449 type: "http",
450 url: "https://api.example.com/mcp",
451 headers: {
452 Authorization: `Bearer ${process.env.API_TOKEN}`
453 }
454 }
455 },
456 allowedTools: ["mcp__secure-api__*"]
457 }
458 };
459 ```
460 
461 ```python Python theme={null}
462 options = ClaudeAgentOptions(
463 mcp_servers={
464 "secure-api": {
465 "type": "http",
466 "url": "https://api.example.com/mcp",
467 "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
468 }
469 },
470 allowed_tools=["mcp__secure-api__*"],
471 )
472 ```
473 </CodeGroup>
474 </Tab>
475 
476 <Tab title=".mcp.json">
477 ```json theme={null}
478 {
479 "mcpServers": {
480 "secure-api": {
481 "type": "http",
482 "url": "https://api.example.com/mcp",
483 "headers": {
484 "Authorization": "Bearer ${API_TOKEN}"
485 }
486 }
487 }
488 }
489 ```
490 
491 The `${API_TOKEN}` syntax expands environment variables at runtime.
492 </Tab>
493</Tabs>
494 
495For a complete working example of a remote server authenticated with headers, see [List issues from a repository](#list-issues-from-a-repository).
496 
497### OAuth2 authentication
498 
499The [MCP specification supports OAuth 2.1](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for authorization. The SDK doesn't open a browser or run an interactive OAuth flow. When a configured server returns an authorization challenge and no stored token is available, the agent run continues without that server's tools, and the server reports status `needs-auth`. The `mcp_servers` array of the [system init message](/docs/en/agent-sdk/typescript#sdksystemmessage) may still show `pending` for that server when it's emitted. To confirm whether a server needs credentials, poll `mcpServerStatus()` in the TypeScript SDK or [`get_mcp_status()`](/docs/en/agent-sdk/python#methods) in Python.
500 
501To supply credentials, complete the OAuth flow in your own application and pass the resulting access token in the server's `headers`:
502 
503<CodeGroup>
504 ```typescript TypeScript theme={null}
505 // After completing OAuth flow in your app.
506 // Implement getAccessTokenFromOAuthFlow for your OAuth provider.
507 const accessToken = await getAccessTokenFromOAuthFlow();
508 
509 const options = {
510 mcpServers: {
511 "oauth-api": {
421**In code**
422 
423```typescript TypeScript hidelines={1,-1} theme={null}
424const _ = {
425 options: {
426 mcpServers: {
427 "secure-api": {
512428 type: "http",
513429 url: "https://api.example.com/mcp",
514430 headers: {
515 Authorization: `Bearer ${accessToken}`
431 Authorization: `Bearer ${process.env.API_TOKEN}`
516432 }
517433 }
518434 },
519 allowedTools: ["mcp__oauth-api__*"]
520 };
521 ```
522 
523 ```python Python theme={null}
524 # After completing OAuth flow in your app.
525 # Implement get_access_token_from_oauth_flow for your OAuth provider.
526 access_token = await get_access_token_from_oauth_flow()
527 
528 options = ClaudeAgentOptions(
529 mcp_servers={
530 "oauth-api": {
531 "type": "http",
532 "url": "https://api.example.com/mcp",
533 "headers": {"Authorization": f"Bearer {access_token}"},
534 }
535 },
536 allowed_tools=["mcp__oauth-api__*"],
537 )
538 ```
539</CodeGroup>
435 allowedTools: ["mcp__secure-api__*"]
436 }
437};
438```
439 
440```python Python theme={null}
441options = ClaudeAgentOptions(
442 mcp_servers={
443 "secure-api": {
444 "type": "http",
445 "url": "https://api.example.com/mcp",
446 "headers": {"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
447 }
448 },
449 allowed_tools=["mcp__secure-api__*"],
450)
451```
452 
453**.mcp.json**
454 
455```json theme={null}
456{
457 "mcpServers": {
458 "secure-api": {
459 "type": "http",
460 "url": "https://api.example.com/mcp",
461 "headers": {
462 "Authorization": "Bearer ${API_TOKEN}"
463 }
464 }
465 }
466}
467```
468 
469The `${API_TOKEN}` syntax expands environment variables at runtime.
470 
471For a complete working example of a remote server authenticated with headers, see [List issues from a repository](#list-issues-from-a-repository).
472 
473### OAuth2 authentication
474 
475The [MCP specification supports OAuth 2.1](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for authorization. The SDK doesn't open a browser or run an interactive OAuth flow. When a configured server returns an authorization challenge and no stored token is available, the agent run continues without that server's tools, and the server reports status `needs-auth`. The `mcp_servers` array of the [system init message](/docs/en/agent-sdk/typescript#sdksystemmessage) may still show `pending` for that server when it's emitted. To confirm whether a server needs credentials, poll `mcpServerStatus()` in the TypeScript SDK or [`get_mcp_status()`](/docs/en/agent-sdk/python#methods) in Python.
476 
477To supply credentials, complete the OAuth flow in your own application and pass the resulting access token in the server's `headers`:
478 
479```typescript TypeScript theme={null}
480// After completing OAuth flow in your app.
481// Implement getAccessTokenFromOAuthFlow for your OAuth provider.
482const accessToken = await getAccessTokenFromOAuthFlow();
483 
484const options = {
485 mcpServers: {
486 "oauth-api": {
487 type: "http",
488 url: "https://api.example.com/mcp",
489 headers: {
490 Authorization: `Bearer ${accessToken}`
491 }
492 }
493 },
494 allowedTools: ["mcp__oauth-api__*"]
495};
496```
497 
498```python Python theme={null}
499# After completing OAuth flow in your app.
500# Implement get_access_token_from_oauth_flow for your OAuth provider.
501access_token = await get_access_token_from_oauth_flow()
502 
503options = ClaudeAgentOptions(
504 mcp_servers={
505 "oauth-api": {
506 "type": "http",
507 "url": "https://api.example.com/mcp",
508 "headers": {"Authorization": f"Bearer {access_token}"},
509 }
510 },
511 allowed_tools=["mcp__oauth-api__*"],
512)
513```
540514 
541515## Examples
542516 
from line 524
550524export GITHUB_TOKEN=YOUR_GITHUB_PAT
551525```
552526 
553<CodeGroup>
554 ```typescript TypeScript theme={null}
555 import { query } from "@anthropic-ai/claude-agent-sdk";
556 
557 for await (const message of query({
558 prompt: "List the 3 most recent issues in anthropics/claude-code",
559 options: {
560 mcpServers: {
561 github: {
562 type: "http",
563 url: "https://api.githubcopilot.com/mcp/",
564 headers: {
565 Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
566 }
527```typescript TypeScript theme={null}
528import { query } from "@anthropic-ai/claude-agent-sdk";
529 
530for await (const message of query({
531 prompt: "List the 3 most recent issues in anthropics/claude-code",
532 options: {
533 mcpServers: {
534 github: {
535 type: "http",
536 url: "https://api.githubcopilot.com/mcp/",
537 headers: {
538 Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
567539 }
568 },
569 allowedTools: ["mcp__github__list_issues"]
540 }
541 },
542 allowedTools: ["mcp__github__list_issues"]
543 }
544})) {
545 // Verify MCP server connected successfully
546 if (message.type === "system" && message.subtype === "init") {
547 console.log("MCP servers:", message.mcp_servers);
548 }
549 
550 // Log when Claude calls an MCP tool
551 if (message.type === "assistant") {
552 for (const block of message.message.content) {
553 if (block.type === "tool_use" && block.name.startsWith("mcp__")) {
554 console.log("MCP tool called:", block.name);
555 }
570556 }
571 })) {
572 // Verify MCP server connected successfully
573 if (message.type === "system" && message.subtype === "init") {
574 console.log("MCP servers:", message.mcp_servers);
575 }
576 
577 // Log when Claude calls an MCP tool
578 if (message.type === "assistant") {
579 for (const block of message.message.content) {
580 if (block.type === "tool_use" && block.name.startsWith("mcp__")) {
581 console.log("MCP tool called:", block.name);
582 }
583 }
584 }
585 
586 // Print the final result
587 if (message.type === "result" && message.subtype === "success") {
588 console.log(message.result);
589 }
590 }
591 ```
592 
593 ```python Python theme={null}
594 import asyncio
595 import os
596 from claude_agent_sdk import (
597 query,
598 ClaudeAgentOptions,
599 ResultMessage,
600 SystemMessage,
601 AssistantMessage,
602 )
603 
604 
605 async def main():
606 options = ClaudeAgentOptions(
607 mcp_servers={
608 "github": {
609 "type": "http",
610 "url": "https://api.githubcopilot.com/mcp/",
611 "headers": {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
612 }
613 },
614 allowed_tools=["mcp__github__list_issues"],
615 )
616 
617 async for message in query(
618 prompt="List the 3 most recent issues in anthropics/claude-code",
619 options=options,
620 ):
621 # Verify MCP server connected successfully
622 if isinstance(message, SystemMessage) and message.subtype == "init":
623 print("MCP servers:", message.data.get("mcp_servers"))
624 
625 # Log when Claude calls an MCP tool
626 if isinstance(message, AssistantMessage):
627 for block in message.content:
628 if hasattr(block, "name") and block.name.startswith("mcp__"):
629 print("MCP tool called:", block.name)
630 
631 # Print the final result
632 if isinstance(message, ResultMessage) and message.subtype == "success":
633 print(message.result)
634 
635 
636 asyncio.run(main())
637 ```
638</CodeGroup>
557 }
558 
559 // Print the final result
560 if (message.type === "result" && message.subtype === "success") {
561 console.log(message.result);
562 }
563}
564```
565 
566```python Python theme={null}
567import asyncio
568import os
569from claude_agent_sdk import (
570 query,
571 ClaudeAgentOptions,
572 ResultMessage,
573 SystemMessage,
574 AssistantMessage,
575)
576 
577 
578async def main():
579 options = ClaudeAgentOptions(
580 mcp_servers={
581 "github": {
582 "type": "http",
583 "url": "https://api.githubcopilot.com/mcp/",
584 "headers": {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
585 }
586 },
587 allowed_tools=["mcp__github__list_issues"],
588 )
589 
590 async for message in query(
591 prompt="List the 3 most recent issues in anthropics/claude-code",
592 options=options,
593 ):
594 # Verify MCP server connected successfully
595 if isinstance(message, SystemMessage) and message.subtype == "init":
596 print("MCP servers:", message.data.get("mcp_servers"))
597 
598 # Log when Claude calls an MCP tool
599 if isinstance(message, AssistantMessage):
600 for block in message.content:
601 if hasattr(block, "name") and block.name.startswith("mcp__"):
602 print("MCP tool called:", block.name)
603 
604 # Print the final result
605 if isinstance(message, ResultMessage) and message.subtype == "success":
606 print(message.result)
607 
608 
609asyncio.run(main())
610```
639611 
640612In the `MCP servers:` line, a `status` of `connected` for `github` confirms the token works. If Claude Code has a [cached tool list](#connection-timing) for the server, the status can read `pending` instead and the server connects on its first tool call. If the status is `failed` or `needs-auth`, see [Error handling](#error-handling) before trusting the result, since Claude can fall back to built-in tools when the server is unavailable.
641613 
from line 634
662634export DATABASE_URL=postgresql://user:password@localhost:5432/mydb
663635```
664636 
665<CodeGroup>
666 ```typescript TypeScript theme={null}
667 import { query } from "@anthropic-ai/claude-agent-sdk";
668 
669 for await (const message of query({
670 // Natural language query - Claude writes the SQL
671 prompt: "How many users signed up last week? Break it down by day.",
672 options: {
673 mcpServers: {
674 postgres: {
675 command: "npx",
676 // dbhub.toml sets readonly = true, so execute_sql rejects writes
677 args: ["-y", "@bytebase/dbhub", "--config", "dbhub.toml"]
678 }
679 },
680 allowedTools: ["mcp__postgres__execute_sql"]
681 }
682 })) {
683 if (message.type === "result" && message.subtype === "success") {
684 console.log(message.result);
685 }
686 }
687 ```
688 
689 ```python Python theme={null}
690 import asyncio
691 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
692 
693 
694 async def main():
695 options = ClaudeAgentOptions(
696 mcp_servers={
697 "postgres": {
698 "command": "npx",
699 # dbhub.toml sets readonly = true, so execute_sql rejects writes
700 "args": [
701 "-y",
702 "@bytebase/dbhub",
703 "--config",
704 "dbhub.toml",
705 ],
706 }
707 },
708 allowed_tools=["mcp__postgres__execute_sql"],
709 )
710 
711 # Natural language query - Claude writes the SQL
712 async for message in query(
713 prompt="How many users signed up last week? Break it down by day.",
714 options=options,
715 ):
716 if isinstance(message, ResultMessage) and message.subtype == "success":
717 print(message.result)
718 
719 
720 asyncio.run(main())
721 ```
722</CodeGroup>
637```typescript TypeScript theme={null}
638import { query } from "@anthropic-ai/claude-agent-sdk";
639 
640for await (const message of query({
641 // Natural language query - Claude writes the SQL
642 prompt: "How many users signed up last week? Break it down by day.",
643 options: {
644 mcpServers: {
645 postgres: {
646 command: "npx",
647 // dbhub.toml sets readonly = true, so execute_sql rejects writes
648 args: ["-y", "@bytebase/dbhub", "--config", "dbhub.toml"]
649 }
650 },
651 allowedTools: ["mcp__postgres__execute_sql"]
652 }
653})) {
654 if (message.type === "result" && message.subtype === "success") {
655 console.log(message.result);
656 }
657}
658```
659 
660```python Python theme={null}
661import asyncio
662from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
663 
664 
665async def main():
666 options = ClaudeAgentOptions(
667 mcp_servers={
668 "postgres": {
669 "command": "npx",
670 # dbhub.toml sets readonly = true, so execute_sql rejects writes
671 "args": [
672 "-y",
673 "@bytebase/dbhub",
674 "--config",
675 "dbhub.toml",
676 ],
677 }
678 },
679 allowed_tools=["mcp__postgres__execute_sql"],
680 )
681 
682 # Natural language query - Claude writes the SQL
683 async for message in query(
684 prompt="How many users signed up last week? Break it down by day.",
685 options=options,
686 ):
687 if isinstance(message, ResultMessage) and message.subtype == "success":
688 print(message.result)
689 
690 
691asyncio.run(main())
692```
723693 
724694## Error handling
725695 
from line 705
735705 
736706Check for `"failed"` or `"needs-auth"` to detect servers that won't be usable:
737707 
738<CodeGroup>
739 ```typescript TypeScript theme={null}
740 import { query } from "@anthropic-ai/claude-agent-sdk";
741 
742 try {
743 for await (const message of query({
744 prompt: "Process data",
745 options: {
746 mcpServers: {
747 // Replace dataServer with your server configuration
748 "data-processor": dataServer
749 }
750 }
751 })) {
752 if (message.type === "system" && message.subtype === "init") {
753 const unavailableServers = message.mcp_servers.filter(
754 (s) => s.status === "failed" || s.status === "needs-auth"
755 );
756 
757 if (unavailableServers.length > 0) {
758 console.warn("Unavailable MCP servers:", unavailableServers);
759 }
760 }
761 
762 if (message.type === "result" && message.subtype === "error_during_execution") {
763 console.error("Execution failed");
708```typescript TypeScript theme={null}
709import { query } from "@anthropic-ai/claude-agent-sdk";
710 
711try {
712 for await (const message of query({
713 prompt: "Process data",
714 options: {
715 mcpServers: {
716 // Replace dataServer with your server configuration
717 "data-processor": dataServer
764718 }
765719 }
766 } catch (error) {
767 // A single-shot query() throws after yielding an error result. If the
768 // failure was an error result, the error subtype branch above has
769 // already run; a failure to start or reach the Claude Code process
770 // yields no result message. MCP servers that fail to connect don't
771 // throw: use the status check above, and note that servers still
772 // "pending" at init need a later status check.
773 console.log(`Session ended with an error: ${error}`);
774 }
775 ```
776 
777 ```python Python theme={null}
778 import asyncio
779 from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, ResultMessage
780 
781 
782 async def main():
783 # Replace data_server with your server configuration
784 options = ClaudeAgentOptions(mcp_servers={"data-processor": data_server})
785 
786 try:
787 async for message in query(prompt="Process data", options=options):
788 if isinstance(message, SystemMessage) and message.subtype == "init":
789 unavailable_servers = [
790 s
791 for s in message.data.get("mcp_servers", [])
792 if s.get("status") in ("failed", "needs-auth")
793 ]
794 
795 if unavailable_servers:
796 print(f"Unavailable MCP servers: {unavailable_servers}")
797 
798 if (
799 isinstance(message, ResultMessage)
800 and message.subtype == "error_during_execution"
801 ):
802 print("Execution failed")
803 except Exception as error:
804 # A single-shot query() raises after yielding an error result. If the
805 # failure was an error result, the error subtype branch above has
806 # already run; a failure to start or reach the Claude Code process
807 # yields no result message. MCP servers that fail to connect don't
808 # raise: use the status check above, and note that servers still
809 # "pending" at init need a later status check.
810 print(f"Session ended with an error: {error}")
811 
812 
813 asyncio.run(main())
814 ```
815</CodeGroup>
720 })) {
721 if (message.type === "system" && message.subtype === "init") {
722 const unavailableServers = message.mcp_servers.filter(
723 (s) => s.status === "failed" || s.status === "needs-auth"
724 );
725 
726 if (unavailableServers.length > 0) {
727 console.warn("Unavailable MCP servers:", unavailableServers);
728 }
729 }
730 
731 if (message.type === "result" && message.subtype === "error_during_execution") {
732 console.error("Execution failed");
733 }
734 }
735} catch (error) {
736 // A single-shot query() throws after yielding an error result. If the
737 // failure was an error result, the error subtype branch above has
738 // already run; a failure to start or reach the Claude Code process
739 // yields no result message. MCP servers that fail to connect don't
740 // throw: use the status check above, and note that servers still
741 // "pending" at init need a later status check.
742 console.log(`Session ended with an error: ${error}`);
743}
744```
745 
746```python Python theme={null}
747import asyncio
748from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, ResultMessage
749 
750 
751async def main():
752 # Replace data_server with your server configuration
753 options = ClaudeAgentOptions(mcp_servers={"data-processor": data_server})
754 
755 try:
756 async for message in query(prompt="Process data", options=options):
757 if isinstance(message, SystemMessage) and message.subtype == "init":
758 unavailable_servers = [
759 s
760 for s in message.data.get("mcp_servers", [])
761 if s.get("status") in ("failed", "needs-auth")
762 ]
763 
764 if unavailable_servers:
765 print(f"Unavailable MCP servers: {unavailable_servers}")
766 
767 if (
768 isinstance(message, ResultMessage)
769 and message.subtype == "error_during_execution"
770 ):
771 print("Execution failed")
772 except Exception as error:
773 # A single-shot query() raises after yielding an error result. If the
774 # failure was an error result, the error subtype branch above has
775 # already run; a failure to start or reach the Claude Code process
776 # yields no result message. MCP servers that fail to connect don't
777 # raise: use the status check above, and note that servers still
778 # "pending" at init need a later status check.
779 print(f"Session ended with an error: {error}")
780 
781 
782asyncio.run(main())
783```
816784 
817785A remote server's status can also change after it reports `"connected"`. When the connection to it drops mid-session, Claude Code moves the server back to `"pending"` while [reconnecting](/docs/en/mcp#automatic-reconnection). A later `mcpServerStatus()` call in TypeScript, or [`ClaudeSDKClient.get_mcp_status()`](/docs/en/agent-sdk/python#methods) in Python, can then report `"pending"` for a server you saw connected earlier, with no configuration change on your side.
818786 
from line 792
824792 
825793Check the `init` message to see which servers failed to connect:
826794 
827<CodeGroup>
828 ```typescript TypeScript theme={null}
829 if (message.type === "system" && message.subtype === "init") {
830 for (const server of message.mcp_servers) {
831 if (server.status === "failed") {
832 console.error(`Server ${server.name} failed to connect`);
833 }
795```typescript TypeScript theme={null}
796if (message.type === "system" && message.subtype === "init") {
797 for (const server of message.mcp_servers) {
798 if (server.status === "failed") {
799 console.error(`Server ${server.name} failed to connect`);
834800 }
835801 }
836 ```
837 
838 ```python Python theme={null}
839 if isinstance(message, SystemMessage) and message.subtype == "init":
840 for server in message.data.get("mcp_servers", []):
841 if server.get("status") == "failed":
842 print(f"Server {server['name']} failed to connect")
843 ```
844</CodeGroup>
802}
803```
804 
805```python Python theme={null}
806if isinstance(message, SystemMessage) and message.subtype == "init":
807 for server in message.data.get("mcp_servers", []):
808 if server.get("status") == "failed":
809 print(f"Server {server['name']} failed to connect")
810```
845811 
846812A `"pending"` status doesn't mean the server failed. See [Error handling](#error-handling) for the cases it covers at init. To get updated statuses later in the session, call the query's `mcpServerStatus()` method in the TypeScript SDK, or [`ClaudeSDKClient.get_mcp_status()`](/docs/en/agent-sdk/python#methods) in Python.
847813 
from line 822
856822 
857823If Claude sees tools but doesn't use them, check that you've granted permission with `allowedTools`:
858824 
859<CodeGroup>
860 ```typescript TypeScript hidelines={1,-1} theme={null}
861 const _ = {
862 options: {
863 mcpServers: {
864 // your servers
865 },
866 allowedTools: ["mcp__servername__*"] // Auto-approve calls from this server
867 }
868 };
869 ```
870 
871 ```python Python theme={null}
872 options = ClaudeAgentOptions(
873 mcp_servers={
874 # your servers
875 },
876 allowed_tools=["mcp__servername__*"], # Auto-approve calls from this server
877 )
878 ```
879</CodeGroup>
825```typescript TypeScript hidelines={1,-1} theme={null}
826const _ = {
827 options: {
828 mcpServers: {
829 // your servers
830 },
831 allowedTools: ["mcp__servername__*"] // Auto-approve calls from this server
832 }
833};
834```
835 
836```python Python theme={null}
837options = ClaudeAgentOptions(
838 mcp_servers={
839 # your servers
840 },
841 allowed_tools=["mcp__servername__*"], # Auto-approve calls from this server
842)
843```
880844 
881845### A tool is missing from an SDK MCP server
882846 
883847 
Feedback