test-mcp
A minimal MCP server that tests Authgear's Dynamic Client Registration and resource-indicator flow, verifying JWTs bound to a specific resource.
README
test-mcp
A minimal MCP resource server for manually testing Authgear's Dynamic Client
Registration (DCR) + resource-indicator support (docs/specs/dcr.md,
docs/specs/access-token-audience-binding.md in the authgear-server repo).
It does nothing interesting on its own — its only job is to sit behind
Authgear-as-authorization-server and let a real MCP client exercise the
whole flow: discovery → DCR self-registration → PKCE authorize+consent →
token exchange bound to this server's resource → an authenticated MCP tool
call.
How the pieces fit together
MCP client --1. GET /mcp (no token)--> test-mcp
<--2. 401 + WWW-Authenticate: Bearer resource_metadata="..."--
MCP client --3. GET /.well-known/oauth-protected-resource--> test-mcp
<--4. { resource, authorization_servers: [Authgear] }--
MCP client --5. GET /.well-known/oauth-authorization-server--> Authgear
<--6. { registration_endpoint, authorization_endpoint, ... }--
MCP client --7. POST /oauth2/register--> Authgear (DCR)
MCP client --8. /oauth2/authorize + consent, resource=<RESOURCE_URI>--> Authgear
MCP client --9. POST /oauth2/token, resource=<RESOURCE_URI>--> Authgear
<--10. JWT access token, aud=[RESOURCE_URI]--
MCP client --11. POST /mcp, Authorization: Bearer <token>--> test-mcp
<--12. tool result (or 401 if scope/audience don't match)--
Steps 1-2 and 11-12 happen against this server. Everything in between is Authgear, discovered automatically by any spec-compliant MCP client — you don't configure the client with Authgear's URL directly.
Prerequisites
-
A running Authgear instance with DCR enabled, e.g. in
authgear.yaml:oauth: dynamic_client_registration: enabled: true initial_access_token_required: false # open registration, for easy testing -
A Resource registered in that project matching
RESOURCE_URIbelow, withaccess_policy.allow_dynamic_third_party_client_access: trueon the Resource itself and on every Scope the test tools need — otherwise a DCR client'sresource=request getsinvalid_target/invalid_scope. Create it via the Admin API GraphQL playground (oradmin_api_graphqlin an e2e test, if you're doing this from within theauthgear-serverrepo):mutation { createResource(input: { resourceURI: "https://localhost:8090" name: "test-mcp" accessPolicy: { allowDynamicThirdPartyClientAccess: true } }) { resource { id } } } mutation { createScope(input: { resourceURI: "https://localhost:8090" scope: "read:tools" accessPolicy: { allowDynamicThirdPartyClientAccess: true } }) { scope { id } } } mutation { createScope(input: { resourceURI: "https://localhost:8090" scope: "execute:tools" accessPolicy: { allowDynamicThirdPartyClientAccess: true } }) { scope { id } } }https://localhost:8090must matchRESOURCE_URIbelow byte-for-byte, and must be this server's own real origin (scheme + host + port), not an arbitrary placeholder. Two independent constraints pin it down:- Authgear requires every Resource URI to be
https://(pkg/lib/resourcescope/formats.go). - RFC 9728 protected resource metadata's
resourcefield is expected to match the URL (or origin) the client actually connected to, and strict clients enforce this — MCP Inspector will refuse to connect with an error likeProtected resource ... does not match expected ... (or origin)if you pointRESOURCE_URIat an unrelated identifier instead of the server's real address.
That combination is exactly why this server defaults to serving HTTPS (self-signed) rather than plain HTTP:
https://localhost:<PORT>is simultaneously a valid Authgear Resource URI and this server's genuine origin. If you changePORT, update the Resource's URI (andRESOURCE_URIbelow) to match. - Authgear requires every Resource URI to be
Setup
npm install
npm run setup # generates a self-signed TLS cert for localhost (see below)
Running
npm start
Environment variables (all optional):
| Var | Default | Meaning |
|---|---|---|
PORT |
8090 |
Port this server listens on. |
AUTHGEAR_ENDPOINT |
http://localhost:4000 |
Base URL of your Authgear instance. Use http://localhost:3000 if you're hitting the make start process directly, or http://localhost:3100 if you're going through the conventional local-dev nginx proxy (docker compose up -d proxy) — either way this must be wherever /.well-known/openid-configuration actually resolves. |
RESOURCE_URI |
https://localhost:<PORT> |
The RFC 8707 resource identifier — must match the Resource created above, and must be this server's real origin (see above). |
USE_HTTP |
unset | Set to 1 to serve plain HTTP instead of HTTPS. Not recommended: with USE_HTTP=1, RESOURCE_URI can no longer equal this server's real origin (it'd have to be http://..., which Authgear rejects as a Resource URI), so a strict MCP client's resource-match check will fail. Only use this against a client you know doesn't enforce that check. |
Testing with a real MCP client
MCP Inspector (recommended first step)
npx @modelcontextprotocol/inspector
Open the printed local URL, set the server URL to https://localhost:8090/mcp,
and connect — Inspector's "Auth" panel walks through discovery, DCR, and the
authorize/token exchange step by step, so you can see exactly what each
response contains.
Since the cert is self-signed, you may need to tell Node to trust it for the Inspector's own outgoing requests:
NODE_EXTRA_CA_CERTS=$(pwd)/certs/localhost.crt npx @modelcontextprotocol/inspector
(Only do this for local testing — never disable certificate validation for anything that talks to a real server.)
mcp-remote (for testing against Claude Desktop)
npx mcp-remote https://localhost:8090/mcp
and point Claude Desktop's config at the resulting local stdio bridge per
mcp-remote's own docs.
What to look for
- No
resource=requested (a plain OIDC client, or an MCP client that doesn't sendresource): Authgear issues an opaque token to a third-party/DCR client by default. This server can't verify an opaque token at all (it isn't a JWT), so every tool call fails with 401 — this is the intended behavior (docs/specs/dcr.md,access-token-audience-binding.md): an unbound third-party token is only usable at Authgear's own/oauth2/userinfo, nowhere else. resource=<RESOURCE_URI>requested: Authgear issues a JWT withaud: [RESOURCE_URI].whoamishould now succeed regardless of granted scopes;list_widgets/run_widgetsucceed only if the corresponding scope (read:tools/execute:tools) was granted at consent time.- A resource-bound token from a different resource, or one whose
Resource/Scope lacks
allow_dynamic_third_party_client_access: rejected at Authgear itself (invalid_target/invalid_scope) before it ever reaches this server.
Troubleshooting
Failed to connect ... Protected resource <X> does not match expected <Y> (or origin)(MCP Inspector, or another RFC-9728-strict client) —RESOURCE_URIis set to something other than this server's real origin. FixRESOURCE_URI(and the matching Resource in Authgear) to behttps://localhost:<PORT>, not an arbitrary placeholder — see "Prerequisites" above.invalid_targetat/oauth2/authorizeor/oauth2/token— the Resource (and/or the specific Scope) doesn't haveaccess_policy.allow_dynamic_third_party_client_access: true, or theresource=value the client sent doesn't exactly match what's registered.- 401 from this server with
error_description: "fetch failed"— this server couldn't reachAUTHGEAR_ENDPOINTto fetch discovery metadata; check Authgear is actually running there. - 401 with a JWT-verification error — the token is real but either expired,
signed by a different issuer, or bound to a different
audthanRESOURCE_URI.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.