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
/
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
No line in this hunk matches that.