slack-archive-mcp
Provides read-only MCP tools for searching and retrieving messages, channels, threads, and surrounding context from Slack export archives.
Click on "Deploy 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., "@slack-archive-mcpsearch #engineering for messages about the database migration"
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.
slack-archive-mcp
Turn overlapping Slack exports into portable community memory: a versioned SQLite archive with full-text search and read-only MCP tools over stdio or Streamable HTTP. Built with Effect 4 on Bun. MIT licensed. No embeddings or external services required.
local exports ─┐
├─ Exports port → materialize → local SQLite snapshot
S3 exports ────┘ ├─ local generation store
└─ optional S3 publication
mounted SQLite → ArchiveReader port → stdio / HTTP MCP → agentThe MCP server needs only its database. It does not need exports, S3 credentials, an object store connection, or a writable filesystem.
Quick start
Requires Bun 1.4.0+; dependencies are pinned by bun.lock. All tests use synthetic
exports. Put exports in ignored root data-* directories or native .zip files.
bun install --frozen-lockfile
bun run check
bun run ingest --workspace community
bun run verify
bun run search "book club" --channel bookclub --limit 5
bun run smokeDefault discovery naturally sorts export names oldest to newest. Explicit source arguments preserve the order supplied:
bun run ingest ./data-2023 ./data-2025 ./data-2026 --workspace community
# Native ZIPs, extracted directories, or both; one export per ZIP:
bun run ingest ./older.zip ./data-2025 ./latest.zip --workspace communityUse one workspace per archive directory. Refresh preserves its namespace if
--workspace is omitted. Use --archive-dir PATH for a separate archive.
Originals stay untouched; reconciliation happens inside SQLite.
Related MCP server: my-kb
Containerized MCP
Pull the published serving image and mount one completed SQLite file:
docker pull ghcr.io/bioshazard/slack-archive-mcp:2.2.0
docker run --rm -i --read-only --network none \
--mount type=bind,src=/absolute/path/archive.sqlite,dst=/data/archive.sqlite,readonly \
ghcr.io/bioshazard/slack-archive-mcp:2.2.0The default is stdio MCP: a client launches the command and speaks JSON-RPC over
stdin/stdout. Keep -i; do not allocate a TTY. For Kubernetes or network clients,
select HTTP mode below.
ARCHIVE_SQLITE_PATH overrides the mounted file location:
docker run --rm -i --read-only --network none \
-e ARCHIVE_SQLITE_PATH=/mounted/custom.sqlite \
--mount type=bind,src=/absolute/path/archive.sqlite,dst=/mounted/custom.sqlite,readonly \
ghcr.io/bioshazard/slack-archive-mcp:2.2.0The image runs as the unprivileged bun user and contains only the bundled server
and Bun runtime. The Docker context uses an allowlist, excluding all data and Git
history. Build both images, then run bun run docker:smoke. It ingests synthetic
ZIPs in the CLI container and exercises MCP SDK round trips against the resulting
database mounted read-only. For local builds, run
docker build -f Dockerfile.mcp -t slack-archive-mcp:local . and use that image name.
Published images support Linux AMD64 and ARM64. Releases provide version tags
(2.2.0, 2.2), latest for stable releases, and sha-<full-commit> tags.
Prefer an exact version or image digest for repeatable deployments. The ingest
CLI is also available as ghcr.io/bioshazard/slack-archive-cli:2.2.0.
Configure your MCP client using mcp.docker.example.json.
For a local Bun process, bun run configure:mcp generates ignored .mcp.json
with absolute paths; copy that entry into your client configuration. No global
client settings are modified.
Streamable HTTP / Kubernetes
The same image supports stateless MCP Streamable HTTP at /mcp:
# Set MCP_AUTH_TOKEN in your environment before running; pass it through securely.
docker run --rm --read-only \
--publish 127.0.0.1:3000:3000 \
-e MCP_TRANSPORT=http -e MCP_HOST=0.0.0.0 -e MCP_AUTH_TOKEN \
--mount type=bind,src=/absolute/path/archive.sqlite,dst=/data/archive.sqlite,readonly \
ghcr.io/bioshazard/slack-archive-mcp:2.2.0Connect an MCP Streamable HTTP client to http://localhost:3000/mcp; if configured,
send Authorization: Bearer <MCP_AUTH_TOKEN>. There is no built-in OAuth flow.
Token authentication is optional for deployments protected by an existing access
gateway. Use TLS termination and access controls appropriate to the archive's
audience when exposing the endpoint.
For Kubernetes, mount the completed SQLite file and set MCP_TRANSPORT=http,
MCP_HOST=0.0.0.0, and optionally MCP_PORT (default 3000). Supply the token through
your existing secret mechanism. No stdin attachment is needed. The image remains
non-root (UID/GID 1000) and works with a read-only root filesystem and database.
GET /healthz and GET /readyz return minimal status without bearer authentication;
readiness checks the pinned archive reader. Startup fails before listening if the
database cannot be opened. A configured host/origin allowlist also applies to probes,
so configure their Host header accordingly. Shutdown drains HTTP requests before
closing SQLite.
Each POST gets its own SDK transport/server over one shared read-only reader.
Responses are JSON; there is no session state, sticky routing, or long-lived GET
SSE stream. GET /mcp and DELETE /mcp return 405; use a Streamable HTTP client,
not a legacy SSE client. Requests are limited to 64 KiB. Browser origins are rejected
unless explicitly allowed. The server never trusts forwarded host headers for
its optional host allowlist. It serves one workspace per process, with no per-user
archive permissions.
Local serving and configuration
bun run mcp --sqlite-path /absolute/path/archive.sqlite
# Or follow a local generation pointer at startup:
bun run mcp --archive-dir /absolute/path/.archiveSetting | Meaning |
| Single self-contained SQLite file; ideal for containers |
| Local generation store, default |
| Explicit flags override environment selection |
|
|
| Bind address, default |
| HTTP port, default 3000 |
| Optional shared bearer token for |
| Optional comma-separated exact Host values, including port when sent |
| Comma-separated browser origins; default rejects any Origin header |
| Optional S3-compatible endpoint for ingestion/publication |
| S3 region, default |
| S3 credentials, used only by ingestion/publication |
With no file setting, local MCP follows current.json once at startup. Readers
pin a generation until restarted. A standalone database embeds its snapshot
metadata; its original build timestamp is not embedded and is reported as unknown.
Never mount an actively changing SQLite database.
S3 adapters
S3 is optional and never on the MCP query path. Input accepts native ZIP objects
or extracted JSON under <prefix>/<export-name>/.... Prefix discovery selects
data-* directories and immediate .zip objects. Selectors specify directory
names or ZIP filenames; their order defines precedence.
# Configure credentials in the environment or ignored .env; see .env.example.
bun run ingest --source-s3 s3://my-bucket/raw --workspace community
bun run ingest data-2023 data-2026 --source-s3 s3://my-bucket/raw
bun run ingest older.zip latest.zip --source-s3 s3://my-bucket/raw
# A direct object needs no bucket listing permission:
bun run ingest --source-s3 s3://my-bucket/raw/latest.zip
# Mix local sources and direct S3 ZIP URIs in oldest-to-newest order:
bun run ingest ./older.zip ./data-2025 s3://my-bucket/raw/latest.zip
# Publish a locally built generation to an S3-compatible store:
bun run ingest --publish-s3 s3://my-bucket/archivePublication uploads SQLite, then manifest, then the current-generation pointer. Failed remote publication leaves the completed local generation available for retry. A failure before the pointer update leaves the remote pointer unchanged. Use one publisher per target prefix; remote pointer updates are last-writer-wins. Keys use SHA-256 fingerprints; S3 ETags are not treated as content hashes.
Recommended bucket layout
Give each workspace its own prefix; keep original exports separate from derived
artifacts. For example, in a bucket named archive-bucket:
slack/<workspace>/
raw/
exports/
2024-01-01.zip
2024-06-01.zip
2025-01-01.zip
archive/
current.json
generations/<fingerprint>/
archive.sqlite
manifest.jsonUpload one original export per ZIP without repackaging. Treat those objects as immutable: use a new filename for different bytes, even if the export date is the same. ISO-date names sort oldest to newest; explicit source arguments can override precedence. Prefixes organize objects; no empty folder objects are required.
bun run ingest --workspace community \
--archive-dir .archive \
--source-s3 s3://archive-bucket/slack/community/raw/exports \
--publish-s3 s3://archive-bucket/slack/community/archiveReplace community with your workspace namespace in both paths and the flag.
The command reads remote ZIPs, builds and validates SQLite locally, then publishes
the completed generation and updates the remote pointer last. Keep old generations
for rollback. MCP serves a local copy of the selected completed SQLite file.
Download a completed generation's archive.sqlite through your preferred S3
client and mount it for MCP. Automatic cache synchronization and live Slack API
ingestion are outside this release.
ZIP input is staged as a private compressed file in the OS temporary directory, then removed when ingestion ends. Members are never extracted. Stored/deflated entries, ZIP64, data descriptors, and one optional enclosing export folder are supported. JSON bytes receive CRC-32 and SHA-256 checks; citations include the ZIP filename, container SHA-256 and original member path. Retain the source ZIP for exact-byte evidence. Encrypted archives, links, unsafe/duplicate paths and bundles containing multiple export roots are rejected.
Default ZIP limits: 8 GiB compressed, 100,000 entries, 256 MiB per JSON file,
8 GiB total JSON. Override with --zip-max-archive-bytes, --zip-max-entries,
--zip-max-entry-bytes, and --zip-max-json-bytes. Non-JSON members are not
decompressed or indexed. Staging requires temporary disk space for each ZIP;
JSON is read one bounded entry at a time.
The separate Dockerfile builds the ingest/query CLI. Mount raw
exports read-only, use --root for their location, and mount a writable output
directory at /output; use --user when host ownership requires it. Mount only
necessary paths and pass credentials only to this ingestion container.
Tools and retrieval
MCP tool | Purpose |
| Generation, source inventory, coverage and import warnings |
| IDs, names, historical aliases and observed date ranges |
| BM25 search with channel, author, date and chronology filters |
| Preferred message plus optional original observations |
| Conversation-scoped parent and replies |
| Nearby chronological messages, including unthreaded discussion |
Search supports all, any, phrase, and exact substring modes. Use exact
for URLs, punctuation and code; it scans text rather than using FTS. Prefix
matching applies in all/any modes. Dates are UTC YYYY-MM-DD or timezone-qualified
ISO timestamps: after inclusive, before exclusive.
bun run search "chapter schedule" --mode any --after 2024-01-01
bun run search "https://example.com/a_b" --mode exact
bun run search "retriev" --prefix --sort oldest --limit 10Search returns stable message IDs, generation IDs and source citations. Expand
hits with thread/context tools before interpreting them. archive:// citation
URIs identify evidence; they are not web links. Follow-up tools accept a generation
guard. Pages return nextOffset; context indicates whether more messages exist.
Text and raw-JSON truncation are explicitly flagged.
Preservation and operations
Identity includes workspace, stable conversation ID and exact timestamp string.
All observations and their original JSON survive reconciliation. Preferred text, blocks, reactions and attachments come from one complete observed version.
Explicit edits determine revision precedence; export order breaks ties. Absence does not prove deletion. Explicit tombstones are excluded from search.
Invalid records are quarantined with original JSON and reasons. Builds balance source counts and validate SQLite, foreign keys, canonical identities and FTS.
Completed generations are atomically published. Failed builds preserve current; unchanged input reuses its verified generation. Restart MCP to refresh.
Attachment metadata/previews are searchable; linked attachment bytes are not downloaded. Gaps in export coverage are not filled or interpreted as activity.
Generated files live under ignored .archive/generations/<fingerprint>/ with a
manifest and database. bun run verify checks the current generation's SHA-256,
SQLite integrity and accounting. Old generations are retained. After a hard kill,
remove ingest.lock only after confirming no importer is running.
For a standalone mount, bun run verify --sqlite-path PATH validates its embedded
metadata and SQLite/accounting integrity and computes its digest; verifying it
against an earlier artifact still requires that artifact's trusted manifest hash.
Anyone with the database can read its entire contents, including historical observations. Keep audience-specific archives separate. Archived text is untrusted source material, never agent instructions. HTTP supports a shared bearer token; per-user ACLs and OAuth are not implemented.
Development
Architecture · Effect conventions · Development · Contributing · License
src/domain/ Schemas, identities, source/query models and errors
src/ports/ Effect service interfaces
src/application/ Materialization use case; no storage imports
src/adapters/storage/ Local and S3 export/publication implementations
src/adapters/sqlite/ Native Bun SQLite materializer and read-only query layer
src/cli.ts Effect CLI and BunRuntime composition
src/mcp.ts MCP SDK adapter, ManagedRuntime and scoped serving
src/adapters/mcp/ Stateless Streamable HTTP adapter
scripts/ Smoke checks and local client configuration
tests/ Synthetic integration and architecture checksSource exports, generated databases, local reports, credentials and .mcp.json
are ignored. Earlier private experiments/research remain under ignored
local/history/; they are excluded from the public source and Docker context.
No data or credentials are supplied with this project.
This server cannot be deployed
Maintenance
Related MCP Connectors
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
Securely search and manage workspace context files for AI agents and teams.
Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.
A read-only verified record of agent-operable GTM tools: search, fetch, compare, track changes.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to securely search, analyze, and draft posts grounded in a local X/Twitter archive, with encrypted storage, scoped tool access, and full audit logging.1-
- FlicenseNot gradedqualityCmaintenanceEnables agents to search a local, fully offline personal document library (Markdown/txt/PDF/docx) via SQLite FTS5 trigram search with subject-terminology expansion, returning every hit with its domain, document title, section path, and page or offset provenance. It also exposes domain statistics, incremental ingestion, and document deprecation as tools, alongside a read-only, runtime-persona-scoped ask/coverage mode.-
- AlicenseNot gradedqualityBmaintenanceEnables searching and retrieving ChatGPT and Claude conversation exports locally, allowing assistants to fetch handoff context and continue conversations through read-only MCP tools.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to search and retrieve a locally stored archive of past conversations, notes, and captured Codex events, preserving full original text and provenance in SQLite full-text search. It offers read-only access through two tools, search_memory and get_memory, so context from earlier chats can be reused without pasting it again.MIT