We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide12 min read

MCP SDK, a shared way to connect tools to models

The Model Context Protocol standardises connecting tools and data to models. Tools, resources, transports, the stateless 2026-07-28 revision, and security.

MCP SDK, a shared way to connect tools to models

The Model Context Protocol solves a problem that blocked sensible integrations for a year: every assistant had its own way of describing tools, so connecting a database or a ticketing system meant separate code for each. MCP introduces one description that every compliant client understands.

What the protocol actually defines

On the server side there are three kinds of thing you can expose, and the difference between them shapes a correct integration design.

Tools are functions the model can call. They carry a name, a description, and an argument schema, and running one changes state or fetches data that requires action. Sending a message, creating a ticket, running a query are tools.

Resources are content identified by a URI that the client can read and place into context. A file, a record, a query result. The key difference is that reading a resource has no side effects, so a client can fetch it without asking the user for approval.

Prompts are ready made instruction templates the user picks from a list. It is the least used part of the protocol, which is a pity, since a well written template saves explaining the same thing to the model every time.

The distinction between a tool and a resource gets confused often, and one example settles it. Reading a file from disk is a resource, since it changes nothing and the client can fetch it unaided. Writing a file is a tool, since it has an effect that cannot be undone without a deliberate decision. That split determines when a client asks the user for approval and when it acts quietly.

The client can offer capabilities back to the server. Sampling lets the server ask for a model call. Elicitation lets it ask the user for missing information mid run. Roots name the directories the server should work within.

Two of those three are on the way out, though. The specification revision of 28 July 2026 marked sampling and roots as deprecated, along with the separate logging feature. They keep working through the transition window, but in a new integration pass directories as a tool argument or a resource URI and call the model directly at the provider. Elicitation stays, and after this change it runs through multi round trip requests.

Transports and what the July 2026 revision changed

The protocol defines two ways to communicate, and the choice follows from where the server runs.

Transport over standard input and output serves servers launched locally as a subprocess. The client starts the program and talks to it over streams, with no network and no authentication. That is the default for tools running on the user's machine.

Transport over HTTP serves remote servers. A single endpoint accepts POST requests and allows streaming responses with server sent events when an operation runs long.

The specification revision of 28 July 2026 changed something fundamental here: the protocol core became stateless. Protocol level sessions disappeared along with the header identifying them, so the same request can be handled by any server instance behind ordinary load balancing.

The practical consequence is large. Previously a remote server required pinning a client to one instance, which complicated deployment and hindered scaling. Now an MCP server deploys like an ordinary stateless service, on Cloudflare for instance, or in containers behind a standard proxy.

The same revision brought multi round trip requests, header based routing, cacheable list results, authorisation hardening, and a formal extensions mechanism. With an existing integration, check which protocol version your library supports, since the statelessness change touches the transport layer.

Your first server

Code
Bash
npm install @modelcontextprotocol/sdk zod
Code
TypeScript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

const server = new McpServer({ name: 'orders', version: '1.0.0' })

server.registerTool(
  'order_status',
  {
    description: 'Returns order status by number in ORD-12345 format. Call this when the user asks about a specific order.',
    inputSchema: { number: z.string().describe('Order number, ORD-12345 format') },
  },
  async ({ number }) => {
    const order = await db.orders.findUnique({ where: { number } })
    return {
      content: [{ type: 'text', text: order ? JSON.stringify(order) : 'not found' }],
    }
  }
)

await server.connect(new StdioServerTransport())

Registration goes through the registerTool method, taking a name, a configuration object, and a handler function. The older tool form with the description and schema as separate arguments still works, but the TypeScript library marks it deprecated, so new code should use the first shape.

The tool description is the most important part of that code. The model sees only the name, the description, and the argument schema, so a sentence stating plainly when to call the tool lifts accuracy more than any prompt side change. A description reading "manages data" settles nothing.

In Python the shape is analogous, and the argument schema derives from function type annotations, so the description is generated from the signature and docstring.

Connecting to clients

Client side configuration reduces to stating how to launch the server or where to find it.

Code
JSON
{
  "mcpServers": {
    "orders": {
      "command": "node",
      "args": ["/path/to/server/index.js"],
      "env": { "DATABASE_URL": "postgres://..." }
    }
  }
}

The same server works in every compliant client: in the Claude app, in Cursor, in command line tools, and in agent libraries. That is the point of the protocol: you write the integration once.

Test a locally running server before attaching it to an assistant. The inspector tool from the development kit launches the server and lets you call every function by hand, cutting diagnosis from an hour of guessing to a few minutes. Without that step, a fault in an argument schema surfaces as inexplicable model behaviour.

It is worth knowing that some platforms expose their integrations through MCP rather than a proprietary API. Zapier opens hundreds of application connections this way, so instead of writing your own server, connecting an existing one sometimes suffices.

Security, the part you cannot skip

An MCP server gives a model the ability to act, and that changes the threat picture compared with an ordinary chat.

The first matter is trust in the server itself. Installing somebody else's server grants it access to whatever you configure: keys, files, databases. Treat it as installing a dependency with permission to execute code, because that is what it is.

The second is content passing through tools. A document, a message, or a page read by a tool can carry instructions aimed at the model rather than the user. A tool result must be treated as data, never as a command, and it pays to state that plainly in the system instruction.

The third is permission scope. A server reading tickets does not need the right to delete them. Separate credentials with minimal permissions cost five minutes of configuration and limit the fallout from a model mistake. With a database this is the only boundary that holds, because a restriction written into the tool's code can be worked around: in the now retired PostgreSQL reference server read only mode fell to a query that closed the open transaction.

The fourth is confirmation on irreversible operations. The protocol lets a client ask the user before executing a tool, and for anything sending messages or changing data that is worth using.

The July 2026 revision hardened the authorisation layer, so with remote servers check whether your library supports the current requirements before exposing anything publicly.

Designing tools the model will actually use

The server works correctly and the model still ignores it or uses it wrongly. That is the most common complaint on a first integration, and it almost always traces to descriptions rather than code.

A name should describe the action, not the system. A name like crm_query means nothing to the model, while find_customer_by_email describes what happens. The model matches names against user intent, so the closer to natural phrasing, the better.

The description should carry the trigger condition. Not "fetches customer data", but "call this when the user asks about a specific customer and supplied an email address or an identifier". That one sentence does more for accuracy than the rest of the configuration.

Describe arguments with format and example. A text field named date with no format guidance produces three different notations across consecutive calls. An annotation saying "ISO format, for example 2026-07-28" settles it once.

Return results filtered and labelled. The model handles five human readable fields better than a full API response. When there is no result, say so plainly instead of returning an empty structure, since the model reads that as success.

The last point concerns granularity. One tool doing five things depending on a parameter is harder to call accurately than five separate ones with clear descriptions. Split along user intent rather than along your API's structure.

MCP against other approaches

ApproachAdvantageDrawbackPick it when
MCPOne integration for every client, a standard descriptionYoung protocol, changes between revisionsA tool used by different assistants
Function calling in a vendor APISimpler with one model, full controlSeparate code per vendorApplication tied to one model
A specific tool's pluginBest integration with that toolWorks only thereExtending one editor
Plain REST APIFamiliar to everyone, matureThe model does not know when or how to use itSystem to system integration, no model involved

These approaches combine. An externally exposed MCP server serves assistants, while the same code called directly serves your own application, with no duplicated logic.

The choice depends on how many clients should use the integration. With one application built on OpenAI or Claude, calling functions directly is often simpler. With a tool meant for a team using different assistants, MCP saves writing the same thing three times.

Deploying a remote server

A local server suffices for personal tools, while an integration used by a team or by a product needs a remote edition, and that carries different requirements.

Start with authentication. A server exposed at a public address with no access control gives everyone what it gave the model. The July 2026 revision hardened requirements here, so check whether your library implements them before exposing anything.

Statelessness simplifies the rest. Since any instance can handle a request, deployment reduces to an ordinary service behind load balancing, with no client pinned to a particular process. That is good news for container and serverless deployments.

Scope credentials per installation. If the server serves many clients, each should run on its own access data rather than a shared key with full permissions. With one key, a filtering mistake means access to somebody else's data.

Log tool calls along with who made them and with what arguments. When diagnosing a report that "the assistant did something strange", that is the only source of answers, and with data changing tools it is often an audit requirement too.

The last point is versioning. Changing an argument schema or a tool's meaning affects every connected client at once, so treat a tool description as a public API rather than an internal implementation detail.

Common mistakes

The first is generic tool descriptions. The model picks a tool from its description, so "manages data" guarantees random calls or none at all.

The second is exposing too many tools at once. Thirty functions in one server lengthen the prompt on every call and raise the rate of wrong picks. Split them across a few thematic servers.

The third is returning raw API responses. The model then receives three hundred lines of JSON of which it needs three fields. Filter the result on your side, since that lowers cost and improves accuracy.

The fourth is no error handling. A tool that throws instead of returning a readable message stalls the agent with no information about what went wrong.

The fifth is ignoring the protocol version when upgrading. A change in the transport layer can disconnect clients built on an older library, so read the change list before raising the version.

The sixth is keeping keys in the client configuration in plain text. That file gets synchronised or reaches a repository, so secrets are better supplied through system environment variables.

FAQ

How does MCP differ from function calling?

Function calling is a mechanism inside a specific vendor's API: you describe tools in that vendor's format and handle them in your code. MCP is a protocol above that, letting the same server work with every compliant client unchanged. Underneath, the client still uses function calling.

Do I have to write the server in a particular language?

No, libraries exist for several languages, and the protocol rests on message exchange, so an implementation in any environment is possible. TypeScript and Python have the most mature support.

What did the 2026-07-28 revision change?

The headline change is a stateless protocol core: sessions and the header identifying them are gone, so a remote server scales like an ordinary stateless service. It also brought multi round trip requests, header based routing, cacheable lists, hardened authorisation, and formal extensions. The same revision marked sampling, roots, and logging as deprecated, so new implementations should no longer add them.

Is an MCP server safe?

The protocol itself does not settle trust. Installing somebody else's server grants it access to the resources you configure, so treat it as a dependency with execution rights. Restrict credential permissions, require confirmation on irreversible operations, and treat tool results as data rather than commands.

Where do I start?

With one tool solving a specific problem you currently repeat by hand. A server with a single function and a good description delivers more than ten hastily described ones, and along the way it shows how the model actually reads your descriptions. Connect existing integrations rather than writing them where you can, through LangChain or through servers exposed by automation platforms.

The specification sits at modelcontextprotocol.io, and the change summary on the project blog.