Déjà MCP Server
Provides decision-governance tools for Slack workspace, allowing agents to check proposals against standing decisions and recall past decisions.
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., "@Déjà MCP ServerCheck decision: migrate job queue to Temporal"
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.
Déjà — the decision-governance layer for your Slack workspace
Slack is filling up with agents. None of them know what your team already decided. Déjà does — it watches them, and now they can ask.
Most AI guardrails make you write the rules. Déjà reads them from what your team already decided.
When a decision, claim, or proposal comes up in a channel — from a human or an agent — Déjà checks it against the team's standing decisions and, only when it conflicts, drops a sourced guardrail:
⚠️ Conflicts with a standing decision · #eng "Opening a PR to migrate the job queue to Temporal." — the team rolled this back on Apr 23 (@maya): "duplicate task execution under a network partition… sticking with Redis." · 🔗 source
Two consumers, one engine:
Ambient (Mode B) — Déjà reads every message, human and agent, and brakes conflicts. No opt-in needed; you don't grant permission, you're watched. ALLOW stays silent — the channel stays clean.
MCP (collaborative) — any agent (or Slackbot) calls
check_decision(proposal)→ALLOW | CONFLICTS | INCONCLUSIVE, always sourced. Any agent in Slack can adopt this in five lines.
Slack Agent Builder Challenge · New Slack Agent track. Required technologies: RTS (permission-
aware assistant.search.context) + MCP (two tools — recall_memory + check_decision), plus
agent-to-agent governance and ambient agent watching. The LLM trigger runs on a Claude Max
subscription — no paid API key. · powered by Legibright
Quick start
pip install -e ".[test]"
cp .env.sample .env # SLACK_USER_TOKEN (xoxp) + CLAUDE_CODE_OAUTH_TOKEN (`claude setup-token`)
slack run # the Slack app (Socket Mode): auto-trigger + memory cards
python -m deja.mcp_server # the MCP server (stdio) for external agents
python scripts/verify_all.py # the cross-phase gate — one green table (below)Related MCP server: tidbits-memory
How it was built (the phase story)
Phase | What shipped | Gate proof in |
1 · Skeleton | Bolt app boots, listeners wired |
|
2 · Recall (RTS) | Forgotten thread resurfaces, deterministic |
|
3 · Judge→Recall→Reply | LLM trigger (Max subscription), end-to-end |
|
4 · Block Kit card | Interactive card + App Home + privacy |
|
5 · MCP |
|
|
6 · Seed | Realistic multi-author workspace + decision arcs |
|
6 · Decision arc | Timeline + standing decision + owner + INCONCLUSIVE + save→Canvas |
|
v2 · Governance |
|
|
7 · Docs | Architecture · submission · demo · review | — |
One command proves it all: python scripts/verify_all.py → a phase-by-phase ✅ table
(--no-live for the hermetic subset in CI). See docs/architecture.md ·
docs/SUBMISSION.md · docs/DEMO.md ·
docs/PHASE-REVIEW.md · docs/HARDENING.md.
Does the arc beat search? (benchmark)
Measured on the exact live pipeline (judge(sentence) → recall_arc). On a held-out set we
never tuned on, single-hit search surfaces the standing decision 1/6 times and drifts onto an
unrelated decision 1/4 times. Déjà → 4/6 recurring · 3/5 single, never invents one (0/4).
(Dev set: 6/6 recurring, 7/7 single, 0 false decisions.)
We surface this, we don't hide it: Slack's Real-Time Search is rate-limited to ~1 call every few minutes (measured
Retry-After: 288s), so a 100+-query live benchmark isn't possible. The benchmark runs the real engine including the LLM judge (cached) through a reproducible RTS-free mirror, calibrated to live — sentences that fail live route through the same code here and were verified to match. Held-out recurring is 4/6, not higher, because the live card path is lexical-only (no LLM in the hot path): the semantic-gap cases ('observability stack' → the Datadog decision) need the LLM expansion, which is available but off live for speed. Honest cost, not a hidden failure. Method + limits:docs/BENCHMARK.md·python benchmarks/run.py --md.
Robustness — silence is cheap, a confident wrong answer is fatal
benchmarks/adversarial.py runs the live pipeline over 83 hostile queries (paraphrases,
never-discussed topics, lexical traps, nonsense, typos, multi-topic, other languages,
false-premise provocations) and splits the result honestly: correct 49 · MISS 2 · correct-silent
32 · CONFIDENT-WRONG 0 → recall 96%, zero confident-wrong. It runs against a permissive mirror
(a superset of live search), so a trap like "did we decide to buy a boat?" surfaces the "BUYING
auth" thread and the grounding gate must reject it: a decision shows only if one of the query's
distinctive subject words is in the retrieved threads — a shared action verb (buy · migrate · drop ·
launch) is not a topic match. See docs/ROBUSTNESS.md.
Does the brake fire on the right proposal? (governance benchmark)
benchmarks/governance.py runs 27 labelled proposals through the exact live verdict
(judge → check_decision) — genuine conflicts, aligned proposals, never-discussed topics, lexical
traps, and discussed-but-undecided. Run once, no tuning: false CONFLICTS 0 · sourceless verdict 0 ·
owner attribution 11/11 · precision 100% / recall 62%. The 3 missed brakes are one honest class
(a positive adoption naming its rejected alternative without a rejection cue) — we would rather miss
a brake than raise a false one. Full method + the missed cases: docs/GOVERNANCE.md.
External validation on 8 real OSS decision histories: docs/EXTERNAL.md.
The governance contract — any agent can ask before it acts
Déjà's MCP server exposes two tools. The second is the interface contract: any agent in Slack can adopt this in five lines — call it before a consequential action and honour the verdict.
verdict = await mcp.call("check_decision", {"proposal": "Migrate the job queue to Temporal"})
if verdict["verdict"] == "CONFLICTS":
# the team already rolled this back — stop and cite verdict["sources"]
raise Halt(verdict["standing_decision"], by=verdict["owner"], at=verdict["decided_at"])
# ALLOW / INCONCLUSIVE → proceed (INCONCLUSIVE = discussed, never decided — Déjà won't invent one)check_decision(proposal) → {verdict: ALLOW|CONFLICTS|INCONCLUSIVE, standing_decision, owner, decided_at, times_discussed, sources: [permalink, …], rationale}. The verdict runs
the same engine as the ambient guardrail (judge → recall_arc → grounding gate). A CONFLICTS with
no sources downgrades to INCONCLUSIVE — a fabricated brake is worse than none. Measured:
docs/GOVERNANCE.md — false-conflicts 0, sourceless 0, owner 11/11.
recall_memory(query, channel=None, limit=3) → {summary, memories:[{source_message, what_happened_next, channel, author, ts, permalink, score}], searched} is unchanged and still there
for pure lookup. Both run on the installer's user token, so they only ever reach the channels the
installing account can access (not per-caller — see Honest limits). Verify end-to-end with python scripts/mcp_smoke.py.
python -m deja.mcp_server # stdio (Cursor/Claude Desktop); DEJA_MCP_TRANSPORT=streamable-http for remote{ "mcpServers": { "deja": {
"command": ".venv/bin/python", "args": ["-m", "deja.mcp_server"],
"cwd": "/absolute/path/to/slackhack"
} } }Agents on trial — the brake, live (Mode B, no cooperation needed)
Déjà also watches the channel. A separate demo app, the Planner Bot (planner_bot/), posts action
proposals with no awareness of Déjà — Déjà catches the conflicting one and drops a sourced card,
stays silent on the aligned one, and refuses to invent a verdict on the undecided one. Governance
without the agent's opt-in. See planner_bot/README.md.
Layout
deja/ — the engine (recall/RTS · trigger/LLM · thread enrichment · card · store ·
govern/the verdict · mcp_server/two tools) · listeners/ — Slack events (incl. the ambient
watcher)/actions/views · planner_bot/ — the demo agent Déjà puts on trial · scripts/ — seed +
verify + smoke · benchmarks/ · tests/ · docs/. The Bolt starter-template README this was
scaffolded from is preserved below.
Scaffolded from Slack's Bolt for Python starter template (MIT). The recall engine, decision arc, governance layer, MCP tools, benchmarks, and everything above are Déjà's own.
Starter Agent for Slack (Bolt for Python and Claude Agent SDK)
A minimal starter template for building AI-powered Slack agents with Bolt for Python and the Claude Agent SDK using models from Anthropic. Works with the Slack MCP Server to search messages, read channels, send messages, and manage canvases — all from within your agent.
App Overview
The starter agent interacts with users through four entry points:
App Home — Displays a welcome message with instructions on how to interact.
Direct Messages — Users message the agent directly. It responds in-thread, maintaining context across follow-ups.
Channel @mentions — Mention the agent in any channel to get a response without leaving the conversation.
Assistant Panel — Users click Add Agent in Slack, select the agent, and pick from suggested prompts or type a message.
The template also includes one example tool (emoji reactions). Add your own tools to customize it for your use case.
Slack MCP Server
When connected to the Slack MCP Server, the agent can search messages and files, read channel history and threads, send and schedule messages, and create and update canvases. When deployed with OAuth (HTTP mode), the agent automatically connects to the Slack MCP Server using the user's token.
Setup
Before getting started, make sure you have a development workspace where you have permissions to install apps.
Developer Program
Join the Slack Developer Program for exclusive access to sandbox environments for building and testing your apps, tooling, and resources created to help you build and grow.
Create the Slack app
Install the latest version of the Slack CLI for your operating system:
You'll also need to log in if this is your first time using the Slack CLI.
slack loginInitializing the project
slack create my-starter-agent --template slack-samples/bolt-python-starter-agent --subdir claude-agent-sdk
cd my-starter-agentCreate Your Slack App
Open https://api.slack.com/apps/new and choose "From an app manifest"
Choose the workspace you want to install the application to
Copy the contents of manifest.json into the text box that says
*Paste your manifest code here*(within the JSON tab) and click NextReview the configuration and click Create
Click Install to Workspace and Allow on the screen that follows. You'll then be redirected to the App Configuration dashboard.
Environment Variables
Before you can run the app, you'll need to store some environment variables.
Rename
.env.sampleto.env.Open your apps setting page from this list, click OAuth & Permissions in the left hand menu, then copy the Bot User OAuth Token into your
.envfile underSLACK_BOT_TOKEN.
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKENClick Basic Information from the left hand menu and follow the steps in the App-Level Tokens section to create an app-level token with the
connections:writescope. Copy that token into your.envasSLACK_APP_TOKEN.
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKENInitializing the project
git clone https://github.com/slack-samples/bolt-python-starter-agent.git my-starter-agent
cd my-starter-agentSetup your python virtual environment
python3 -m venv .venv
source .venv/bin/activate # for Windows OS, .\.venv\Scripts\Activate instead should workInstall dependencies
pip install -r requirements.txtProviders
Anthropic Setup
This app uses Claude through the Claude Agent SDK.
Create an API key from your Anthropic dashboard.
Rename
.env.sampleto.env.Save the Anthropic API key to
.env:
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEYDevelopment
Starting the app
Slack CLI
slack runTerminal
python3 app.pyOAuth HTTP Server
This mode uses an HTTP server instead of Socket Mode, which is required for OAuth-based distribution.
Install ngrok and start a tunnel:
ngrok http 3000Copy the
https://*.ngrok-free.appURL from the ngrok output.
Slack CLI
Update
manifest.jsonfor HTTP mode:Set
socket_mode_enabledtofalseReplace
ngrok-free.appwith your ngrok domain (e.g.YOUR_NGROK_SUBDOMAIN.ngrok-free.app)
Create a new local dev app:
slack install -E local(Slack CLI < v4.1.0 only) Enable MCP for your app:
Run
slack app settingsto open your app's settingsNavigate to Agents & AI Apps in the left-side navigation
Toggle Model Context Protocol on
Update your
.envOAuth environment variables:Run
slack app settingsto open App SettingsCopy Client ID, Client Secret, and Signing Secret
Update
SLACK_REDIRECT_URIin.envwith your ngrok domain
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirectStart the app:
slack run app_oauth.pyClick the install URL printed in the terminal to install the app to your workspace via OAuth.
Terminal
Create your Slack app at api.slack.com/apps/new using
manifest.json. Before pasting the manifest, setsocket_mode_enabledtofalseand replacengrok-free.appwith your ngrok domain.Install the app to your workspace and copy the following values into your
.env:Signing Secret — from Basic Information
Bot User OAuth Token — from OAuth & Permissions
Client ID and Client Secret — from Basic Information
SLACK_BOT_TOKEN=xoxb-YOUR_BOT_TOKEN
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirectReplace your-subdomain in SLACK_REDIRECT_URI with your ngrok subdomain.
Start the app:
python3 app_oauth.pyClick the install URL printed in the terminal to install the app to your workspace via OAuth.
Note: Each time ngrok restarts, it generates a new URL. You'll need to update the ngrok domain in
manifest.json,SLACK_REDIRECT_URIin your.env, and re-install the app.
Using the App
Once the agent is running, there are several ways to interact:
App Home — Open the agent in Slack and click the Home tab. You'll see a welcome message with instructions on how to interact.
Direct Messages — Open a DM with the agent. You'll see suggested prompts like Write a Message, Summarize, and Brainstorm — pick one or type your own message. The agent replies in a thread. Send follow-up messages in the same thread and the agent will maintain the full conversation context.
Channel @mentions — Invite the agent to a channel by typing /invite @agent-name in the message box, then @mention it followed by your message. The agent responds in a thread so the channel stays clean.
Assistant Panel — Click Add Agent in the top-right corner of Slack, select the agent from the list, then pick a suggested prompt or type a message.
Linting
# Run ruff check from root directory for linting
ruff check
# Run ruff format from root directory for code formatting
ruff formatProject Structure
manifest.json
manifest.json is a configuration for Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app.
app.py
app.py is the entry point for the application and is the file you'll run to start the server. This project uses AsyncApp from Bolt for Python, with all handlers running asynchronously.
app_oauth.py
app_oauth.py is an alternative entry point that runs the app in HTTP mode instead of Socket Mode. This is intended for deployments that use OAuth for app distribution. See the HTTP Mode section under Development for setup instructions.
/listeners
Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used.
/listeners/events — Handles incoming events:
app_home_opened.py— Publishes the App Home view with a welcome message and MCP status.app_mentioned.py— Responds to @mentions in channels.message.py— Responds to direct messages from users.
/listeners/actions — Handles interactive components:
feedback_buttons.py— Handles thumbs up/down feedback on agent responses.
/listeners/views — Builds Block Kit views:
app_home_builder.py— Constructs the App Home Block Kit view.feedback_builder.py— Creates the feedback button block attached to responses.
/agent
The agent.py file configures the Claude Agent SDK with a system prompt, tools registered via an MCP server, and a run_agent() async function that handles sending queries and collecting responses.
The deps.py file defines the AgentDeps dataclass passed to the agent at runtime, providing access to the Slack client and conversation context.
The tools directory contains one example tool (emoji reaction) defined using the @tool decorator from the Claude Agent SDK.
/thread_context
The store.py file implements a thread-safe in-memory session ID store, keyed by channel and thread. The Claude Agent SDK manages conversation history server-side via sessions, so only session IDs need to be tracked locally for resuming conversations.
Troubleshooting
MCP Server connection error: HTTP error 400 (Bad Request)
If you see an error like:
Failed to connect to MCP server 'streamable_http: https://mcp.slack.com/mcp': HTTP error 400 (Bad Request)This means the Slack MCP feature has not been enabled for your app. There is no manifest property for this yet, so it must be toggled on manually:
Run
slack app settingsto open your app's settings page (or visit api.slack.com/apps and select your app)Navigate to Agents & AI Apps in the left-side navigation
Toggle Slack Model Context Protocol on
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.
Latest Blog Posts
- 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/bogacsmz/deja'
If you have feedback or need assistance with the MCP directory API, please join our Discord server