mcp-resource-server
mcp-resource-server
一个 MCP 服务器,在十个模拟垂直领域(银行、医疗保健、政府、制造业、零售、体育用品、大学、劳动力、Abercrombie & Fitch、航空公司)中公开只读工具,外加一组投资工具。每个垂直领域都读取自己捆绑的 SQLite 数据库——运行它不需要其他服务。投资工具可以选择代理到你提供的银行 API(见下文)。
先决条件
Docker + Docker Compose
一个用于铸造和验证 bearer token 的 PingOne 环境(或任何能够签发具有正确
aud/scope声明的 JWT 并发布 JWKS 端点的 OAuth AS——此服务器仅依赖标准 OIDC 发现,不依赖任何 PingOne 特有的东西)
Related MCP server: mock-mcp
快速开始
cp .env.example .env编辑 .env:为你自己的 PingOne 环境设置 MCP_RESOURCE_SERVER_RESOURCE_URI、PINGONE_ENVIRONMENT_ID 和 PINGONE_REGION。在你有真实 token 之前,保持 PINGONE_ISSUER 被注释掉(见“认证模式”)。
docker compose up --build服务器监听在 http://localhost:8081。SQLite 数据库持久化在 ./data 中(首次使用时从 seed/ 播种;重启永远不会重新播种非空数据库)。
验证它正在运行
curl http://localhost:8081/health
curl http://localhost:8081/.well-known/oauth-protected-resource第二个调用返回资源的广告作用域,如果设置了 PINGONE_ENVIRONMENT_ID/PINGONE_REGION,还返回其授权服务器——这是 MCP 客户端用于 OAuth 发现的 RFC 9728 元数据。
认证模式
token 的签名是否被验证由是否配置了 JWKS 源决定——而不是由 STRICT_AUTH 决定:
设置了 JWKS 源(
PINGONE_ISSUER、PINGONE_JWKS_URI或PINGONE_BASE_URL)——每个 token 都会根据你的 PingOne 环境的密钥进行验证,失败则拒绝。STRICT_AUTH没有效果。一旦真实 token 开始流动,就以这种方式运行。没有 JWKS 源——
STRICT_AUTH=false(随附的默认值)接受格式良好的 token 并带有控制台警告,这样你可以在 PingOne 接入之前用手工制作的 token 练习每个工具;STRICT_AUTH=true则拒绝所有 token。不要让默认值被超过你预期的人访问。
.env.example 随附时所有三个 JWKS 变量都被注释掉,原因就在于此——当你有真实 token 时,取消注释其中一个。
获取 bearer token
每次工具调用都需要一个 bearer token,其 aud 声明必须匹配 MCP_RESOURCE_SERVER_RESOURCE_URI,其 scope 声明必须覆盖你正在调用的工具(参见 tools/list 获取权威的、当前的列表——它是从这个服务器自己的注册表生成的)。
本地测试(STRICT_AUTH=false)——任何具有正确声明的格式良好的 JWT 都可以;签名不会被检查。
node -e "
const b64 = s => Buffer.from(JSON.stringify(s)).toString('base64url');
const header = b64({alg:'none',typ:'JWT'});
const payload = b64({sub:'test-user',scope:'airlines:read',aud:'your-resource-uri',exp:Math.floor(Date.now()/1000)+3600});
console.log(header+'.'+payload+'.');
"(将 aud 替换为你自己的 MCP_RESOURCE_SERVER_RESOURCE_URI 值,将 scope 替换为你正在测试的任何工具)
真实 token(STRICT_AUTH=true)——从你的 PingOne 环境铸造一个。针对你的 PingOne token 端点的 client-credentials 授权,请求此服务器的受众作为资源:
curl -s -X POST "https://auth.pingone.<region>/<env-id>/as/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=<your PingOne worker app client id>" \
-d "client_secret=<your PingOne worker app client secret>" \
-d "scope=<space-separated scopes, e.g. banking:read airlines:read>" \
-d "resource=<MCP_RESOURCE_SERVER_RESOURCE_URI value>"这要求该客户端的应用在 PingOne 中被授权访问此资源和这些作用域(Applications → your app → Resources)——这是 PingOne 端的设置步骤,此服务器不会为你完成。
直接调用工具(健全性检查)
TOKEN="<paste a token from above>"
curl -s -X POST http://localhost:8081/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_airline_bookings","arguments":{}}}'连接 MCP 客户端
服务器在同一端口上通过 WebSocket 和 HTTP(可流式,POST /mcp)说 MCP——ws://localhost:8081 或 http://localhost:8081/mcp。
MCP Inspector(官方开发工具——适用于任何服务器,并允许你设置手动标头,因此是测试此服务器最可靠的方式):
npx @modelcontextprotocol/inspector将传输设置为“Streamable HTTP”,URL 设置为 http://localhost:8081/mcp,并在 Inspector 的连接设置中添加 Authorization: Bearer <token> 标头,然后再连接。
Claude Desktop / Cursor / Windsurf(静态配置,HTTP 传输):
{
"mcpServers": {
"mcp-resource-server": {
"url": "http://localhost:8081/mcp",
"transport": "http"
}
}
}Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.jsonCursor:项目根目录中的
.cursor/mcp.jsonWindsurf:
~/.codeium/windsurf/mcp_config.json
这些配置没有用于静态 bearer token 的字段——当客户端调用受保护的工具时,它会读取 /.well-known/oauth-protected-resource,找到你的 PingOne 环境作为授权服务器,并提示你登录。这只有在你的 PingOne 环境为该特定 MCP 客户端应用注册了 OAuth 客户端,并使用该客户端自己的文档指定的重定向 URI 时才有效——这是 PingOne 端的设置步骤,在此服务器之外。编辑配置后重启客户端。
投资工具
get_investment_accounts、get_investment_balance、get_portfolio_summary 和 get_investment_transactions 开箱即用,从捆绑的 SQLite 数据库(data/invest.db,首次使用时从 seed/invest.seed.json 播种)读取,就像其他每个垂直领域一样。
仅当你运行自己的银行 API 并希望这四个工具将调用者的 bearer token 转发给它并返回它返回的任何内容时,才设置 BANKING_API_BASE_URL。设置后,捆绑的投资数据库将不被使用。
添加工具
工具是代码,不是配置。目录(tools/list)、每个工具的作用域门以及 /.well-known/oauth-protected-resource 中广告的 scopes_supported 都源自 src/tools/registry.ts 中的 ALL_TOOLS,因此你添加到垂直领域列表中的工具在重建镜像后(快速开始中的 --build 命令)就会在所有地方生效。
1. 向现有垂直领域添加工具
两次编辑,无需注册表更改:
将工具定义追加到该垂直领域的
src/tools/<vertical>Tools.ts数组中,例如在sportingGoodsTools.ts中:{ name: 'gear_return_status', description: 'Show the status of a sporting-goods return.', inputSchema: { type: 'object', properties: { orderId: { type: 'string', description: 'Order ID' } }, required: ['orderId'], }, requiredScopes: ['read'], // the bearer token must carry every scope listed readOnly: true, intentHints: ['check my gear return'], // required — tests/registry.test.ts asserts it },在
src/tools/<vertical>ToolHandler.ts的switch中添加匹配的case 'gear_return_status':。处理器只返回 JSON——从该垂直领域的src/db/<vertical>Db.ts读取,或任何其他内容。
2. 添加新的垂直领域
与上面相同的两个文件(src/tools/<vertical>Tools.ts 导出 <VERTICAL>_TOOLS: McpToolDef[],以及 src/tools/<vertical>ToolHandler.ts 导出 dispatch<Vertical>Tool),另外:
数据(可选):
src/db/<vertical>Db.ts+seed/<vertical>.seed.json。复制sportingGoodsDb.ts——它打开data/<vertical>.db,创建模式,并且仅在表为空时应用种子。Dockerfile已经复制了seed/,compose 文件已经挂载了data/。注册在
src/tools/registry.ts中:导入两个导出,将<VERTICAL>_TOOLS展开到ALL_TOOLS中,添加const <VERTICAL>_TOOL_NAMES = new Set(<VERTICAL>_TOOLS.map((t) => t.name)),并在dispatch()中添加一行:if (<VERTICAL>_TOOL_NAMES.has(toolName)) return dispatch<Vertical>Tool(toolName, args, subject);(subject是 token 的sub——当读取必须限定到调用者时,在你的处理器中接受它,就像银行和航空公司所做的那样。)资源(可选):
tools/list是自动的,但 MCP 资源(resources/list、resources/read)来自src/index.ts中手工维护的RESOURCE_CATALOG——如果垂直领域还应将其列表工具作为资源公开,请在那里添加条目。
容易踩的坑
requiredScopes必须是你的 PingOne 资源实际授予的作用域。缺少任何一个的 token 在tools/call上会得到 403,并且tools/list会完全隐藏该 token 的工具。工具名称是全局的。
dispatch()按第一个匹配的名称集路由,因此跨两个垂直领域重用的名称会静默地转到先检查的那个。使用数据支持的垂直领域(例如体育用品)作为模板,而不是 invest——invest 是
dispatch()的回退,并且带有你不需要的代理与 SQLite 切换。
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityDmaintenanceA lightweight MCP server that simulates financial data interactions with dummy authentication and static JSON datasets for testing financial applications.2MIT
- FlicenseNot gradedqualityBmaintenanceA production-grade MCP server for a fictional digital bank, exposing tools for an AI copilot to service customers across the full risk spectrum from read-only lookups to money movement and destructive admin actions, with OAuth 2.1 security and a realistic dataset.131
- FlicenseNot gradedqualityDmaintenanceA Test/Sandbox MCP server that integrates with the SnapTrade API Sandbox environment to provide portfolio oversight, market data, and trading capabilities.
Related MCP Connectors
Hosted MCP endpoint with realistic fake data for prototyping agents. 12 tools, no setup.
Multi-tenant FastMCP server for Charles Schwab brokerage data, monetized via DPYC Tollbooth
MCP server exposing the Backtest360 engine API as tools for AI agents.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/curtismu7/mcp-resource-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server