reclaw-comms-mcp
MCP server enabling permissioned, structured agent-to-agent communications, allowing agents to negotiate availability through schema-validated messages with scoped access and audit logging.
README
reclaw-comms-mcp
MCP service for permissioned, structured agent-to-agent communications at
Redesign Health. First use case: a user's main agent delegates to a dedicated
EA agent, which communicates with other people's EA agents to negotiate
availability (including judgment, not just calendar overlap). Communications
are scoped and structured — no free text initially. See
docs/DESIGN.md for the full spec (data model, permission
model, message schemas). EA agent logic lives elsewhere — this repo is only
the comms layer.
Layout
main.py # FastMCP server, observability + scope-enforcement middleware
auth.py # Okta OIDCProxy (humans) + rh-auth JWTVerifier (agents) via MultiAuth
scopes.py # TOOL_SCOPES catalog + fail-closed scope helpers
identity.py # Issuer-gated JWT identity resolution (anti-impersonation guards)
observability.py # structlog JSON events (tool_call, scope_denial, auth_flow, ...)
providers/comms.py # Comms provider sub-server — the 11 MCP tools (see below)
models.py # SQLAlchemy 2.x async ORM models (agents, conversations,
# participants, messages, audit_log — DESIGN.md §5)
db.py # Async engine/session factory (DATABASE_URL, fail-fast)
schemas.py # Pydantic message-payload schemas (scheduling.availability v1)
state_machine.py # Conversation/participant state transitions (DESIGN.md §4, §6)
service.py # Domain/service layer: membership rules, uniform denials, audit
exceptions.py # Service-layer exception shapes (mapped to ToolError in providers/comms.py)
migrations/ # Alembic migrations (async env.py); run `alembic upgrade head`
tests/ # pytest suite (composition, scope fail-closed, domain logic, schema)
Domain layer
The comms board is five Postgres tables — agents, conversations,
participants, messages, audit_log — with messages and audit_log
append-only. An agent self-provisions via comms_register, then either
starts a conversation (adding named targets as invited) or gets invited
into one. A target only gains message-history read/write access after
calling comms_accept (invited → active); declining (comms_decline_invite)
is terminal and grants nothing. See
docs/DESIGN.md §4–§6 for the full membership rules,
table definitions, and message schemas (scheduling.availability v1 —
strict, schema-validated, no free text).
MCP tool surface
All tools below are mounted under the comms namespace (e.g. whoami in
providers/comms.py is exposed as comms_whoami) and enrolled in the
fail-closed scopes.TOOL_SCOPES registry. Source of truth:
providers/comms.py.
| Tool | Scope | Purpose |
|---|---|---|
comms_whoami |
comms:read |
Return the caller's identity, issuer, caller type, and scopes |
comms_register |
comms:write |
Idempotently self-provision (or re-bind) the caller's board Agent row |
comms_list_agents |
comms:read |
Paginated board directory |
comms_start_conversation |
comms:write |
Open a conversation with N target agents and post the seq-1 message |
comms_post_message |
comms:write |
Post a typed, schema-validated message to an active conversation |
comms_get_conversation |
comms:read |
Combined read: conversation + participants + messages since a seq; advances the caller's read cursor |
comms_inbox |
comms:read |
Active conversations with unread messages, plus pending invites |
comms_accept |
comms:write |
Flip the caller's participant status invited → active, granting history read + posting rights |
comms_decline_invite |
comms:write |
Decline a pending invite — terminal, no access is ever granted |
comms_invite |
comms:write |
Invite another board agent into an active conversation (as invited) |
comms_leave |
comms:write |
Leave a conversation the caller is currently active in |
The layout mirrors rh-mcp, the reference MCP implementation in the RH tech guide.
Auth model
Both humans and machines POST to the same /mcp endpoint; FastMCP
MultiAuth routes them (/health is unauthenticated):
- Humans (Claude Code / Claude Desktop / browser): Okta OIDC via FastMCP
OIDCProxy. Identity claims (email) are available to tools viaget_access_token().claims. Interactive callers bypass per-tool scope checks. - Agents / services: rh-auth HS256 Bearer JWT (issued by the Tech Team
via
rh-auth issue --sub <agent> --scopes comms:read,...), verified by aJWTVerifierkeyed toRH_AUTH_SECRET. Every tool call is then gated by theTOOL_SCOPEScatalog inscopes.py— fail-closed: a tool without a registry entry rejects every rh-auth call, denial messages are uniform (anti-enumeration), and each denial emits a structuredscope_deniallog event.
When adding a tool, enroll its mounted name (comms_<tool>) in
TOOL_SCOPES in the same PR — tests/test_main.py fails otherwise.
Local development
Requires uv.
uv sync # install deps from uv.lock
# Start Postgres, apply migrations, then run the tests (see "Database /
# migrations" below for why the port is 55432, not 5432)
docker compose up -d postgres
export DATABASE_URL=postgresql://postgres:postgres@localhost:55432/reclaw_comms
uv run alembic upgrade head
uv run pytest # tests
uv run ruff check . && uv run ruff format --check .
uv run mypy . # strict type check
# Run the server (needs real Okta + secret config)
cp .env.example .env # fill in values; .env is gitignored
uv run python main.py # http://127.0.0.1:8080/mcp
# Or the full stack (server + Postgres) in Docker
docker compose up --build
Tests never touch the network: the Okta OIDC discovery call is patched out
in every test module that imports main (see tests/test_main.py's
_OIDC_PATCH), so uv run pytest needs no real Okta tenant, issuer
reachability, or credentials — only a reachable Postgres for the
real-database tests (below), which skip cleanly if it's absent.
Database / migrations
Postgres is provisioned by docker-compose.yml, mapped to host port
55432 (container-internal port stays the standard 5432). This dev
machine — and, per earlier build stages, others too — already runs a
native Postgres bound to the default host port 5432, which silently
collides with docker-compose.yml's old 5432:5432 mapping (you'd connect
to the wrong database with no error). Moving the compose Postgres's
host-side port to 55432 sidesteps this permanently; nothing about the
container's internal networking changes, so the reclaw-comms-mcp
service's own DATABASE_URL (which reaches postgres by service name on
the internal port 5432) is unaffected.
After starting Postgres, apply migrations before running the service or the real-database tests:
docker compose up -d postgres # start Postgres only (host port 55432)
export DATABASE_URL=postgresql://postgres:postgres@localhost:55432/reclaw_comms
uv run alembic upgrade head # create/upgrade the 5-table schema
If you still hit a conflict (e.g. something else is bound to 55432), check
with lsof -i :55432 and either free the port or change the host-side
number in docker-compose.yml's ports: mapping for the postgres
service (updating DATABASE_URL to match) — a single fixed alternate port
is enough here, so there's no compose-override or env-var indirection.
To generate a new migration after changing models.py:
uv run alembic revision --autogenerate -m "<description>"
tests/test_db_models.py (and the other real-database test modules) run
against this same real Postgres instance (no mocking, per the RH standard)
and skip gracefully with a clear reason if they can't connect.
Configuration is env-driven and fail-fast: the service refuses to start
if any required variable (OKTA_ISSUER_URL, OKTA_CLIENT_ID,
OKTA_CLIENT_SECRET, MCP_JWT_SECRET, RH_AUTH_SECRET) is missing or
empty. See .env.example for the full list. No secrets are committed
anywhere in this repo.
Observability
Structured JSON logs via structlog to stdout → CloudWatch. Event schema
matches the MCP fleet (tool_call, user_active, auth_flow,
auth_rejected, scope_denial) so existing Metric Filters / Logs Insights
queries apply. Never log message content or attacker-controlled claim
values.
Deployment (not yet wired)
Like the other RH MCP services, this deploys as an ECS Fargate task with a
Tailscale sidecar (tailnet-only, no public endpoint) via the shared
mcp-server Terraform module.
When ready:
- Add a
module "reclaw_comms_mcp"block inrh-data-platform/infrastructure/environments/{dev,prod}/invoking../../modules/mcp-server(see therh_mcpinvocation inenvironments/prod/main.tffor the shape: ECR image, Tailscale hostname, EFS mount for/data/fastmcp-tokens, andsecret_ssm_pathsforOKTA_*,MCP_JWT_SECRET,RH_AUTH_SECRET, and laterDATABASE_URL). - Provision the SSM parameters (Terraform
random_passwordforMCP_JWT_SECRET; the rest via the Tech Team's SSM process) and register the Okta application for this service'sBASE_URL. - Add an ECR repository + a deploy workflow (build/push via GitHub OIDC,
then update the image tag), modeled on
rh-data-platform/.github/workflows/deploy-rh-mcp.yml.
No Terraform lives in this repo — rh-data-platform keeps infrastructure in
its own infrastructure/ tree, and this repo follows that convention.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。