shared-skill-mcp

shared-skill-mcp

Enables claude.ai custom connectors to run SQL-like gviz queries against Google Sheets through a shared AWS-hosted MCP server protected by Cognito Google login, with room to add more tools over time.

Category
访问服务器

README

shared-skill-mcp

An AWS-hosted MCP server for claude.ai custom connectors, built to hold more than one tool over time — not restricted to the Google Sheets query tool it started with. One shared Cognito auth layer (Google login) + one Bedrock AgentCore Gateway (the actual MCP server) + one Lambda per tool.

Full spec, architecture diagram, and phase-by-phase history for the original query_sheet tool: Reel AI Workers/skills-spec/sheet-gviz/sheet-gviz.md.

Status (2026-08-24)

Live and confirmed working end-to-end, including a real claude.ai connector completing Google login and calling the tool — not just curl.

Thing Value
MCP server URL (Gateway) https://sheets-gviz-gateway-63psdjvcgs.gateway.bedrock-agentcore.eu-west-1.amazonaws.com/mcp
Cognito domain sheets-gviz-b24dc744.auth.eu-west-1.amazoncognito.com
User Pool ID eu-west-1_sfGqYcC0a
AWS account 423566941862, eu-west-1

Get current values (including secrets) any time:

AWS_PROFILE=<your profile> terraform -chdir=terraform output
AWS_PROFILE=<your profile> terraform -chdir=terraform output -raw cognito_client_secret

Tools currently exposed: query_sheet — runs a gviz query (SQL-like: select/where/group by/pivot/order by) against a Google Sheet.

Architecture

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

Two Google OAuth clients exist and must stay distinct: one Cognito uses for login (federated identity), one gviz.js uses to read Sheets (a service credential, refresh-token based, never used interactively). Reusing one for both was deliberately avoided — see the spec doc's "two identities" framing.

How this repo got here (worth reading before changing the auth setup)

The first working version used a hand-rolled Lambda Function URL as the MCP server, with the Lambda itself verifying Cognito JWTs and serving OAuth discovery metadata (RFC 9728 / RFC 8414) by hand. It worked over curl and a manual Postman OAuth flow — full round-trip, real Google login, real Sheet data — but claude.ai's actual connector client failed silently against it every time (Couldn't connect / Authorization failed), with zero indication of why: the Lambda's logs showed claude.ai fetching discovery metadata once, then going silent — no token exchange, no error, nothing.

Working theory at the time: Cognito's access tokens don't carry an aud claim (a known, real Cognito limitation — confirmed by decoding a real token), and the MCP spec expects the resource parameter a client sends to be reflected into that claim. Recent, credible-looking evidence from elsewhere in the ecosystem seemed to back this up as the answer. It turned out to be a red herring — a working reference from a separate project (same person, different repo) proved Cognito + claude.ai connectors work fine, using Bedrock AgentCore Gateway in front of Cognito instead of a hand-rolled server, with its CUSTOM_JWT authorizer matching by allowed_clients (Cognito's client_id), not aud. The actual root cause of the original failure was never conclusively pinned down — most likely something in the hand-rolled JSON-RPC/discovery implementation not quite matching what claude.ai's client expects, despite passing every manual test thrown at it.

Lesson for next time: a spec-compliant-looking hand-rolled MCP server that passes curl and Postman tests is not proof it works against claude.ai's actual client — the two can diverge in ways that produce zero error signal. Prefer AWS's own MCP server implementation (AgentCore Gateway) over reimplementing MCP + OAuth discovery by hand, even though it means one more AWS service to learn and a currently-rougher Terraform provider surface (see gotchas below).

The hand-rolled Function URL server (sheets-gviz-mcp Lambda, modules/mcp-lambda, src/lambda-handler.mjs, src/auth.mjs) was decommissioned once Gateway was confirmed working — torn down via Terraform (zero impact to the still-live Cognito User Pool/domain/app client, which the Gateway path reuses as-is) and deleted from the repo. It's preserved in git history if the reasoning or code is ever useful again.

Gotchas hit along the way (fixed in code, worth knowing before touching this again)

Sheets/gviz layer (src/query-sheet/gviz.js):

  1. The OAuth token needs both .../auth/spreadsheets and .../auth/spreadsheets.readonly scopes — readonly alone gets a 401 that looks like an HTML login page, not a clean error.
  2. gviz's /tq endpoint needs an /a/<domain>/ path segment even for OAuth bearer auth — docs.google.com/a/google.com/spreadsheets/d/<id>/gviz/tq. Configurable via GVIZ_DOMAIN_SEGMENT if google.com doesn't work for your account's domain.
  3. Always pass headers=1 (hardcoded in querySheet). Without it, gviz's header-row auto-detection can get it wrong and silently fold real data rows into cols[].label as one giant string, losing them entirely.

AWS/Terraform layer: 4. IAM identity-policy changes can take tens of seconds to a few minutes to actually take effect, even after aws iam simulate-principal-policy confirms them correct immediately. Expect a fresh plan/apply to 403 once right after granting a new action — not a sign anything is wrong, just retry after a short wait. 5. ESM .js files need their own package.json ({"type": "module"}) if they're zipped without the repo root's — gviz.js uses export/import and only resolves as ESM because of that file being present alongside it in every Lambda's zip (src/query-sheet/package.json). 6. aws_bedrockagentcore_* Terraform resources are recent and evolving — confirm actual argument shapes against the provider's own schema (terraform providers schema -json) rather than trusting docs/blog posts, which lag. Requires provider >= 6.0. 7. AgentCore Gateway's CUSTOM_JWT authorizer matches callers by allowed_clients (Cognito's client_id), not allowed_audience — this is what makes it work with Cognito's non-standard (no aud) access tokens without any extra token-minting layer. 8. Reorganizing live Terraform resources into modules risked destroying them. Both the Phase 4→module refactor and the later Function URL decommission used terraform state mv and a real plan check (0 destroy of anything meant to survive) before every apply — the app client claude.ai already has credentials for was moved twice this way without ever being destroyed/recreated.

Setup

1. Prove the Sheets credential works (standalone, no AWS needed)

cp .env.example .env   # fill in GOOGLE_CLIENT_ID/SECRET/REFRESH_TOKEN, SPREADSHEET_ID
node scripts/phase1-test.mjs "select *"

Done when: it prints {columns, rows} for a real query. Failures here are Google-side (scope, sharing, Sheets API not enabled) — cheapest place to catch them before touching AWS.

2. Deploy Cognito + Gateway + the tool Lambda

Needs AWS credentials with the permissions in terraform/iam-policy.json, and a second Google OAuth client (Web application, separate from the Sheets-reading one) for Cognito login. Its redirect URI needs Cognito's domain, which doesn't exist yet — break that chicken-and-egg with a partial apply first:

scripts/tf.sh apply -target=module.auth.aws_cognito_user_pool.this \
  -target=module.auth.aws_cognito_user_pool_domain.this

Create the Google OAuth client with redirect URI https://<that domain output>/oauth2/idpresponse, fill in GOOGLE_LOGIN_CLIENT_ID/GOOGLE_LOGIN_CLIENT_SECRET in .env, then:

scripts/tf.sh apply

CLAUDE_OAUTH_REDIRECT_URI doesn't need setting — defaults to https://claude.ai/api/mcp/auth_callback, confirmed working for a real connector.

3. Add it as a claude.ai connector

Settings → Connectors → Add custom connector:

  • Server URL: the gateway_url output
  • Advanced settings → OAuth Client ID/Secret: cognito_client_id / cognito_client_secret outputs

Should trigger a real Google login via Cognito's Hosted UI, then let Claude call query_sheet (visible in-chat as a tool-use block).

Adding a new tool

  1. Write a Lambda handler using the AgentCore Lambda-target contract — flat event = the tool's arguments, no JSON-RPC wrapping (Gateway handles MCP framing). See src/query-sheet/gateway-tool-handler.mjs for the pattern.
  2. Add a module "..." { source = "./modules/gateway-tool-lambda" ... } block in main.tf.
  3. Add an aws_bedrockagentcore_gateway_target block for it — either extend modules/agentcore-gateway to take a list of targets, or add the resource directly in main.tf pointed at module.gateway.gateway_id.

No new Google OAuth client, no new Cognito domain, no new Gateway needed — everything in module.auth and module.gateway is shared.

Layout

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.

推荐服务器

Baidu Map

Baidu Map

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

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

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

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

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

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

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

官方
精选
本地
TypeScript
VeyraX

VeyraX

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

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

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

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

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

官方
精选
TypeScript
Neon MCP Server

Neon MCP Server

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

官方
精选
Exa MCP Server

Exa MCP Server

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

官方
精选
mcp-server-qdrant

mcp-server-qdrant

这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。

官方
精选