Auth-specific code: 5 lines · Total example: 45 lines · SDK: go-sdk v0.1.1
When your MCP server needs to call another resource — another Mint MCP
server, an admin service, any backend that trusts Authplane — it
acquires a token via the github.com/authplane/go-sdk/core Auth Client.
This example isolates that outbound OAuth call so you can see what the
SDK does: build a client, request a client_credentials token, present
it on the call. In your real MCP server, this code lives inside the
tool handler that needs to talk to the other resource — the standalone
main.go shape here is purely for readability.
- How to acquire an Authplane access token from a Go agent with the
github.com/authplane/go-sdk/coreSDK - How the
client_credentialsgrant pairs with theresource=parameter to mint a JWT whose audience matches a registered Mint Resource - How to call a protected MCP tool over HTTP with the resulting bearer token — and how the tier-01 server validates it before invoking the tool
| Time to run | ~30 seconds once tier-01 is up |
| Prereqs | The tier-01 example is up (cd ../01-mcp-server-basic && make run && make verify), plus go 1.25+, curl, jq |
| SDK | github.com/authplane/go-sdk/core v0.1.1 (Go module proxy) |
| Pairs with | Tier 01 — Basic MCP server (Go) — the agent calls into the MCP server that tier brings up |
# In one terminal: bring up tier-01.
cd ../01-mcp-server-basic && make run && make verify
# Back in this directory:
cp .env.example .env
make verify # no-op today — see callout abovemake verify registers a fresh
client_credentials client against the running authserver, writes the
returned client_id / client_secret into .env, runs the agent
(go run ./), and asserts that the agent successfully called the tier-01
MCP server's echo tool. make clean removes the .env file the run
target created.
The make verify script automates every step below; the bullets here
describe what's happening so you can reproduce the flow by hand.
If you want to run the curl examples manually instead of via
make verify, first load the env vars and capture client credentials as you go:set -a; source .env; set +a # exports AUTHPLANE_ADMIN_API_KEY etc.Steps 2 and 4 emit
client_id,client_secret, andaccess_tokenin their responses; assign each to a shell variable (CLIENT_ID=...,CLIENT_SECRET=...,ACCESS_TOKEN=...) before the next step uses it.
-
Confirm tier-01 is running. The agent calls
http://localhost:8080/mcp— that's the tier-01 MCP server. Without it, the agent has nothing to call. Tier-01'smake verifyalso registers the Mint Resource the AS needs to mint a matching JWT audience.curl -fsS http://localhost:9000/.well-known/oauth-authorization-server curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/mcp # expect 401
-
Register a fresh OAuth client. The tier-01 verify script also registers a client, but it lives in that script's local scope. The simplest path is to register a new one here so the credentials live in this directory's
.env. The CLI form is also valid (seedocs/reference/cli.md#cli-admin-client-create):curl -sS -X POST http://localhost:9001/admin/clients \ -H "Authorization: Bearer $AUTHPLANE_ADMIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "client_name": "demo-mcp-agent", "grant_types": ["client_credentials"], "token_endpoint_auth_method": "client_secret_basic", "scope": "mcp:echo" }'
The response carries
client_idandclient_secret. The secret is shown once. -
(Reference) Mint a token by hand. The SDK does this internally — you do not call
/oauth/tokenfrom Go code. Curl version for reference; seedocs/reference/http-api.md#http-public-oauth-token:curl -sS -X POST http://localhost:9000/oauth/token \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -d "grant_type=client_credentials" \ -d "scope=mcp:echo" \ --data-urlencode "resource=http://localhost:8080/mcp"
The Go agent in
main.gobuilds the same request viaclient.ClientCredentials(ctx, scopes, resources). The response includesaccess_token,token_type=Bearer,expires_in, andscope. There is no refresh token forclient_credentials. -
Run the agent. With
AUTHPLANE_CLIENT_ID/AUTHPLANE_CLIENT_SECRETin.env:set -a; source .env; set +a go run ./
The agent POSTs a
tools/callJSON-RPC request tohttp://localhost:8080/mcpcarrying the bearer token, and prints the echoed text on stdout. Drop theAuthorizationheader and you'll see a 401 — proof that the tier-01 server is actually enforcing auth.
A plain Go agent that calls an unauthenticated MCP server:
body := []byte(`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{...}}`)
req, _ := http.NewRequest(http.MethodPost, mcpURL, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)The same agent with an Authplane-minted bearer token:
+ client, _ := authplane.NewClient(ctx, os.Getenv("AUTHPLANE_ISSUER"),
+ authplane.WithClientCredentials(os.Getenv("AUTHPLANE_CLIENT_ID"), os.Getenv("AUTHPLANE_CLIENT_SECRET")),
+ )
+ defer client.Close()
+ tok, _ := client.ClientCredentials(ctx, []string{"mcp:echo"}, []string{resource})
body := []byte(`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{...}}`)
req, _ := http.NewRequest(http.MethodPost, mcpURL, bytes.NewReader(body))
+ req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)The auth-specific lines live inside the // authplane:begin /
// authplane:end markers in main.go. The example uses the
same small must[T] generic helper as tier-01 (defined at the bottom of
main.go) to keep the auth call concise; swap it for explicit
if err != nil { ... } in production. Run
go run ./tools/loccount examples/go/02-agent-basic from the repo root
to see the count.
authplane.NewClient performs RFC 8414 AS metadata discovery against the
issuer, fetches the JWKS, and constructs a Client that knows how to talk
to the AS's token endpoint. The
ClientCredentials method
posts grant_type=client_credentials to that endpoint with the
configured client credentials in an HTTP Basic header (RFC 6749 §2.3.1),
the requested scope, and the resource= parameter. The AS verifies
the client and the requested scope against the target Resource, then
issues an access token whose aud claim is the Resource URI. The SDK caches the token until shortly before exp so subsequent
calls are local. The agent embeds the token in an Authorization: Bearer
header on its outbound MCP call; the tier-01 server's adapter validates
the signature, audience, scope, and expiry before invoking the tool.
Tier 03 — MCP server + agent with DPoP + per-tool scopes
adds RFC 9449 proof-of-possession so a stolen bearer token alone is not
enough to call the resource, and demonstrates per-tool scope enforcement.
Tier 04 puts an MCP server in front of a Broker
upstream (GitHub) with ConsentRequiredError handling.
To build the AS from this checkout rather than pulling
authplane/authserver:latest, follow the LOCAL BUILD ESCAPE
HATCH comment block in
../../_shared/docker-compose.authserver.yml.
Mirror the change in this example's docker-compose.yml (which inlines
the same service definition) — replace the image: line with the
build: block shown in the shared file.