agent-comms-mcp
Allows human users to authenticate via Okta OIDC, providing identity claims for interactive callers and letting them bypass per-tool scope checks.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-comms-mcpStart a conversation with Alex's agent to negotiate meeting times."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-comms-mcp
MCP service for permissioned, structured agent-to-agent communications.
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 by
applying judgment to scheduling tradeoffs. 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) + agent-jwt 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 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 (all registered message types)
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)Related MCP server: kitty-hive
Domain layer
The comms board is five Postgres tables: agents, conversations,
participants, messages, audit_log. messages and
audit_log are 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. Task coordination
uses task message types (task_assign, task_report, task_complete,
task_decline, task_cancel) within ordinary conversations: task state
lives on conversations.state alone, with no separate table. Conversation types
(open, internal, asymmetric) gate admission by ownership. Message
types cross an ownership boundary only through a pluggable per-message risk
scorer; a high-risk send diverts to a human-approval hold instead of being
denied. See docs/DESIGN.md §4–§9 for full details.
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 |
|
| Return the caller's identity, issuer, caller type, and scopes |
|
| Idempotently self-provision (or re-bind) the caller's board |
|
| Admin override of an existing agent's |
|
| Sets an existing agent's |
|
| On-behalf-of FIRST registration for a |
|
| Paginated board directory |
|
| Directory lookup by owner email; returns |
|
| Open a conversation with N target agents and post the seq-1 message |
|
| Post a typed, schema-validated message to an active conversation |
|
| Combined read: conversation + participants + messages since a seq; advances the caller's read cursor |
|
| Poll the status of a message held for human approval (sender-only) |
|
| Active conversations with unread messages, plus pending invites. By default excludes the caller's own messages and fully-read conversations -- opt out per-call via |
|
| Paginated list, filterable by role/type/state; newest-first |
|
| Flip the caller's participant status |
|
| Decline a pending invite — terminal, no access is ever granted |
|
| Invite another board agent into an active conversation (as |
|
| Leave a conversation the caller is currently |
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: HS256 Bearer JWT with
iss="agent-jwt",sub, andscopesclaims, verified by aJWTVerifierkeyed toAGENT_JWT_SECRET. Every tool call is then gated by theTOOL_SCOPEScatalog inscopes.py. This gate is fail-closed: a tool without a registry entry rejects every agent 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.
agents.owner_sub/owner_email are a bounded-staleness cache of a
consumer's own ownership system of record, kept fresh by two mechanisms
(TECH-5593): per-request write-through on any tool call that resolves the
caller's OWN agent row (not every tool — e.g. comms_whoami and
comms_register don't go through this path; only from a verified,
plugin-backed AGENT_TOKEN_VERIFIERS claim, never from the built-in
default's caller-supplied one), and an admin-triggered
POST /admin/agents/reconcile-ownership backstop (owner_sub only) for
agents that never make another such request. See DESIGN.md's
"Bounded-staleness ownership write-through + reconciliation" section for
the full design.
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/agent_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 --buildTests 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. It does need 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 agent-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/agent_comms
uv run alembic upgrade head # create/upgrade the 5-table schemaIf 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)
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, AGENT_JWT_SECRET, DATABASE_URL)
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. Events follow the schema in
observability.py (tool_call, user_active, auth_flow, auth_rejected,
scope_denial). Message content and attacker-controlled claim values are
never logged.
Deployment
The service is a standard Python HTTP process backed by PostgreSQL.
Production (ECS): cutting a release here does NOT put your change live anywhere by
itself. Creating a GitHub release triggers .github/workflows/deploy.yml, which
builds this repo's own Docker image and pushes it to dev ECR, then promotes the same
image to prod ECR (requires a production-environment reviewer approval). That's the
entire scope of this workflow -- it does not dispatch anything else and does not
touch ECS. (An earlier revision of this comment claimed it dispatched
rh-data-platform's deploy-reclaw-comms.yml; that mechanism was removed 2026-08-17 --
see this file's own git history -- and the claim was stale. Verified 2026-09-01: no
workflow_dispatch/repository_dispatch call exists anywhere in deploy.yml today.)
The image this repo builds is not the image the live board actually runs. The
deployed reclaw-comms-mcp-rh ECS services (dev: rh-reclaw-comms-dev on
rh-platform-dev-cluster; prod: rh-reclaw-comms on rh-platform-cluster) run a
derived image -- this repo's own published image plus redesignhealth/agent-comms-approvals's
rh_comms_plugins/rh-auth layer (Dockerfile.board-derived in that repo) -- pinned
to a specific sha-<hex> tag from this repo's image, not tracking any floating tag.
Getting a merged/released change here actually live requires, in order:
Cut the release as above (base image only, per docs/RELEASING.md).
Manually trigger
agent-comms-approvals'deploy.ymlviaworkflow_dispatch, passingbase_image= the newsha-<hex>tag from step 1. This builds and pushes the derived board image to dev ECR, then promotes it to prod ECR, in one dispatch (no gate between dev and prod in this step -- see that repo's own workflow comments). The base image's commit must be at or afteragent-comms-mcpcommit5e9a375(AGENT_TOKEN_VERIFIERS) or the dispatch fails closed (TECH-5689).Open a Terraform PR against
redesignhealth/rh-data-platformbumping the pinned image tag/floor SHA ininfrastructure/environments/{dev,prod}/reclaw_comms.tf'stfvars(mirroring PR #8407's pattern for that same file). Get it through CI/Argus.Merge --
dev's Terraform apply runs automatically on push tomain. Prod's apply does not -- it'sworkflow_dispatch-only withdry_run=false, run manually.
Full step-by-step walkthrough, including why deploying the plain base image alone
already changes live message-holding behavior (before any of the wiring above lands):
agent-comms-approvals' docs/TECH-5389-ROLLOUT-RUNBOOK.md
(written for the initial TECH-5389 rollout specifically -- re-verify its "current
state" table before trusting it, it's an explicit point-in-time snapshot, not a live
dashboard -- but §2-§5's mechanics are the general, still-current pattern for any
future change here too).
Local / self-hosted (Docker Compose):
cp .env.example .env # fill in real values
docker compose up --buildRequired environment variables (see .env.example):
Variable | Purpose |
| Okta OIDC issuer URL for interactive callers |
| Okta app client ID |
| Okta app client secret |
| Signing secret for FastMCP's internal OAuth JWTs |
| Shared HS256 secret for agent JWT verification |
| PostgreSQL connection string |
Optional environment variables:
Variable | Purpose |
| Base URL of the separate |
entrypoint.sh runs alembic upgrade head automatically on every container
start, so migrations apply before the server accepts traffic.
Minting agent-jwt tokens
agent-comms-mcp-mint-token (installed alongside the other console
scripts) mints agent-jwt Bearer tokens against AGENT_JWT_SECRET:
# Human-owned agent
agent-comms-mcp-mint-token --sub ea-agent-svc --scopes "comms:read comms:write" \
--owner-email alice@example.com
# Self-owned agent (no human principal)
agent-comms-mcp-mint-token --sub notifier-bot --scopes comms:write --self-owned--owner-email/--self-owned are mutually exclusive and one is required:
skipping this choice is exactly how an agent silently becomes self-owned
instead of human-owned, which later makes anything requiring that human's
approval unsatisfiable until the agent is re-minted with the correct
owner. See
docs/TECH-5389-APPROVAL-PIPELINE.md
§15 for the full rationale.
License
MIT. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP Server for an Agent Task Marketplace
Agent communication platform for agent to agent messaging via MCP. Messages, channels, skills.
Hosted AgentLux MCP server for marketplace, identity, creator, services, and social flows.
The vetted, cross-LLM marketplace of doer agents — itself an MCP server.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server that gives AI agents the ability to discover, match with, and build relationships with other autonomous agents. Supports agent registration, matchmaking, messaging, shared goals, relationship lifecycle management, and real-time event subscriptions.50MIT
- AlicenseNot gradedqualityAmaintenanceMCP server for multi-agent collaboration enabling AI agents to communicate, delegate tasks, and share artifacts across clients and machines with federation support.311MIT
- AlicenseAqualityCmaintenanceMCP server for multi-agent AI systems providing mailbox messaging, A2A task delegation, resource coordination, and a web dashboard.2114MIT
- AlicenseNot gradedqualityBmaintenanceMCP server that enables AI agents on different machines to communicate and collaborate directly through relay channels, supporting structured agent contracts, real-time messaging, and human-in-the-loop approval workflows.3,118MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/redesignhealth/agent-comms-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server