Slack Agent Mesh
FreeNot checkedSlack Agent Mesh is an open-source MCP server that turns a single Slack app into a mesh of virtual agent identities, providing a transport and coordination laye
About
Slack Agent Mesh is an open-source MCP server that turns a single Slack app into a mesh of virtual agent identities, providing a transport and coordination layer where agents can be addressed by name and collaborate through durable, human-visible Slack threads.
README
A portable, open-source Slack transport and coordination layer for the agents you already run.
Slack Agent Mesh turns a single Slack app into a mesh of virtual agent identities
(@rook, @scout, …) that humans and agents can address by name. A deterministic
broker admits the right messages, invokes your existing agents through pluggable
adapters, routes agent-to-agent delegation, and keeps everything in one durable,
human-visible Slack thread — without you adopting any particular model SDK.
Status: release candidate. 146 automated tests pass (
npm test+npm run test:acceptance). The credential-free demo runs with no network access. A live Slack workspace test is NOT RUN in this tree — see Limitations.
Table of contents
- What it is / what it is not
- Architecture
- Five-minute demo (no Slack credentials)
- Real Slack setup
- Agent configuration examples
- MCP server and clients
- How admission and routing work
- Deployment
- Security
- Reliability
- Troubleshooting
- Buzz parity matrix
- Limitations and non-parity
- CLI reference
- Contributing
- Security policy
- License
- References
What it is / what it is not
It is:
- A transport and coordination layer. Slack is the durable, human-visible channel; your existing agents remain the brains.
- Model-agnostic. The routing and Slack code import no model-provider SDK.
Agents are reached over a
command(stdin/stdout) adapter, a signedwebhookadapter, or the built-inmockadapter for tests and the demo. - One app, many identities. A single Slack app hosts virtual agents
(
@rook,@scout) that the broker routes by name. - Loop-safe by construction. Persisted event dedupe, stable trace IDs, hop limits, per-trace message budgets, and a visited-agent set stop recursive storms deterministically.
- Socket Mode first. No public ingress server is required.
- MCP-native. An MCP server exposes mesh messaging, directory, and thread tools to any MCP-capable agent.
It is not:
- Not an agent framework or model runtime. It does not think, plan, or call an LLM. It carries prompts to your agents and their replies back to Slack.
- Not a Slack replacement or a chatbot builder. It is the wiring between Slack and agents you already operate.
- Not a hosted coordination SaaS. Beyond Slack and the agent endpoints you configure, there is no remote service or telemetry. Secrets stay in your environment. See Security.
- Not a claim of exact "Buzz" parity. See the parity matrix and limitations.
Architecture
┌──────────────────────────── Slack workspace ───────────────────────────┐
│ #incident thread │
human ── @rook … ─────▶│ ├─ @rook: Acknowledged. … @scout please pull logs. │
│ └─ @scout: Confirmed: bad DATABASE_URL. Recommend rollback. │
└───────────────▲───────────────────────────────────┬─────────────────────┘
│ one reply object per turn │ Socket Mode events
│ (chat.postMessage, │ (app_mention, message.*)
│ chat.update for progress) ▼
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ slack-agent-mesh (this repo) │
│ │
│ Slack transport ──▶ Ingress normalize ──▶ Router (admission) Store (node:sqlite) │
│ (@slack/bolt, drop bot/self/edited • dedupe event_id • events • traces │
│ Socket Mode) echoes; collect • resolve targets • visited set • outputs │
│ attachments • hop / budget / • threads • delegations │
│ visited / ttl │
│ │ │
│ ▼ │
│ Broker (serial per-trace queue) │
│ • post exactly one visible reply per accepted turn │
│ • scan TRUSTED agent output for @mentions │
│ • enqueue delegates DIRECTLY (never via Slack echo) │
│ │ ▲ │
│ Adapter registry │ mesh_send / mesh_reply / … │
│ ┌──────────────┬─────────┴────────┐ │ │
│ ▼ ▼ ▼ │ │
│ command webhook mock MCP server (stdio) ◀── MCP-capable agent │
│ (stdin/stdout) (signed HTTP) (tests/demo) │
└────────────┬───────────────┬───────────────────────────────────────────────────────────────────┘
▼ ▼
Hermes / Claude remote agent
Code / Codex / (any HTTP
any CLI endpoint)
Key properties that fall out of this shape:
- Directed admission. Ordinary channel chatter never wakes an agent. The
router admits a human turn only via an explicit app mention,
/mesh, a DM, a configured virtual-agent mention, or a reply in a thread the mesh already owns. - Broker-driven delegation. When an agent's reply mentions another agent, the broker enqueues that agent directly. Correctness never depends on Slack re-delivering the bot's own message.
- One turn → one reply object. Progress uses Slack update APIs, not extra messages.
Source layout: src/slack (transport + ingress + gateway), src/core
(router, mentions, ids, redact, types), src/broker (coordinator),
src/adapters (command/webhook/mock), src/store (SQLite persistence),
src/mcp (MCP server), src/demo (scripted demo), src/config (schema + loader).
Five-minute demo (no Slack credentials)
You need Node.js ≥ 22.13 and nothing else — no Slack app, no tokens, no network.
git clone https://github.com/nickvasilescu/slack-agent-mesh.git
cd slack-agent-mesh
npm ci
npm run demo -- --script examples/demo-conversation.json
Expected output (deterministic):
=== slack-agent-mesh demo: Incident triage: human -> @rook -> @scout ===
[human U_HUMAN] @rook production API is throwing 500s, please investigate
↳ *@rook*: Acknowledged. The 500s line up with the 15:42 deploy. @scout please pull the deploy and error logs for the last hour and confirm the cause.
↳ *@scout*: Confirmed: the 15:42 deploy shipped a bad DATABASE_URL. Errors started at 15:43. Recommend immediate rollback. No further delegation needed.
--- trace receipts ---
{"trace_id":"trc_demo_1","channel":"C_DEMO","hops":1,"messages":2,"max_hops":4,"max_messages":12,"visited":["rook","scout"],"status":"active"}
The demo drives the real broker, router, store, and mock adapter — the same code paths used in production, with Slack swapped for an in-memory gateway. The trace receipt shows the loop-prevention accounting (hops, message budget, visited agents) that also governs live traffic.
Want to exercise the MCP tool surface locally, still credential-free?
# Uses the mock gateway when no Slack tokens are present.
npm run mcp -- --config mesh.config.example.yaml
Real Slack setup
1. Create the app from the manifest
This repo ships a ready-to-use Socket Mode manifest at slack/manifest.json.
- Go to https://api.slack.com/apps → Create New App → From an app manifest.
- Pick your workspace, choose JSON, and paste the contents of
slack/manifest.json. - Create the app. The manifest already declares the bot scopes, the
/meshslash command, the event subscriptions, and enables Socket Mode and interactivity.
2. Tokens and scopes
You need two tokens. Never commit them. Copy .env.example to .env and
fill in real values (the shell in step 4 reads that file).
| Token | Starts with | Where to get it | Notes |
|---|---|---|---|
| Bot token | xoxb- |
OAuth & Permissions → Install to Workspace | Carries the bot scopes below. |
| App-level token | xapp- |
Basic Information → App-Level Tokens → add token with connections:write |
Required for Socket Mode. |
| Signing secret | — | Basic Information → App Credentials | Only needed if you run an HTTP receiver instead of Socket Mode. |
Bot scopes declared in the manifest (and echoed in .env.example):
app_mentions:read channels:history groups:history im:history mpim:history
chat:write chat:write.customize commands reactions:write
chat:write.customize lets each virtual agent post under its own display name
and icon. reactions:write powers the mesh_add_reaction tool. Prompts use
Slack's stable sender ID, so the app does not request profile-directory access.
cp .env.example .env
# edit .env:
# SLACK_BOT_TOKEN=xoxb-…
# SLACK_APP_TOKEN=xapp-…
Invite the app to the channels it should watch (/invite @agent-mesh).
3. Configure agents
Copy the example config and point each agent at your real brain:
cp mesh.config.example.yaml mesh.config.yaml
The config references environment-variable names, not secret values. A
literal xoxb-…, xapp-…, or signing secret in the file fails validation. See
Agent configuration examples below and the fully
annotated mesh.config.example.yaml.
Validate everything before you start:
npm run doctor -- --config mesh.config.yaml
doctor reports config validity, missing env vars, each adapter's resolved
settings, and the active loop limits. Example:
✓ config schema: valid, no inline secrets
✓ @rook command: ./bin/hermes-agent (timeout 60000ms, cap 65536B)
! @scout url env SCOUT_WEBHOOK_URL: not set (webhook agent will fail closed)
✓ loop limits: max_hops=4, max_messages=12, ttl=3600s
4. Run the broker
npm run serve -- --config mesh.config.yaml
The process opens a Socket Mode connection and stays alive. Address an agent from Slack:
@agent-meshapp mention naming an agent, e.g.@agent-mesh @rook look at the 500s/mesh @rook investigate the 500s- a DM to the app naming an agent
- a reply in a thread the mesh already owns (no re-mention needed)
Agent configuration examples
Every agent is one entry under agents: with a name, a description, and an
adapter. Two adapter kinds reach real agents: command (local process) and
webhook (remote HTTP). The mock adapter is for tests and the demo.
The command adapter passes non-secret turn context to the child as environment
variables: MESH_AGENT, MESH_TRACE_ID, MESH_HOP, MESH_CHANNEL,
MESH_THREAD_TS, MESH_FROM. The prompt arrives on stdin; the agent's
stdout is the reply. The child does not inherit your environment — only
env_allowlist + env_pass names are forwarded — so mesh secrets never leak into
an arbitrary command.
Hermes (command adapter)
- name: hermes
description: General operator agent. Handles ops, research, and delegation.
presence: available
adapter:
type: command
command: ["./examples/agents/hermes-agent.sh"]
timeout_ms: 120000
max_output_bytes: 131072
# Only these names are forwarded to the child process.
env_allowlist: ["PATH", "HOME", "LANG"]
env_pass: ["HERMES_PROFILE", "OP_SERVICE_ACCOUNT_TOKEN"]
The shipped wrapper reads the prompt from stdin and calls the real Hermes
non-interactive interface, hermes chat -Q --source tool -q. Set
HERMES_PROFILE when you want a non-default Hermes profile. If Hermes needs
secrets, expose only their environment-variable names via env_pass; never put
their values in mesh.config.yaml.
Claude Code (command adapter)
- name: claude
description: Claude Code. Reads a repo, writes patches, runs tests.
adapter:
type: command
command: ["claude", "-p", "--output-format", "text"]
timeout_ms: 300000
max_output_bytes: 262144
env_allowlist: ["PATH", "HOME", "LANG", "ANTHROPIC_API_KEY"]
claude -p (print mode) reads the prompt on stdin and writes the final message
to stdout — exactly the command-adapter contract. Raise timeout_ms for
long-running coding turns.
Codex (command adapter)
- name: codex
description: OpenAI Codex CLI. Implements and reviews code changes.
adapter:
type: command
command: ["codex", "exec", "--ephemeral", "--color", "never", "-"]
timeout_ms: 300000
max_output_bytes: 262144
env_allowlist: ["PATH", "HOME", "LANG", "OPENAI_API_KEY"]
Any CLI that reads a prompt from stdin and prints a response works the same way; adjust the argv to whatever your build expects for non-interactive execution.
Generic stdin/stdout command
The lowest common denominator is a script. See examples/agents/echo-agent.sh:
#!/usr/bin/env bash
set -euo pipefail
prompt="$(cat)" # prompt on stdin
# … call your model / tool here …
echo "[$MESH_AGENT] handled a ${#prompt}-char prompt on trace ${MESH_TRACE_ID}."
- name: rook
description: Incident commander. Triages production issues and delegates.
adapter:
type: command
command: ["./examples/agents/echo-agent.sh"]
timeout_ms: 60000
max_output_bytes: 65536
env_allowlist: ["PATH", "HOME", "LANG"]
If the process times out, exits non-zero, floods stdout past
max_output_bytes, or fails to spawn, the turn fails closed with a single
:warning: reply — never a hang or a partial post.
Webhook (remote) agent
For an agent that lives behind HTTP:
- name: scout
description: Log and metrics investigator. Runs read-only diagnostics.
presence: available
adapter:
type: webhook
url_env: SCOUT_WEBHOOK_URL # env NAME holding https://scout.internal/agent
secret_env: SCOUT_WEBHOOK_SECRET # env NAME holding the shared HMAC secret
timeout_ms: 30000
max_output_bytes: 65536
The mesh POSTs JSON and signs it (when secret_env is set) so the receiver can
authenticate the caller:
POST <SCOUT_WEBHOOK_URL>
content-type: application/json
x-mesh-timestamp: <unix-seconds>
x-mesh-signature: v0=<hex hmac-sha256 of "v0:<timestamp>:<body>">
{ "agent": "scout", "prompt": "…", "trace": { "traceId": "…", "hop": 1, "channel": "…", "threadTs": "…", "from": "…" } }
Your endpoint must reply 200 with JSON matching:
{ "text": "your agent's response", "trace_id": "optional-echo" }
Verify the signature on your side with the same construction the mesh uses
(WebhookAdapter.verify in src/adapters/webhook.ts is the reference). A non-200
status, a body over max_output_bytes, non-JSON, a schema mismatch, or a timeout
all fail the turn closed.
MCP server and clients
The MCP server exposes the mesh to any MCP-capable agent over stdio. Start it with:
npm run build
node dist/cli.js mcp --config mesh.config.yaml
Tools (every write tool accepts or derives an idempotency key; every tool returns structured JSON):
| Tool | Purpose |
|---|---|
mesh_list_agents |
List configured agents, adapter kind, and presence. |
mesh_send |
Post a new message as an agent identity, starting a trace. |
mesh_reply |
Reply into an existing thread as an agent identity. |
mesh_read_thread |
Read a thread, including which agent authored each output. |
mesh_get_trace |
Fetch a trace receipt: hops, budget usage, visited agents. |
mesh_set_presence |
Set an agent identity's presence string. |
mesh_add_reaction |
Add an emoji reaction to a message. |
Claude Code (.mcp.json or claude mcp add):
{
"mcpServers": {
"slack-agent-mesh": {
"command": "node",
"args": ["/abs/path/slack-agent-mesh/dist/cli.js", "mcp", "--config", "/abs/path/slack-agent-mesh/mesh.config.yaml"],
"env": { "SLACK_BOT_TOKEN": "xoxb-…", "SLACK_APP_TOKEN": "xapp-…" }
}
}
}
Codex (~/.codex/config.toml):
[mcp_servers.slack-agent-mesh]
command = "node"
args = ["/abs/path/slack-agent-mesh/dist/cli.js", "mcp", "--config", "/abs/path/slack-agent-mesh/mesh.config.yaml"]
Generic MCP client — any client that speaks stdio MCP launches the same
command: node /abs/path/slack-agent-mesh/dist/cli.js mcp --config <path>. Pass Slack tokens through
the client's env block (or omit them to run against the credential-free mock
gateway for local testing). More detail and copy-paste snippets live in
skills/slack-agent-mesh/references/mcp-clients.md.
How admission and routing work
A human turn is admitted only when it is directed at the mesh:
| Ingress | Admitted when |
|---|---|
App mention (@agent-mesh …) |
it names a configured agent |
/mesh @agent … |
it names a configured agent |
| Direct message | it names a configured agent |
| Channel message | it mentions a configured agent (plain chatter is ignored, not diagnosed) |
| Thread reply | the thread is one the mesh already owns |
Before any adapter runs, the router rejects — with one concise diagnostic at most, and never for ordinary chatter or duplicates:
- a duplicate Slack
event_id(persisted dedupe), - a mention inside fenced code, inline code, a block quote, a Slack attachment, or prior tool output (untrusted content never delegates),
- an agent mentioning itself,
- a revisit of an already-run agent in the same trace,
- a hop beyond
max_hops, - a trace beyond
max_messages, - an expired trace (older than
trace_ttl_seconds).
Each human turn starts a fresh trace (new hop/visited/budget) threaded into the same Slack thread, so a legitimate follow-up is never blocked by an earlier turn's visited set. Agent-to-agent delegation reuses the trace, accumulating hops, budget, and visited agents until a limit stops it.
Deployment
Docker. A multi-stage Dockerfile builds and runs the broker.
docker build -t slack-agent-mesh .
docker run --rm --env-file .env \
-v "$PWD/mesh.config.yaml:/app/mesh.config.yaml:ro" \
-v "$PWD/.mesh:/app/.mesh" \
slack-agent-mesh serve --config /app/mesh.config.yaml
docker-compose. See docker-compose.example.yaml for a service definition with the SQLite state volume mounted for persistence across restarts.
Bare process. npm ci && npm run build && node dist/cli.js serve --config mesh.config.yaml, supervised by systemd/pm2/your orchestrator. Socket Mode means
no inbound ports and no public ingress are required.
State. mesh.state_path (default .mesh/state.sqlite) holds event dedupe,
traces, the visited set, output receipts (including in-flight pending claims),
threads, and delegation edges. Mount it on a durable volume so dedupe and routing
survive restarts. This keeps restarts consistent but does not make delivery
exactly-once across a crash: a crash mid-post can leave a pending claim that needs
manual cleanup (see Reliability). Use :memory: only for
ephemeral/testing runs.
Security
The threat model is: untrusted humans and untrusted agent output share a Slack channel, and secrets must never leak.
- Secrets stay in the environment. Config references env-var names. The
loader (
assertNoInlineSecrets) rejects any config containing a literalxoxb-/xapp-/xoxp-token or a secret-bearing key holding a literal value..gitignoreexcludes.env*(except.env.example). - Least-privilege child processes. Command adapters start with an empty
environment; only the names in
env_allowlist+env_passare forwarded. The mesh's own secrets are never visible to an agent CLI unless you explicitly pass them. Child stderr is drained but never retained or copied into errors/logs. - Signed webhooks. Remote agents are called over HMAC-SHA256-signed HTTP
(
v0:<timestamp>:<body>), verifiable in constant time. - Untrusted-content separation. Mentions inside fenced code, inline code,
block quotes, Slack attachments/quoted text, or prior tool-output blocks are
not treated as delegations. The mesh never executes instructions merely
because quoted text contains
@agent. - Prompt-injection framing. When a message carries attachments/quoted content, the composed prompt explicitly marks it as untrusted data, not commands.
- Redacted logs. Slack token classes are redacted from logs and snapshots,
and complete prompts are never logged unless
mesh.debug: trueis set — and even then tokens stay redacted. - No hidden telemetry or extra coordination SaaS. The broker talks only to Slack and the agent endpoints/CLIs you configure; nothing else phones home.
Report vulnerabilities per SECURITY.md.
Reliability
- Deterministic loop prevention. Persisted
event_iddedupe, stable trace IDs,max_hops,max_messages, a visited-agent set, self-mention rejection, and trace TTL. A defense-in-depthreserveTurnguard re-checks every invariant atomically immediately before an adapter runs. - Idempotent output (concurrent- and retry-safe). Each visible post is keyed
by
(trace, agent, cause)and reserved as an atomic pending claim before the Slack call. A retried turn, a duplicated adapter completion, and two concurrent same-key injections therefore collapse to a single visible post; the losing caller sees the existing receipt or an honestoutput_pending, never a second message or a fabricated timestamp. - Bounded exactly-once, not crash-proof. The guarantee above suppresses
concurrent and in-process retries. It is not exactly-once across a process
crash. A crash after Slack accepts a post but before the local claim is
finalized leaves a pending claim behind: the message was delivered, no receipt
was written, and a later same-key retry is refused as
output_pendinguntil the stale row is cleared. Recovery is manual — delete thependingrow for thatoutput_keyin the SQLiteoutputstable (operator/SQLite cleanup); the mesh does not auto-reap it. - One reply object per accepted turn. Progress uses Slack update APIs.
- Fail-closed adapters. Timeouts, non-zero exits, oversized output, spawn failures, malformed webhook responses, and HTTP errors produce a single diagnostic, never a hang or partial state.
- Serial per-trace drain. The broker processes a trace's turns on a serial queue, so ordering and budget accounting are stable and awaitable.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
doctor says env not set |
Export SLACK_BOT_TOKEN / SLACK_APP_TOKEN (and any adapter env vars). serve needs both Slack tokens. |
| Config fails validation with "inline secret" | You pasted a real token into mesh.config.yaml. Use an env-var name and put the value in .env. |
| Agent never responds to a channel message | Plain channel chatter is ignored by design. Use an app mention, /mesh, a DM, or reply in an owned thread. Confirm the app is invited to the channel. |
| Agent named but nothing happens | Run doctor; the agent name must match agents[].name (lowercase, -/_). Check the adapter command/URL resolves. |
:warning: could not complete this turn (timeout) |
Raise timeout_ms; confirm the child actually reads stdin and writes stdout. |
:warning: … (output_cap) |
Your agent printed more than max_output_bytes. Raise the cap or trim the reply. |
| Webhook agent fails closed | Endpoint must return 200 with { "text": "…" } JSON under the cap. Verify the HMAC signature and x-mesh-timestamp. |
| Duplicate replies after a restart | Ensure state_path is on a durable volume; :memory: loses the dedupe/output store. |
Agent inject keeps returning output_pending |
A crash left a stale pending claim (post likely delivered, receipt never written). Delete the pending row for that output_key in the SQLite outputs table. See Reliability. |
| Socket Mode won't connect | The app-level token needs connections:write; Socket Mode must be enabled (it is, in the shipped manifest). |
Turn on mesh.debug: true for verbose (still token-redacted) logs while
diagnosing.
Buzz parity matrix
This project is inspired by Block's Buzz, an open-source workspace where people and agents are first-class members backed by a Nostr relay and signed event log. Slack Agent Mesh recreates the narrow agent-routing experience on Slack; it does not reproduce Buzz's protocol, identity model, forge, workflows, or complete collaboration surface.
| Capability | Buzz | Slack Agent Mesh | Notes |
|---|---|---|---|
| Distinct first-class agent identities | ✅ | ⚠️ | One Slack app renders virtual identities via chat:write.customize; agents do not get independent Slack accounts or cryptographic keys. |
Address agents by @name |
✅ | ✅ | App mention, /mesh, DM, virtual mention, owned thread. Virtual names are broker syntax, not native Slack user mentions. |
| Directed admission (no chatter) | ✅ | ✅ | Router admits only directed turns. |
| Agent-to-agent delegation | ✅ | ✅ | Broker enqueues delegates directly, not via Slack echo. |
| Deterministic loop prevention | ✅ | ✅ | Dedupe, trace IDs, hop/budget/visited/TTL. |
| Untrusted-content separation | ✅ | ✅ | Code/quote/attachment mentions do not delegate. |
| One reply object per turn | ✅ | ✅ | Progress via update APIs. |
| Thread continuity | ✅ | ✅ | Downstream turns stay in the triggering thread. |
| Idempotent output | ✅ | ⚠️ | Concurrent and in-process retries collapse by (trace, agent, cause); exactly-once across a process crash is not guaranteed. |
| Model-agnostic agent transport | ✅ | ✅ | command / webhook / mock adapters. |
| MCP tool surface | ✅ | ✅ | This repo's seven MCP tools cover mesh messaging, directory, traces, presence, and reactions, not Buzz's full surface. |
| Socket Mode (no ingress server) | ➖ | ✅ | Default Slack transport. |
| Reactions / presence tools | ✅ | ⚠️ | mesh_add_reaction, mesh_set_presence implemented; presence is a directory string, not native Slack presence. |
| Cryptographic identity + signed event log | ✅ | ❌ | Slack authentication and audit facilities replace Nostr keys, but are not portable or protocol-equivalent. |
| Unified search and tamper-evident audit chain | ✅ | ❌ | Slack retains message history; this broker stores routing receipts only. |
| YAML workflows and approval gates | ✅ | ❌ | Not implemented. Use Slack Workflow Builder or an external orchestrator. |
| Git forge, patches, CI/review events | ✅ | ❌ | Not implemented. Existing GitHub/Slack integrations continue to work independently. |
| Canvases, huddles, media, file operations | ✅ | ❌ | Text routing only; inbound attachment text is treated as untrusted context. |
| Rich interactive components | ✅ | ❌ | No Block Kit buttons or modals; replies are Slack mrkdwn text. |
| Live-workspace end-to-end verification | ✅ | ⚠️ | NOT RUN in this tree (no disposable workspace credentials). See below. |
Legend: ✅ implemented · ⚠️ partial/with caveats · ➖ not applicable · ❌ not implemented.
Limitations and non-parity
- No live Slack test in this tree. All 146 automated tests and the demo pass without network access, but an end-to-end run against a real workspace is NOT RUN here because no disposable workspace credentials were available. Treat live behavior as verified-by-construction (unit + acceptance) until you run it in your own workspace.
- No exact Buzz parity claim. The matrix above is a best-effort mapping, not a certified equivalence.
- Virtual, not sovereign identities. The default one-app topology cannot provide Buzz's per-agent cryptographic identity, portable history, or signed events. Slack and workspace administrators remain the trust boundary.
- Text replies only. No Block Kit, buttons, modals, or interactive
components.
interactivityis enabled in the manifest to leave room for future work, but the broker posts Markdown text today. - No file re-upload. Inbound attachment text is surfaced to agents as untrusted context; binary files are not fetched or re-posted.
- Presence is advisory.
presenceis a directory string, not Slack's native presence/status system. - Single-workspace focus.
org_deploy_enabledisfalsein the manifest; org-wide/Enterprise Grid distribution is untested. - Socket Mode assumed by default. The HTTP receiver path (signing secret) exists conceptually but Socket Mode is the supported, tested transport.
CLI reference
slack-agent-mesh serve [--config <path>] Start the Socket Mode broker (needs Slack creds)
slack-agent-mesh mcp [--config <path>] Run the MCP server over stdio
slack-agent-mesh doctor [--config <path>] Validate config and environment
slack-agent-mesh demo --script <path> Run the credential-free scripted demo
Options: --config <path> (default mesh.config.yaml), --script <path>
(default examples/demo-conversation.json), -h/--help.
Release-gate commands (all green on this tree):
npm ci
npm run lint
npm run typecheck
npm test # 119 unit tests
npm run build
npm run test:acceptance # 27 acceptance tests
npm run demo -- --script examples/demo-conversation.json
Contributing
See CONTRIBUTING.md. In short: Node ≥ 22, npm, strict TypeScript, Biome for lint/format, Vitest for tests. Run the release-gate commands above before opening a PR, and never commit tokens.
Agent onboarding (Hermes, Claude Code, Codex, generic MCP clients) lives in skills/slack-agent-mesh/ and AGENTS.md.
Security policy
See SECURITY.md for how to report a vulnerability and the supported-version policy.
License
MIT. No hidden telemetry, no remote SaaS dependency.
References
Official Slack documentation:
Slack API home — https://api.slack.com/
Socket Mode — https://api.slack.com/apis/socket-mode
App manifests — https://api.slack.com/reference/manifests
Events API — https://api.slack.com/apis/events-api
OAuth scopes — https://api.slack.com/scopes
Token types — https://api.slack.com/authentication/token-types
Model Context Protocol — https://modelcontextprotocol.io/
Buzz source — https://github.com/block/buzz
Block announcement — https://block.xyz/inside/introducing-buzz-where-humans-and-agents-work-together
This project does not vendor or depend on Buzz code.
Installing Slack Agent Mesh
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/nickvasilescu/slack-agent-meshFAQ
Is Slack Agent Mesh MCP free?
Yes, Slack Agent Mesh MCP is free — one-click install via Unyly at no cost.
Does Slack Agent Mesh need an API key?
No, Slack Agent Mesh runs without API keys or environment variables.
Is Slack Agent Mesh hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Slack Agent Mesh in Claude Desktop, Claude Code or Cursor?
Open Slack Agent Mesh on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Gmail
Read, send and search emails from Claude
by GoogleSlack
Send, search and summarize Slack messages
by SlackRunbear
No-code MCP client for team chat platforms, such as Slack, Microsoft Teams, and Discord.
Discord Server
A community discord server dedicated to MCP by [Frank Fiegel](https://github.com/punkpeye)
Compare Slack Agent Mesh with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All communication MCPs
