sondahub

sondahub / MCP OAuth test server

MCP server with OAuth, for testing the authorization flow

The sondahub MCP server, behind OAuth 2.1 as the MCP specification lays it out: a 401 that points to protected-resource metadata, an authorization server with dynamic client registration, PKCE, tokens bound to the resource, and scopes that tell reads from writes. Free, with people to sign in as.

Connect a client

Server
https://api.sondahub.com/mcp/secure
Transport
Streamable HTTP, stateless
Scopes
mcp:read, mcp:write
Sign in as
sonda / probe, or any directory user / probe

A client that implements MCP authorization needs nothing but the address: it meets the 401, discovers the rest, registers itself, opens the consent screen and comes back with a token.

Claude Code
claude mcp add --transport http sondahub-secure https://api.sondahub.com/mcp/secure
MCP Inspector
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP · URL: https://api.sondahub.com/mcp/secure — it runs the OAuth flow when the 401 comes back

The flow, step by step

What a client does, one request at a time — for building one, or for finding where yours stops.

1. Call without a token

401, and where to look
curl -i -X POST https://api.sondahub.com/mcp/secure \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.sondahub.com/.well-known/oauth-protected-resource/mcp/secure", scope="mcp:read"

2. Read the metadata

Protected resource (RFC 9728)
curl https://api.sondahub.com/.well-known/oauth-protected-resource/mcp/secure
Authorization server (RFC 8414)
curl https://api.sondahub.com/.well-known/oauth-authorization-server

3. Register

Dynamic client registration (RFC 7591)
curl -X POST https://api.sondahub.com/v1/utils/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"My MCP client","redirect_uris":["http://127.0.0.1:33418/callback"],"token_endpoint_auth_method":"none"}'

4. Authorize, with PKCE and the resource

Open this in a browser with your client_id; sign in, allow, and the browser lands on your redirect URI with a code (and iss, RFC 9207):

https://api.sondahub.com/v1/utils/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2Fcallback&scope=mcp%3Aread%20mcp%3Awrite&state=xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.sondahub.com%2Fmcp%2Fsecure

5. Exchange the code

Token request
curl https://api.sondahub.com/v1/utils/oauth/token \
  -d grant_type=authorization_code -d client_id=CLIENT_ID \
  -d code=THE_CODE -d redirect_uri=http://127.0.0.1:33418/callback \
  -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk \
  -d resource=https://api.sondahub.com/mcp/secure

6. Call the server

Who am I?
curl https://api.sondahub.com/mcp/secure \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

A token minted for another audience answers 401 invalid_token naming the audience it has and the one it needs — the mistake clients make most. For a quick token without a browser: the password grant with resource=https://api.sondahub.com/mcp/secure and scope=mcp:read, as on the OAuth page.

Questions

Which version of the MCP authorization spec does it follow?

The one of protocol 2025-11-25 and 2025-06-18: the resource server answers 401 with WWW-Authenticate naming its protected-resource metadata (RFC 9728), which names the authorization server; that server publishes RFC 8414 metadata with a registration endpoint (RFC 7591), requires PKCE with S256 of public clients (a client the spec says must use it either way), and binds tokens to the resource (RFC 8707). Clients built for the older spec, which fetch /.well-known/oauth-authorization-server from the server's origin, find it there too.

What do the scopes allow?

mcp:read calls every read-only tool, lists and reads resources and prompts. Write tools need mcp:write; without it they answer 403 insufficient_scope with a WWW-Authenticate naming the scopes to ask for — the step-up a good client handles by asking again.

Who is the user?

Whoever signs in on the consent screen: the playground user sonda / probe, or anyone in the Identity directory with the password probe. The whoami tool answers with the token's claims, so a client can show who it is acting as.

Does registration store my client?

No. The client_id is the registration, signed, so it works forever and on any copy of the hub. Redirect URIs you registered are enforced on every authorization request.