1. Why I build agents as MCP servers
I build my agents once, as MCP servers, and then use them from Claude, ChatGPT or any other app that speaks the protocol. My operations agent, my cold-chain research twin and my lab-bench coach all live outside any one chat app. The model on top can change; the agents and their data stay mine.
This guide explains how that works under the hood, shows the smallest server you can write, and walks through connecting it to Claude and ChatGPT. It assumes you can write a little TypeScript or Python and deploy something to a URL.
2. What MCP is
The Model Context Protocol (MCP) is an open standard for connecting AI apps to outside tools and data. Think of it as USB-C for agents: you build the device once, and any compatible port can use it.
There are three roles:
| Role | What it is | Example |
|---|---|---|
| Host | The app the person talks to | Claude, ChatGPT, Claude Code, an IDE |
| Client | The connector inside the host that speaks MCP to one server | One client per connected server |
| Server | Your program, exposing tools and data | iriis-hq, seryn-twin, iriis-vision |
Messages are JSON-RPC 2.0. They travel over one of two transports:
- stdio: the host starts your server as a local process and talks over standard input and output. Good for desktop apps and local dev.
- Streamable HTTP: your server sits at a URL like
https://yourdomain.com/mcpand the host sends HTTP POSTs to it. This is what web apps like claude.ai and ChatGPT use.
The current spec revision is 2026-07-28. It made the protocol stateless: there is no longer an initialize handshake or session. Each request carries its protocol version in _meta (and in the MCP-Protocol-Version header on HTTP), and a client can call server/discover to learn a server’s versions and capabilities in one request. Older tutorials that start with a handshake describe the 2025 revisions.
3. Tools, resources and prompts
A server can offer three kinds of things. Tools do most of the work in practice.
| Primitive | Who decides to use it | What it is | Example |
|---|---|---|---|
| Tools | The model | A function with a name, a description and a JSON input schema | seryn_insulation_compare compares two insulation options |
| Resources | The app or the user | Read-only data addressed by a URI, like a file or a record | A research paper the model can open and read |
| Prompts | The user | A reusable template, often shown as a slash command | A “/weekly-report” command |
The most important thing to understand about tools: the model only sees the name, the description and the input schema. It never sees your code. If the description is vague, the model calls the wrong tool or fills arguments badly. Writing tool descriptions is closer to writing documentation for a new colleague than to writing code.
4. What happens during a tool call
Say I ask Claude: “Compare PU foam and a vacuum panel for a 40-litre crate.” Here is the round trip.
- Discovery. When the connector is added, the client calls
tools/liston my server. It gets back every tool’s name, description and input schema. - The model chooses. The host puts those tool definitions in the model’s context with my question. The model decides a tool fits and writes a call with arguments that match the schema.
- The client sends it. The host’s MCP client sends a
tools/callrequest to my server, as an HTTP POST to/mcp. - My code runs. The server validates the arguments, runs the real logic (a database query, a simulation, an API call) and returns content, usually text or structured JSON.
- The model reads the result. The host adds the result to the conversation. The model may call another tool, or write its answer.
A useful mental model: the model is the planner, your server is the hands. The model never touches your database or API keys directly. It can only ask your server to run the tools you chose to expose, with the inputs your schema allows.
On the wire, a tool call looks roughly like this:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "seryn_insulation_compare",
"arguments": { "a": "pu_foam", "b": "vip", "volume_l": 40 },
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" }
}
}The argument names above are illustrative; the real ones are whatever your schema defines.
5. My agents as MCP servers
I run four servers. Each one shows a different way to shape an agent for MCP.
iriis: a small front door. Three tools: ask_iriis, iriis_status and speak. The host model hands a whole request to my own agent and gets an answer back. This is the simplest pattern: one agent, wrapped as one tool, usable from any host. (How the agent behind it works is in Iriis.)
iriis-hq: an operations layer. Dozens of focused tools for the work around my companies: drafting and sending email, scheduling with Calendly, research sessions, searching my codebase, goals and weekly reports. Each tool does one job. That lets the model chain them (find a contact, draft an email, schedule a follow-up) instead of me writing that workflow by hand.
seryn-twin: a domain expert. A digital twin for cold-chain crate design. Tools like seryn_insulation_compare, seryn_simulate_crate, seryn_eutectic and seryn_shelf_life run real calculations, and seryn_search_papers and seryn_verify_source ground answers in literature. When I ask a design question in Claude or ChatGPT, the model calls the simulation instead of guessing numbers.
iriis-vision: an agent with eyes. A lab-bench coach. Tools like bench_look, bench_readings, bench_start_procedure and bench_coach let the model see what is on the bench and guide a procedure step by step.
The point: the hard part (the domain logic, the data, the integrations) is written once. Claude, ChatGPT or the next app just becomes a different front end to the same agents.
6. Build a minimal MCP server
The quickest route to a remote server is a Next.js route with Vercel’s mcp-handler. Version 2 supports the 2026-07-28 spec and the TypeScript SDK v2, and still serves older Streamable HTTP clients from the same endpoint. It needs Node.js 20 or later.
npm install mcp-handler@^2 @modelcontextprotocol/server@^2 zod@^4Then one file, app/api/mcp/route.ts, with one tool:
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";
const handler = createMcpHandler((server) => {
server.registerTool(
"shelf_life_estimate",
{
description:
"Estimate how many hours a crate keeps food below a target temperature. " +
"Use when the user asks how long produce will stay cold in transit.",
inputSchema: z.object({
ambient_c: z.number().describe("Outside temperature in °C"),
target_c: z.number().describe("Maximum safe temperature in °C"),
ice_kg: z.number().positive().describe("Mass of ice or PCM in kg"),
}),
},
async ({ ambient_c, target_c, ice_kg }) => {
const hours = await runMyModel({ ambient_c, target_c, ice_kg }); // your real logic
return {
content: [{ type: "text", text: `Estimated ${hours.toFixed(1)} hours below ${target_c} °C.` }],
};
},
);
});
export { handler as GET, handler as POST };Deploy it and your server lives at https://yourdomain.com/api/mcp. That is the whole server. runMyModel stands in for whatever your agent actually does.
Three things in that file matter more than they look:
- The description says when to use the tool, not only what it does. That sentence is how the model picks it.
- Every argument has units and a
.describe(). The model fills arguments from the schema alone. - The result is plain, short text. Return what the model needs to answer, not a dump of your database.
Prefer Python? The official Python SDK offers the same ideas (a server object, decorated tool functions, a Streamable HTTP app). Check the MCP docs for the SDK version that matches the current spec.
7. Connect it to Claude and ChatGPT
The same URL works in both. Your server must be reachable over HTTPS; neither web app can start a local stdio process on your laptop.
Claude (claude.ai and the apps)
- Open Settings, then Connectors.
- Add a custom connector and paste your server URL.
- Approve the sign-in if your server uses OAuth.
- In a chat, turn the connector on from the tools menu and ask a question that needs it.
On Team and Enterprise plans an owner may need to add the connector first.
Claude Code (terminal)
claude mcp add --transport http my-agent https://yourdomain.com/api/mcpChatGPT
- Open Settings, then Apps, then Advanced settings, and turn on Developer mode.
- Create an app and paste your server URL.
- In a chat, open the + menu, choose Developer mode and select your app.
Developer mode is a beta on paid plans (Plus, Pro, Business, Enterprise, Education) and works on the web, not mobile. Write actions go through ChatGPT’s confirmation step. Menu names move around between releases, so check OpenAI’s current help page if yours look different.
Test it like this: first ask something that should call exactly one read-only tool. Check that the right tool ran with sensible arguments. Then try a question that needs two tools in a row.
8. What I’ve learned: design and security
Designing tools
- One job per tool.
draft_emailandsend_emailas two tools beats one email tool with a mode flag. The model chains small tools well. - Name tools by prefix. Every seryn-twin tool starts with
seryn_and every bench tool withbench_. With several servers connected, prefixes stop the model mixing them up. - Fewer, better tools beat many. Every tool definition takes space in the model’s context. Past a few dozen, the model gets less reliable at picking the right one.
- Return errors the model can act on. “No crate found with id 42; call list_crates to see valid ids” lets the model recover. A stack trace does not.
Security
- Anything on a public URL needs auth. The spec defines an OAuth 2.1 profile for HTTP servers, and Claude and ChatGPT both support it. Never ship an open endpoint that can read your data.
- Split read from write. Mark read-only tools with the
readOnlyHintannotation. Keep tools that send, pay or delete separate, and require a draft-then-confirm step for them. - Treat tool inputs as untrusted. The model may be steered by text it read in a web page or email (prompt injection). Validate every argument on the server, and never pass model input straight into SQL or a shell.
- Give the server only the access it needs. A read-only database user for read tools, scoped API keys, and logs of every call.
- Don’t publish your internals. When writing about your servers, describe what tools do. Leave out endpoints, keys and how auth is set up.
9. Where this leaves you
If you already have an agent, an API or a dataset you rely on, wrapping it as an MCP server is a weekend job. Start with one read-only tool, connect it to the chat app you use most, and watch how the model calls it. Then add tools one at a time.
The payoff is that your work stops being tied to one AI app. When a better model ships, you switch hosts and your agents come with you.
Sources
- MCP versioning, 2026-07-28 spec
- Vercel: mcp-handler 2.0 changelog
- How to connect an MCP server to ChatGPT
- MCP Station: connecting AI clients