A pale new pipe segment joined into a grid of ordinary muted plumbing by a single warm-orange coupling: MCP composing the Internet's existing machinery instead of reinventing it.

The Nuts and Bolts of a Modern MCP Servereb13844

By

On this page

I had a remote MCP server that worked with every client I pointed at it. Claude connected and ran tools. ChatGPT connected and ran tools. The OAuth flow issued tokens, the tokens validated, and the whole thing looked finished. Then I connected Grok, and its OAuth initialization asked my server a discovery question no other client had asked, and the integration stopped cold before a single tool call. My authentication was fine, my MCP implementation was fine, and my OAuth server was fine. What failed was an assumption I did not know I was making, and this article is about that class of assumption.

The first MCP servers most of us built were local adapters. You launched a process, talked to it over stdio, exposed a few tools, and suddenly Claude could drive your application. That was enough to prove the idea. It was not enough to build an Internet service, because the moment you put an MCP server on a public URL, all the boring questions software eventually has to answer show up at once. How does a client discover it? How does authentication work? Which OAuth resource is a token actually for? How do you host multiple MCP endpoints behind one domain, route them through a load balancer, and run them on Lambda, containers, or an ordinary reverse proxy? Can a server have an identity, can tools have icons, and can a tool return an actual user interface instead of describing one in text?

As of August 2026, the current MCP specification is the 2026-07-28 revision,1 and it is aimed squarely at those questions. The protocol core is now stateless, the old initialization handshake and session ID are gone, discovery is explicit, routing information can travel in HTTP headers, caching is becoming first-class, OAuth has been hardened, and extensions finally have a formal home. That is not a routine pass over the tool schema. MCP is growing up, and it is growing up by turning into ordinary Internet infrastructure.

Streamable HTTP and the End of Hidden State

For remote MCP, the transport that matters is Streamable HTTP. The older HTTP plus SSE transport is now legacy, with a one-year compatibility off-ramp in the July specification, and stdio remains exactly right for local servers. The distinction is useful: a server running next to Claude Code on my laptop can stay beautifully boring over stdio, and a server living at `https://mcp.example.com/mcp` wants Streamable HTTP.

The bigger change hides underneath the transport. The July specification eliminates the protocol-level `initialize` and `initialized` handshake along with `Mcp-Session-Id`, so every request carries enough information to be handled on its own, and discovery moves into a separate `server/discover` operation. If this article has a villain, it is hidden state: the connection that quietly remembers things, the session that pins a request to one instance, the handshake that has to happen before anything else is allowed to. Hidden state is what made the first generation of remote MCP servers awkward to operate, and this revision spends most of its energy hunting it down.

That one decision pays for a lot. I can put ten instances of a server behind a completely ordinary round-robin load balancer without caring which instance handled the previous request, and Lambda, containers, autoscaling, retries, and blue-green deployments all get easier for the same reason: no instance has to remember anything.

Application state still exists, and it should. A tool can start a workflow, return a workflow ID, and let a later call operate on that workflow. The difference is that the state now belongs to the application, visible in the IDs it hands out, instead of secretly belonging to the connection. That is a much healthier boundary.

HTTP Can Finally See What MCP Is Doing

The next change sounds small until you have operated one of these things in production. The specification introduces HTTP headers such as `Mcp-Method` and `Mcp-Name`, which let infrastructure understand what kind of MCP request is passing through without parsing the JSON-RPC body. A proxy can now tell `tools/call` from `resources/read`, and potentially which specific tool is being invoked.

That matters because the MCP server is never the whole system. Around it sit a reverse proxy, an authorization layer, a rate limiter, a telemetry pipeline, and probably a pile of dashboards, and until now every one of them saw the same opaque `POST /mcp`. With the method and tool name visible at the HTTP layer, I can rate-limit `deploy_production` to ten requests a minute while `search_documents` gets a hundred, collect per-tool metrics without teaching half my infrastructure to parse MCP JSON, and apply different security policies to different operations.

None of this looks exciting in a demo. It gets very exciting when you are the one responsible for the thing at two in the morning. Boring production is a feature.2

OAuth Is Where Remote MCP Gets Serious

Authentication has consumed more of my recent attention than any other part of this, because once your MCP server is public, authentication stops being a bearer token you made up and shared out of band. The MCP authorization model is built on the existing OAuth ecosystem rather than on a new MCP-specific authentication protocol, and that was the right call. We already paid for this machinery.

The standards that matter: RFC 6750 defines bearer token usage. RFC 8414 defines authorization server metadata, which answers where the authorization endpoint, token endpoint, and the rest of the OAuth services live. RFC 8707 defines Resource Indicators, which let a client tell an authorization server exactly which protected resource it wants a token for. RFC 9728 defines Protected Resource Metadata, which lets the MCP resource describe itself and name the authorization servers that can issue credentials for it.3 RFC 9207 lets the authorization response identify its issuer, which defends against authorization-server mix-up attacks, and the July MCP revision specifically hardened authorization around that behavior. RFC 7591, Dynamic Client Registration, is still in the picture for compatibility, though MCP is moving away from it and toward Client ID Metadata Documents.

That reads like RFC soup until you map the responsibilities. Each party publishes its own description: the resource says what it is and who vouches for it, the authorization server says where its endpoints are, the client says who it is, and the authorization request names the resource the token is intended for. Laid out that way, it is a clean system.

The RFC 9728 Detail That Broke My Server

Suppose you expose one MCP server at `https://mcp.example.com/mcp`. Its Protected Resource Metadata lives at `https://mcp.example.com/.well-known/oauth-protected-resource`, and that is the form most people meet first. Now suppose you are building a multi-tenant service where each endpoint has its own resource identity: `/mcp/acme`, `/mcp/contoso`, `/mcp/12345`. RFC 9728 defines how resource identifiers containing paths are handled, and for `https://mcp.example.com/mcp/acme` the metadata location becomes:

https://mcp.example.com/.well-known/oauth-protected-resource/mcp/acme

The well-known segment is inserted between the host and the path, and the `resource` property in the returned document must correspond to the identifier used to derive it. A minimal response looks like:

{
  "resource": "https://mcp.example.com/mcp/acme",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "resource_name": "Acme MCP"
}

This is what makes dynamically provisioned MCP endpoints practical. You do not need a subdomain per customer just to establish distinct OAuth resources, because the URL itself carries the resource identity.

It is also exactly where the server I opened this article with fell over. My endpoint's identity lived in the path, and Grok's OAuth initialization required the path-derived metadata form far more strictly than the clients I had been testing against. My authentication worked and my tools worked, but my discovery topology was incomplete, and that distinction is worth sitting with, because nothing in my code was wrong. The specification is simply ahead of complete client convergence. ChatGPT now explicitly documents RFC 9728 discovery, sends the resulting identifier as the OAuth `resource` parameter, recommends binding it to the token audience, and supports CIMD alongside DCR. Claude supports remote MCP servers with OAuth through its connector infrastructure. Grok performs OAuth automatically for servers that require it. Each client exercises a slightly different slice of the spec, and the client you have not tested yet is the one that finds your assumption.

The lesson is not that one client is right and another is wrong. Implement the path-derived form correctly, keep serving root-level metadata where it helps compatibility, and do not build your OAuth architecture around whichever client happens to accept the least metadata today. Build to the protocol.

There is a wonderfully mundane routing consequence hiding in here too. If your MCP endpoint is `POST /mcp/{id}`, you already have a nice route handing `{id}` to your application. Then a client requests `GET /.well-known/oauth-protected-resource/mcp/{id}`, which is a completely different route, and your application may know exactly how to answer it and never receive it, because a proxy, router, or hosting layer rejected or rewrote the path first. That failure mode makes MCP feel less like AI development and more like ordinary distributed systems engineering. I consider that progress.

Tokens Should Know Where They Work

Protected Resource Metadata tells the client what resource exists. RFC 8707 lets the client request authorization for that specific resource, carrying `resource=https://mcp.example.com/mcp/acme` through the authorization flow so the server can issue a token intended for that resource and nothing else. That matters because an access token should not merely mean that Bryan logged in successfully. It should mean that this client is authorized to reach this resource with these scopes, and if your tokens are JWTs, that resource typically becomes part of the audience you validate. Authentication tells me who you are. Resource binding tells me where that credential is allowed to work, and those are different questions.

Client registration is getting the same treatment. Dynamic Client Registration made sense for MCP because clients and servers frequently meet for the first time at runtime; the authorization server has never seen this particular client before, and RFC 7591 lets the client register itself on the spot. It works, but it creates persistent registration state just so two Internet services can introduce themselves. With Client ID Metadata Documents, the client ID itself is an HTTPS URL pointing at a document that describes the client, including its name and redirect URIs, and the authorization server can fetch and validate that document without maintaining a registration lifecycle. Notice the pattern: hidden state keeps disappearing, and the protocol gets easier to operate every time it does.

MCP Servers Have Faces Now

Icons sound cosmetic. They are not. SEP-973 added richer metadata for MCP implementations, tools, resources, resource templates, and prompts, including icons with image sources, MIME types, size hints, and light and dark theme variants. A tool is no longer just a snake_case string; it can carry a human-readable title, a description, and an icon, and a server can expose an identity and a website.

Nobody cares about any of this while a user has one MCP server. Once they have fifteen, the difference between a list of lowercase identifiers and a list of recognizable applications with names and icons is the difference between a config file and a product. Tool discovery has been treated as a machine problem since the day MCP shipped, and it never was only that. Humans live in this system too.

MCP Apps Turn Tools into Applications

For a long time MCP had a simple mental model. The model calls a tool, the server does something, the server returns text or structured data, and the model explains the result. MCP Apps break that ceiling: a tool can reference a `ui://` resource containing HTML, the host fetches that resource, renders it inside a sandboxed iframe, and connects it back through the MCP Apps bridge, so the interface can receive tool results and initiate further tool calls through the host.

Imagine, hypothetically, a deployment MCP. Before Apps, I would ask for the status of release 4832 and the model would retrieve fifteen fields and narrate them into prose. With Apps, the tool can return an actual deployment view showing the version, environment, commit, build status, approvals, health checks, and a rollback control. The model stays useful because I can talk to the system, and the UI stays useful because I do not need a language model to read a dashboard to me one field at a time. That combination is genuinely powerful.

The sandbox is what keeps it from being terrifying. The application runs inside a sandboxed iframe, MCP Apps defines controls around the UI resource and its content security policy, and interaction with the host goes through defined messaging rather than giving third-party code access to the surrounding Claude or ChatGPT page. Without that boundary, the feature would amount to running arbitrary third-party JavaScript inside your AI client, and nobody wants that architecture.

MCP Apps grew out of work including MCP-UI and OpenAI's Apps SDK, and the MCP project shipped it as the protocol's first official extension in January 2026, with support across hosts including Claude, ChatGPT, VS Code, and Goose. This is the point where I stopped thinking of MCP as a protocol for exposing tools. It is becoming an application integration layer.

The Quiet Changes Are the Operational Ones

Three smaller changes in the July revision round out the picture, and they are the ones an operator feels first.

Discovery is explicit now. A server implements `server/discover` and advertises its supported protocol versions, capabilities, instructions, and identifying metadata, so a client can ask who you are and what you can do without creating a session just to hear the answer.

Caching is becoming part of the contract. List and read operations carry cache metadata, including TTL and scope hints, and the specification tightened deterministic ordering specifically because stable responses matter for caching and prompt reuse. That will not excite anyone shipping their first three-tool server, but if every interaction re-fetches the same hundred tool descriptions, schemas, and prompts, you pay for it in network traffic, server execution, latency, tokens, and prompt cache invalidation. Once MCP is infrastructure, cacheability stops being an optimization somebody might add someday and becomes part of the protocol contract.

And stateless does not mean non-interactive. The protocol now supports multi-round-trip behavior where a server responds with `input_required`, describing the additional input it needs, and the client gathers that information and retries the operation. A stateless transport carrying a stateful human workflow sounds like a contradiction, and it is not. The state just becomes explicit, which is the right mental model for nearly everything in this revision.

What I Would Build Today

If I were starting a production remote MCP service today, my baseline would look like this.

1. Use Streamable HTTP for the public remote transport, and stdio only where local integration makes sense.

2. Design the HTTP layer to be stateless, with application state represented explicitly through IDs and handles rather than connection affinity.

3. Support `server/discover` and the current protocol metadata rather than treating the old initialization handshake as permanent architecture.

4. Preserve `Mcp-Method` and `Mcp-Name` through proxies and routing layers, so observability, rate limiting, and security infrastructure can use them.

5. Implement OAuth discovery properly: RFC 9728 Protected Resource Metadata, RFC 8414 authorization server metadata, RFC 8707 resource indicators, bearer-token handling, PKCE where appropriate, and RFC 9207 issuer validation.

6. If MCP resources have path-based identities such as `/mcp/{id}`, serve the RFC 9728 path-derived metadata endpoint rather than assuming the root form is sufficient.

7. Prefer Client ID Metadata Documents for new OAuth client integration, and keep DCR compatibility where the clients you support still need it.

8. Add real implementation and tool metadata, including titles, descriptions, icons, and website identity, because it matters more with every integration a user accumulates.

9. Use MCP Apps where an interaction is genuinely easier as a form, visualization, dashboard, chooser, or approval screen, and do not build a UI merely because the protocol allows one.

10. Design for caching, observability, authorization boundaries, retries, version compatibility, and failure from the beginning.

That last item is the one that matters most. The MCP server is bigger than the function behind `tools/call`. The system is what happens after the tool ships.

MCP Is Becoming Boring, and That Is Exciting

The most interesting thing about MCP right now is not a flashy AI feature. It is almost the opposite: the protocol keeps adopting machinery we already know how to operate. HTTP, OAuth, well-known URLs, protected resources, explicit audiences, discoverable metadata, stateless requests, cache semantics, headers that infrastructure can route on, sandboxed web applications, and standardized extensions. None of those are AI inventions, and that is a relief. We spent decades learning painful lessons about how Internet services identify themselves, authenticate clients, route requests, isolate untrusted code, scale horizontally, and recover from failure, and MCP does not need to reinvent any of that. It needs to compose it correctly.

That is also the honest reading of the failure I opened with. The server Grok refused to talk to was not failing at anything AI-shaped. It was failing at one of the oldest jobs on the Internet: telling a stranger exactly who you are and who vouches for you. The first generation of MCP proved that an AI could call my software. This generation is figuring out how that software survives contact with the Internet, and that is where the real engineering starts.

The RFCs Worth Keeping Open

RFC 9728: OAuth 2.0 Protected Resource Metadata. The important one for discovering the OAuth configuration associated with an MCP resource, including resources identified by URL paths.

RFC 8414: OAuth 2.0 Authorization Server Metadata. Describes the authorization server itself, including its authorization and token endpoints and supported capabilities.

RFC 8707: Resource Indicators for OAuth 2.0. Defines the `resource` parameter that identifies which protected resource an access token is intended for.

RFC 6750: OAuth 2.0 Bearer Token Usage. Defines how bearer access tokens are used with HTTP protected resources.

RFC 9207: OAuth 2.0 Authorization Server Issuer Identification. Adds explicit issuer identification and protects against mix-up attacks.

RFC 7591: OAuth 2.0 Dynamic Client Registration. Still relevant for compatibility while MCP moves toward Client ID Metadata Documents.

MCP Specification 2026-07-28. The revision that moves the core protocol toward stateless operation, adds explicit discovery and routable HTTP metadata, strengthens authorization, formalizes extensions, and begins retiring several older protocol mechanisms.

MCP Apps, SEP-1865. Defines the interactive UI model that lets tools supply sandboxed application interfaces inside MCP hosts.

SEP-973. Added richer metadata, including icons, for implementations, tools, resources, templates, and prompts.

Frequently asked questions

What transport should a remote MCP server use?

Streamable HTTP. The 2026-07-28 MCP specification makes it the remote transport going forward and gives the older HTTP plus SSE transport a one-year compatibility off-ramp. Stdio remains the right choice for local MCP servers that run next to the client.

Is MCP stateless now?

At the protocol level, yes. The 2026-07-28 revision removes the initialize/initialized handshake and the Mcp-Session-Id header, so every request is self-contained and any instance behind a load balancer can serve it. Application state still exists, but it is carried explicitly in IDs and handles the application returns, not hidden in the connection.

Which OAuth standards does a remote MCP server need to implement?

RFC 9728 Protected Resource Metadata for describing the resource and its authorization servers, RFC 8414 Authorization Server Metadata, RFC 8707 Resource Indicators for binding tokens to a specific resource, RFC 6750 bearer token usage, and RFC 9207 issuer identification. RFC 7591 Dynamic Client Registration still matters for compatibility, but MCP is moving toward Client ID Metadata Documents as the preferred client registration mechanism.

Where does RFC 9728 metadata live for an MCP endpoint with a path-based identity?

The well-known segment is inserted between the host and the path. For the resource https://mcp.example.com/mcp/acme, the metadata document lives at https://mcp.example.com/.well-known/oauth-protected-resource/mcp/acme, and its resource property must match the identifier used to derive it. Some clients require this path-derived form strictly, so serving only the root-level well-known endpoint can break OAuth discovery.

What are MCP Apps?

MCP Apps are the protocol's first official extension, shipped in January 2026. They let a tool reference a ui:// resource containing HTML, which the host renders inside a sandboxed iframe connected back through a defined messaging bridge, so the interface can receive tool results and initiate further tool calls without getting access to the surrounding client page. Supported hosts include Claude, ChatGPT, VS Code, and Goose.

Why does an MCP server work with one AI client and fail with another?

Because the specification is ahead of complete client convergence. Clients exercise different slices of the spec, particularly around OAuth discovery: one client may accept root-level protected resource metadata while another strictly requires the RFC 9728 path-derived form. The durable fix is to build to the protocol rather than to whichever client accepts the least metadata.

Footnotes

  1. Model Context Protocol Specification, 2026-07-28 revision The authoritative protocol requirements for the current revision.
  2. Boring Software
  3. RFC 9728: OAuth 2.0 Protected Resource Metadata Section 3.1 defines the path-derived well-known metadata location for resource identifiers that contain path components.

Conversation

    Log in to join the conversation.

    © 2026 ABWaters. Thinking out loud.