Blog · For developers

OAuth for MCP servers, in practice

You paste an address, a browser tab opens, you press Allow. Quite a lot happens between those three things.

By the CosVoice team · · 5 minute read

There's no box for the key

The first time you add a remote MCP server to an agent, you look for the field where the API key goes. Often there isn't one. There's a box for a URL and that's it. You paste the address, a browser tab opens on the service's own site, you sign in, you press Allow, and the tab closes. The agent now has tools.

That's OAuth doing its job. MCP uses OAuth 2.1 for remote servers, and the flow is arranged so that a client which has never heard of a server can connect to it knowing only the address. We'll go through it in the order it happens, using our own server at cosvoice.com/mcp when a concrete example helps. (If you only want the clicks, they're on the Claude page.)

First the client finds out who to ask

The client starts out knowing one thing, the server's address. It needs to learn where to send the person to sign in, where to swap a code for a token, and where to register itself. It gets all of that from two small JSON documents at well-known paths.

The first describes the protected resource, which is the MCP server, and names the authorization server that guards it. Ours is at cosvoice.com/.well-known/oauth-protected-resource. The second describes the authorization server: its endpoints and what it supports. Ours is at cosvoice.com/.well-known/oauth-authorization-server.

In the usual flow the client makes a request with no token, gets turned away with a pointer to the first document, and follows the trail from there. Nobody types any of it. That's the point of discovery.

Then it registers itself, on the spot

In older OAuth setups a developer visited a portal, created an app, and copied a client id into their code. That doesn't work when the client is an agent somebody installed this morning and the server is one they found this afternoon. There are too many pairs for anyone to set up by hand.

Dynamic client registration fixes it. The client posts a short description of itself to the registration endpoint (a name, and the redirect addresses it will use) and gets a client id back. No portal, no waiting for approval.

There's a catch, and it's worth being clear about. Because registration is open, the name a client gives isn't proof of anything. A client can call itself whatever it likes. So the safety of the flow can't rest on registration. It rests on the next two steps.

PKCE, for clients that can't keep a secret

An agent running on a laptop can't hold a secret. Anything baked into it can be read back out. So the flow doesn't depend on a client secret, and uses PKCE instead (people say "pixie").

Before sending the person off to sign in, the client makes up a long random string, the verifier, and keeps it to itself. It sends only a hash of that string, the challenge, with the authorization request. After the person approves, the client gets a one-time code back on its redirect address. To swap that code for a token it has to present the original verifier. The server hashes it and compares.

So a code stolen on the way back is useless. Whoever took it doesn't have the verifier, and a hash doesn't run backwards.

The consent screen is the only part a person sees

Everything so far was machines talking to machines. The consent screen is where a person decides. It should say plainly who is asking and what they'll be able to do, and the person should be reading it on the real service, in their own browser, signed in as themselves.

On ours, the owner signs in (or creates an account) and presses Allow. If the account has more than one line, they pick which line on that screen.

One habit worth having, on any service: read the screen. If the thing asking for access isn't the thing you just pasted an address into, close the tab. Open registration means the consent screen is the checkpoint, and you're the one standing at it.

Tokens, and what the owner can take back

After the swap, the client holds an access token and sends it as a bearer token with every request to the MCP server. The token stands for a grant: this client, this account, this access. It isn't the owner's password and can't be turned into one.

With CosVoice the token is scoped to one line. To work with a second line, the agent connects again and the owner picks the other one on the approval screen. The owner can cut off any connected assistant in Settings, under Connect. Closing a line revokes its connections as well.

Compare that with a pasted key. A key in a config file tends to get copied into a second tool, then a third, then a chat message. When you want to cut off one of them, you replace the key and update it everywhere else. With OAuth each client got its own grant, so you can take one back and leave the rest alone.

When a client can't do the dance

Not every client supports the full flow. Our server has two fallbacks, described on the docs page. Both are keys, so treat them like keys, and use OAuth whenever the client offers it.

  • A bearer header. If the client lets you set a static Authorization header, the owner creates a key in Settings and the client sends it as a bearer token. The same key works for the REST API.
  • An address with the key in it. For connectors that accept a plain URL and nothing else, there's a form of the MCP address that carries the key in its path. That address is a password. Keep it out of shared chats, and make a new key if it leaks.

Common questions

Why doesn't my MCP client ask for an API key?

Because remote MCP servers use OAuth. The client discovers the server's sign-in endpoints from its address, you approve access in your browser, and the client receives a token. Nothing is pasted, so nothing sits in a config file.

What is dynamic client registration?

It's a way for a client to register with an authorization server by itself, by posting its name and redirect addresses and receiving a client id. It lets any MCP client connect to any MCP server without a developer creating an app by hand first.

Is PKCE required for MCP?

OAuth 2.1 requires PKCE for the authorization code flow, and MCP builds on OAuth 2.1. In practice an MCP client that supports sign-in handles it for you.

How do I disconnect an agent from my CosVoice line?

Open Settings, go to Connect, and cut off the assistant you want removed. If you connected with a key and not OAuth, replace the key.

Read next: Connect Claude to a phone line · What the tools do once you're connected · Trust and safety

Hear it for yourself.

Call our own AI agent and ask her anything, or get a number in your area code in about a minute.

More on for developers

Give your Chief of Staff a phone.

Pick a number, save the contact, make the first call. About a minute.

Get your line