An MCP SDK is a library that implements the Model Context Protocol for you: the JSON-RPC messages, the transports (stdio and Streamable HTTP), schema generation, and the client side of OAuth. As of October 14, 2026 there are ten official SDKs under the modelcontextprotocol GitHub org. Six are Tier 1: TypeScript, Python, Go, C#, Rust and Ruby. Java is Tier 2. Kotlin, Swift and PHP are Tier 3.
Most people end up choosing between two packages: mcp on PyPI (version 2.3.0) and the TypeScript pair @modelcontextprotocol/server and @modelcontextprotocol/client (2.3.1). The package most TypeScript tutorials tell you to install, @modelcontextprotocol/sdk, is now the v1 maintenance line. It will never speak the 2026-07-28 protocol revision.
This guide covers the whole SDK list, then builds the same small server and client in Python and TypeScript. We ran every snippet on October 14 and crossed them over (Python client to TypeScript server and the reverse). One difference between the two SDKs never shows up in a quickstart: which protocol revision the client speaks by default. If you only want a Python server walkthrough, our Python SDK v2 tutorial goes deeper on that.
The official MCP SDKs, October 2026
Versions and dates below come from each package registry and the SDK's GitHub releases on October 14. The last column is about the current spec revision, 2026-07-28, which drops the initialize handshake and session IDs in favor of a stateless protocol with a server/discover call (what changed on the wire).
| Language | Package | Latest | Tier | 2026-07-28 |
|---|---|---|---|---|
| TypeScript | @modelcontextprotocol/server, @modelcontextprotocol/client | 2.3.1 (Oct 5) | 1 | Yes, opt-in |
| TypeScript (v1) | @modelcontextprotocol/sdk | 1.32.1 (Oct 5) | maintenance | No, and not planned |
| Python | mcp | 2.3.0 (Oct 2) | 1 | Yes, on by default |
| Go | github.com/modelcontextprotocol/go-sdk | v1.8.0 (Sep 14) | 1 | Yes; on for clients, HTTP server needs Stateless: true |
| C# | ModelContextProtocol (NuGet) | 2.2.0 (Aug 13) | 1 | Yes, on by default |
| Rust | rmcp | 3.5.1 (Oct 5) | 1 | Yes; server by default, client opt-in |
| Ruby | mcp gem | 1.7.0 (Oct 5) | 1 | Yes; client probes by default |
| Java | io.modelcontextprotocol.sdk:mcp | 2.0.1 (Aug 19) | 2 | Not yet; planned for 3.x |
| Kotlin | io.modelcontextprotocol:kotlin-sdk | 0.15.0 (Jul 28) | 3 | Not yet |
| Swift | modelcontextprotocol/swift-sdk (SwiftPM) | 0.12.1 (May 7) | 3 | Not yet |
| PHP | mcp/sdk (Composer) | v0.8.1 (Aug 29) | 3 | Yes; still labelled experimental |
Several SDKs are co-maintained with an outside organization. Go is maintained with Google, C# with Microsoft, Kotlin with JetBrains, Java with the Spring AI team, and PHP with the PHP Foundation and Symfony. Ruby started at Shopify. GitHub runs its own MCP server on the Go SDK.
Be careful with older copies of the SDK page. Rust was promoted to Tier 1 on August 21 and Ruby on September 28. Cached pages and the frozen 2025-11-25 docs still show them as Tier 2 and Tier 3. When the spec shipped on July 28, the launch post named only four Tier 1 SDKs.
What the tiers actually promise
The tiers come from SEP-1730. The first tier assignments were published on February 23, 2026, and the SDK Working Group moves SDKs between tiers by consensus. The rules are measurable, which makes the label more useful than a star count:
| Tier 1 | Tier 2 | Tier 3 | |
|---|---|---|---|
| Conformance tests | 100% pass | 80% pass | No minimum |
| New spec features | Before the spec release | Within 6 months | No commitment |
| Issue triage | 2 business days | A month | None |
| Critical bugs | 7 days | Two weeks | None |
An SDK can be moved down if its conformance tests fail for four straight weeks. In practice, Tier 1 means an SDK ships the new protocol with the spec, and Tier 2 means it gets there eventually. Java shows the gap. Its roadmap planned 3.0.0 milestones with 2026-07-28 support for September 2026, but as of mid-October, Maven Central still ends at 2.0.1. The six-month Tier 2 window runs to about late January 2027.
None of this stops you from using a Tier 3 SDK. Kotlin is very active. But if you write a client that has to talk to every server on the internet, Tier 1 is the safer bet.
TypeScript: the package everyone installs is the old one
The TypeScript SDK split into separate packages for v2, released July 27, 2026:
@modelcontextprotocol/serverfor building servers, and@modelcontextprotocol/clientfor clients- thin runtime adapters:
@modelcontextprotocol/node,/express,/fastifyand/hono @modelcontextprotocol/codemod, which rewrites v1 imports:npx @modelcontextprotocol/codemod@latest v1-to-v2 .
The old single package, @modelcontextprotocol/sdk, still publishes. The day we checked, both lines had shipped a release on October 5. Its README says v1 implements the spec "up to 2025-11-25" and that "support for the 2026-07-28 spec is not planned for v1.x." It gets bug fixes and security updates for "at least six months after the v2 release." That puts the earliest end at late January 2027. Several of the tutorials ranking for "mcp sdk" when we checked still import from @modelcontextprotocol/sdk/server/mcp.js, so check the package name before you copy anything.
Two more v2 changes trip people up. v2 needs Node 20 or newer, and Zod v3 is no longer supported. The SDK depends on zod ^4.2.0 directly, so it's no longer a peer dependency you install yourself. Your tool schemas can use any Standard Schema library: Zod v4, Valibot or ArkType.
Python: one package, renamed classes
Python kept one package. pip install mcp has installed 2.x since July 28, and the latest release, 2.3.0, supports Python 3.10 and up (the main branch is moving to 3.11). The rename that breaks old code is FastMCP becoming MCPServer. from mcp.server.fastmcp import FastMCP now raises ModuleNotFoundError. v2 also adds a high-level Client class that takes a URL, a command or an in-process server.
The name FastMCP now refers to two different projects. The standalone fastmcp package (PrefectHQ, 4.1.0 on October 8) is a separate framework built on top of the official mcp 2.x. Its 1.0 was folded into the official SDK in 2024. Pick it for its extras, such as deployment and auth tooling, but don't mix its docs with the official SDK docs.
The same server in both SDKs
One tool, forecast, that takes a city and returns typed data, served over Streamable HTTP. Python first:
from pydantic import BaseModel
from mcp.server import MCPServer
mcp = MCPServer("weather")
class Forecast(BaseModel):
city: str
days: int
summary: str
@mcp.tool()
def forecast(city: str, days: int = 1) -> Forecast:
"""Return a canned forecast for a city."""
return Forecast(city=city, days=days, summary="sunny")
if __name__ == "__main__":
mcp.run(transport="streamable-http") # http://127.0.0.1:8000/mcp
The return annotation matters more than it looks. With -> Forecast, the SDK publishes an output schema and the client gets structured_content back as a dict. When we first wrote the tool as a bare -> dict, the call still worked, but structured_content came back None and the client had only text. If you want the model or your code to get typed data, annotate a pydantic model, a TypedDict, or a parameterized type such as dict[str, str].
The TypeScript version, on plain node:http:
import { createServer } from 'node:http';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { toNodeHandler, localhostHostValidation, localhostOriginValidation } from '@modelcontextprotocol/node';
import * as z from 'zod/v4';
const handler = createMcpHandler(() => {
const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.registerTool(
'forecast',
{
description: 'Return a canned forecast for a city.',
inputSchema: z.object({ city: z.string(), days: z.number().int().default(1) }),
outputSchema: z.object({ city: z.string(), days: z.number(), summary: z.string() }),
},
async ({ city, days }) => {
const out = { city, days, summary: 'sunny' };
return { content: [{ type: 'text', text: JSON.stringify(out) }], structuredContent: out };
},
);
return server;
});
const nodeHandler = toNodeHandler(handler);
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();
createServer((req, res) => {
if (!validateHost(req, res) || !validateOrigin(req, res)) return;
void nodeHandler(req, res);
}).listen(3000, '127.0.0.1');
Three things here differ from Python and from v1 tutorials:
- The factory runs once per HTTP request.
createMcpHandlerbuilds a freshMcpServerfor every request, so register tools inside the factory. Keep database pools and caches at module scope. This is what makes the endpoint stateless and lets it scale horizontally. It replaces v1'sStreamableHTTPServerTransportplusconnect()wiring. - The handler trusts its caller. It checks no
Hostheader, noOriginand no token. On plain Node you add the localhost guards yourself, as above. The Express, Hono and Fastify app helpers turn them on by default. The Python server has a similar guard. When it binds to localhost it accepts only localhostHostheaders, but bound to0.0.0.0for a deployment it accepted anyHostin our test, so set allowed hosts and origins yourself before you expose it. - You write the structured result by hand. Python derives it from the return type. In TypeScript you declare
outputSchemaand returnstructuredContentnext to a text fallback.
On Cloudflare Workers, Deno or Bun, export default handler mounts it. You still put the Host, Origin and token checks in front, because the handler does none of them.
The same client in both SDKs
Python's client is a context manager. Give it a URL and it uses Streamable HTTP:
import asyncio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
tools = await client.list_tools()
print([t.name for t in tools.tools])
result = await client.call_tool("forecast", {"city": "Austin", "days": 2})
print(result.structured_content) # {'city': 'Austin', 'days': 2, 'summary': 'sunny'}
asyncio.run(main())
TypeScript separates the client from the transport:
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const client = new Client(
{ name: 'demo-client', version: '1.0.0' },
{ versionNegotiation: { mode: 'auto' } },
);
await client.connect(new StreamableHTTPClientTransport(new URL('http://127.0.0.1:3000/mcp')));
console.log(client.getProtocolEra()); // 'modern'
const result = await client.callTool({ name: 'forecast', arguments: { city: 'Austin', days: 2 } });
console.log(result.structuredContent);
await client.close();
We ran all four pairings: each client against each server. Every one listed forecast and returned the same structured result, so use whatever language fits each side. The wire protocol is the contract, not the SDK.
The default that differs: which protocol your client speaks
Look at the versionNegotiation option in the TypeScript client. Without it, the same client reported 'legacy' against both of our servers. The TypeScript docs say it plainly: "Nothing in v2 puts a 2026-07-28 byte on the wire by default." A v2 client runs the 2025 initialize handshake "byte for byte" like v1 unless you opt in. The Python Client does the opposite. Its mode defaults to "auto", and it reported protocol version 2026-07-28 against the same server. With mode="legacy", it reported 2025-11-25.
| SDK | Client default | How to change it |
|---|---|---|
| Python | Auto: probes server/discover, falls back | Client(url, mode="legacy") or a pinned revision |
| TypeScript v2 | Legacy 2025 handshake | versionNegotiation: { mode: 'auto' }, or { mode: { pin: '2026-07-28' } } to require it |
| Go | New protocol on for new clients | See the SDK's protocol version options |
| C# | Probes server/discover first, falls back | Client options |
Rust (rmcp) | Legacy with plain serve() | serve_with_lifecycle with a discover or auto mode |
For plain tool calls the era rarely matters, because current servers answer both. It matters when you need something only the new revision has, such as subscriptions/listen or cacheable discovery. It also matters if you maintain a gateway or test harness that classifies traffic. The TypeScript team kept the conservative default on purpose. Their docs say spawn-per-invocation CLIs and debugging tools should keep it, for two reasons. Against an old stdio server that ignores unknown requests, the probe stalls connect() until it times out, and the extra round trip changes recorded transcripts.
The servers differ too. The Python MCPServer and the TypeScript createMcpHandler both serve 2025 and 2026 clients from one endpoint, as our tests showed. Go's HTTP server accepts 2026-07-28 only with Stateless turned on. Java can't serve it yet.
Connecting to a real remote server: auth comes first
Point either client at a production server and the first thing you'll hit is OAuth, not tools. Here's what our own endpoint, https://mcp.dialmcp.com/mcp, returns to an unauthenticated request:
HTTP/2 401
www-authenticate: Bearer realm="OAuth", error="invalid_token",
error_description="Missing or invalid access token",
resource_metadata="https://mcp.dialmcp.com/.well-known/oauth-protected-resource/mcp"
That resource_metadata URL is where a client learns which authorization server to use (the full MCP OAuth flow). The SDKs report the 401 in different ways. TypeScript in auto mode threw a typed SdkHttpError with code CLIENT_HTTP_AUTHENTICATION and the message "Version negotiation failed: the server requires authorization (HTTP 401)". Its docs say an auth status is never treated as a sign of the protocol era, so the client doesn't quietly fall back. Python raised a generic MCPError ("Server returned an error response") inside an exception group. That's harder to catch, so wrap remote connects and check for auth failures explicitly.
To get past the 401:
- TypeScript: pass
authProviderin the transport options. For an API key or a gateway-issued token,{ token: async () => process.env.API_KEY }is enough, and an optionalonUnauthorizedlets the transport refresh and retry once. For interactive OAuth, pass anOAuthClientProviderinstead. - Python:
mcp.client.auth.OAuthClientProviderhandles discovery, client registration, PKCE and token storage. You supply a token store, a redirect handler and a callback handler.Clienthas no auth parameter, so pass the provider ashttpx2.AsyncClient(auth=provider)tomcp.client.streamable_http.streamable_http_client(url, http_client=...)and hand that transport toClient. If the server supports client ID metadata documents, it can use an HTTPS URL as the client ID instead of dynamic registration.
The interactive part, opening a browser and catching the redirect, is the host's job. That's why most people use an MCP host such as Claude, ChatGPT or Cursor for remote servers, and only reach for a client SDK when they're building a host or an agent themselves. If that's you, MCP client vs server explains who owns which part of the job.
How to pick an SDK
- Building a server: use the language of the system you're wrapping. A server is mostly glue around your existing code, and current Tier 1 server libraries still answer 2025-era clients, so you won't lock out older hosts.
- Building a client or agent that connects to many servers: pick a Tier 1 SDK, decide your protocol default on purpose, and plan for OAuth from day one.
- On TypeScript: start new code on
@modelcontextprotocol/serverand/client. Leave@modelcontextprotocol/sdkfor code you haven't migrated yet, and migrate before early 2027. - On Python: put an upper bound on
mcp(mcp>=2.3,<3). The 1.x to 2.x jump broke unpinned servers, and the next major will too. - On the JVM: Java and Kotlin are fine for 2025-era servers. If you need 2026-07-28 today, wait for Java 3.x or put the server in another language.
Test whatever you build with MCP Inspector before you connect it to a model. For the server design questions an SDK can't answer for you, such as transport, hosting and side effects, see how to build an MCP server.
Further reading
- Official MCP SDK list and SDK tier requirements
- MCP Python SDK docs
- MCP TypeScript SDK v2 docs and supporting 2026-07-28
- Remote vs local MCP servers
- Connect DialMCP to an MCP client
Need a phone-call tool for the agent you're building? DialMCP is a hosted remote MCP server. Any MCP client, from Claude and ChatGPT to your own SDK client, can use it to place real phone calls from your own verified number and get back a transcript and a structured outcome. Connect to https://mcp.dialmcp.com/mcp and sign in with OAuth. US and Canada, free during launch.
FAQ
What is the MCP SDK?
There isn't one MCP SDK. The Model Context Protocol project publishes ten official SDKs: TypeScript, Python, Go, C#, Rust and Ruby (Tier 1), Java (Tier 2), and Kotlin, Swift and PHP (Tier 3). Each one implements the protocol messages, transports and auth helpers so you can build MCP servers or clients without handling JSON-RPC yourself.
Is @modelcontextprotocol/sdk deprecated?
It isn't deprecated, but it is the v1 maintenance line. It implements the spec up to 2025-11-25 and will not get 2026-07-28 support. It receives bug fixes and security updates for at least six months after v2's July 27, 2026 release. New TypeScript code should use @modelcontextprotocol/server and @modelcontextprotocol/client.
Should I use the official mcp package or FastMCP in Python?
The official package is mcp on PyPI, and its server class is now called MCPServer (it was FastMCP in v1). The standalone fastmcp package from PrefectHQ is a separate framework built on top of mcp 2.x. Start with the official SDK. Add fastmcp only if you need its extra tooling.
Which MCP SDKs support the 2026-07-28 spec?
As of October 14, 2026: TypeScript v2, Python, Go, C#, Rust, Ruby and PHP. In TypeScript it is opt-in on both the client and the server, and Go's HTTP server needs stateless mode. Java, Kotlin and Swift have no release with it yet; Java plans it for 3.x.
Do I need an SDK to use an MCP server?
No. To use an existing server you only need an MCP host such as Claude, ChatGPT, Cursor or VS Code: add the server's URL as a connector. You need an SDK only to build a server, or to build your own client or agent.