An AI agent is only as useful as the tools it can reach. The Model Context Protocol (MCP) is the open standard for giving it those tools: a small server offers a set of functions, and your agent connects to it as a client. In this guide you'll build a tiny MCP server, connect it to a Python agent, then add a remote server and an open-source one next to it. Every snippet was run against Promptise Foundry 1.2.1, and the output you see is what it printed.
How do you connect an MCP server to an AI agent?
You tell the agent where each server lives, and it does the rest. With Promptise Foundry, that's the servers argument of build_agent:
from promptise import build_agent
from promptise.config import HTTPServerSpec, StdioServerSpec
agent = await build_agent(
model="openai:gpt-5-mini",
servers={
"orders": StdioServerSpec(command="python", args=["orders_server.py"]),
"crm": HTTPServerSpec(url="https://mcp.example.com/mcp"),
},
)When the agent starts, it connects to every server, asks each one which tools it offers, and hands that list to the model. When the model decides a tool will help, the agent calls it on the right server and passes the result back. You write no glue code per tool. The rest of this guide shows each piece working.
[02]
How MCP fits into your agent
Think of an MCP server as a counter between your agent and your systems. The agent asks; the server decides what's allowed and does the work. The agent never touches your database directly.
Rendering diagram…
That boundary is what makes MCP worth it. You can swap the model, change frameworks, or add a second agent, and the servers stay exactly as they are.
Servers talk to agents in one of two ways:
| stdio | HTTP |
|---|---|---|
How it runs | The agent starts the server as a child process | The server runs on its own; the agent connects by URL |
Good for | Local tools, development, single-user apps | Shared services, production, anything behind auth |
In Promptise | StdioServerSpec | HTTPServerSpec |
[03]
What you need
Python 3.10 or newer.
An API key for a model provider. This guide uses OpenAI. Anthropic, Gemini, Azure, Bedrock, Ollama and others work by changing the model string, as listed in Models & Providers.
Promptise Foundry, which installs from PyPI:
pip install promptise
export OPENAI_API_KEY="sk-..."[04]
Build and connect your first MCP server
We'll give a support assistant one job: tell customers where their order is.
Write a small MCP server
An MCP server is a normal Python file. Each function you decorate with @server.tool() becomes a tool the agent can call. Promptise builds the tool's input schema from your type hints.
from promptise.mcp.server import MCPServer
server = MCPServer("orders")
# A stand-in for your real database or API.
ORDERS = {
"A-1001": {"status": "shipped", "carrier": "DHL", "eta": "2026-10-14"},
"A-1002": {"status": "processing", "carrier": None, "eta": None},
}
@server.tool()
async def get_order_status(order_id: str) -> dict:
"""Get an order's status, carrier and expected delivery date.
Args:
order_id: The order ID, for example "A-1001".
"""
order = ORDERS.get(order_id.strip().upper())
if order is None:
return {"error": f"No order found with ID {order_id}."}
return {"order_id": order_id.strip().upper(), **order}
if __name__ == "__main__":
server.run()Two details matter more than they look:
The docstring is written for the model. Its first paragraph becomes the tool's description, and each entry under Args: describes a parameter. That's what the model reads when it decides whether to call the tool and what to pass, so say plainly what the tool does and what each input looks like.
A missing order comes back as data, not an exception. The model can read "No order found" and tell the customer something useful.
Connect it to your agent
Create a second file next to the first one:
import asyncio
import sys
from promptise import build_agent
from promptise.config import StdioServerSpec
async def main():
agent = await build_agent(
model="openai:gpt-5-mini",
servers={
"orders": StdioServerSpec(command=sys.executable, args=["orders_server.py"]),
},
instructions=(
"You are a friendly support assistant. Use your tools to answer "
"questions about orders. Only offer help your tools can provide."
),
trace_tools=True,
)
try:
print("Tools:", agent.tool_names)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "Hi! Where is my order A-1001?"}]}
)
print(result["messages"][-1].content)
finally:
await agent.shutdown()
asyncio.run(main())A few things are happening here:
StdioServerSpec tells the agent to start orders_server.py itself. Using sys.executable runs the server with the same Python, and the same installed packages, as your agent.
trace_tools=True prints every tool call, so you can watch the agent work.
agent.shutdown() closes the connection and stops the server process. The try/finally makes sure that happens even if something fails.
Run it
python agent.pyTools: ['get_order_status']
→ Invoking tool: get_order_status with {'order_id': 'A-1001'}
✔ Tool result from get_order_status: {"order_id": "A-1001", "status": "shipped", "carrier": "DHL", "eta": "2026-10-14"}
Your order A-1001 is shipped via DHL and is expected to be delivered on 2026-10-14. …Follow the arrows. The agent found one tool on the server. The model chose it, pulled the order ID out of the question, and wrote its answer from the result. Your agent code doesn't mention orders anywhere: it learned about the tool from the server. The wording of the final reply will be a little different each time you run it, because the model writes it fresh.
[05]
Connect a remote MCP server over HTTP
stdio is perfect while you build. Once several agents need the same server, or it lives on another machine, run it over HTTP instead. On the server, that's a one-line change at the bottom of orders_server.py:
if __name__ == "__main__":
server.run(transport="http", host="127.0.0.1", port=8080)Start it in its own terminal with python orders_server.py. It now listens at http://127.0.0.1:8080/mcp. In your agent, swap the stdio spec for an HTTP one that points at that address:
from promptise.config import HTTPServerSpec
servers = {
"orders": HTTPServerSpec(url="http://127.0.0.1:8080/mcp"),
}Nothing else changes. The agent finds the same tool and answers the same way.
[06]
Use several MCP servers in one agent
Real agents rarely live on one server. Add as many as you need to servers and Promptise merges their tools into one list for the model.
Here the agent uses your orders server over HTTP, plus mcp-server-time, an open-source server from the MCP project. uvx, which comes with uv, downloads and starts it for you.
import asyncio
from promptise import build_agent
from promptise.config import HTTPServerSpec, StdioServerSpec
async def main():
agent = await build_agent(
model="openai:gpt-5-mini",
servers={
# Your own server, already running over HTTP.
"orders": HTTPServerSpec(url="http://127.0.0.1:8080/mcp"),
# An open-source server from the MCP project, started on demand.
"time": StdioServerSpec(command="uvx", args=["mcp-server-time"]),
},
instructions=(
"You are a friendly support assistant. Use your tools to answer "
"questions about orders. Only offer help your tools can provide."
),
trace_tools=True,
)
try:
print("Tools:", agent.tool_names)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "How many days until order A-1001 arrives?"}]}
)
print(result["messages"][-1].content)
finally:
await agent.shutdown()
asyncio.run(main())Tools: ['get_order_status', 'get_current_time', 'convert_time']
→ Invoking tool: get_order_status with {'order_id': 'A-1001'}
→ Invoking tool: get_current_time with {'timezone': 'Europe/Zurich'}
✔ Tool result from get_current_time: {
"timezone": "Europe/Zurich",
"datetime": "2026-10-10T17:20:24+02:00",
"day_of_week": "Saturday",
"is_dst": true
}
✔ Tool result from get_order_status: {"order_id": "A-1001", "status": "shipped", "carrier": "DHL", "eta": "2026-10-14"}
Order A-1001 is marked "shipped" with DHL and expected on 2026-10-14.
That’s 4 days from today (today is 2026-10-10 in Europe/Zurich). …No single server could answer that question. The agent asked both at once, the orders server for the delivery date and the time server for today's date, then did the subtraction itself. Promptise runs independent tool calls in parallel, which is why both calls start before either result comes back. That's the real payoff of MCP: small, focused servers that combine into something more capable.
Rendering diagram…
[07]
Connect to an MCP server that needs an API key
A server on a network should know who's calling. Promptise servers can check an API key before they answer anything:
import os
from promptise.mcp.server import APIKeyAuth, AuthMiddleware, MCPServer
server = MCPServer("orders", require_auth=True)
server.add_middleware(
AuthMiddleware(APIKeyAuth(keys={os.environ["ORDERS_API_KEY"]: "support-agent"}))
)require_auth=True puts every tool behind the key. A client without a valid key gets 401 Unauthorized and never sees the tool list.
On the agent side, pass the key to the spec. Promptise sends it in the x-api-key header:
import os
from promptise.config import HTTPServerSpec
servers = {
"orders": HTTPServerSpec(
url="http://127.0.0.1:8080/mcp",
api_key=os.environ["ORDERS_API_KEY"],
),
}If your server uses JSON Web Tokens from an identity provider instead, pass bearer_token= and Promptise sends it as Authorization: Bearer …. Either way, keep keys in environment variables or a secret manager, never in your code. Authentication & Security covers JWTs, roles and per-tool guards.
[08]
When the agent doesn't use your tools
Most connection problems come down to one of these:
The tool is never called. The model doesn't understand when it's useful. Rewrite the docstring's first paragraph so it says plainly what the tool does and when to use it.
The stdio server isn't found. Relative paths in args are resolved from the folder you run the agent in. Use an absolute path, or set cwd= on StdioServerSpec.
The model offers things it can't do. Models like to be helpful. Say what the agent may and may not offer in instructions, and keep in mind that the model still words its own replies.
Nothing happens with a local model. The model has to support tool calling. With Ollama, pick a model that does.
The HTTP server refuses the connection. build_agent stops with an MCPConnectionRejectedError that names the server and the status, for example
Server 'orders' rejected the connection: 401 Unauthorized.A 401 or 403 means the key or token on the spec doesn't match the server's. If the URL doesn't end in /mcp, the error readsFailed to connect to server 'orders': … (McpError: Session terminated)instead.
[09]
Frequently asked questions
What is an MCP server?
A program that offers tools, data or prompts to AI applications through the Model Context Protocol. Anything that speaks MCP can use it: your own agent, Claude Desktop, an IDE assistant. You write the integration once and every client can use it. What is MCP? explains the protocol in more depth.
Do I need Promptise to use MCP?
No. MCP is an open protocol, and any MCP client can connect to the servers in this guide. Promptise Foundry is a framework for the agent around those connections: reasoning, memory, guardrails, human approval and a runtime for production. Its MCP client is built in, so connecting servers takes one argument.
Which models can I use?
Any model with tool calling. Promptise ships with providers for OpenAI, Anthropic, Google Gemini and Vertex AI, Azure, Amazon Bedrock, Ollama, Mistral, Groq, OpenRouter and more. You switch by changing the model string, for example anthropic:claude-sonnet-4-5 or ollama:llama3.1.
Should I use stdio or HTTP?
Start with stdio: there's nothing to deploy and the agent manages the server for you. Move to HTTP when a server needs to be shared between agents, run on another machine, or sit behind authentication.
Can I turn my existing REST API into an MCP server?
Yes. If your API has an OpenAPI spec, MCPcast generates a complete MCP server from it, with read-only defaults and human approval on anything that writes.
Is it safe to let an agent call tools?
It's as safe as the server makes it. Keep each server's tools narrow, require authentication, and ask a human before anything that changes data. Promptise servers support all three, including approval gates that pause a tool call until someone says yes.
[10]
Where to go next
Quick Start: the shortest path from install to a working agent.
Server configuration: every option on StdioServerSpec and HTTPServerSpec.
MCP server fundamentals: resources, prompts, routers and middleware.
Deployment: running MCP servers in production.
Promptise Foundry on GitHub: the source, open under Apache 2.0.