test-mcp

test-mcp

A minimal MCP server that tests Authgear's Dynamic Client Registration and resource-indicator flow, verifying JWTs bound to a specific resource.

Category
访问服务器

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_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

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 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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选