shared-skill-mcp
shared-skill-mcp
Развёрнутое в AWS описание сервера MCP для пользовательских коннекторов claude.ai, думать о котором как об MCP-сервере, рассчитанном на то, чтобы со временем содержать более одного инструмента, — не только инструмент запросов к Google Sheets, с которого он начинался. Один общий слой аутентификации Cognito (вход через Google) + один Bedrock AgentCore Gateway (собственно MCP-сервер) + одна Lambda на инструмент.
Полная спецификация, схема архитектуры и поэтапная история исходного инструмента query_sheet: Reel AI Workers/skills-spec/sheet-gviz/sheet-gviz.md.
Статус (2026-08-24)
Работает в проде и подтверждённо работает «от и до» — включая реальный коннектор claude.ai, который выполнил вход через Google и вызвал инструмент, а не просто curl.
Параметр | Значение |
URL MCP-сервера (Gateway) |
|
Домен Cognito |
|
ID пользовательского пула |
|
Аккаунт AWS |
|
Актуальные значения (включая секреты) можно получить в любой момент:
AWS_PROFILE=<your profile> terraform -chdir=terraform output
AWS_PROFILE=<your profile> terraform -chdir=terraform output -raw cognito_client_secretСейчас доступен инструмент: query_sheet — выполняет
gviz
запрос (SQL-подобный: select/where/group by/pivot/order by) к Google
Sheets.
Related MCP server: Google Workspace MCP
Архитектура
claude.ai connector
│ OAuth 2.1 (real Google login, via Cognito's Hosted UI)
▼
Cognito User Pool ──federates to──> Google (login only)
│ issues an access token (no "aud" claim — see gotcha below)
▼
AgentCore Gateway (CUSTOM_JWT authorizer, matches by client_id)
│ invokes under its own service role
▼
Lambda tool target (gateway-tool-handler.mjs) ──> gviz.js ──> Google Sheets APIЕсть два клиента Google OAuth-приложений, которые должны оставаться разными: один используется Cognito для входа (федеративная идентичность), другой — gviz.js для чтения таблиц (сервисные учётные данные на базе refresh-токена, для интерактивного режима не применяются). Использовать один и тот же клиент для обеих целей намеренно избегали — см. концепцию «two identities» (двух идентичностей) из документа спецификации.
Как этот репозиторий пришёл к текущему виду (стоит прочитать перед изменением настройки auth)
Первая рабочая версия использовала самодельный Лямбда-шлюз Function URL как MCP-сервер, где сама Lambda проверяла JWT Cognito и вручную отдавала discovery-метаданные OAuth (RFC 9728 / RFC 8414). Она работала по curl и ручному OAuth-сценарию в Postman — полный круговой путь: реальный вход Google, реальные данные таблицы, — но фактический клиент-коннектор claude.ai при обращении к этому серверу каждый раз молча отваливался (Couldn't connect / Authorization failed), не давая ни намёка на причину: в логах Lambda было видно, что claude.ai один раз запрашивает discovery-метаданные, а потом замирает — ни обмена токеном, ни ошибки, ничего.
Рабочая гипотеза в тот момент: access-токены Cognito не содержат claim aud (известное реальное ограничение Cognito — подтверждено декодированием настоящего токена), а спецификация MCP ожидает, что параметр resource, отправляемый клиентом, будет отражён в этом claim. Недавние, выглядевшие правдоподобно факты из других мест экосистема, казалось, подтверждали: это тот самый ответ. Оно оказалось ложным следом — рабочий эталон из внешнего проекта (тот же человек, другой репозиторий) доказал, что связка Cognito + коннектор claude.ai работает прекрасно, если перед Cognito, а не самодельным сервером, стоит Bedrock AgentCore Gateway, и его авторизатор CUSTOM_JWT сопоставляет вызывающих по allowed_clients (это client_id Cognito), a (а не) не по aud. Настоящая причина исходного сбоя так и не была однозначно установлена — вероятнее всего, что-то в самодельной реализации JSON-RPC/discovery не вполне совпадало с тем, чего ждёт клиент claude.ai, несмотря на прохождение всех проверок вручную, которые ему устроили.
Урок на будущее: самодельный MCP-сервер, который выглядит соответствующим спецификации и проходит тесты curl и Postman, еще не доказывает, что он работает с настоящим клиентом claude.ai, — они могут расходиться так, что не дают ни одной сигнальной ошибки. Ставьте собственную реализацию MCP-сервера от AWS (AgentCore Gateway) вместо ручной реализации MCP + OAuth discovery, даже если ради этого придётся освоить ещё один AWS-старвис и иметь дело с более шероховатой Terraform-поверхностью провайдера (см. грабли ниже).
Самодельный сервер на Function URL (sheets-gviz-mcp на Lambda, modules/mcp-lambda, src/lambda-handler.mjs, src/auth.mjs) был выведен из эксплуатации, как только Gateway подтвердил работу: снёсен через Terraform (ноль влияния на остающийся живым Cognito User Pool/domain/app client, который путь Gateway использует как есть) и удалён из репозитория. Он в истории git, если решения и код снова понадобятся.
Попутные грабли (мелочи уже исправлены в коде, но знать о них стоит, прежде чем снова всё это трогать)
Слой Sheets/gviz (src/gviz.js):
OAuth-токену нужны две области —
.../auth/spreadsheetsи.../auth/spreadsheets.readonly; одногоreadonlyдаст 401, который похожа на HTML-страницу входа, а не на нормальную ошибку.У gviz эндпоинт
/tqтребует сегмент пути/a/<domain>/, даже для OAuth-доступа (bearer):docs.google.com/a/google.com/spreadsheets/d/<id>/gviz/tq. Это настраивается черезGVIZ_DOMAIN_SEGMENT, еслиgoogle.comне подходит для твоего домена учетной- эки-записи.Всегда передавай
headers=1(жёстко зашито вquerySheet). Без него автоопределение строки заголовков у gviz может ошибиться и молча сложить реальные строки данных вcols[].labelодной огромной строкой, полностью потеряв их.
AWS/Terraform-слой:
4. Изменения identity-политик IAM могут реально вступать в силу в течение десятков секунд или даже нескольких минут, даже если после это сразу aws iam simulate-principal-policy подтверждает их корректность. Ожидай: свежий plan/apply после того, как дали новое действие, вернёт 403, — это нормально; просто подожди немного и повтори.
5. ESM-файлам .js нужен собственный package.json ({"type": "module"}), если из катают без корневого package.json из репозитория. gviz.js использует export/import и распознается как ESM только потому, что этот package.json находится рядом с ним в zip-архиве каждого Lambda (src/package.json).
6. Terraform-ресурсы aws_bedrockagentcore_* — недавние и текучие; сверь фактическую форму аргументов сную схему самого провайдера, абез доверяя устаревающим статьям и докам (terraform providers schema -json). Нужен провайдер >= 6.0.
7. Авторизатор CUSTOM_JWT у шлюза AgentCore Gateway сопоризует вызывающих по allowed_clients (то есть client_id Cognito), а не по allowed_audience — именно это позволяет ему работать со средствами нестандартными (без aud) токенами Cognito без дополнительного слоя создания токенов.
8. Упаковка существующих живых Terraform-ресурсов в модули была рискованной: и рефакторинг фазы 4 в модули, и последующий вывод Function URL из эксплуатации делались через terraform state mv и реальную проверку plan (0 destroy на всё, что должно остаться) перед каждым apply. Так app client, для которого у claude.ai уже есть учётные данные, перемещался дважды и никогда при этом ломался / не пересоздавался.
Setup
1. Доказать, что учётные данные для Sheets работают (автономно, без AWS)
cp .env.example .env # fill in GOOGLE_CLIENT_ID/SECRET/REFRESH_TOKEN, SPREADSHEET_ID
node scripts/phase1-test.mjs "select *"Готово, когда: выведет {columns, rows} для реального запроса. Ошибки тут — со стороны Google (области доступа, шаринг, незапущенный Sheets API), и это самое дешёвое место, чтобы поймать их перед работой с AWS.
2. Деплой Cognito + Gateway + Lambda инструмента
Нужны AWS-креды с разрешениями из terraform/iam-policy.json и второй ГмGoogle OAuth-клиент (веб-приложение, отдельный от того, ЭМ-файл от Google Sheets, для логина через Cognito). Его redirect URI должен ссылаться на домен Cognito, которого ещё нет. Разорви этот курице-и-яйцо-цикл сначала частичным apply:
scripts/tf.sh apply -target=module.auth.aws_cognito_user_pool.this \
-target=module.auth.aws_cognito_user_pool_domain.thisСоздайте Google OAuth-клиент с redirect URI
https://<field from the output>/oauth2/idpresponse, наполните
GOOGLE_LOGIN_CLIENT_ID/GOOGLE_LOGIN_CLIENT_SECRET в .env, а затем:
scripts/tf.sh applyCLAUDE_OAUTH_REDIRECT_URI настраивать не нужно — по умолчанию это
https://claude.ai/api/mcp/auth_callback, и рабоспособность с реальным коннектором подтверждена.
3. Добавить его как коннектор в claude.ai
Настройки → Коннекторы → Добавить пользовательский коннектор:
URL сервера: output
gateway_urlДополнительно → OAuth Client ID/Secret: outputs
cognito_client_id/cognito_client_secret
Он должен спровоцировать настоящий вход через Google Cognito Hosted UI и затем дать Claude вызывать query_sheet (в чате видно как блок tool-use).
Добавление нового инструмента
Напишите Lambda-handler, следуя контракту AgentCore Lambda-target: плоское событие
event= аргументы инструмента, без JSON-RPC-обёртки (Gateway сам занимается MCP-фреймлей). Пример паттерна —src/gateway-tool-handler.mjs.Добавьте блок
module "..." { source = "./modules/gateway-tool-lambda" ... }в файлmain.tf.Добавьте для него ресурс
aws_bedrockagentcore_gateway_target— либо расширьтеmodules/agentcore-gateway, чтобы он принимал список таргетов, либо добавь ресурс прямо вmain.tf, указавmodule.gateway.gateway_id.
Новый Google OAuth-клиент, домен Cognito и новог Gateway не узнат — всё в module.auth и module.gateway является общим.
Структура
src/
gviz.js Sheets-reading logic — token refresh, gviz query, response
parsing. Host-agnostic; used by gateway-tool-handler.mjs.
gateway-tool-handler.mjs AgentCore Gateway Lambda-target contract for query_sheet —
flat event-in/JSON-out, no JSON-RPC framing (Gateway
handles MCP protocol translation itself).
package.json {"type": "module"} — required for gviz.js's ESM syntax to
resolve once zipped alone, without the repo root's
package.json alongside it.
scripts/
phase1-test.mjs Standalone local proof the Sheets credential + gviz query
round-trip works, no AWS involved.
tf.sh Wraps `terraform` with GOOGLE_*/Cognito vars sourced from
.env — use this instead of calling terraform directly.
terraform/
main.tf Root — provider, variables, the shared auth module, the
claude.ai connector's Cognito app client, the Gateway, and
the query_sheet tool Lambda.
modules/mcp-auth/ Cognito User Pool + Google identity provider + Hosted UI
domain. Shared — instantiate once per AWS account.
modules/agentcore-gateway/ The Gateway (CUSTOM_JWT authorizer) + the query_sheet
Gateway Target. Extend for more targets, or add more
gateways for a genuinely separate trust boundary.
modules/gateway-tool-lambda/ A standalone tool Lambda for a Gateway target — no
Function URL, no public permissions, no own Cognito
client. Gateway is the only caller, via its service role.
iam-policy.json Deploy-time IAM policy for whatever AWS identity runs
scripts/tf.sh. Broad on bedrock-agentcore:* deliberately —
that service/provider surface is new and evolving.This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
Hosted MCP server for GA4, Google Ads and Search Console. Google OAuth, nothing to install.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Live Google Ads, GA4, Search Console, Meta Ads and GBP data in Claude, ChatGPT and any MCP client
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceMCP server for Google Drive, Docs, and Sheets — built for Claude Code. Gives Claude Code direct read/write access to Google Sheets (cell-level edits, formatting, structure), Google Docs (insert, replace, append), and Drive (search).49 npm1MIT
- FlicenseNot gradedqualityDmaintenanceEnables interaction with Google Sheets, Docs, Slides, and Drive through a remote MCP server hosted on Cloudflare Workers, with OAuth authentication and Claude-native connect.-
- FlicenseNot gradedqualityDmaintenanceMCP server for Claude Desktop that provides tools to read/write Google Sheets, manage Gmail, schedule Google Calendar events, and run queries on Neon Postgres databases.-
- AlicenseBqualityCmaintenanceMCP server that gives Claude full read-write access to Google Drive, Docs, Sheets, and Slides using your own Google OAuth credentials and hosted server.25MIT