Lark AgentCore Gateway Interceptor Server
БесплатноНе проверенEnables Bedrock agents to act as a Lark user, performing document operations (list, create, edit, delete) and identity verification via Lark's REST API, with pe
Описание
Enables Bedrock agents to act as a Lark user, performing document operations (list, create, edit, delete) and identity verification via Lark's REST API, with per-user credential injection through a Gateway Interceptor.
README
A reference implementation of enterprise identity on Amazon Bedrock AgentCore, using Lark (Feishu) as the identity provider. A simple agent is reachable from two Lark entrypoints — chat messages and a desktop-client-embedded web UI — that both resolve to the same lark:{open_id} identity. That identity is forwarded to downstream MCP tools through an AgentCore Gateway Request Interceptor (the agent never holds a downstream credential), and the tools then act as the user against Lark with the user's own token, so they reach only what that user can — Lark itself decides. In short, the agent inherits both who you are and what you're allowed to do, adding nothing of its own.
This is the Gateway Interceptor variant: downstream tools are Lambda targets, and a custom Gateway Request Interceptor forwards identity and injects the per-user credential (self-managed token store). The sibling repo lark-identity-on-agentcore-native achieves the same guarantees with the AgentCore Identity Token Vault (OAuth 3LO, driven agent-side) instead; the two differ only in how the downstream hop resolves per-user credentials.
Architecture
┌─────────────────────┐ Lark desktop web UI ──▶ SPA (S3/CloudFront)
Lark message ──▶ │ Router Lambda │ h5sdk requestAccess ──▶ login code
(webhook) │ verify/decrypt │ │
│ resolve user │ ▼
└──────────┬──────────┘ web_api Lambda
│ POST /api/lark/auth code ─▶ Cognito JWT (Lark is IdP)
│ POST /api/session JWT ─▶ presigned WSS URL
│ │
InvokeAgentRuntime (SigV4) │ │ browser opens WSS
payload carries actorId ▼ ▼ (platform bridges to /ws)
┌─────────────────────────────────────────────────────┐
│ Agent container (ARM64, AgentCore Runtime) │
│ :8080 /ping /invocations(POST) /ws(WebSocket) │
│ Strands Agent + AgentCore Memory (per-user STM) │
└───────────────────────┬─────────────────────────────┘
│ MCP call, Bearer = user's Cognito ACCESS token
▼
┌───────────────────┐
│ AgentCore Gateway │ MCP server; customJWTAuthorizer (Cognito)
│ + Interceptor λ │ passRequestHeaders=true; injects end-user id
└─────────┬─────────┘ (Gateway invokes its Lambda target, not Lark)
▼
┌───────────────────┐ whoami — identity proof
│ Tool Lambda │ list_my_docs / create / edit / delete — act AS the
└─────────┬─────────┘ user with THIS user's Lark user_access_token (by open_id)
│ HTTPS, Bearer = user_access_token
▼
┌─────────────────────────────────────────────────────┐
│ Lark REST API → returns only what THIS user can see │
└─────────────────────────────────────────────────────┘
Identity: both entrypoints resolve to lark:{open_id} (shared session/memory).
Lark is NOT standard OIDC → web_api exchanges the login code for a Cognito JWT.
Pass-through (whoami) proves WHO; inheritance (list_my_docs, user_access_token)
means the agent can only reach what the user can — Lark adjudicates.
See docs/architecture.md for the full layered design, per-hop auth matrix, and sequence diagrams.
Layout
| Path | What |
|---|---|
app.py, cdk.json |
CDK app (uv-managed deps) — 6 stacks |
stacks/ |
security, agentcore, router, webui, gateway, observability |
agent/ |
Strands agent container (HTTP contract + WS + AgentCore Memory + MCP Gateway client) |
lambda/router/ |
Lark webhook: verify/decrypt/tenant-token/send |
lambda/web_api/ |
Lark login exchange + session bootstrap (presigned WSS) |
lambda/interceptor/ |
Gateway Request Interceptor (per-user credential injection) |
lambda/tools/ |
MCP tool targets: whoami (identity proof) + list_my_docs/create_doc/edit_doc/delete_doc (all act as the user against Lark) |
web-ui/ |
Lark-embedded SPA (no build step; renders agent replies as Markdown via marked + DOMPurify) |
scripts/ |
deploy / setup-lark / manage-allowlist / test |
docs/architecture.md |
Full architecture: layers, per-hop auth, sequence diagrams |
Deploy
Prereqs: uv, Docker, the AgentCore CLI (npm i -g @aws/agentcore), and AWS credentials.
Scripts default to the default profile / us-west-2; override with PROFILE=... REGION=....
The AgentCore Runtime and Gateway have no CloudFormation resources in this region:
the Gateway is created by deploy.sh via the control-plane CLI (IDs fed back into cdk.json),
the Runtime image is built (ARM64, CodeBuild) and deployed with the AgentCore CLI.
cp .env.example .env # fill in Lark appId/appSecret/encryptKey/token + your open_id
scripts/deploy.sh --base # CDK base stacks (security, agentcore, router, gateway, observability)
scripts/deploy.sh --gateway # create the MCP Gateway + interceptor + demo target
scripts/deploy.sh --runtime # build ARM64 image (CodeBuild) + deploy the Runtime (AgentCore CLI)
scripts/setup-lark.sh # read .env → Secrets Manager; print webhook/SPA URLs; allowlist you
scripts/deploy.sh --frontend # deploy WebUI stack, inject SPA config, upload SPA
# or: scripts/deploy.sh # run every step in order
Config in cdk.json: default_model_id (global.anthropic.claude-sonnet-5),
lark_api_domain (https://open.larksuite.com international / open.feishu.cn),
registration_open (false = allowlist only), presigned_url_expires.
Tear down
scripts/destroy.sh # delete everything deploy.sh created (asks for confirmation)
# or: scripts/destroy.sh --yes # skip the prompt
Deletes in dependency order — Gateway targets → Gateway → Runtime (all CLI-created, so cdk destroy alone can't remove them) → the six CDK stacks → the dynamic per-user token secrets ({prefix}/user-tokens/*, created at runtime and not owned by any stack). Idempotent: re-running skips already-gone resources. Your Lark console app config is not touched; re-seed credentials from .env via scripts/setup-lark.sh on the next deploy.
Lark console setup (do this once, in order)
The webhook/SPA URLs come from deploy.sh output. In the Lark developer console:
Add features: enable Bot + Web app.
Permissions & Scopes — add all of these, then note that subscribing to the message event requires the p2p scope or single-chat messages are never pushed:
im:message,im:message:readonlyim:message.p2p_msg:readonly← required for single-chat messagesim:message:send_as_bot(reply),im:resource(images)contact:user.base:readonly(login → open_id)
For permission inheritance (the doc tools act as the user), also add these under the User Token Scopes tab — they are user-identity scopes and usually need admin approval before they take effect; the web app's
tt.requestAccessscopeListmust match them exactly or it fails with 20027:drive:drive(list/create/manage the user's Drive files),docx:document(create/edit docx),offline_access(refresh_token — the user_access_token lives only ~2h)
Events & Callbacks: subscription mode = Send to developer's server; set Request URL to the webhook URL; enable Encryption (note the Encrypt Key); add event
im.message.receive_v1.Security Settings: add the SPA URL to Redirect URLs and H5 trusted domains. Leave IP allowlist empty (Lambda egress IPs are dynamic).
Web app: set Desktop + Mobile homepage to the SPA URL.
Version Management & Release: create a version and publish. Any change to scopes/events requires a re-publish to take effect.
Then scripts/setup-lark.sh stores credentials and allowlists your open_id.
New users: they message the bot, get a rejection with their lark:ou_... id, and an admin runs scripts/manage-allowlist.sh add lark:ou_... (or set
registration_open: true to let anyone in).
Test
scripts/test.sh # agent (8) + router (7) + web_api (4)
Cost
This deploys billable AWS resources. All the always-on pieces are consumption- or per-unit-priced (no fixed reservation), so an idle single-user demo in us-west-2 is on the order of a couple USD/month before model usage; the variable cost is dominated by the agent's Bedrock calls. Verify current rates on the AWS pricing pages — figures below are as researched, not a quote.
- Bedrock model invocations — the main usage-sensitive line; priced per input/output token on the model in
default_model_id. A chatty demo is cents-to-dollars; a load test is not. - AgentCore Runtime — metered per-second, not a reserved instance: CPU (
~$0.0895/vCPU-hour) is billed only during active processing (free while waiting on the model/tools), memory (~$0.00945/GB-hour) accrues continuously while the microVM is alive. An idle deployment still costs memory-time until the session's microVM is torn down. - AgentCore Gateway — per-invocation (
~$0.005 per 1,000tool/list calls); negligible at demo volume. - AgentCore Memory (STM) — billed per event written (
~$0.25 per 1,000create-event calls), not for retention duration; the 30-day window itself adds no storage fee. - Lambda + API Gateway (HTTP + WSS) — router, web_api, interceptor, tools; effectively free at demo volume.
- Secrets Manager —
$0.40/secret/montheach: the Lark-creds and tool-key secrets, plus one per-user token secret created on first authorization (so ~$1.20/month at one user, growing with users). - Cognito, DynamoDB (on-demand) — the identity plane; negligible at demo volume.
- S3 + CloudFront — SPA hosting; pennies at demo volume.
scripts/destroy.sh removes everything, including the dynamic per-user token secrets, so you aren't left paying for orphaned resources. Costs are usage-driven — an idle deployment is cheap, but leaving it up still accrues the per-secret charges and the Runtime's memory-time until its microVM shuts down.
Security considerations
This is a reference implementation, not production-ready as-is. Before any real use:
- IAM is scoped but a sample. Secret access is path-scoped to
{prefix}/*; two grants useresources=["*"]only because the AWS actions (ecr:GetAuthorizationToken,secretsmanager:CreateSecret) cannot be resource-scoped — writes are still constrained to{prefix}/user-tokens/*. Re-review least-privilege for your account. - CORS is wide open (
allow_origins=["*"]on the HTTP API and CloudFront). Lock it to your SPA origin for production. - Per-user Lark tokens live in Secrets Manager, one secret per user (
{prefix}/user-tokens/{open_id}), refreshed on expiry. The agent never holds them — the tool Lambda loads them at call time. Treat the account hosting these as sensitive. - The agent's output is untrusted — the web UI sanitizes it with DOMPurify before rendering, and the marked/DOMPurify CDN scripts are pinned with SRI. Keep it that way if you touch the render path.
- Webhook verification is fail-closed — missing/invalid signature or a timestamp outside the replay window is rejected. Don't relax this.
- No secrets in this repo — Lark credentials come from
.env→ Secrets Manager viascripts/setup-lark.sh;.envis git-ignored.
Deployment status
All four goals verified end-to-end on an AWS account:
- ✅ Lark chat: a real single-chat message → Router (verify + AES decrypt) → resolve
lark:{open_id}→ AgentCore Runtime → model reply back in Lark./whoamiin Lark reports the caller's identity. - ✅ Desktop embed: opening the Web app inside the Lark client runs h5sdk 免登 →
POST /api/lark/auth(code → Cognito JWT) →POST /api/session(presigned WSS) → the AgentCore platform bridges the browser's WSS to the agent's/wson port 8080 → streaming reply. Shows the user's display name. The minted JWT is cached insessionStorage, so a refresh reuses it and skipsrequestAccess— otherwise Lark re-prompts the consent popup on every load (it has no silent login-state reuse; the SPA must do it). - ✅ Unified identity: both entrypoints resolve to the same
lark:{open_id}(session/workspace shared). - ✅ MCP identity pass-through: the agent mints the user's Cognito access token → AgentCore Gateway (
customJWTAuthorizer) → Request Interceptor reads the identity and injects the per-tenant downstream key → thewhoamitool reports the real end-user id and that a credential was injected, while the agent never holds the key. - ✅ Conversation memory: the agent is a Strands agent with an AgentCore Memory (STM) session manager keyed by
(actor_id, session). Verified across two different runtime sessions for the same user: it recalls a fact stated earlier — so memory persists across reconnects and both entrypoints (30-day event retention). - ✅ Permission inheritance: after the user authorizes in the web app, the doc tools act as that user — each loads the user's Lark
user_access_token(stored by open_id, refreshed on expiry) and calls the Lark API, so access is scoped to what that user can see/do in Lark, adjudicated by Lark, and the agent never holds the token. Verified end-to-end:list_my_docsreturned the user's real folder and, given afolder_token, descended into it to list the nested docs;create_doc/edit_doc/delete_doccreated a doc (real document_id), appended content, and trashed it — all as the user. - ✅ Markdown rendering: agent replies (bold, lists, tables, code) render as sanitized HTML in the web chat —
markedparses,DOMPurifystrips anything unsafe (agent output is untrusted); streaming re-renders each delta; plain-text fallback if the CDN scripts fail.
Build & deploy notes (learned the hard way)
- Runtime image must be ARM64 and built via CodeBuild.
A QEMU cross-build on an x86 host produces an image that fails to start on Graviton (500 with no logs).
The runtime is built + deployed with the AgentCore CLI (
agentcore configure/agentcore deploy), which runs CodeBuild in the cloud. The agent image includesaws-opentelemetry-distroand runs underopentelemetry-instrument(required for AgentCore log/trace collection). - Runtime container logs:
aws logs tail /aws/bedrock-agentcore/runtimes/<runtime_id>-DEFAULT --since 15m. - WebSocket endpoint
/wson port 8080 (same app as/ping+/invocations), matching the AgentCore SDK contract — not a separate port. - Gateway auth:
send the Cognito access token (has the
client_idclaim thatallowedClientsvalidates); an ID token 403s withinsufficient_scope. - Errors from the agent are returned as HTTP 200
{error: ...}— AgentCore wraps non-2xx asRuntimeClientErrorand drops the body.
Notes & limitations
- Session granularity: one long conversation thread per user (the Memory
session_idis derived fromactor_id). All of a user's turns — webhook, web UI, and reconnects — share one context. There is no "new chat" affordance yet. - Memory is STM-only: the Memory resource stores raw turns (30-day retention), no long-term extraction/summarization strategies are configured.
whoamireports thelark:{open_id}, not a display name — the tool only sees what the Gateway interceptor injects (identity from the JWT), and the JWT carries no name claim.
from github.com/aws-samples/sample-lark-identity-on-agentcore-interceptor
Установка Lark AgentCore Gateway Interceptor Server
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/aws-samples/sample-lark-identity-on-agentcore-interceptorFAQ
Lark AgentCore Gateway Interceptor Server MCP бесплатный?
Да, Lark AgentCore Gateway Interceptor Server MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Lark AgentCore Gateway Interceptor Server?
Нет, Lark AgentCore Gateway Interceptor Server работает без API-ключей и переменных окружения.
Lark AgentCore Gateway Interceptor Server — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Lark AgentCore Gateway Interceptor Server в Claude Desktop, Claude Code или Cursor?
Открой Lark AgentCore Gateway Interceptor Server на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Lark AgentCore Gateway Interceptor Server with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
