This is the most basic MCP (Model Context Protocol) server implementation. It demonstrates the fundamental structure of an MCP server with a single tool.
- Basic MCP server setup
- Registering a single tool (
echo) - Tool input schema definition
- Handling tool execution
- Using stdio transport for communication
server-stdio-echo.py- The MCP server implementation using https://pypi.org/project/mcp/
The server exposes one tool:
- echo: Takes a message string and echoes it back
- Create a virtual environment:
python3 -m venv .venv
source .venv/bin/activate # On Windows: venv\Scripts\activate- Install dependencies:
pip install -r requirements.txtThe MCP server uses stdio (standard input/output) for communication, so it's designed to be run by an MCP client rather than directly:
python 01-simple-server/server-stdio-echo.pyHowever, running it directly will wait for input. It's meant to be used with a client (see Example 2) or an MCP host like Claude Desktop or Cline. Or MCP inspector.
cntrl-cbrew install mcp-inspectormcp-inspector python 01-simple-server/server-stdio-echo.pymcp-inspector python 01-simple-server/server-stdio-math.pyThis is the most basic MCP (Model Context Protocol) client implementation. It demonstrates how to connect to an MCP server and interact with it.
- Connecting to an MCP server using stdio transport
- Initializing a client session
- Listing available tools from a server
- Calling tools with arguments
- Handling tool responses
You need the Example 1 server to run this client. Make sure the server is in the correct relative path (../01-simple-server/server.py).
python 02-simple-client/client-stdio-echo.pypython 02-simple-client/client-stdio-math.pypython 02-simple-client/client-stdio-math-divide-by-zero.pyConnected to MCP server!
=== Listing Available Tools ===
Tool: echo
Description: Echoes back the input text
Input Schema: {'type': 'object', 'properties': {...}, 'required': ['message']}
==================================================
=== Calling the 'echo' Tool ===
Result: Echo: Hello from the MCP client!
==================================================
=== Calling 'echo' Again ===
Result: Echo: MCP is working!
python 03-http-server-fastmcp/fastmcp-server-hello-world.pypython 03-http-server-fastmcp/fastmcp-server-math.pyResources are read-only data sources:
- Represent data that can be retrieved
- Use URIs for identification
- Good for: configuration, documentation, data exports
Tools are executable functions:
- Perform actions or computations
- Can modify state
- Good for: operations, transformations, API calls
-
note://static/welcome
- Plain text welcome message
- Always returns the same content
-
note://static/info
- JSON server information
- Includes timestamp (changes each read)
- note://notes/{id}
- Access notes by ID
- Available IDs:
note1,note2 - Example:
note://notes/note1
Run the example, the client invokes the server
python 04-server-with-resources/mcp-client-resources.py==================================================
AVAILABLE RESOURCES
==================================================
π Welcome Note
URI: note://static/welcome
Type: text/plain
Description: A static welcome message
π Server Info
URI: note://static/info
Type: application/json
Description: Information about this MCP server
==================================================
RESOURCE TEMPLATES
==================================================
π Note by ID
URI Pattern: note://notes/{id}
Type: text/plain
Description: Access a specific note. Available IDs: note1, note2
==================================================
READING: note://static/welcome
==================================================
Welcome to the MCP Resource Server!
This server demonstrates static and dynamic resources.
==================================================
READING: note://static/info
==================================================
{
"name": "Resource Demo Server",
"version": "1.0.0",
"capabilities": [
"static_resources",
"resource_templates"
],
"timestamp": "2025-10-09T18:05:14.764054"
}
==================================================
READING: note://notes/note1
==================================================
Title: Meeting Notes
Created: 2024-01-15
Discussed project timeline and milestones
==================================================
READING: note://notes/note2
==================================================
Title: Ideas
Created: 2024-01-16
New features to implement in Q2
==================================================
ALL TESTS COMPLETED SUCCESSFULLY
==================================================
python 05-complete-server/mcp-client-resources-tools.pyskipping for now
mcp-inspector python 07-weather-api-server/server-stdio-weather-dot-gov.pymcp-inspector python 07-weather-api-server/server-stdio-weather-open-meteo.pypython 07-weather-api-server/server-fastmcp-weather-dot-gov.pymcp-inspectorpip install -r 08-fastapi-mcp-wrapper/requirements.txtpython 08-fastapi-mcp-wrapper/fastapi_app.pyThis to see if it compiles, there is no output
python 08-fastapi-mcp-wrapper/api-wrapper-stdio.pycontrol-c a few times
mcp-inspector python 08-fastapi-mcp-wrapper/api-wrapper-stdio.pyFastMCP version
Make sure the API is up
python 08-fastapi-mcp-wrapper/fastapi_app.pyStart the MCP server
python 08-fastapi-mcp-wrapper/api-wrapper-fastmcp.pymcp-inspectorThis example includes a FastAPI endpoint that interacts with the MCP Server. No LLM involved.
cd 09-kubernetes-deploymentoc new-project mcp-servers-basicor
kubectl create namespace mcp-servers-basic
kubens mcp-servers-basickubens comes from brew install kubectx, it tweaks your KUBECONFIG file.
kubectl config view | grep -B 3 -A 3 "mcp-servers-basic"
~/.kube/config
This script assumes the creation of a public container image at quay.io therefore no pull-secret setup required on the Kubernetes side of things.
./deploy.shYou will need the route (or ingress if using vanilla kubernetes)
export MCP_URL=https://$(oc get routes -l app=mcp-server -o jsonpath="{range .items[*]}{.status.ingress[0].host}{end}")/mcp
echo $MCP_URLmcp-inspectorpython 10-basic-langgraph-agent/mcp-server-math/mcp-server.pyUse mcp-inspector to test
pip install -r 10-basic-langgraph-agent/requirements.txtexport OPENAI_API_KEY=sk-proj-ghs22s90wrow0-more-stuffpython 10-basic-langgraph-agent/my-langgraph-agent/langgraph_agent_add.pyexport OPENAI_API_KEY=sk-proj-ghs22s90wrow0-more-stuffpip install -r 11-agent-with-api/requirements.txtThis assumes you also pip install the requirements.txt in the root and you are using python3 -m venv .venv
Run the MCP Server
python 11-agent-with-api/mcp-server-math/mcp-server.pypython 11-agent-with-api/my-lpython 11-agent-with-api/my-langgraph-agent/fastapi_wrapper_langgraph.pycurl http://localhost:8000/healthcurl -X POST http://localhost:8000/add \
-H "Content-Type: application/json" \
-d '{"a": 2.0, "b": 3.0}'You can .env.example .env and change the URL to the MCP server along with the port for FastAPI
A pod for the Agent with its API A pod for the MCP Server
cd 12-agent-with-api-openshiftoc new-project agent-with-mcpUsing podman and quay.io instead of docker and docker.io. Pick your favorite and make it so you can build and push images to a known location. Note: quay.io marks your container image private by default, you will wish to dig through the settings and make it public. See ./images/quayio-private-to-public.png for a screenshot.
brew install podman
podman machine start
podman login quay.iopodman build --arch amd64 --os linux -t quay.io/burrsutter/mcp-server-math:1.0.0 ./mcp-server-math
podman push quay.io/burrsutter/mcp-server-math:1.0.0Test the container image for the MCP server
podman run \
-p 9005:9005 \
quay.io/burrsutter/mcp-server-math:1.0.0Test connectvitiy with curl
curl -N -H "Accept: text/event-stream" http://localhost:9005/mcpThe fact that you got an error message "Missing session ID" instead of connection refused means the server IS running and responding! The error is expected because streamable HTTP MCP requires session establishment.
It might respond with curl: (7) Failed to connect to localhost port 9005 after 0 ms: Couldn't connect to server in which case curl did not find something running on port 9005
Test connectivity with mcp-inspector
mcp-inspectorCntrl-C to kill the podman containers
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESDeploy to Kubernetes/OpenShift. You should be within the agent-with-mcp context. You can check that with oc project or kubectx commands.
kubectl apply -f mcp-server-math-kubernetes/mcp-server-deployment.yaml
kubectl apply -f mcp-server-math-kubernetes/mcp-server-service.yamlAnd if using OpenShift, use routes. If using vanilla Kubernetes, ask your sys admin for how to configure ingress
oc get podskubectl apply -f mcp-server-math-kubernetes/mcp-server-route.yamlexport MCP_URL=https://$(oc get routes -l app=mcp-server -o jsonpath="{range .items[*]}{.status.ingress[0].host}{end}")/mcp
echo $MCP_URLNote: the https beginning and the mcp ending
Test with mcp-inspector to see that you have connectivity.
Note: you might NOT want a public route to your mcp servers, especially ones with no security authN/authZ as this one is right now.
podman build --arch amd64 --os linux -t quay.io/burrsutter/my-langgraph-agent:1.0.0 ./my-langgraph-agent
podman push quay.io/burrsutter/my-langgraph-agent:1.0.0Test as a local container, first make sure you are running the mcp server
podman run -p 9005:9005 quay.io/burrsutter/mcp-server-math:1.0.0podman run -p 8000:8000 \
-e MCP_SERVER_URL=http://host.containers.internal:9005/mcp \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
quay.io/burrsutter/my-langgraph-agent:1.0.0Test via curl
curl -X POST http://localhost:8000/add \
-H "Content-Type: application/json" \
-d '{"a": 2.0, "b": 3.0}'and
curl -sS -X POST http://localhost:8000/add \
-H "Content-Type: application/json" \
-d '{
"a": 42.5,
"b": 57.5
}' | jq{
"result": 100.0,
"query": "Use the add tool to add 42.5 and 57.5",
"agent_response": "The result of adding 42.5 and 57.5 is 100.0."
}
Deploy to Kubernetes/OpenShift. You should be within the agent-with-mcp context. You can check that with oc project or kubectx commands.
Update the openai-secret.yaml with the appropriate key.
apiVersion: v1
kind: Secret
metadata:
name: openai-secret
type: Opaque
stringData:
api-key: "sk-proj-g2n-blah-blah"
kubectl apply -f my-langgraph-agent-kubernetes/openai-secret.yamlkubectl apply -f my-langgraph-agent-kubernetes/fastapi-langgraph-deployment.yamlThe health endpoint also checks to see if the mcp server is available which means you can get crashloops/errors while it is trying to make the connection.
kubectl get pods
NAME READY STATUS RESTARTS AGE
mcp-server-deployment-58b48b8fd4-77dk9 1/1 Running 0 47m
my-langgraph-agent-deployment-86cccbc894-64d6s 1/1 Running 0 3m47sLook for two happy pods where restarts are 0
kubectl apply -f my-langgraph-agent-kubernetes/fastapi-langgraph-service.yamlkubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mcp-server-service ClusterIP 172.30.117.54 <none> 9005/TCP 47m
my-langgraph-agent-service ClusterIP 172.30.139.88 <none> 8000/TCP 3sAnd if using OpenShift, use routes. If using vanilla Kubernetes, ask your sys admin for how to configure ingress
kubectl apply -f my-langgraph-agent-kubernetes/fastapi-langgraph-route.yamlkubectl get routesexport AGENT_URL=https://$(oc get routes -l app=my-langgraph-agent -o jsonpath="{range .items[*]}{.status.ingress[0].host}{end}")
echo $AGENT_URLcurl -X POST $AGENT_URL/add \
-H "Content-Type: application/json" \
-d '{"a": 2.0, "b": 3.0}'curl -sS -X POST $AGENT_URL/add \
-H "Content-Type: application/json" \
-d '{
"a": 42.5,
"b": 57.5
}' | jq{
"result": 100.0,
"query": "Use the add tool to add 42.5 and 57.5",
"agent_response": "The result of adding 42.5 and 57.5 is 100.0."
}
And if you are monitoring the pod logs you will see something like:
mcp-server-deployment-58b48b8fd4-77dk9 mcp-server Add: 42.5 + 57.5 = 100.0























