Skip to main content
Glama
tung2744
by tung2744

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.

Related MCP server: MCP Server OAuth Toy

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_URI below, with access_policy.allow_dynamic_third_party_client_access: true on the Resource itself and on every Scope the test tools need — otherwise a DCR client's resource= request gets invalid_target/invalid_scope. Create it via the Admin API GraphQL playground (or admin_api_graphql in an e2e test, if you're doing this from within the authgear-server repo):

    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:8090 must match RESOURCE_URI below 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 resource field 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 like Protected resource ... does not match expected ... (or origin) if you point RESOURCE_URI at 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 change PORT, update the Resource's URI (and RESOURCE_URI below) to match.

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

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 send resource): 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 with aud: [RESOURCE_URI]. whoami should now succeed regardless of granted scopes; list_widgets/run_widget succeed 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_URI is set to something other than this server's real origin. Fix RESOURCE_URI (and the matching Resource in Authgear) to be https://localhost:<PORT>, not an arbitrary placeholder — see "Prerequisites" above.

  • invalid_target at /oauth2/authorize or /oauth2/token — the Resource (and/or the specific Scope) doesn't have access_policy.allow_dynamic_third_party_client_access: true, or the resource= value the client sent doesn't exactly match what's registered.

  • 401 from this server with error_description: "fetch failed" — this server couldn't reach AUTHGEAR_ENDPOINT to 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 aud than RESOURCE_URI.

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A proof-of-concept MCP server implementing OAuth 2.1 authorization with CIMD client registration and PKCE, demonstrating protected resource access and step-up authentication.
  • -
    license
    Not graded
    quality
    F
    maintenance
    A minimal remote (Streamable HTTP) MCP server that is an OAuth 2.1 resource server, demonstrating the MCP authorization spec with token validation and audience checks.
  • A
    license
    Not graded
    quality
    C
    maintenance
    A demo MCP server protected by OAuth (DCR), enabling hands-on exploration of OAuth flow for local MCP servers.
    MIT

View all related MCP servers

Related MCP Connectors

  • MCP server for verifying EUDI/Talao wallet data via OIDC4VP (pull) for AI agents.

  • Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.

  • The official MCP Server from Mia-Platform to interact with Mia-Platform Console

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tung2744/test-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server