Remote MCP servers: what changes when the server is not yours
- mcp
- agent-security
- agent-tools
When I first wrote this on August 11, I checked my agent's server list. Twelve servers connected. Two ran locally as processes I wrote. Ten were remote, hosted by someone else over HTTP. Of those ten, one rejected its token that morning and two were asking to be authorised again. The two local ones were working. That was the August 11 snapshot.
That ratio is the whole argument. Remote MCP servers are convenient and they move three things off your machine: the credential, the failure mode, and the trust boundary.
- The credential moves. A hosted server means a token or an OAuth session, held somewhere, expiring on somebody else's schedule.
- The failure mode changes. Local servers break when you break them. Remote ones break on a morning when you changed nothing.
- The trust boundary moves. Whatever you ask a hosted server, its operator can see. That is not a scandal, it is the deal.
- Public servers exist. Some remote servers need no token at all, which makes them pleasant and also worth thinking about twice.
- Local still wins sometimes. When the data is already on my machine, adding a network hop buys nothing.
If you are picking what to connect in the first place, that decision is in the MCP servers review.
What a remote MCP server actually changes
The protocol is the same either way. The agent lists tools, calls one, gets a result. What changes is the transport underneath, and everything that follows from it.
A local stdio server runs as a process on your machine. Its connection to the MCP client needs no network listener, though the process can still call external APIs. Credentials remain wherever that process is configured to load them. A remote server runs over HTTP, and the agent authenticates to it, usually with a bearer token or an OAuth session. The mechanics of both transports are covered in what an MCP server is; this post is about what the choice costs you afterwards.
Three consequences, in the order they will bite you:
- Availability stops being yours. A hosted server is a dependency with its own uptime.
- Auth becomes an ongoing job. Tokens expire, sessions lapse, scopes change.
- Your queries leave the building. The operator sees what you asked and what it returned.
None of those makes hosted a bad choice. They make it a choice with a bill attached, and the bill arrives later than the setup.
The connection snapshot from August 11
Here is the state recorded for the original article, not a claim about today's list.
| Type | Count | Working | Not working |
|---|---|---|---|
| Local, written by me | 2 | 2 | 0 |
| Remote, hosted by a vendor | 10 | 7 | 3 |
The three unavailable connections pointed to authentication problems, not evidence of a vendor outage. One was a design tool whose OAuth session expired. Two were waiting for me to authorise them again after a scope or session change. The auth was what needed attention.
This is the part I would have found useful two months ago, so I will say it flatly: with ten hosted servers connected, I was getting a re-authorisation request roughly every other week. That was the maintenance cost of this list, and it is a good reason to keep the connected list short rather than collect servers you might need someday.
Public MCP servers: the ones with no token at all
One of the remote servers in that snapshot needed no authentication. It served component documentation for a public design system, and anyone could point an agent at it. No account, no token, no expiry. It had not needed attention when I wrote the original article.
Public MCP servers are the easiest case and the one people are least careful about. Two things are worth holding in your head.
First, no token means no leverage. If the operator changes the tool surface, renames something, or shuts the endpoint off, you find out when your agent gets a confusing answer. There is no contract and no support queue.
Second, no token does not mean no traffic. You are still sending queries to a third party, and a query can carry context you did not intend. An agent that is midway through work on your codebase and then asks a public server about a component name has just told a stranger which component you are working on. That is usually harmless. It is not always harmless, and the point is to know it is happening.
My rule for public servers is narrow and it works: fine for public reference material, never for anything that takes my data as an argument.
The auth failure nobody warns you about
The most interesting break in my list is worth writing down, because it is a configuration trap rather than a bug.
One server supports both OAuth and a plain authorisation header. I had configured the header. The token behind it expired, and the server answered with an invalid token error, which is expected. What was not expected is the second half of the message: because an authorisation header was explicitly configured, the OAuth fallback was disabled. The configured header took precedence over that client's OAuth path. An available renewal path was no help while the client was using the expired token I had supplied.
It looked like an outage on the vendor's side. It was my configuration keeping the client on a manual token instead of its supported OAuth path. Whether that path can renew without another login still depends on the refresh token and the server's policy.
Two things to take from that:
- Prefer a supported OAuth flow over a pasted token when the server offers both. It can renew when a refresh token is issued and remains valid; it can still require login.
- When a hosted server dies, read the whole error before you blame the vendor. Mine contained the exact reason and I still had to read it twice.
Who is on the other side
Some hosted MCP servers hold a live connection into one of your accounts; public reference servers may hold none. Worth naming the categories, because they are not equally risky.
Vendor servers for their own product. A design tool serving its own files, a CRM serving its own contacts. The operator already held that data. The MCP connection changes who can ask for it and which scopes the client receives. I still review that scope.
Vendor servers reaching your other systems. These deserve more thought. A server that can act across accounts is a new key to a door that already existed, and the key lives in their session store.
Third-party servers wrapping someone else's API. The riskiest shape and the most common in directories. You are trusting an intermediary with a credential to a platform the intermediary does not own. If I cannot tell who operates a server, I do not connect it to anything that holds customer data.
The habit that follows: never hand a hosted server a credential for a different system. If a server asks for a key to something it does not operate, that key now lives in a place you cannot audit, and revoking it is your job when their breach becomes news.
When a local server beats a hosted one
Both my local servers could have been remote. Neither should be.
The calendar one reads a feed. The credential is a URL, the data is a text file, and running it locally avoids handing the URL to a hosted MCP intermediary. The feed still has its own availability; I do not need a second service in that path. Hosting the MCP server would add another network hop, an auth story, and a second party, in exchange for nothing.
The ad platform one is a stronger case. It can create objects that spend money. I wrote about the safe-state default in the notes on writing your own server, and the same logic applies to where it runs: the more consequential the tool, the more I want the code in front of me and the process on my own machine.
The rule I use:
| Situation | Local | Hosted |
|---|---|---|
| Data already on my machine | Yes | No reason |
| Credential I do not want stored elsewhere | Yes | No |
| Tools that change or spend something | Yes | Only with a real reason |
| Vendor's own platform, read-heavy | Sometimes | Usually right |
| Public reference material | No point | Yes |
Five checks before you connect a remote server
- Who runs it? If you cannot name the operator in one sentence, that is the answer.
- What does it get? A token, an OAuth session, or nothing. Match that against what the server actually needs to do its job, not what it asks for.
- OAuth or pasted token? Prefer a supported flow with a clear renewal path. A hardcoded header turns an expiry into an outage, and can silently disable the fallback that would have fixed it.
- What does it see? Assume every query and every result is visible to the operator. Decide if that is acceptable before you connect, not after.
- What happens when it is gone? If the answer is "my Monday stops", you have a dependency, not a convenience. Have the manual path written down.
The larger judgment about which servers earn a slot at all, hosted or not, is in choosing an MCP server for AI agents. Remote or local is the second question. The first is still whether the thing deserves to be in the context window.
The full shortlist of what I keep connected is on the tools page.
An MCP OAuth walkthrough: follow the request, not the login screen
The header failure above is why a login screen is a poor place to stop inspecting a connection. You can finish login and still send a token to the wrong resource, use the wrong issuer, or leave the client on the old header. The request after login is the part that tells you whether the connection works.
The flow here follows the July 28, 2026 MCP specification. The domains are invented and the complete hosted OAuth exchange has not been tested for this article. It is a way to inspect the implementation, not a packet capture from my account.
Write down the identities first: MCP endpoint https://mcp.example.com/mcp, authorization
server https://auth.example.com, pre-registered client example-client. Those names
need to stay distinct. Authorizing that client to use the HTTP MCP resource also does not
approve the agent's next write, send or spend action. The action controls still have a job.
Discover the protected resource and its issuer
An unauthenticated request can receive a challenge like this. Every value is illustrative:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource", scope="files:read"
The challenge points the client to resource metadata. That metadata names an authorization server, whose metadata supplies its endpoints. Follow those identities through the flow and validate the issuer. A document containing a token endpoint is not enough to establish that you reached the right server. The specification also defines well-known discovery when the challenge omits the metadata URL. See the MCP discovery requirements.
For troubleshooting, endpoint names, issuer and requested scope are useful. Access tokens, cookies and authorization codes are not something I want copied into the same report. That turns an auth failure into another place where credentials need cleaning up.
Establish the client identity before opening authorization
A client ID belongs to the authorization server that recognizes it. An ID from another server is not a portable account credential. The specification supports pre-registration, Client ID Metadata Documents and Dynamic Client Registration. Available pre-registered information comes first, supported metadata documents next, then dynamic registration as a fallback. DCR remains for compatibility; it is not a service every server must expose. These choices are described in MCP client registration.
This example uses pre-registration so there is only one branch to follow. If a real client fails before consent appears, check its registration path. Asking for a broader scope does not fix a client identity the server does not recognize.
Bind the code and the token to this request
PKCE answers a specific question: can whoever intercepted the authorization code redeem
it without the verifier held by the client that started the request? The challenge goes
with the authorization request; the client keeps the verifier for the code exchange.
It does not encrypt tool results, and it is not an access token. The specification
requires the client to verify PKCE support and use S256 when technically capable.
See PKCE and
MCP authorization code protection.
The callback has to match the registered redirect, and the code has to remain bound to the transaction that requested it. Finishing browser login does not show you whether either check happened. That is why I would inspect this part of the client, rather than accept the consent screen as proof of a sound implementation.
Then check where the token is meant to work. The resource parameter belongs in both
the authorization and token requests and names the intended MCP server. That server must
validate the audience. A token accepted by some other API is not evidence it belongs here.
Send it in the Authorization header, never the query string. These are requirements in the
MCP authorization overview.
Separate connection authorization from upstream API access
If the MCP server then calls a vendor's API, draw a second connection on the map. The client's token is for the MCP resource. The upstream API needs its own credential and permission boundary. Forwarding the client's token upstream is forbidden by the MCP token privilege restrictions.
Now the earlier "who holds the key" question has an implementation answer. Name the process storing each credential and the operator able to revoke it. Ask what remains connected after revoking the client session. Disconnecting an MCP client and revoking the server's upstream access may be different operations.
For a server you build, keep this map beside the tool interface and credential design. For connection risks beyond the login flow, use the MCP security checklist.
Diagnose 401 before repeating authorization
A 401 is a reason to inspect the rejected request, not automatically repeat the login. The trace I want for a harmless read is small: request time, resource URI, issuer, status, challenge fields and whether refresh was attempted. Record the expiry as metadata without recording the token. Those fields distinguish the failures that the login button hides.
| Observation | Next check | What I would avoid |
|---|---|---|
| Login succeeds, first request is rejected | Issuer, intended audience and endpoint identity | Sending the same token to other hosts |
| A previously working connection expires | Refresh-token availability and renewal result | Endless login or tool-call retries |
Response is 403 insufficient_scope | Scope challenge and explicit approval for added access | Asking for every possible permission |
| Client uses an explicit auth header | That client's configured header versus OAuth behavior | Assuming automatic fallback exists |
After renewal, check a harmless read before resuming work that changes anything. If the earlier write timed out, inspect its outcome before sending it again. Renewing a token cannot tell you whether the first write already committed. Getting auth back and making the next action safe are two separate jobs.
The next test needs one working read, then an expired access token, a wrong resource and a deliberately wrong PKCE verifier. Each negative case should fail for its intended reason while the ordinary read works. That complete test is still pending here. The August connection snapshot is real; it does not establish that this OAuth flow passes.
Frequently asked questions
When does an MCP server need OAuth?
OAuth is optional in MCP. The authorization specification addresses protected HTTP transports. Public resources may not need it; local stdio servers normally obtain credentials from their environment instead. Choose according to the resource and client.
What do PKCE and the resource parameter protect?
PKCE binds code redemption to the client that initiated the authorization request.
resource identifies the intended MCP resource so tokens can be audience-bound. Neither
replaces per-action approval or input validation. The mechanisms are defined separately
in PKCE and
OAuth resource indicators.
Why does a valid token still return 401?
"Valid" may mean valid for another issuer or audience. A protected server rejects tokens that are not intended for it. Check resource, issuer, expiry and client configuration; insufficient scope is a different case, generally reported with 403. The status distinction also appears in OAuth bearer-token errors.
How should the client handle an expired token?
Use the supported refresh flow if a usable refresh token was issued. Otherwise request authorization again. Refresh-token issuance is not guaranteed. Bound recovery attempts, keep credentials out of logs, and verify a read before resuming work.