All insights

Coding agents

How to Build an MCP Server with Python and a Production Model API

Build a local Python MCP server with FastMCP, add a tool that calls OpenAI’s Responses API, keep the API key out of source code, and test successful and rejected tool calls with an MCP client.

Illustration for How to build an mcp server

Build a local MCP server by exposing a Python function as a tool, calling a model API from that function, and testing the tool with an MCP client. This tutorial uses FastMCP 4.x, stdio transport, and OpenAI’s Responses API. The client starts the server locally; this tutorial does not deploy a remotely hosted server. FastMCP documents local Python-file clients, tool discovery, and Python return values through its client API. (gofastmcp.com)

The MCP server is the bridge: an MCP client calls its tool, the tool calls the model API, and the server returns the model’s text. You need one tool to build this example; resources and prompts are optional.

Provider note: This example calls OpenAI’s API. Token Forge Cloud’s public product description does not establish the endpoint, authentication, or request schema needed for a verified Token Forge integration. This code is not a Token Forge Cloud integration; don’t substitute an endpoint or model name by guesswork.

What you’ll build

A local Python server with an ask_model tool. The tool accepts a question, sends it to the OpenAI Responses API, and returns the generated text. A small FastMCP client script will list the tool, call it, and check the returned text.

Use Python 3.10 or later. FastMCP’s current stable major release is 4, and its package metadata specifies Python 3.10 or later; the OpenAI Python library also supports Python 3.10 or later. You’ll also need an OpenAI API key and access to the model you select. (github.com)

1. Create a project and install the packages

In a terminal, create a project directory and virtual environment:

mkdir mcp-model-server
cd mcp-model-server
python -m venv .venv

Activate it:

# macOS or Linux
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1

Install FastMCP 4.x and the OpenAI Python library:

python -m pip install "fastmcp>=4,<5" openai

This major-version range scopes the example to FastMCP 4.x; it does not pin exact package releases or claim that a particular combination was tested. For a team project, record and test the resolved versions in your lockfile. FastMCP’s installation guide documents package installation, and its documentation identifies version 4 as stable. (gofastmcp.com)

2. Set the API key outside your source code

Set the key in the terminal you’ll use to start the server or run the test. Don’t put it in the Python file or commit it to source control.

# macOS or Linux
export OPENAI_API_KEY='your-api-key'
# Windows PowerShell
$env:OPENAI_API_KEY = 'your-api-key'

The OpenAI Python SDK reads OPENAI_API_KEY when you create OpenAI() without passing an explicit key. It sends requests to the SDK’s default OpenAI API base URL, https://api.openai.com/v1, unless you configure a different base URL. This tutorial uses the default destination and does not configure a custom endpoint. (github.com)

3. Write the MCP server

Create server.py:

import logging
import os

from fastmcp import FastMCP
from openai import OpenAI, OpenAIError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

if not os.environ.get("OPENAI_API_KEY"):
    raise RuntimeError("Set OPENAI_API_KEY before starting this server.")

client = OpenAI()
mcp = FastMCP("Model API MCP Server")


@mcp.tool
def ask_model(question: str) -> str:
    """Send a question to the configured model and return its text response."""
    question = question.strip()
    if not question:
        raise ValueError("question must not be blank")
    if len(question) > 6000:
        raise ValueError("question must be 6000 characters or fewer")

    try:
        response = client.responses.create(
            model="gpt-5.5",
            input=question,
        )
        return response.output_text
    except OpenAIError as exc:
        # Log only the exception class; do not include the message or traceback.
        logger.error("Model API request failed (%s)", type(exc).__name__)
        raise RuntimeError(
            "Model API request failed; check the server configuration and logs."
        ) from None


if __name__ == "__main__":
    mcp.run()

The @mcp.tool decorator exposes the function to MCP clients. FastMCP uses the function signature to create the input schema and the docstring as its description. The OpenAI SDK’s Responses API accepts a model and input, and the response exposes generated text through output_text. The example model identifier follows the SDK’s current Python example; choose a model available to your account. (gofastmcp.com)

The blank and length checks run before the outbound API request. A missing key stops the server at startup. If an OpenAI SDK error occurs, the tool returns a generic failure message; the server logs only the exception class, without the exception message or traceback. This sample does not add remote-user authentication, rate limiting, persistence, or deployment hardening.

4. Check syntax and run the server

Before starting the client test, you can ask Python to check both files for syntax errors:

python -m py_compile server.py test_client.py

This is a check for you to run; it is not a claim that the files were executed during research.

To start the server directly, use FastMCP’s CLI:

fastmcp run server.py

FastMCP uses stdio by default when running a server this way. In the client test below, Client("server.py") launches and manages the local server process, so you do not need to run the CLI in a separate terminal for that test. (gofastmcp.com)

5. Test successful and rejected calls with an MCP client

Create test_client.py in the same directory:

import asyncio

from fastmcp import Client


async def main():
    async with Client("server.py") as client:
        tools = await client.list_tools()
        tool_names = [tool.name for tool in tools]
        print("Available tools:", tool_names)
        assert "ask_model" in tool_names

        result = await client.call_tool(
            "ask_model",
            {"question": "In one sentence, what does an MCP server do?"},
        )
        answer = result.data
        assert isinstance(answer, str) and answer.strip()
        print("Model response:", answer)

        # Use the raw MCP result to check a tool error without raising it
        # as a client exception.
        rejected = await client.call_tool_mcp(
            "ask_model",
            {"question": "   "},
        )
        assert rejected.isError, "Expected the blank question to be rejected"
        print("Blank-question call rejected as expected.")


asyncio.run(main())

Run the test from the project directory, with OPENAI_API_KEY still set:

python -m py_compile server.py test_client.py
python test_client.py

list_tools() returns a list of tool objects. The regular call_tool() returns a FastMCP result whose .data property holds the Python return value. For the failure check, call_tool_mcp() returns the raw MCP tool result, which includes an isError flag; the assertion checks that the blank question is rejected. (gofastmcp.com)

Expected behavior: the tool list contains ask_model; the successful call returns non-empty model text; and the blank-question call is flagged as an error. The exact model wording varies. These are expected behaviors for the example, not results claimed from an execution here.

If something fails

  • Missing-key error: set OPENAI_API_KEY in the terminal running the test, then retry.
  • Authentication or model-access error: check that the key is valid and the selected model is available to your account. The server reports a generic tool error and logs the exception class only.
  • Tool not listed: check that server.py is in the project directory, the virtual environment is active, and the server imports successfully.
  • Blank question not rejected: confirm that the complete server.py has the blank-input check shown above and that the test calls call_tool_mcp() for the error assertion.

Local server versus remote service

This example is a local stdio server: the MCP client starts it on the same machine. Hosting an MCP server at a network URL is a separate setup involving a remote transport, deployment, and access-control decisions. FastMCP supports HTTP transports, but this tutorial does not configure or deploy one. (gofastmcp.com)

Adapting the tool to another model API

Keep the MCP tool and client flow, but replace the outbound API call only after consulting that provider’s current developer documentation. Verify its base URL, model identifier, authentication, request fields, response format, and error behavior. The available Token Forge Cloud product description is not enough to infer those integration details, so this tutorial does not provide Token Forge-specific code.

Sources

FastMCP Installation

FastMCP project metadata

FastMCP 4 is GA

The FastMCP Client

Calling Tools — FastMCP

FastMCP Client Tool Methods

Running Your FastMCP Server

OpenAI Python Library README

OpenAI Python SDK client implementation

Token Forge Cloud

Contact us