Skip to main content
Glama
Beever Atlas

Beever Atlas

Official
by Beever-AI

Beever Atlas pulls the conversations your team already has on Slack, Discord, Microsoft Teams, and Mattermost, extracts atomic facts, deduplicates them, and clusters them into topic pages with citations. A graph store links the people, decisions, and projects mentioned across channels. Ask questions in natural language and get answers cited back to the source messages — through the dashboard, or through MCP into Claude Code and Cursor.

If you want a knowledge base that grows on its own from the chats your team already has, this is it.


✨ Features in action

Six short clips — connect a workspace, sync history, watch memory build, browse the auto-generated wiki, ask questions, plug external AI agents in via MCP.


Related MCP server: Mnela MCP Server

🏗️ Architecture

Conversations from any supported platform flow into a unified ingestion pipeline that produces two complementary memory systems — a 3-tier semantic store (channel / topic / atomic fact) for fast hybrid search, and a graph store that extracts entities and their relationships. Those memories fuel two consumer surfaces: the LLM Wiki (distilled, auto-maintained) and QA Agents (served through the dashboard directly, or through MCP into Claude Code / Cursor).

Under the hood, three services (backend, bot, frontend) are backed by four data stores (Weaviate, Neo4j, MongoDB, Redis). See the architecture overview on the documentation site for the full design — component responsibilities, dual-memory internals, and the smart query router.


💡 Why Wiki-First RAG?

Most RAG systems answer questions by retrieving raw message snippets and feeding them straight to an LLM. Beever Atlas takes a different approach: it continuously distils conversations into a structured, auto-maintained wiki — with topic pages, entity graphs, decisions, and citations — before any query is issued. When you ask a question, the retrieval layer works against clean, deduplicated knowledge rather than noisy chat history. This means answers are more consistent, citations are traceable to source messages, and the wiki itself becomes a useful artifact your team can browse independently of the Q&A interface. The dual-memory architecture (semantic + graph) lets the query router pick the right retrieval strategy per question, keeping latency low and context precise.

The inspiration: LLMs read wikis, not chat logs

The per-channel wiki concept is directly inspired by Andrej Karpathy's observation that LLMs are far better at reasoning over curated, encyclopedic content (books, docs, wikis) than over raw conversational transcripts. Chat history is noisy, redundant, temporally scattered, and full of implicit context that only humans resolve. A wiki, by contrast, is the already-distilled form of that knowledge — deduplicated, structured, citation-bearing, and organised by topic rather than by timestamp.

Beever Atlas operationalises this insight: every synced channel gets its own auto-generated, continuously-updated wiki — sections for topics, entities, decisions, open questions, and timelines — rebuilt incrementally as new messages arrive. The QA agent retrieves against this wiki first, falling back to raw messages only when a fact hasn't been distilled yet.

What this unlocks in practice

  • Better answers, fewer hallucinations — retrieval operates on fact-dense prose with explicit entity relationships, not on fragmented turn-by-turn chat.

  • Traceable citations — every wiki claim links back to the source messages that produced it, so answers are auditable all the way down to the original Slack/Discord/Teams thread.

  • A browsable artifact, not just a Q&A box — the wiki is useful on its own. New teammates onboarding to a channel can read the distilled wiki instead of scrolling three months of history.

  • Cheaper inference at query time — the expensive distillation work happens once, at ingestion. Queries hit compact, pre-digested context instead of re-summarising raw logs on every request.

  • Graph-aware reasoning — the entity graph built alongside the wiki lets the query router answer relational questions ("who worked on X with Y?") that pure vector RAG struggles with.

For a detailed comparison with other LLM knowledge tools, see the comparison page on the documentation site.


🚀 Quick Start

Beever Atlas ships as a Docker Compose stack (backend + bot + web + 4 datastores). You can try a seeded demo in 30 seconds with zero keys, then pick one of three deployment options to install it for real.

1. Get the code

git clone https://github.com/beever-ai/beever-atlas.git
cd beever-atlas

2. Try the demo first (optional, no keys needed for seeding)

make demo

make demo brings up the full stack pre-loaded with a public Wikipedia corpus (Ada Lovelace + Python history). Seeding uses pre-computed fixtures — no API keys required. Asking questions via /api/ask needs a free-tier GOOGLE_API_KEY because the QA agent calls Gemini. See demo/README.md for curl examples.

Skip this step if you're ready to install for real.

3. Before you start: get your API keys

Two free keys are required before installing. Both offer generous free tiers — enough to sync a small team's channels for testing.

Key

Purpose

Where to get it

GOOGLE_API_KEY

Gemini — extraction, entity graph, answers

aistudio.google.com/apikey

JINA_API_KEY

Jina v4 embeddings (2048-dim) for semantic search

jina.ai/api-dashboard

Optional (skip unless you know you need them):

Key

What it enables

TAVILY_API_KEY

External web search when QA retrieval confidence is low — tavily.com

OLOSTEP_API_KEY

Olostep web search (alternative to Tavily). Set WEB_SEARCH_PROVIDER=olostep in your .env — olostep.com/dashboard

Slack / Discord / Teams bot tokens

Configured via the web UI after setup, not .env — the bot stores platform credentials encrypted in MongoDB

Tip: Keep the two required keys handy before you start. Option 1 prompts for them interactively; Options 2 and 3 need them pasted into .env.

4. Choose a deployment option

Option

When to use

Time to "up"

1. One-line install (recommended)

You want the fastest path to a running stack.

~2 min first run

2. Manual Docker

CI/CD, ops environments, or when you want explicit control over every step.

~3 min first run

3. Local development

Active contributors who need hot-reload on backend and frontend.

varies

./atlas

The atlas installer walks you through a guided 5-step checklist:

  1. Embedding model — pick a provider (Jina / OpenAI / Cohere / Voyage / Gemini / Mistral / Ollama), then its API key.

  2. Agent LLM provider — pick a provider for the 16 ADK agents (Google Gemini / OpenAI / Anthropic / Mistral / DeepSeek / Groq / MiniMax / Ollama / Custom); optional second provider for hybrid setups.

  3. Graph backend — Neo4j (default) or skip.

  4. Optional integrations — Tavily web search, MCP server for Claude Code / Cursor.

  5. Auth tokens — keep dev defaults or rotate now.

Under the hood it verifies docker + docker compose, copies .env.example → .env (preserves your values on re-run, chmod 600), auto-generates CREDENTIAL_MASTER_KEY (64 hex) and WEAVIATE_API_KEY (32 hex), runs a port-conflict preflight, launches the stack via docker compose up -d --build --force-recreate --remove-orphans, and polls /api/health before printing the ready card.

When you see "Beever Atlas is ready", open http://localhost:3000 — then Settings → AI Setup to manage providers, assign LLMs per-agent, run Test Connection, or discover models. For CI / Docker / GitOps, configure declaratively: BEEVER_LLM_API_KEY=... (single-provider shortcut), BEEVER_ENDPOINTS='[...]' + BEEVER_PRESET=..., or commit an atlas.yaml and run atlas apply — see docs/runbooks/ai-setup.md and docs/runbooks/atlas-yaml.md.

For CI or unattended installs — skip prompts, pre-seed keys from shell env:

GOOGLE_API_KEY=... JINA_API_KEY=... ./atlas --non-interactive

Re-running ./atlas on an existing stack is idempotent.

Option 2 — Manual Docker

Full control, step-by-step.

cp .env.example .env

Open .env and fill in the two required keys:

GOOGLE_API_KEY=your_gemini_key
JINA_API_KEY=your_jina_key

Generate two required secrets and paste them into .env:

# CREDENTIAL_MASTER_KEY — AES-256-GCM key for stored platform credentials (64 hex chars)
python -c "import secrets; print(secrets.token_hex(32))"

# WEAVIATE_API_KEY — auth between backend and Weaviate (required by docker-compose)
python -c "import secrets; print(secrets.token_hex(16))"

Launch:

docker compose up -d --build

Open http://localhost:3000.

Services started:

Service

Port

Description

Web (nginx)

:3000

React dashboard

Backend

:8000

FastAPI + ADK agents

Bot

:3001

Platform bridge (Slack / Discord / Teams)

Weaviate

:8080

Semantic memory

Neo4j

:7474 / :7687

Graph memory

MongoDB

:27017

State + wiki cache

Redis

:6380

Sessions (internal :6379)

First run takes 2–3 minutes while images build and databases initialize. Subsequent runs start in seconds.

Option 3 — Local development

Databases in Docker, app services native for hot-reload.

Prerequisites: Python 3.12+ with uv, Node.js 20+

cp .env.example .env
# Fill in GOOGLE_API_KEY, JINA_API_KEY, CREDENTIAL_MASTER_KEY, WEAVIATE_API_KEY (same as Option 2)

# Start just the databases
docker compose up -d weaviate neo4j mongodb redis

# Backend (terminal 1)
uv sync
uv run uvicorn beever_atlas.server.app:app --reload --port 8000

# Bot (terminal 2)
cd bot && npm install && npm run dev

# Web (terminal 3) — Vite dev server with HMR
cd web && npm install && npm run dev

Open http://localhost:5173 (the Vite dev port — not :3000).

The Vite dev server proxies /api/* to http://localhost:8000 (configured via VITE_API_URL).

Before going to production

.env.example defaults are tuned for local testing. Before any real deploy, rotate the secrets that ship with placeholder values and flip the environment flag:

What to change

Why

How

BEEVER_API_KEYS, BEEVER_ADMIN_TOKEN

Ship as dev-key-change-me / dev-admin-change-me — public placeholders

python -c "import secrets; print(secrets.token_hex(24))" per token

BRIDGE_API_KEY

Shared secret between backend and bot; blank by default, required outside local dev

Same secrets.token_hex(24)

VITE_BEEVER_API_KEY, VITE_BEEVER_ADMIN_TOKEN

Vite bakes these into the web bundle at build time — must mirror the rotated backend values above

Copy the rotated BEEVER_API_KEYS / BEEVER_ADMIN_TOKEN values

NEO4J_PASSWORD + password half of NEO4J_AUTH

Dev password is public in this repo

Pick a strong password; both values must match

BEEVER_ENV=production

Enables fail-fast startup that rejects every dev default above

Flip the value in .env

Option 1 (./atlas) handles all of this through the "Rotate auth tokens" prompt in step 4 of the checklist — answer Y and the installer generates random tokens and mirrors the VITE_* values for you. If you used Option 2 or 3, you can re-run ./atlas on the existing .env, skip every other prompt with Enter, and only accept the rotation prompt.

5. Open the dashboard

Navigate to the URL for your chosen option:

From there:

  • Real mode (default, ADAPTER_MOCK=false): connect a workspace in Settings → Connections — Slack / Discord / Teams tokens are entered through the UI, not .env.

  • Mock mode (ADAPTER_MOCK=true): uses fixture data — opt in for local UI iteration without platform credentials.

6. Sync a channel

From the dashboard: Connections → Add Workspace → Select channels → Sync.

Or via API (auto-extracts your bearer token from .env):

curl -X POST http://localhost:8000/api/channels/C12345/sync \
  -H "Authorization: Bearer $(grep -E '^BEEVER_API_KEYS=' .env | cut -d= -f2 | cut -d, -f1)"

Media shared in synced channels (images, PDFs, video) is persisted durably so it keeps rendering after the platform CDN link expires. It defaults to in-database storage with zero extra infra, and can use MinIO/S3 at scale. See docs/media-persistence.md for the mechanism, CHANNEL_MEDIA_* configuration, the MinIO/S3 backend, and backfilling existing channels.

MCP server (for external AI agents)

Beever Atlas exposes a curated MCP (Model Context Protocol) server at /mcp for AI agents like Claude Code and Cursor. This allows external code assistants to query your team's knowledge base without using the dashboard.

See docs/mcp-server.md for:

  • Tool catalog — 28 tools for discovery, retrieval, wiki reading, graph traversal, and long-running operations

  • Auth setup — generating and managing BEEVER_MCP_API_KEYS

  • Client configuration — ready-to-use .mcp.json templates for Claude Code and Cursor

  • Rate limits — principal-keyed limits to prevent one agent from throttling others

It also ships a standalone stdio mode (python -m beever_atlas.api.mcp_server / beever-atlas-mcp) that exposes the same tool catalog with no HTTP server or backing stores — handy for MCP registries (Glama.ai) and local introspection. See docs/mcp-server.md.

Quick example (Claude Code):

{
  "mcpServers": {
    "beever-atlas": {
      "url": "https://atlas.example.com/mcp",
      "transport": "streamable-http",
      "headers": {
        "Authorization": "Bearer ${BEEVER_MCP_KEY}"
      }
    }
  }
}

Common commands

docker compose up -d                     # Start in background
docker compose logs -f beever-atlas      # Tail backend logs
docker compose down                      # Stop (keeps data)
docker compose down -v                   # Stop and DELETE all indexed data
make demo                                # Full stack + seeded demo corpus
make docker-up                           # Shortcut for `docker compose up -d`

🔒 Privacy & Telemetry

Beever Atlas collects no telemetry. No usage data, error reports, or analytics are sent anywhere by default. All LLM calls go through API keys you configure in your own .env, and all data stays in the databases you control.


📐 API Stability

All /api/* endpoints are UNSTABLE in 0.1.0. v0.2.0 will introduce a /api/v1/* prefix; clients pinning current paths will break. See SECURITY.md.


💬 Community & Contact

Commercial support, partnerships, or press: tech@beever.ai.


📜 License

Apache License 2.0 © 2026 Beever Atlas contributors. Third-party attributions in NOTICE.

Security policy: SECURITY.md | Community standards: CODE_OF_CONDUCT.md

Available Tools

28 tools
ask_channelA

Answer a natural-language QUESTION about one channel with synthesized, cited reasoning. The flagship retrieval tool — call it when the user asks anything that needs an ANSWER rather than raw rows.

When to use: any question about a channel's content where you want a composed answer with citations and reasoning across multiple messages ("what did we decide about X", "why did the project slip").

When NOT to use: exact keyword/semantic lookup of individual facts (use search_channel_facts); cross-channel recall when you don't know which channel holds the answer (use search_memory); a deterministic substring scan (use find_facts). Those are faster and return raw rows, not prose.

Prerequisites: a channel_id from list_channels.

Returns (instant for 'quick', long-running up to a 90s hard cap for 'deep'/'summarize'): a dict {answer: str, citations: [{fact_id, text, permalink, author, ts}], follow_ups: [str], metadata: {mode, ...}}. Read-only — no side effects, triggers no jobs.

Error modes (all returned as {error: ...} dicts, never exceptions): 'authentication_missing' (no principal); 'invalid_parameter' (empty/over- 4000-char question, or mode not in quick/deep/summarize); 'channel_access_denied' (token lacks access to channel_id); 'answer_timeout' (exceeded the 90s cap — retry with mode='quick'); 'adk_error' (internal pipeline failure).

Inputs: channel_id (required), question (required), mode, session_id. Returns: object

find_decisionsA

List the DECISIONS recorded in one channel, each with its rationale and rejected alternatives. Call it to answer "what did the team decide and why" when you want the structured decision record, not free-text facts. Returns a bare LIST and collapses missing-auth, access-denied, and internal errors into an EMPTY LIST — so [] means either no decisions OR no access; it never returns a structured error object and never raises.

Disambiguation among the decision tools: use find_decisions for the current decision RECORDS in one channel (with rationale + alternatives_rejected). Use trace_decision_history to follow how one decision SUPERSEDED earlier ones over time (a graph timeline). Prefer find_decisions over find_facts(fact_type='decision') because only this tool enriches each result with rationale and alternatives.

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): a LIST (not a dict) of decisions sorted by decided_at descending, each {fact_id, decision (first sentence), decided_by, decided_at (YYYY-MM-DD), rationale (null if not yet extracted), alternatives_rejected, page_slug (empty if no host page yet)}. No side effects.

Error handling: on missing auth, access denial, or internal error this tool returns an EMPTY LIST [] rather than an error object (it never raises). An empty list therefore means either no decisions or no access — confirm access with list_channels if unexpected.

Inputs: channel_id (required), since, author, limit. Returns: result

find_expertsA

Rank the PEOPLE most knowledgeable about a topic in one channel.

Call this to answer "who should I ask about X in #channel?" or to route a question to the right person. This is the only tool that ranks PEOPLE; use search_channel_facts / find_facts to find FACTS, and find_experts only when you specifically need a human.

Prerequisite: a channel_id from list_channels. Do NOT call with a channel display name.

Returns (instant, read-only, no side effects): {"experts": [...]} — a list ranked by expertise_score descending. Each entry has handle (e.g. '@dana'), expertise_score (relative float, higher = more authoritative; not a fixed 0–1 scale, only meaningful for ranking within this result), fact_count (number of contributing facts), and top_topics (list of related topics that person engages with). An empty list means no graph signal for that topic — not an error.

Error modes: {"error": "authentication_missing"} if the caller is unauthenticated; {"error": "channel_access_denied", "channel_id": ...} if the principal cannot read the channel; {"error": "invalid_parameter", ...} for a malformed channel_id. Other backend failures degrade gracefully to {"experts": []}.

Inputs: channel_id (required), topic (required), limit. Returns: object

find_factsA

Find facts in one channel whose text literally CONTAINS a substring (deterministic, case-insensitive). Call it when you know an exact keyword and want every matching raw fact row, not a ranked or synthesized result. Returns a bare LIST and collapses missing-auth, access-denied, empty-query, and internal errors into an EMPTY LIST — so [] means no match OR no access; it never returns a structured error object.

Disambiguation: find_facts is a deterministic substring filter (predictable, no relevance ranking). For meaning-based / fuzzy retrieval use search_channel_facts (BM25+vector). For a synthesized answer use ask_channel. For decisions with rationale use find_decisions.

When to use: exact-keyword scans ("every fact mentioning 'rollback'"), optionally narrowed by fact_type. When NOT to use: you want semantically related results for a phrase (use search_channel_facts).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): a LIST (not a dict) of up to limit facts, sorted by importance DESC then recency (message_ts) DESC. The importance values that drive the sort rank, highest first, are 'critical' > 'high' > 'medium' > 'low' (any other/empty value sorts lowest). Each item is {fact_id, memory_text, fact_type, importance, author_name, message_ts, page_slug (empty if not yet on a page)}. No side effects.

Error handling: on missing auth, access denial, empty query, or internal error this tool returns an EMPTY LIST [] (it never raises) — an empty list means no match OR no access; confirm with list_channels if unexpected.

Inputs: channel_id (required), query (required), fact_type, limit. Returns: result

get_extraction_statusA

Report how far fact EXTRACTION has progressed for a channel's messages, as a count per status. Call it to judge whether a channel's knowledge is fully ingested before you trust retrieval results, or to track progress after triggering a sync.

Distinguish from get_job_status: this counts MESSAGES by extraction state (corpus readiness); get_job_status reports the lifecycle of one async JOB by job_id. Use this for "is this channel done extracting?"; use get_job_status for "did my trigger_sync/refresh_wiki job finish?".

When to use: gauge corpus completeness, or detect a backlog (high pending) or failures (non-zero failed) before relying on ask_channel/search_channel_facts.

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): {channel_id, counts: {pending, extracting, done, failed}, total} where each count is the number of messages in that state and total is their sum. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'extraction_status_failed' (internal error reading the queue).

Inputs: channel_id (required). Returns: object

get_job_statusA

Check the progress and result of a background job started by trigger_sync or refresh_wiki. Call this AFTER one of those tools returns a job_id, to learn whether the sync/wiki-generation has finished. This is read-only and instant; it neither starts work nor answers channel questions (use the retrieval tools for that).

WHEN TO USE: poll after trigger_sync/refresh_wiki to wait for completion before reading the freshly ingested/regenerated data. POLLING CADENCE: wait ~2–3s between polls and back off on repeats; do NOT hot-loop. Sync/wiki jobs typically take seconds to a few minutes — stop once status is a terminal value (done / error / cancelled).

PREREQUISITES: a job_id previously returned by trigger_sync or refresh_wiki; the job must belong to the calling principal.

LATENCY & SIDE EFFECTS: instant, no side effects.

RETURNS a dict: {job_id, kind ('sync' | 'wiki'), status, progress, started_at, updated_at, ended_at, result, error, target}. status: 'queued' | 'running' | 'done' | 'error' | 'cancelled'. progress: float 0.0–1.0, or null when not yet available. result/error are populated only once the job reaches a terminal state.

ERROR MODES (returned as {error: ...}): 'authentication_missing'; 'invalid_parameter' (malformed job_id); 'job_not_found' — returned both for ids that do not exist AND for jobs owned by another principal, so no cross-principal job information is disclosed.

Reading the atlas://job/<job_id> resource is an equivalent alternative for clients that prefer resources/read over tool calls.

Inputs: job_id (required). Returns: object

get_recent_activityA

List the most RECENT facts from one channel, newest first, optionally scoped to a topic. Call it for time-bounded "what happened lately" questions.

When to use: "what's been discussed in this channel this week", "what happened with topic X in the last N days". When NOT to use: search not bounded by recency (use search_channel_facts); a synthesized answer or reasoning across the items (use ask_channel).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): {activity: [{text, author, timestamp, channel_id, topic_tags, fact_id}, ...]} sorted by timestamp descending. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id). Other internal failures return an empty {activity: []}.

Inputs: channel_id (required), days, topic, limit. Returns: object

get_tensionsA

List unresolved TENSIONS in one channel — points of open disagreement or conflicting positions surfaced across its wiki. Call it to find what is still contested or undecided, as opposed to settled decisions (find_decisions).

When to use: surfacing open conflicts, blockers, or competing stances. When NOT to use: settled decisions (find_decisions) or general fact lookup (find_facts).

Prerequisites: a channel_id from list_channels.

Note: tension detection is currently empty for most channels — the wiring is in place but few channels have tension data yet, so an empty result is normal and does not indicate an error. The same call returns real data automatically once tensions exist, with no signature change.

Returns (instant, read-only): a LIST (not a dict) of {tension_id, title, status, since (YYYY-MM-DD), positions: [{author, stance, fact_id}], page_slug}. No side effects.

Error handling: on missing auth, access denial, or internal error this tool returns an EMPTY LIST [] (it never raises) — indistinguishable from "no tensions"; confirm access with list_channels if unexpected.

Inputs: channel_id (required), status. Returns: result

get_wiki_graphA

Return the map of how a channel's WIKI PAGES link to one another, as a node/edge graph. Call it to understand the wiki's structure — which pages reference which — or to plan a traversal across related pages.

Disambiguation: this is the WIKI PAGE-LINK graph (nodes are wiki pages, edges are cross-links between them). For the KNOWLEDGE graph of entities and their relationships (people, systems, concepts), use search_relationships instead.

When to use: visualizing or navigating wiki page structure, or finding clusters of related pages. When NOT to use: reading a page's content (use read_wiki_page) or querying entity relationships (search_relationships).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): Cytoscape-format {channel_id, nodes: [{data: {id, label, kind, page_kind?, version?, last_updated?}}], edges: [{data: {id, source, target, kind}}]}. Returns empty nodes/edges arrays (not an error) when the graph backend is unavailable, so it is always safe to call. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id).

Inputs: channel_id (required). Returns: object

get_wiki_pageA

Fetch one pre-compiled wiki page from the LEGACY fixed-page set (overview, faq, decisions, people, glossary, activity, topics). Call it for a fast, whole-channel summary keyed by a fixed page_type.

Disambiguation: this is the legacy fixed-page surface. For the redesigned slug-keyed wiki — arbitrary topic/entity pages, structured kind payloads, and the cross-link graph — use list_wiki_pages to discover pages then read_wiki_page(slug=...). Prefer those for anything beyond the seven fixed pages above.

When to use: you want a quick structured summary of a known aspect of a channel without running the QA pipeline. When NOT to use: you need a specific answer (use ask_channel) or a non-fixed wiki topic (use read_wiki_page).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): the page dict {page_type, channel_id, content, summary, text}. content is null when that page has not been generated yet (run a sync / refresh_wiki first). No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id). Other internal failures return the page dict with content: null.

Inputs: channel_id (required), page_type. Returns: object

lint_wikiA

Audit a channel's wiki for health problems and return a list of findings. Call it to check whether wiki pages are stale, orphaned, duplicated, or internally inconsistent before relying on them or recommending a refresh.

When to use: validating wiki quality, or diagnosing why an answer looked wrong. When NOT to use: routine reading (use read_wiki_page / list_wiki_pages) — linting is heavier. Set run_coherence_check=false to avoid the per-page LLM cost when you only need structural checks.

Prerequisites: a channel_id from list_channels.

Returns (long-running when run_coherence_check=true — one LLM call per page; read-only, writes nothing): {findings: [{severity, category, page_id, section_id, message, suggested_action}, ...], pages_scanned: N}.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'lint_failed' (returned as {findings: [], error: 'lint_failed'} on an internal lint error).

Inputs: channel_id (required), target_lang, run_coherence_check. Returns: object

list_channelsA

List the channels the bot can actually read on one connection — the ground-truth source for what channels exist and their indexing state.

Call this after list_connections/whoami (you need a valid connection_id), once per connection you care about, BEFORE any retrieval tool (ask_channel, search_channel_facts, get_wiki_page, ...) so you can pass a real channel_id. Prefer this over list_connections.selected_channel_count — that count is a sync pick-list, not the channel inventory.

Latency: instant for cached results; may take a few seconds when it queries the live platform bridge. Read-only — does not trigger a sync.

Returns {"channels": [<entry>, ...]} (empty list if none/bridge error). Each entry:

  • channel_id (str): pass to retrieval/sync tools, e.g. "C0A955E29MX".

  • name (str): display name, e.g. "engineering".

  • platform (str): e.g. "slack", "discord", "file".

  • last_sync_ts (str|null): ISO timestamp of last index, null if never.

  • sync_status (str): "synced", "never_synced", or "n/a" (file connections). "never_synced" is normal and does NOT mean the channel is inaccessible — it just is not indexed yet; call trigger_sync(channel_id) to ingest it before querying its content.

  • message_count_estimate (int|null): approx synced messages, null if not yet synced.

Scoping: matches the dashboard "CONNECTED" view. If the user picked specific channels for sync, those are returned; otherwise every channel where the bot is a member (and thus can read) is returned. File connections return every uploaded file.

Error modes: {"error": "connection_access_denied", "connection_id": ...} if you do not own the connection (existence is not leaked); {"error": "invalid_parameter", "parameter": "connection_id"} for a malformed id; {"error": "authentication_missing"} if no principal.

Inputs: connection_id (required). Returns: object

list_connectionsA

List the platform connections (Slack workspaces, Discord servers, file imports, etc.) this principal owns, with each connection's metadata.

Use this when you need a connection's platform, status, or sync metadata — not just its id. If you only need the connection ids, whoami is cheaper. After picking a connection here, call list_channels(connection_id) to see its actual channels. Results are ownership-filtered: you only see your own.

Latency: instant (read-only; no sync triggered).

Returns {"connections": [<entry>, ...]} (empty list if none). Each entry:

  • connection_id (str): e.g. "conn_abc123" — pass to list_channels.

  • platform (str): e.g. "slack", "discord", "file".

  • display_name (str): human label, e.g. "Acme Workspace".

  • status (str): connection health, e.g. "connected".

  • last_synced_at (str|null): ISO timestamp of the most recent sync of a PICKED channel; null when the pick-list is empty (see caveat below).

  • selected_channel_count (int): size of the sync pick-list (see caveat).

  • source (str): how the connection was created, e.g. "oauth".

Caveats (do NOT misread): selected_channel_count is the user's opted-in sync pick-list, NOT how many channels exist. A value of 0 does not mean the connection is empty — a Slack workspace with 0 picks can still have dozens of bot-readable channels. last_synced_at is scoped to the same pick-list, so an empty pick-list yields null even if channels were synced another way. For ground-truth channel availability, always call list_channels(connection_id) — never infer it from these counts.

Error modes: returns {"error": "authentication_missing"} when no valid principal is attached. Never raises access-denied (output is self-scoped).

Inputs: no named parameters. Returns: object

list_wiki_pagesA

List the wiki pages in one channel as lightweight summaries (no page bodies). The RECOMMENDED first call when exploring the redesigned slug-keyed wiki: use it to discover slugs, then fetch a page with read_wiki_page(slug=...).

When to use: browsing or discovering which pages exist, or finding a slug. When NOT to use: you already know the slug and want the body (call read_wiki_page directly).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): {channel_id, target_lang, scope, pages: [{slug, title, kind, version, last_updated, pinned, hidden}, ...]}. The content_md body is intentionally omitted to keep the payload bounded — follow up with read_wiki_page(slug=...) for a page's content. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'wiki_list_failed' (internal error).

Inputs: channel_id (required), kind, scope, target_lang. Returns: object

read_provenanceA

Trace ONE fact back to the original chat message it was extracted from. Call it to verify or cite a fact — given a fact_id from another tool, it returns where the fact came from (platform, message, author, timestamp, and the raw message text when reachable).

When to use: confirming a fact's source, building a citation, or auditing provenance. Prerequisites: a fact_id surfaced by find_facts, find_decisions, search_channel_facts, search_memory, or an ask_channel citation.

Returns (instant, read-only): {fact_id, memory_text, source: {platform, message_id, url, author, ts}, raw_message}. raw_message is the original chat body, or an empty string if the source message is no longer reachable — every other field is still populated in that case. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'fact_not_found' (unknown fact_id — also returned, deliberately, when the caller lacks access to the fact's channel, so cross-tenant existence is never leaked); 'provenance_read_failed' (internal error).

Inputs: fact_id (required). Returns: object

read_wiki_moduleA

Fetch ONE structured module from a wiki page without downloading the whole page. Call it when you already know the page slug and the module anchor you want, and reading the full page via read_wiki_page would waste tokens.

When to use: you need just one module's structured data (e.g. the key_facts items, or a decision_banner's rationale + alternatives). When NOT to use: you need a narrative prose section (use read_wiki_section) or the whole page (use read_wiki_page). To learn a page's module anchors, read it once with read_wiki_page first.

Prerequisites: a channel_id (list_channels), a page_slug (list_wiki_pages), and an anchor (from the page's modules).

Returns (instant, read-only): {channel_id, page_slug, anchor, module_id, data} where data is the module's structured payload. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'wiki_page_not_found' (no such slug); 'module_not_found' (page exists but has no module with that anchor); 'wiki_module_read_failed' (internal error).

Inputs: channel_id (required), page_slug (required), anchor (required), target_lang. Returns: object

read_wiki_pageA

Read ONE full wiki page by its slug from the redesigned slug-keyed wiki. Call it after list_wiki_pages tells you which slug you want, to get a page's complete content and structured payload.

Disambiguation: this is the slug-keyed redesign surface (arbitrary topic/entity pages). For the seven LEGACY fixed pages (overview, faq, decisions, ...) use get_wiki_page(page_type). Typical sequence: list_wiki_pages -> read_wiki_page(slug). To save tokens when you need only a slice, use read_wiki_module (one module) or read_wiki_section (one narrative section) instead of the whole page.

Prerequisites: a channel_id from list_channels and a slug from list_wiki_pages.

Returns (instant, read-only): the full WikiPage document including content_md (markdown body), kind + kind_schema (structured payload agents can iterate without re-parsing markdown), cross_links (title->slug), cross_links_broken (linked titles with no page yet), pin_state, and last_updated. Hidden pages are excluded unless the token carries the read:hidden_pages scope. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'wiki_page_not_found' (no such slug, or it is hidden and the token lacks read:hidden_pages); 'wiki_read_failed' (internal error).

Inputs: channel_id (required), slug (required), target_lang. Returns: object

read_wiki_sectionA

Fetch ONE narrative (prose) section of a wiki page without loading the whole page. Call it when you know the page slug and section anchor and want just that article slice, to save tokens.

Disambiguation: read_wiki_section returns PROSE sections (paragraphs + citations); read_wiki_module returns a STRUCTURED module payload (key_facts, decision_banner, etc.); read_wiki_page returns the whole page. Use find_facts for fact-text search across pages.

Prerequisites: a channel_id (list_channels), a page_slug (list_wiki_pages), and an anchor (from the page's narrative sections).

Returns (instant, read-only): {anchor, heading, paragraphs, citations, visual, page_slug, page_title, channel_id} — page_title and channel_id are included so you can attribute the section without a second call. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id); 'page_not_found' (no such slug); 'section_not_found' (page exists but lacks the anchor — the result lists available_anchors to retry with); 'narrative_not_available' (page has no narrative sections — includes has_modules and a suggestion to use read_wiki_page for module data); 'internal_error'.

Inputs: channel_id (required), page_slug (required), anchor (required), target_lang. Returns: object

refresh_wikiA

Re-render a channel's pre-compiled wiki pages (overview, FAQ, decisions, etc.) from facts ALREADY ingested into the knowledge base. Use this to rebuild stale wiki content; the refreshed pages are then read with get_wiki_page / read_wiki_page / list_wiki_pages. It does NOT ingest new messages — that is trigger_sync's job — and it does NOT answer questions (use the retrieval tools for that).

WHEN TO USE: after a sync has added new facts (i.e. after trigger_sync completes), or when the user explicitly asks to regenerate the wiki. WHEN NOT TO USE: do not call routinely — the standard sync pipeline already rebuilds wiki pages automatically, so calling this after a normal sync is usually redundant.

PREREQUISITES: a valid channel_id from list_channels; the channel must have ingested facts (run trigger_sync first if it has none); the calling principal must have access.

LATENCY & SIDE EFFECTS: asynchronous and expensive (runs an LLM generation pass). Returns within ~5s with a job envelope while generation runs in the background; this WRITES/overwrites the channel's wiki pages. Shape: {job_id: 'job_def456', status_uri: 'atlas://job/job_def456', status: 'queued'}. Track progress with get_job_status(job_id) or the atlas://job/<job_id> resource.

ERROR MODES (returned as {error: ...}, never raised): 'authentication_missing'; 'invalid_parameter' (malformed channel_id); 'channel_access_denied'; 'cooldown_active' (refreshed too recently; includes retry_after_seconds); 'service_unavailable' (includes service); 'internal_error'.

Inputs: channel_id (required), page_types. Returns: object

search_channel_factsA

Find SPECIFIC facts in ONE channel by hybrid (BM25 + vector) search and return them as raw, ranked rows. Call it when you want the cited source facts themselves, not a composed answer.

When to use: targeted lookup of facts in a known channel ("find facts about the postgres migration"). Faster and more precise than ask_channel for retrieval-only tasks.

When NOT to use: you need a synthesized answer with reasoning (use ask_channel); you don't know which channel holds the facts (use search_memory, which fans this same search across every accessible channel); you want a deterministic substring match rather than ranked relevance (use find_facts).

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): {facts: [{text, author, timestamp, permalink, channel_id, confidence, topic_tags}, ...]}. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id). On any other internal failure it returns an empty {facts: []} rather than erroring.

Inputs: channel_id (required), query (required), time_scope, limit. Returns: object

search_channel_knowledgeA

DEPRECATED — do not call. This is a compatibility shim for the retired 'search_channel_knowledge' tool. Use 'ask_channel' for natural-language questions answered with citations, or 'search_channel_facts' for targeted keyword+vector fact search within one channel. Calling this always returns a structured {"error": "tool_renamed", "replacement": [...]} payload and performs no work (no backend call, exempt from rate limiting).

Inputs: channel_id, query. Returns: object

search_media_referencesA

Find messages that SHARED a file, image, PDF, or link in one channel. Call it when the user is hunting for an attachment or URL ("where's the design doc", "find the screenshot Alice posted") rather than for the knowledge in the text.

When to use: locating shared documents, images, or links. When NOT to use: general fact/knowledge search (use search_channel_facts or ask_channel) — this tool only returns messages that carry media.

Prerequisites: a channel_id from list_channels.

Returns (instant, read-only): {media: [{text, media_urls, link_urls, link_titles, author, timestamp, media_type, fact_id}, ...]}. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'channel_access_denied' (token lacks access to channel_id). Other internal failures return an empty {media: []}.

Inputs: channel_id (required), query (required), media_type, limit. Returns: object

search_memoryA

Find facts ACROSS MANY channels by hybrid (BM25 + vector) search when you do NOT know which channel holds the answer. Call it first for broad recall, then drill into a specific channel with the tools below.

Routing rule for the three search tools:

  • search_memory(scope='all') — unknown channel; fans the search across every channel the principal can access and merges/ranks the hits.

  • search_channel_facts(channel_id) — known channel; same hybrid search, scoped to one channel, returning the richer per-fact shape.

  • search_memory(scope='channel:') — single channel with the search_memory hit shape (use search_channel_facts instead if you want author/permalink/topic_tags on each row). For a synthesized ANSWER rather than rows, use ask_channel.

Prerequisites: none for scope='all'; a channel_id (from list_channels) for scope='channel:'.

Returns (instant for one channel, longer when fanning across many; read-only): {hits: [{fact_id, text, score, channel_id, cluster_id, entity_tags}, ...], query: <echo of query>} ranked by hybrid score. No side effects.

Error modes (returned as dicts): 'authentication_missing' (no principal); 'invalid_parameter' (empty/over-4000-char query, or scope not 'all'/ 'channel:'); 'channel_access_denied' (only for an explicit 'channel:' the token cannot reach — under scope='all' unreachable channels are silently skipped).

Inputs: query (required), scope, limit. Returns: object

search_relationshipsA

Find how named ENTITIES connect in a channel's knowledge graph.

Call this to answer "how is X related to Y?" or "what touches the billing service?" by returning the subgraph of nodes and edges around the given entities. This explores the KNOWLEDGE graph of entities/relationships — distinct from get_wiki_graph, which returns the wiki PAGE-LINK graph (which wiki pages reference which). Use find_experts to rank people and trace_decision_history to follow decision supersession.

Prerequisite: a channel_id from list_channels and at least one entity name. Names should match how the channel refers to the entity; unknown names simply yield an empty subgraph.

Returns (instant for small hop counts, read-only, no side effects): {"nodes": [...], "edges": [...], "text": str, "entities_searched": [...]}. Each node has name and type (e.g. 'person', 'system', 'concept'); each edge has source, target, type (relationship label), confidence (0–1, extraction confidence), and context (snippet explaining the edge). text is a human-readable summary. Empty nodes/ edges means no connections were found — not an error.

Error modes: {"error": "authentication_missing"} if unauthenticated; {"error": "channel_access_denied", "channel_id": ...} if the channel is not readable; {"error": "invalid_parameter", ...} for a malformed channel_id. Other backend failures degrade to {"nodes": [], "edges": [], "channel_id": ...}.

Inputs: channel_id (required), entities (required), hops. Returns: object

start_new_sessionA

Mint a fresh conversation session id that starts a new ask_channel thread.

Call this to begin a clean conversation that carries no memory of prior ask_channel turns — e.g. when the user switches to an unrelated topic or explicitly asks to "start over" / "forget previous context". Pass the returned id as the session_id argument on subsequent ask_channel calls so they share one continuous thread; reuse the same id for follow-ups, and mint a new one only when you want a clean break.

When NOT to use: do not call this before every question. ask_channel auto-creates a session when session_id is omitted, so a new session id is only needed to intentionally drop earlier conversation context.

Prerequisites: none. Requires an authenticated MCP principal (the caller's connection token); no channel_id or other input is needed.

Returns: a dict {"session_id": "mcp:<principal>:<short>"} where the value is a fresh opaque conversation handle scoped to the caller, e.g. {"session_id": "mcp:conn_abc123:9f3c1a2b"}. On a missing or invalid principal it returns {"error": "authentication_missing"} instead.

Side effects: none — this allocates a new conversation boundary marker and does not delete, persist, or mutate any stored data. Latency: instant (no network or LLM call); safe to call synchronously inline.

Inputs: no named parameters. Returns: object

trace_decision_historyA

Reconstruct how a decision EVOLVED over time in a channel.

Call this to answer "how did the team arrive at the current approach for X?" or "what earlier choices were overridden?" It walks SUPERSEDES edges in the knowledge graph to build an ordered timeline of superseded → current decisions. Distinct from find_decisions (which lists current decision facts with no history) and search_channel_facts (current state only, no chronology); use this tool specifically when you need the chronological "why we changed" trail.

Prerequisite: a channel_id from list_channels. Best results on mature channels where decisions have been revised; new channels often have no supersession chain yet (empty result, not an error).

Returns (instant, read-only, no side effects): {"decisions": [...]} ordered oldest → newest. Each item has entity (the decision that was made), superseded_by (the decision that replaced it, or empty for the current one), relationship (edge label, typically 'SUPERSEDES'), confidence (0–1 extraction confidence), context (snippet explaining the change), and position (0-based index in the timeline). An empty list means no recorded supersession chain.

Error modes: {"error": "authentication_missing"} if unauthenticated; {"error": "channel_access_denied", "channel_id": ...} if the channel is not readable; {"error": "invalid_parameter", ...} for a malformed channel_id. Other backend failures degrade to {"decisions": []}.

Inputs: channel_id (required), topic (required). Returns: object

trigger_syncA

Ingest a chat channel's messages into the Atlas knowledge base so they become searchable by the retrieval tools (ask_channel, search_channel_facts, search_memory). This is the WRITE/ingestion entry point; it does NOT answer questions — use the retrieval tools for that. It is distinct from refresh_wiki, which only re-renders wiki pages from already-ingested facts.

WHEN TO USE: only when the user EXPLICITLY asks to sync/refresh a channel, OR when retrieval tools return empty/stale results AND the channel was last synced over 24h ago. WHEN NOT TO USE: do not call before every question or as a precautionary warm-up — prefer the data already indexed. Sync is expensive and rate-limited (cooldown) per channel.

PREREQUISITES: get a valid channel_id (and ideally connection_id) from list_channels first. The calling principal must have access to the channel.

LATENCY & SIDE EFFECTS: asynchronous. Returns within ~5s with a job envelope while ingestion runs in the background; this WRITES facts to the knowledge base. Shape: {job_id: 'job_abc123', status_uri: 'atlas://job/job_abc123', status: 'queued'}. Track progress by calling get_job_status(job_id) or reading the atlas://job/<job_id> resource.

IDEMPOTENT: if a queued or running sync already exists for the channel, its existing job_id is returned instead of starting a duplicate. A new job is created only when no active job exists, or after the prior one completed or failed.

ERROR MODES (returned as {error: ...}, never raised): 'authentication_missing' (no principal); 'invalid_parameter' (malformed channel_id/connection_id); 'channel_access_denied' (principal lacks access); 'cooldown_active' (synced too recently; includes retry_after_seconds); 'service_unavailable' (backing service down; includes service); 'internal_error' (unexpected failure).

Inputs: channel_id (required), sync_type, connection_id. Returns: object

whoamiA

Confirm who you are authenticated as and which connection ids you can reach.

Call this FIRST in any session, before any other tool, to (1) verify your auth token resolved to a principal and (2) get the connection ids needed by list_channels. Returns only connection IDS here; use list_connections when you also need each connection's platform, status, and sync metadata.

When to use: once at session start. Do NOT call repeatedly — the response is stable for the whole session.

Latency: instant (single in-memory/DB lookup; never triggers a sync or job).

Returns a dict:

  • principal_id (str): your authenticated identity, e.g. "user_42".

  • connections (list[str]): connection ids you may access, e.g. ["conn_abc123", "conn_def456"]. Empty list if you own no connections.

  • server_version (str): deployed Atlas version, e.g. "0.1.0".

Error modes: returns {"error": "authentication_missing"} when the request carries no valid principal (token absent/invalid). No access-denied path — the response is always scoped to the caller.

Inputs: no named parameters. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.7/5.0

    Scored across 28 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose, with descriptions that explicitly contrast similar tools (e.g., find_facts vs. search_channel_facts vs. ask_channel, or read_wiki_page vs. read_wiki_module). Overlapping functionality is minimal and well-documented.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern in snake_case (e.g., list_channels, find_facts, read_provenance). There is no mixing of naming conventions, making the set predictable.

    Tool Count4/5

    28 tools is on the higher end but appropriate for the server's complex domain (knowledge management with multiple search modes, wiki operations, decision tracking, etc.). The count feels slightly heavy but each tool earns its place.

    Completeness5/5

    The tool set covers the full lifecycle: channel discovery, multiple retrieval methods, wiki management (both legacy and redesigned), decision history, tensions, experts, media search, relationship graphs, job tracking, and sync. Obvious gaps (e.g., fact deletion) are intentionally omitted as read-only. The deprecated tool is a minor blemish.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      End-to-end agent-managed company brain. Humans and any MCP agent co-author living docs (Markdown + extensions), 40+ visual diagrams (Mermaid, BPMN, D2, PlantUML, ELK, Excalidraw), plans, and a self-learning Knowledge Graph. 163 tools across 16 categories. Auth: OAuth 2.1 or API key. Lean, secure, affordable — from individuals to enterprise.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables MCP clients to read and write into a self-hosted personal knowledge base built from imported chats, documents, and notes, with a knowledge graph and AI-powered enrichment.
      1
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables teams to capture and distill knowledge from sessions, meetings, and feeds into a searchable, vector-embedded graph, then retrieve or synthesize it conversationally via semantic search and citations.
      17
      25
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI apps like Claude, ChatGPT, Copilot, Claude Code and Codex to read and write a shared company wiki over MCP, where meetings, work sessions and notes become living pages about people, projects and decisions. It also serves search, briefs, transcripts, decisions and action items, and lets teams connect their own git-hosted team wiki.
      1
      MIT