Building an MCP Server in Python: One Server for Both Claude and OpenAI

MCP unifies Agent tool integration, so one server works with both Claude and OpenAI.
Based on a tutorial by a former Microsoft and former Apple Intelligence engineer, this article explains how to write an MCP Server in Python and connect it to both Claude Code and the OpenAI Agent SDK. The core value is writing tools once and reusing them in multiple places, avoiding repeated client code for every Agent. It explains how MCP reduces point-to-point Agent-tool integrations to a unified protocol connection, introduces practices such as defining tools with decorators, using docstrings as descriptions, and raising ToolError instead of regular exceptions on failure, and compares the style differences between Claude and OpenAI integration code. The remote server section emphasizes security: treat remote servers as untrusted, use allowed tools whitelists, require approval for write operations, and treat tool returns as unstructured input to prevent prompt injection.
MCP (Model Context Protocol) is becoming the universal language for AI Agent tool integration. Based on a tutorial by a former Microsoft and former Apple Intelligence engineer, this article explains how to write an MCP Server in Python once and connect it to both Claude Code and the OpenAI Agent SDK. The core value is straightforward: write a tool once, reuse it in multiple places, and skip writing client code for every Agent.
What Integration Problem Does MCP Solve
Without MCP, AI Agents and external tools (GitHub, databases, order systems, etc.) are connected point-to-point. According to the tutorial author's analysis, the number of Agent-tool integrations grows as the product of both scales—when the number of Agents increases, the maintenance cost of integrations quickly spirals out of control.

The author's analogy is this: originally you had to write a separate integration for every "Agent-tool" pair. Once MCP appears as a middle layer, the Agent side only needs to maintain one protocol connection, and the tool side does the same. From an engineering perspective, this reduces duplicate adapter code and turns tools themselves into assets that can be shared across different Agent frameworks.
This explains why OpenAI chose to support MCP after Anthropic created it—it is not a proprietary protocol of one vendor, but a neutral standard.
Local Server: Defining Tools with Decorators
A local MCP Server uses stdio (standard input/output) as its communication method, which is the most basic form.
In the Python implementation, tools are defined through decorators. A function's docstring directly becomes the tool description, and parameters are extracted from the function signature. The tutorial specifically emphasizes an easy-to-miss detail: when raising exceptions, use ToolError, not a regular ValueError.

The reason lies in the readability of error handling. If you raise a regular exception, the Agent may only see "failed to execute tool," with no returned reason, and may even trigger meaningless automatic retries. But ToolError lets the model see clear information, for example "failed to execute tool cancel_order: order has already shipped," along with the return value, so the Agent can adjust subsequent behavior accordingly.
The tutorial also mentions that MCP supports the resource mechanism, exposing policies, templates, and other items to the Agent as plain text, helping the model make judgments before calling tools.
stdio (standard input/output) is a basic transport method defined by MCP: the client starts the server as a subprocess, and both sides exchange JSON-RPC messages through the process's stdin and stdout, while stderr is usually used for logs. Its advantages are that it does not need to listen on a network port, requires no authentication configuration, and is naturally suited for tool servers running on the local machine; its disadvantages are that the process lifecycle is tied to the client, it cannot be shared by multiple clients at the same time, and it is not suitable for cross-machine calls. For this reason, in addition to stdio, MCP also defines HTTP-based transport methods for remote scenarios.
resource is another primitive in MCP alongside tool. Tools are used to "perform actions," while resources are used to "read content," usually identified by a URI and returning text or binary data, such as policy documents, prompt templates, and configuration files. Clients can list resources and read them on demand, providing them to the model as context. Resources are generally read-only, which also makes it safe to expose them to an Agent for pre-call judgment without triggering side effects.
Connecting the Same Server to Claude and OpenAI
This is the most valuable comparison in the entire approach. When the same MCP Server is connected to two different Agent frameworks, the client code styles differ noticeably.

According to the tutorial author's demonstration, the Claude side is relatively intuitive to configure: specify mcp_servers in agent options, provide the name and command parameters, pass in the server as a parameter, then filter through allowed_tools, while permission mode and session settings follow the previous approach.
The OpenAI side requires additional handling for async. The author mentions needing to create an asynchronous process for mcp_server_stdio, pair it with shared server execution, and then use await to run the agent. The author personally prefers OpenAI's way of writing allowed tools—directly writing the tool name and the require_approval field, which is concise and intuitive.
The behavior of the two also differs slightly: for the same "cancel order" request, Claude and OpenAI do not follow exactly the same execution path. This is precisely the kind of thing that needs to be tested and observed when adapting across multiple frameworks.
Remote Servers and Security Strategy
Remote MCP Servers communicate through streamable HTTP. According to the tutorial's analysis, Python's support for streamable HTTP still has a beta nature (beta version markers can be seen in the code), while the OpenAI side is already using a production-grade implementation, which the author calls "super simple."
The author's security advice for remote servers is quite clear:
- A remote server is someone else's code, so you need to first understand its implementation; if you cannot find the implementation, check the list of available tools and verify it
- Use only allowed tools whitelists, do not expose all tools
- Require approval for any write operation, even if the user believes the written content is harmless, there should be a policy safeguard
- Treat all tool returns as unstructured input, and be wary of prompt injection

The essence of this approach is to treat the MCP Server as an untrusted boundary, rather than as a trusted internal component by default.
prompt injection refers to attackers hiding malicious instructions in content that the model will read, such as web pages returned by tools, issue bodies, or database fields, thereby hijacking the Agent's subsequent behavior. In MCP scenarios, both tool return values and resource content can be untrusted sources. The defense approach is to treat this content as data rather than executing it as instructions, combine that with least-privilege whitelists to limit the range of tools the Agent can call, and enforce manual approval for write operations, so that even if injected instructions are adopted by the model, they cannot cause irreversible damage.
allowed tools whitelist is a client-side filter: even if the server exposes all tools, the client registers only some of them with the model. This can both reduce the context occupied by tool descriptions and lower the risk of mistaken calls. Claude declares it through the allowed_tools parameter, while OpenAI expresses it with tool names plus the require_approval field and requires the server to run in an asynchronous process. The combination of whitelist and approval policy forms the first line of defense against untrusted remote servers.
Common Mistakes and Hands-on Exercise
The most common mistake the author points out is raising a regular exception in an MCP tool instead of ToolError. In comparative experiments, when a regular exception is raised, the Agent only sees "error executing tool cancel_order," with no return value and no reason, leaving the model unable to judge; after switching to ToolError, the model can see the specific reason and returned content, and the quality of handling improves noticeably.
The tutorial ends with an exercise: remove cancel_order from the allowed tools list and observe changes in the Agent's behavior. On the Claude side, you only need to delete one line; on the OpenAI side, create a static tool filter and keep just one tool name.
One-sentence summary: Write a tool once in the MCP Server, and Agents connect through stdio or streamable HTTP; when errors occur, prefer
ToolError.
Follow-up content in the tutorial will cover how coding-focused AI Agents such as Claude Code and Codex connect to MCP Servers, which may be the next practice direction worth watching.
One-line Summary
MCP unifies Agent tool integration, so writing the server once connects both Claude and OpenAI.
Paragraph Summary
Based on a tutorial by a former Microsoft and former Apple Intelligence engineer, this article explains how to write an MCP Server in Python and connect it to both Claude Code and the OpenAI Agent SDK. The core value is writing tools once and reusing them in multiple places, avoiding repeated client code for every Agent. It explains how MCP reduces point-to-point Agent-tool integrations to a unified protocol connection, introduces practices such as defining tools with decorators, using docstrings as descriptions, and raising ToolError instead of regular exceptions on failure, and compares the style differences between Claude and OpenAI integration code. The remote server section emphasizes security: treat remote servers as untrusted, use allowed tools whitelists, require approval for write operations, and treat tool returns as unstructured input to prevent prompt injection.
Related articles

Random Pruning of the DPO Computation Graph: 1.4x Training Speedup and Lower Peak Memory
DPO training too slow and memory-hungry? This paper proposes a random pruning framework for the DPO computation graph, sampling independently across position, sequence, and branch dimensions; branch pruning achieves a 1.426x step-time speedup and cuts training time by about 30%, with in-depth attribution of memory and time gains.

Why Does JEPA Underperform MLM in Longitudinal EHR Representation Learning?
New study compares JEPA and MLM for longitudinal EHR representation learning on MIMIC-IV: three JEPA variants train stably without collapse but underperform MLM; target size, not heterogeneity, drives prediction error.

Transfer Learning + Visible Light Communication: A New Approach to Energy-Efficient Indoor Positioning
A study proposes a transfer learning-based visible light communication indoor positioning method, achieving 47% accuracy improvement on the BOSCH factory dataset, 67.5% model efficiency under high noise, and introducing the ME composite metric for energy-efficient positioning in energy-constrained industrial IoT scenarios.