How to Create an MCP Server in 2026: Current Spec, Both Transports, Claude and ChatGPT
If you last built against the 2025-11-25 revision, most of what you know about the MCP handshake and session headers is now wrong. The 2026-07-28 revision retired both.
- What changed in the 2026-07-28 revision
- The shortest MCP server: TypeScript
- The shortest MCP server: Python
- Step 1: Set up the project
- Step 2: Define tools and resources
- Step 3: Run over stdio for local clients
- Step 4: Serve HTTP for a remote MCP server
- Step 5: Add authorization
- Step 6: Test the server
- Step 7: Deploy the server
- Step 8: Connect it to Claude and ChatGPT
- How to create an MCP server without a codebase
- FAQ
This guide covers how to create an MCP server against the current revision: a working server on your machine over stdio, the same server exposed remotely over HTTP, authorization that current clients will actually accept, and the connector wired into Claude and ChatGPT. There is a second route at the end for people whose product is expertise rather than an API.
Two sentences of context and then we build. MCP is the protocol AI clients use to call your tools, read your resources, and run your prompts. A server is the thing on your side that answers those calls.
Spec status as of September 2026
The 2026-07-28 revision has been final since July 28, 2026, and it is the largest revision since MCP launched. The revision it replaces is 2025-11-25.
What changed, in the order it will affect your build:
- The protocol core is stateless. The initialize/initialized handshake is retired (SEP-2575) and the
Mcp-Session-Idheader is retired (SEP-2567). Every request carries its protocol version, client identity, and client capabilities in_metainstead. - An optional
server/discoverRPC exists for clients that want your capabilities up front. - Streamable HTTP requests must carry
Mcp-MethodandMcp-Nameheaders (SEP-2243), and servers reject requests where the headers and the body disagree. - Multi Round-Trip Requests (MRTR, SEP-2322) replace server-initiated
elicitation/create,sampling/createMessage, androots/list. tools/list,prompts/list,resources/list, andresources/readresponses carryttlMsandcacheScope(SEP-2549).- Tool schemas support full JSON Schema 2020-12.
- Roots, Sampling, and Logging are deprecated (SEP-2577), and the legacy HTTP+SSE transport is deprecated. Both keep working for at least twelve months, and new implementations should not adopt them.
- Tasks left the experimental core for the
io.modelcontextprotocol/tasksextension, with poll-basedtasks/getand a newtasks/update(SEP-2663). - Change notifications moved off the old HTTP GET endpoint to a single
subscriptions/listenstream that clients opt into per notification type.
Everything below is written against the current spec, whose changelog and getting-started docs live on the same site.
The shortest MCP server: TypeScript
TypeScript ships as two new packages at 2.0, @modelcontextprotocol/client and @modelcontextprotocol/server. The older @modelcontextprotocol/sdk line still exists, but it is there for 2025-era servers. Do not start a new build on it.
npm init -y
npm install @modelcontextprotocol/server@2Create one file that does four things. It instantiates a server from @modelcontextprotocol/server, registers a single tool with a JSON Schema 2020-12 input schema, registers a handler that returns content, and attaches a transport. For a first run, attach the stdio transport so the process reads requests on stdin and writes responses on stdout. Add a start script and run it with node.
The exact constructor and registration signatures for the 2.0 packages are in the current SDK docs. Follow those rather than any snippet you find that still calls an initialize handler, because that code targets a revision that no longer applies.
If you are adding MCP to an existing Node service, install @modelcontextprotocol/client alongside it only when your service also needs to consume other MCP servers. A server-only build does not need it.
The shortest MCP server: Python
Python SDK v2 is the current stable line and speaks 2026-07-28 plus every earlier revision. One thing will bite you: pip install mcp now resolves to 2.x.
python -m venv .venv && source .venv/bin/activate
pip install "mcp>=2"If you have an existing server on the 1.x line and you are not migrating today, pin it:
pip install "mcp>=1.28,<2"The v1.x branch receives critical bug fixes and security patches only, so treat that pin as a deadline rather than a resting place.
The Python build has the same four moving parts as the TypeScript one. Create the server object, declare a tool with its input schema, implement the handler, and run it over stdio. Keep handlers async if they touch the network, since the transport will happily run concurrent calls once you move to HTTP.
Go and C# are also Tier 1 and spoke 2026-07-28 on launch day, and the Rust SDK supports it in beta. There is now an official PHP SDK as well, mcp/sdk, a PHP Foundation and Symfony collaboration covering tools, resources, prompts, STDIO and HTTP transports, sessions, and authorization across both protocol eras. It is experimental until its first major release.
Step 1: Set up the project
Decide two things before you write a handler.
First, what the server owns. A server that wraps one product surface and exposes six well-named tools is easier for a model to use correctly than a server that exposes sixty thin endpoint mirrors. Name tools the way you would name a function a junior engineer has to pick from a dropdown.
Second, where it will run. If it will only ever run next to a desktop client, stdio is enough. If anything outside the user's machine will call it, plan for HTTP from the start, because the auth and deployment work is not something you bolt on cleanly later.
Keep the transport wiring in a separate file from your tool definitions. You will be running both transports before you are done.
Step 2: Define tools and resources
Tools are the calls a model can make. Resources are the data it can read. Prompts are reusable instruction templates you hand the client.
Tool schemas now support full JSON Schema 2020-12, which is the practical upgrade in this revision. Use it. Constrain enums instead of accepting free strings, use oneOf where a call has genuinely different shapes, and set required precisely. Every constraint you encode is a class of bad call the model cannot make.
List responses carry ttlMs and cacheScope. Set them deliberately. A tool catalog that changes on deploy can carry a long ttlMs, and a resource listing that reflects live user data should carry a short one or none. Clients will cache according to what you send, so an over-generous ttlMs on volatile data shows up later as a model confidently working from a stale list.
If a tool needs something from the user partway through, that is MRTR now. Instead of the server initiating elicitation or sampling, your handler returns a result with resultType set to "input_required" along with the requests it needs answered, and the client retries the original call with those answers attached in inputResponses. Write handlers so they can be re-entered: the retry arrives as a fresh call, not as a continuation of the first one.
Do not build on Roots, Sampling, or Logging. They are deprecated with a twelve-month minimum window, and adopting them now means rewriting inside the year.
Step 3: Run over stdio for local clients
Stdio is the fastest way to get a real client talking to your code. The client launches your process and speaks JSON-RPC over the pipes.
Two habits save time here. Never write anything to stdout that is not a protocol message, because a stray print statement corrupts the stream and produces errors that look like protocol bugs. Send your own diagnostics to stderr. And make the entry point runnable with a single command with no shell wrapper, since that is the form client configuration expects.
Claude Desktop and Cursor can both run a local stdio server directly, so this is the loop you should be developing in.
Step 4: Serve HTTP for a remote MCP server
Streamable HTTP is the transport for anything remote. The legacy HTTP+SSE transport is deprecated with a year-long offramp, so a new server should not implement it.
Three requirements from this revision shape the HTTP layer.
Requests must carry Mcp-Method and Mcp-Name headers so gateways, rate limiters, and WAFs can route and meter traffic without parsing the JSON body:
POST /mcp HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
Authorization: Bearer <token>
Mcp-Method: tools/call
Mcp-Name: create_invoiceYour server must reject requests where those headers disagree with the body. If you are using a Tier 1 SDK the check is handled for you, but if you are terminating requests in your own framework first, verify it yourself rather than assuming your proxy passed the headers through untouched.
There is no session header anymore. Protocol version, client identity, and client capabilities arrive in _meta on each request. Read them per request rather than caching them against a connection.
Change notifications no longer live on an HTTP GET endpoint. Clients opt in per notification type through a single subscriptions/listen stream. If you were planning a long-lived GET channel, that design is gone.
Step 5: Add authorization
The auth changes in this revision are mostly about tightening the OAuth flow, and clients enforce them.
- Authorization servers should return the
issparameter per RFC 9207, and clients must validate it before redeeming a code (SEP-2468). If you control the authorization server, returniss. If you are relying on a third-party identity provider, confirm it does. - Clients set
application_typeduring Dynamic Client Registration so authorization servers stop rejecting localhost redirects for desktop and CLI apps (SEP-837). This is the fix for the loopback redirect failures that made local client auth miserable. - Client credentials are bound to the issuer that minted them (SEP-2352). Credentials from one issuer are not portable to another.
- Dynamic Client Registration itself is formally deprecated in favor of Client ID Metadata Documents. DCR still works for backward compatibility but will be removed in a future version of the spec.
Build new registration against CIMD and keep DCR only as a fallback for older clients.
Step 6: Test the server
Test at the wire level before you test in a chat window, because a client will swallow the detail you need.
Send tools/list with the required headers and confirm the response carries the ttlMs and cacheScope you intended. Send a tools/call with a deliberately mismatched Mcp-Name and confirm you get a rejection rather than an execution. Exercise the MRTR loop end to end: force a handler to return "input_required", answer it, and confirm the retried call with inputResponses produces the same result a single-shot call would.
Then run the stateless assumption on purpose. Send consecutive requests from the same client to different server instances and confirm nothing breaks. Any failure here is a piece of state you left on the instance.
Finally, run it in a real client. Claude Desktop and Cursor will surface schema problems and naming problems that curl never will, because they show you what the model actually does with your tool descriptions.
Step 7: Deploy the server
Statelessness is the deployment story in this revision. Any request can land on any server instance behind a plain round-robin load balancer with no shared storage. Scale horizontally, deploy rolling, and stop provisioning session affinity.
The C# SDK reached v2.0 with its HTTP server transport running statelessly by default, since HttpServerTransportOptions.Stateless now defaults to true on ASP.NET Core. Other Tier 1 SDKs expose the same posture through their HTTP transports.
Dropping protocol-level sessions does not force your application to be stateless. When a workflow genuinely spans calls, mint an explicit handle from a tool and have the model pass it back as an argument. The state lives in your datastore keyed by that handle, and the protocol stays clean.
Because Mcp-Method and Mcp-Name are on the request line, your gateway can meter and rate limit per tool without reading bodies. Set per-tool limits on anything expensive before you hand the URL out.
Step 8: Connect the MCP server to Claude and ChatGPT
In Claude.ai, open the + menu next to the chat prompt, choose to add a custom connector, and supply your MCP URL plus optional authentication. Once added, the connector toggles on and off from the Connectors menu.
To connect an MCP server to ChatGPT there is one extra step. You must first enable Developer mode for your account in the Security and login panel, then add the custom connector. ChatGPT accepts remote HTTPS endpoints only, so a local stdio server whose run instructions start with npx or node has to be bridged to a public URL first. Claude Desktop and Cursor run that same local server directly with no bridge.
Listing publicly is a separate process. Submitting to the ChatGPT App Marketplace or to Anthropic's Connectors Directory goes through review. Among other requirements, your tools must explicitly specify readOnlyHint, openWorldHint, and destructiveHint, you need a demo account for the reviewers, and OpenAI requires a domain verification endpoint at /.well-known/openai-apps-challenge that returns a token revealed during submission. Add the three hints while you are writing tool definitions rather than retrofitting them the week you submit.
How to create an MCP server without a codebase
Everything above assumes you have software to expose. Plenty of people arrive at this question without one. What they have is judgment: a way of scoping a project, a house style for a deliverable, a review pass that catches what juniors miss. That is a legitimate thing to put behind an MCP server, and it does not require you to write one.
Here are the three routes, compared on what actually differs between them.
| Self-host with an official SDK | Managed MCP hosting platform | Hosted expert connector | |
|---|---|---|---|
| What you need already | A product, API, or dataset to expose, and an engineer | A working server or the code for one | Expertise and the ability to describe it |
| Code required | Yes: TypeScript, Python, Go, C#, or PHP | Yes. The platform runs your server, it does not write it | None |
| Hosting and maintenance | Yours. Deploy, scale, patch, track spec revisions | Platform handles runtime and transport; you own tool logic | None |
| Auth | You implement it, including the RFC 9207 iss return and the move from DCR to CIMD | Usually provided at the platform edge, your logic behind it | Handled by the platform |
| How calls get billed | You build metering and billing, or you do not bill at all | Platform-dependent, typically infrastructure billing to you | Per call, to the buyer |
Publishing an expert connector on Callstand
Callstand is the third route. You publish an expert connector and get a handle at callstand.com/p/yourhandle. The connector exposes three verbs: a plan before work starts, a finished deliverable written in your style, and a review of work before it ships. You set a price for each verb separately, so a quick review can be priced differently from a full deliverable.
Buyers hold prepaid credits and can set per-call spend caps, which means neither side is negotiating scope on every request. The same connector runs as a hosted MCP server, so an assistant can consult you directly in the middle of someone else's workflow, and the underlying expertise API can back custom tools inside a product a developer is already building.
There is no follower minimum, no code, and no hosting. If you have been reading this guide because you wanted to be callable by AI clients rather than because you wanted to write a transport layer, this is the route that matches what you have.
Frequently asked questions
Do I still need to implement the initialize handshake?
No. The initialize/initialized handshake is retired in the 2026-07-28 revision under SEP-2575. Each request carries its protocol version, client identity, and client capabilities in _meta. If a client wants your capabilities up front, it can call the optional server/discover RPC.
What happened to Mcp-Session-Id?
Retired under SEP-2567. The protocol core is stateless, so requests are no longer tied to a session. When your application needs continuity across calls, mint an explicit handle from a tool and have the model pass it back as an argument.
Which SDK should I use to build an MCP server?
TypeScript, Python, Go, and C# are Tier 1 and all supported 2026-07-28 from launch day. Rust supports it in beta. PHP has an official SDK, mcp/sdk, which is experimental until its first major release. Pick the language your team already ships in.
Why did my Python install break after upgrading?
pip install mcp now installs the 2.x line. If you are still on 1.x, pin mcp>=1.28,<2 until you migrate. The v1.x branch receives critical fixes only.
Can I connect a local MCP server to ChatGPT?
Not directly. ChatGPT accepts remote HTTPS endpoints only, so a local stdio server launched with npx or node must be bridged to a public URL first. You also need Developer mode enabled in the Security and login panel before adding a custom connector. Claude Desktop and Cursor run local servers without a bridge.
Is the HTTP+SSE transport still usable?
It is officially deprecated with a year-long offramp. Existing servers keep working, and new servers should use Streamable HTTP with the Mcp-Method and Mcp-Name headers.
What replaced elicitation and sampling?
Multi Round-Trip Requests under SEP-2322. Your server returns resultType "input_required" with the requests it needs answered, and the client retries the original call with answers in inputResponses.
Can I create an MCP server without coding?
Yes, if what you are exposing is expertise rather than software. A hosted expert connector gives you a running MCP server and a callable API without a codebase, hosting, or an auth implementation. That is what Callstand publishes for you.
Changelog: published August 28, 2026; reviewed September 1, 2026. Written against MCP revision 2026-07-28. Check the specification changelog on modelcontextprotocol.io for changes after this date.