← Back to list

From localhost to ChatGPT: wiring up your MCP server in Python

The Model Context Protocol (MCP) is one of the most exciting technologies to emerge from the generative AI boom, first introduced by…

Rhauani Fazul · 2025-10-30 17:58 · 15 claps · 7.8 min read
#model-context-protocol #artificial-intelligence #ai-agent #chatgpt #python
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents AI · AI · General

From localhost to ChatGPT: wiring up your MCP server in Python

The **Model Context Protocol (MCP) is one of the most exciting technologies to emerge from the generative AI boom, first introduced by Anthropic **in 2024 as a standard way for AI models to interact with external tools, data, and environments.

But here’s the thing: finding consistent, working examples is still hard.

As of Oct 2025, the latest LLMs (ChatGPT, Gemini, and even Claude) struggle to produce valid, runnable MCP setups. I discovered this firsthand while trying to connect a local MCP server to Claude Desktop and, more recently, ChatGPT, and found that most AI-generated instructions were outdated, incomplete, or just wrong (they usually get lost in different wrappers out there).

So I decided to try a few things myself.

In this post, I’ll share a step-by-step walkthrough of setting up a minimal MCP environment in Python, running a local server (optionally exposing it via ngrok, and connecting it directly to ChatGPT), and creating a simple, customized client.

MCP lib used in this hands-on tutorial: Model Context Protocol — Python SDK

1. Setting up the environment

First, let’s start clean. I recommend creating a Python virtual env to keep dependencies isolated:

mkdir mcp-tutorial && cd mcp-tutorial/
python3 -m venv env
source env/bin/activate  # on Windows you can find many tutorials online of how to enable it :)

Now install the core dependencies:

pip3 install mcp mcp[cli] uv
pip3 show mcp # Version: 1.19.0

For the CLI, you’ll also need npx, which comes with Node.js. It is a lightweight command runner used to execute npm packages without permanently installing them, great for quickly scaffolding or testing tools.

2. Creating your First MCP server

Let’s make something simple to start with, a minimal MCP server that exposes two illustrative data-ops tools: sort and avg.

# server.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("dataops", stateless_http=True)

@mcp.tool()
def sort(values: list[float], reversed: bool = False) -> list[float]:
    return sorted(values, reverse=reversed)

@mcp.tool()
def avg(values: list[float]) -> float:
    return sum(values) / len(values) if values else 0.0

if __name__ == "__main__":
    print("MCP server is running... ")
    mcp.run()

To run the server:

mcp run server.py -t sse

You should see something like:

Why SSE? MCP supports two primary transports:

stdio: great for local processes or CLI integrations.

sse (Server-Sent Events): ideal for network-based clients, such as ChatGPT or Claude Desktop.

Since we’ll soon connect to ChatGPT, SSE is the right choice. Please don’t take that at face value: take a deeper look into the MCP transport mechanisms used for client–server communication here.

2.1. (optional) Inspecting your server

You can inspect a MCP server interactively with:

mcp dev server.py

This will open your browser at http://localhost:6274/, with a UI that displays all tools you’ve defined, which is quite useful for quick debugging or verifying configuration:

3. (optional) Connecting MCP to ChatGPT

To connect your local server to ChatGPT, you’ll need a ChatGPT Plus or Team plan, since tool integrations require the premium tier as of now. If you don’t have it, you can try integrating with Claude Desktop or skip to the next section, in which we’ll create a custom local MCP client.

First, expose your local server (running on http://127.0.0.1:8000, by default) to the internet using:

ngrok http 8000

Once ngrok gives you a public URL like:

https://foo-bar.ngrok-free.dev

Append /sse at the end:

https://foo-bar.ngrok-free.dev/sse

Then, go to ChatGPT → + → Add sources → Add → Connect more:

This will open a pop-up. At the very bottom, you’ll see an option for Advanced settings; click it and enable Developer mode (currently in beta):

Apps & Connectors

Apps & Connectors

(note: after finishing this test, you can disable dev mode again, as it may limit your experience when using ChatGPT)

Go back to the previous page and you’ll see a new button (Create) after Enabled apps & connectors:

Apps & Connectors

Apps & Connectors

Click on it! Now configure the connector with the generated URL (with /sse at the end), disable authentication (it’s just a test!), and mark the warning checkbox. Click create (note: your MCP server should be running):

And grant permission if needed:

If the connection succeeds (it can take a few seconds), you’ll see something like:

Then, go back + → More → Select your connector (if you don’t find it, you may need to publish it by clicking in the three dots shown after Disconnect on the previous screenshot):

Try chatting with it! If your question doesn’t involve any of the tools you’ve exposed, ChatGPT behaves as usual; you’ll just get a regular generative response.

But when your prompt matches the purpose of a registered MCP tool, something different happens: ChatGPT automatically detects that a relevant tool exists and suggests using it. For example, if you ask:

“Use a dedicated tool to sort these numbers: 1 5 33 225 55.5 5 4 4555 and then another one to calculate the average”

The model will recognize that your server includes tools that fit this purpose, and it will show a confirmation message to execute them:

Once you confirm, ChatGPT sends a structured call to your MCP endpoint using the defined schema, executes the function on your server, and returns the result in the chat:

Behind the scenes, the AI chooses which tool to use based on semantic intent; it interprets your message, compares it with each tool’s metadata (name, description, parameters), and then decides which one best fits the request. In other words, the LLM acts as a “semantic router,” dynamically invoking the right tool when it detects a relevant action.

That’s the beauty of MCP: the model doesn’t just talk, it acts! (you still need to watch out for gen. AI hallucinations though)

4. Creating an MCP Client

Let’s build our own MCP client. This helps you integrate LLMs or custom apps outside external clients like Claude Desktop or ChatGPT.

Here’s a simple Python example:

# client.py 

import asyncio
from mcp import ClientSession, types
from mcp.client.sse import sse_client

async def main():
    async with sse_client("http://localhost:8000/sse") as (read_stream, write_stream):
        # Start a client session
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()

            # List available tools
            tools = await session.list_tools()
            print(f"Available tools: {[tool.name for tool in tools.tools]}")

            # Example: call the 'sort' tool
            result = await session.call_tool("sort", {"values": [5, 3, 9, 1]})
            print("Sort result: ")
            for content in result.content:
                if isinstance(content, types.TextContent):
                    print(f"\t{content.text}")

            # Example: call the 'avg' tool
            result = await session.call_tool("avg", {"values": [10, 20, 30]})
            print("Avg result:")
            for content in result.content:
                if isinstance(content, types.TextContent):
                    print(f"\t{content.text}")

if __name__ == "__main__":
    asyncio.run(main())

Run it with:

python3 client.py

If everything works, you should see the available tools and the results from your functions:

4.1 Integrating with an LLM

The next step (which deserves its own post, but I’ll keep things short) is wiring this client to an LLM orchestrator.

That’s where MCP starts to shine: it’s the bridge between your AI and your systems. Let’s use Gemini API here, but feel free to adapt as needed.

First install the extra dependency:

pip3 install google.generativeai

Then you’ll need an API key. Generate it through https://aistudio.google.com/api-keys by clicking in Create API key:

Name your key and select a project: (if you don’t have one, just click on Create project. It’ll open a pop-up; give it a name, and click in the Create project button on the pop-up)

Click Create key (the pop-up will be diminished) and then you’ll find your brand new API key listed on the UI. Copy it (and don’t share it!).


# client_llm.py

import asyncio
import json
import google.generativeai as genai
from mcp import ClientSession, types
from mcp.client.sse import sse_client

genai.configure(api_key="<YOUR_API_KEY>")  # or use environment variable

# Choose your model
MODEL = "gemini-2.5-pro" # you can use -flash instead of -pro if you prefer

async def run_tool_with_gemini(prompt: str):
    async with sse_client("http://localhost:8000/sse") as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()

            tools = await session.list_tools()
            tool_schemas = [
                {"name": t.name, "description": t.description, "parameters": t.inputSchema}
                for t in tools.tools
            ]

            gemini_prompt = f"""
            You are an AI assistant connected to an MCP server with these tools:

            {json.dumps(tool_schemas, indent=2)}

            Based on this user request:
            "{prompt}"

            Respond ONLY in JSON with:
            {{
                "tool": "<tool_name>",
                "args": {{ ... arguments ... }}
            }}
            """

            model = genai.GenerativeModel(MODEL)
            response = model.generate_content(gemini_prompt)
            text = response.text.strip()

            # Handle ```json ... ``` wrappers
            if text.startswith("```"):
                text = text.strip("`")
                if text.lower().startswith("js  on"):
                    text = text[4:].strip()
                elif text.lower().startswith("json\n"):
                    text = text[5:].strip()

            try:
                choice = json.loads(text)
                tool_name = choice["tool"]
                args = choice.get("args", {})
            except Exception as e:
                print("Gemini parsing error:", e)
                print("Raw response text:", response.text)
                return

            print(f"Gemini chose: {tool_name}({args})")

            # Call the chosen tool
            result = await session.call_tool(tool_name, args)

            print("Result:")
            for content in result.content:
                if isinstance(content, types.TextContent):
                    print(content.text)

if __name__ == "__main__":
    user_prompt = input("Ask Gemini what to do (e.g. 'Sort [1, 3, 55, 12, 1]'): ")
    asyncio.run(run_tool_with_gemini(user_prompt))

Run it with:

python3 client_llm.py

Ask Gemini anything you like! It will automatically try to find the tool that best matches your prompt. Based on the standardized response JSON, a tool in your MCP server will be called:

If no suitable tool is found, it simply returns an empty response, meaning the request couldn’t be handled by any available tool.

This example serves as a naive programmatic bridge between Gemini and your MCP tools based on your natural-language prompt.

If you prefer a higher-level interface, you can achieve the same workflow with LangChain. You can wrap each MCP tool as a StructuredTool object and let the model invoke them using the standard invoke(messages, functions=tools) method. That’s a topic for another day!

Wrapping up

If you’ve made it this far, congrats, you’ve just built your own MCP server and client!

Even though the docs and real-world projects are still catching up, MCP represents a huge step toward modular, interoperable AI systems.

If you’re feeling adventurous, try exploring next:

  • MCP template prompts and resources, for structured, context-aware workflows;
  • Authentication and metadata;
  • Advanced LLM-driven orchestration.

Happy coding!


메타데이터
post_id
ed5ff62976d3
slug
from-localhost-to-chatgpt-wiring-up-your-mcp-server-in-python-ed5ff62976d3
url
https://medium.com/@rfazul/from-localhost-to-chatgpt-wiring-up-your-mcp-server-in-python-ed5ff62976d3
canonical_url
https://medium.com/@rfazul/from-localhost-to-chatgpt-wiring-up-your-mcp-server-in-python-ed5ff62976d3
author_url
https://medium.com/@rfazul
status
ok
fetched_at
2026-06-11 21:11:36