Skip to main content
Glama
curtismu7

mcp-resource-server

by curtismu7

mcp-resource-server

MCP-сервер, предоставляющий инструменты преимущественно для чтения в десяти имитированных вертикалях (банковское дело, здравоохранение, государственное управление, производство, розничная торговля, спортивные товары, университет, рабочая сила, Abercrombie & Fitch, авиалинии), а также набор инвестиционных инструментов. Каждая вертикаль читает свою собственную встроенную базу данных SQLite — для работы не требуется никакой другой сервис. Инвестиционные инструменты могут при необходимости проксировать запросы к предоставленному вами банковскому API (см. ниже).

Предварительные требования

  • Docker + Docker Compose

  • Окружение PingOne для выпуска и проверки bearer-токенов (или любой OAuth AS, способный выдавать JWT с правильными claims aud/scope и публиковать конечную точку JWKS — этот сервер полагается только на стандартное OIDC discovery, а не на что-то специфичное для PingOne)

Related MCP server: mock-mcp

Быстрый старт

cp .env.example .env

Отредактируйте .env: задайте MCP_RESOURCE_SERVER_RESOURCE_URI, PINGONE_ENVIRONMENT_ID и PINGONE_REGION для вашего собственного окружения PingOne. Оставьте 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, его сервер авторизации — это метаданные RFC 9728, которые MCP-клиент использует для OAuth discovery.

Режимы аутентификации

Проверяется ли подпись токена, определяется наличием источника JWKS — а не STRICT_AUTH:

  • Источник JWKS задан (PINGONE_ISSUER, PINGONE_JWKS_URI или PINGONE_BASE_URL) — каждый токен проверяется по ключам вашего окружения PingOne и отклоняется при неудаче. STRICT_AUTH не влияет. Используйте этот режим, когда начнут поступать настоящие токены.

  • Нет источника JWKS — STRICT_AUTH=false (поставляемое по умолчанию) принимает корректно сформированный токен с предупреждением в консоли, так что вы можете опробовать каждый инструмент с вручную созданным токеном до подключения PingOne; STRICT_AUTH=true вместо этого отклоняет каждый токен. Не оставляйте значение по умолчанию доступным для большего числа пользователей, чем необходимо.

.env.example поставляется со всеми тремя переменными JWKS закомментированными по этой причине — раскомментируйте одну, когда у вас появятся настоящие токены.

Получение bearer-токена

Каждый вызов инструмента требует bearer-токен, у которого claim aud совпадает с MCP_RESOURCE_SERVER_RESOURCE_URI, а claim scope покрывает вызываемый инструмент (см. tools/list для авторитетного актуального списка — он генерируется из собственного реестра этого сервера).

Локальное тестирование (STRICT_AUTH=false) — подойдёт любой корректно сформированный JWT с нужными claims; подпись не проверяется.

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 — на те инструменты, которые вы тестируете)

Настоящие токены (STRICT_AUTH=true) — выпустите один из вашего окружения PingOne. Grant client-credentials против конечной точки токенов вашего PingOne, запрашивающий аудиторию этого сервера в качестве ресурса:

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 → ваше приложение → 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-клиента

Сервер говорит по MCP как через WebSocket, так и через HTTP (streamable, POST /mcp) на одном порту — ws://localhost:8081 или http://localhost:8081/mcp.

MCP Inspector (официальный инструмент разработчика — работает с любым сервером и позволяет задать заголовок вручную, так что это самый надёжный способ протестировать этот сервер):

npx @modelcontextprotocol/inspector

Установите Transport в «Streamable HTTP», URL — http://localhost:8081/mcp, и добавьте заголовок Authorization: Bearer <token> в настройках подключения Inspector перед подключением.

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

  • Cursor: .cursor/mcp.json в корне вашего проекта

  • Windsurf: ~/.codeium/windsurf/mcp_config.json

В этих конфигурациях нет поля для статического bearer-токена — когда клиент вызывает защищённый инструмент, он читает /.well-known/oauth-protected-resource, находит ваше окружение PingOne как сервер авторизации и предлагает вам войти. Это работает только после того, как в вашем окружении PingOne зарегистрирован OAuth-клиент для этого конкретного MCP-клиентского приложения, с использованием redirect URI, указанного в документации этого клиента — шаг настройки на стороне PingOne, вне этого сервера. Перезапустите клиент после редактирования его конфигурации.

Инвестиционные инструменты

get_investment_accounts, get_investment_balance, get_portfolio_summary и get_investment_transactions работают из коробки с встроенной базой данных SQLite (data/invest.db, заполняется из seed/invest.seed.json при первом использовании), точно так же, как и любая другая вертикаль.

Задайте BANKING_API_BASE_URL только в том случае, если вы запускаете собственный банковский API и хотите, чтобы эти четыре инструмента пересылали bearer-токен вызывающего к нему и возвращали то, что он возвращает. При заданном значении встроенная база данных invest не используется.

Добавление инструмента

Инструменты — это код, а не конфигурация. Каталог (tools/list), шлюз областей действия для каждого инструмента и scopes_supported, объявляемые в /.well-known/oauth-protected-resource, — всё это выводится из ALL_TOOLS в src/tools/registry.ts, так что инструмент, добавленный вами в список вертикали, становится доступен везде после пересборки образа (команда --build в «Быстром старте»).

1. Добавление инструмента в существующую вертикаль

Два изменения, без изменения реестра:

  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
    },
  2. Добавьте соответствующий case 'gear_return_status': в switch в src/tools/<vertical>ToolHandler.ts. Обработчик просто возвращает 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 — это sub токена; принимайте его в своём обработчике, когда чтение должно быть ограничено вызывающим, как это делают банковское дело и авиалинии.)

  • Ресурсы (необязательно): tools/list автоматический, но MCP ресурсы (resources/list, resources/read) берутся из вручную поддерживаемого RESOURCE_CATALOG в src/index.ts — добавьте запись туда, если вертикаль также должна предоставлять свой список инструментов как ресурс.

Что может укусить

  • requiredScopes должны быть областями действия, которые ваш ресурс PingOne действительно предоставляет. Токену, у которого отсутствует любая из них, возвращается 403 на tools/call, а tools/list полностью скрывает инструмент от этого токена.

  • Имена инструментов глобальны. dispatch() маршрутизирует по первому совпавшему набору имён, поэтому имя, повторно использованное в двух вертикалях, молча уходит в ту, которая проверяется первой.

  • Используйте вертикаль с данными (например, спортивные товары) как шаблон, а не invest — invest является запасным вариантом в dispatch() и несёт переключатель прокси-и-SQLite, который вам не нужен.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A lightweight MCP server that simulates financial data interactions with dummy authentication and static JSON datasets for testing financial applications.
    2
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    A minimal local HTTP MCP mock server for development and testing, providing predictable tool responses with OAuth token support and zero dependencies.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A 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.
    21 npm
    1
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A Test/Sandbox MCP server that integrates with the SnapTrade API Sandbox environment to provide portfolio oversight, market data, and trading capabilities.
    -