DEV Community

Cover image for Building MCP Servers in 2026: What Changed and What Production Needs
iTech Soft Solutions
iTech Soft Solutions

Posted on Originally published at itech-softsolutions.com

Building MCP Servers in 2026: What Changed and What Production Needs

This is a condensed version of our full MCP Development Guide 2026, which includes working TypeScript and Python servers, the full OAuth flow and a security checklist.

If you learned the Model Context Protocol from a 2025 tutorial, parts of it no longer apply. The 2026-07-28 specification made MCP stateless, and that changes how you design, scale and secure a server.

We build and run the hosted MCP server for Feedlio, our social media SaaS, which is listed on the official MCP Registry, Smithery and mcpservers.org. These are the points that mattered most in production.

What changed in 2026-07-28

  • No initialize handshake. Every request carries its protocol version and client capabilities in _meta. Servers implement server/discover to advertise what they support.
  • No protocol sessions. The Mcp-Session-Id header is gone. If you need state across calls, return an explicit handle the model passes back as a normal tool argument.
  • Servers no longer send requests to clients. Elicitation now uses Multi Round-Trip Requests: the server returns resultType: "input_required", and the client retries with the input.
  • Deprecated: roots, sampling, logging, the old HTTP+SSE transport, and Dynamic Client Registration (in favour of Client ID Metadata Documents). There is now a minimum twelve-month deprecation window.

Tools, resources and prompts work as before, so most tool code carries over. The big win: any instance behind a load balancer can answer any request.

Pick the transport

  • stdio for local tools (files, local databases, CLIs). The server runs with the user's privileges and reads credentials from the environment.
  • Streamable HTTP for anything shared or SaaS. One endpoint, one POST per message, and each POST carries MCP-Protocol-Version, Mcp-Method and (for tool calls) Mcp-Name headers, so gateways can route and rate-limit without parsing JSON. Validate the Origin header to block DNS rebinding.
  • HTTP+SSE is deprecated. Don't start new work on it.

Design tools around intent, not endpoints

The most common mistake is mirroring a REST API one endpoint at a time. A model that has to chain get_customer, get_customer_accounts and get_account_invoices makes more mistakes and spends more tokens than one that calls find_overdue_invoices(customer, min_days_overdue).

  • One tool per user intent, no overlapping tools.
  • Separate reads from writes and mark them with readOnlyHint / destructiveHint (hints for hosts, not security controls).
  • Descriptions are prompts: say when to use the tool, give formats and units ("ISO 8601 UTC, e.g. 2026-11-02T09:00:00Z"), and say where IDs come from ("accountId comes from list_social_accounts").
  • Use enums and limits in the schema, not in prose.

Errors the model can act on

MCP has two error channels. Protocol errors (malformed requests, unknown tools) are hard for a model to recover from. Tool execution errors are normal results with isError: true, and clients pass them back to the model so it can self-correct.

So write the error for the model:

  • "404" gives it nothing.
  • "No post with id p_99. Call list_posts to find valid ids." gives it a next step.

Never return stack traces, hostnames or secrets: anything you return can end up in the chat.

Make writes idempotent

Models retry, and under the stateless Streamable HTTP transport a broken stream loses the in-flight request, so the client re-issues it. Without idempotency, a retry creates a duplicate. Accept an idempotency key, store it with the result, and return the original result when the key repeats:

const postId = `post_${idempotencyKey}`;
if (!posts.has(postId)) posts.set(postId, { text, publishAt, status: 'scheduled' });
return { content: [{ type: 'text', text: JSON.stringify({ postId, status: 'scheduled' }) }] };
Enter fullscreen mode Exit fullscreen mode

Authorization for remote servers

Your MCP server is an OAuth 2.1 resource server. The client gets a token from your authorization server (often your existing identity provider) using PKCE and a resource parameter, after discovering it through your Protected Resource Metadata at /.well-known/oauth-protected-resource.

Two rules prevent most serious mistakes:

  1. Validate the audience. Accept only tokens issued for your server.
  2. Never pass tokens through. When your server calls a downstream API, it uses its own credentials. Token passthrough is explicitly forbidden: it breaks audit trails and creates confused-deputy risks.

Design scopes around what a user would approve (posts:read must not publish), and use 403 insufficient_scope for step-up instead of wildcard scopes.

Security you have to plan for

  • Prompt injection through tool results. A customer email or social comment can say "ignore previous instructions". Return only the fields needed, never let a read result unlock a write, and require approval for side effects.
  • Tool poisoning. Descriptions from untrusted servers are untrusted. Allowlist servers, pin versions, alert on description changes.
  • Least privilege. Minimal scopes, per-user tool lists, tenant filtering in the data layer, not in instructions to the model.
  • SSRF. If a tool fetches a model-supplied URL, enforce HTTPS, block private ranges, set size and time limits.

Three lessons from running the Feedlio MCP server

  1. Platform requirements must be in the schema. TikTok needs privacy settings and a music-usage confirmation, YouTube a title and a category. If the schema has no field for it, the model can't send it, however capable it is.
  2. Media arrives as a public URL. add_media_from_url fetches the file server side. Pushing binaries through tool arguments is expensive in context and fragile.
  3. A person approves before anything goes live. The assistant prepares the post; a human confirms it before it's scheduled. Where you can, enforce approval in your product, so it works the same whichever client is connected.

Test it

Use the MCP Inspector (Node 22.19+):

npx @modelcontextprotocol/inspector --server-url http://127.0.0.1:3000/mcp --transport http
Enter fullscreen mode Exit fullscreen mode

Then test with the assistants your users actually run, and add adversarial tests that plant instructions in returned data.

Read the full guide

The full guide covers the architecture, a complete TypeScript (v2 SDK) and Python server, the OAuth flow step by step, a threats-and-controls table mapped to the OWASP LLM Top 10, operations, and realistic timelines from a 1–2 week prototype to an 8–14 week public SaaS server.

👉 Read the full MCP Development Guide 2026

iTech Soft Solutions builds AI agents, LLM integrations and MCP servers: AI development services.

Top comments (0)