Skip to main content
Glama
io.github.norrietaylor/distillery-mcp

io.github.norrietaylor/distillery-mcp

Official
by norrietaylor

What is Distillery?

Distillery is a team knowledge base accessed through Claude Code skills. It refines raw information from working sessions, meetings, bookmarks, and conversations into concentrated, searchable knowledge — stored as vector embeddings in DuckDB and retrieved through natural language. Runs locally over stdio or as a hosted HTTP service with GitHub OAuth for team access.

Distillery captures the highest-value transformation — from noise to signal — and makes it a tool the whole team can use.

Full documentation: norrietaylor.github.io/distillery

Related MCP server: idea-basin-mcp

Skills

Distillery provides 15 Claude Code slash commands:

Skill

Purpose

Example

/distill

Capture session knowledge with dedup detection

/distill "We decided to use DuckDB for local storage"

/recall

Semantic search with provenance

/recall distributed caching strategies

/pour

Multi-entry synthesis with citations

/pour how does our auth system work?

/compass

Contrast internal knowledge vs. ambient intelligence for a directional assessment

/compass our caching strategy

/bookmark

Store URLs with auto-generated summaries

/bookmark https://example.com/article #caching

/minutes

Meeting notes with append updates

/minutes --update standup-2026-03-22

/classify

Classify entries and triage review queue

/classify --inbox

/watch

Manage monitored feed sources

/watch add github:duckdb/duckdb

/radar

Ambient feed digest with source suggestions

/radar --days 7

/tune

Adjust feed relevance thresholds

/tune relevance 0.4

/digest

Team activity summary from internal entries

/digest --days 7 --project myapp

/gh-sync

Sync GitHub issues/PRs into the knowledge base

/gh-sync owner/repo --issues

/investigate

Deep context builder with relationship traversal

/investigate distributed caching

/briefing

Team knowledge dashboard with metrics

/briefing --days 7

/setup

Onboarding wizard for MCP connectivity and config

/setup

Quick Start

Step 1: Install the Plugin

claude plugin marketplace add norrietaylor/distillery
claude plugin install distillery

This installs all 15 skills. The plugin does not configure an MCP server automatically — run /setup (below) to add one. The recommended setup runs locally via uvx --from 'distillery-mcp[fastembed]>=0.6.0' distillery-mcp — a private, self-contained knowledge base on your machine, with on-device fastembed embeddings (no API key required). Requires Python 3.11+ and uv (install: curl -LsSf https://astral.sh/uv/install.sh | sh).

Step 2 (Optional): Use Jina or OpenAI Instead

The default fastembed provider runs offline with no API key. If you'd rather use a hosted embedding service, set DISTILLERY_EMBEDDING_PROVIDER and provide the matching API key:

# Jina (free tier at jina.ai)
export DISTILLERY_EMBEDDING_PROVIDER=jina
export JINA_API_KEY=jina_...

# Or OpenAI
export DISTILLERY_EMBEDDING_PROVIDER=openai
export OPENAI_API_KEY=sk-...

uvx inherits these from your shell environment. See distillery.yaml.example for the full provider configuration block (including Option C for fastembed model selection).

Restart Claude Code and run the onboarding wizard:

/setup

Try the Hosted Demo (Opt-In)

Want to evaluate without installing anything locally? Configure the hosted demo at distillery-mcp.fly.dev instead of a local server:

claude mcp add distillery --scope user --transport http --url https://distillery-mcp.fly.dev/mcp

Demo Server: distillery-mcp.fly.dev is for evaluation only. Do not store sensitive or confidential data.

See the Local Setup Guide for full configuration options, or deploy your own instance for team use.

Development

uv pip install -e ".[dev]"
# or
pip install -e ".[dev]"
pytest                              # run tests
mypy --strict src/distillery/       # type check
ruff check src/ tests/              # lint

See Contributing for the full guide.

License

Apache 2.0 — see LICENSE for details.

Available Tools

17 tools
distillery_classifyA

Apply a pre-computed classification to an existing entry.

USE WHEN: you have determined an entry's type and confidence via LLM or heuristic analysis and want to persist the classification result.

PARAMS:

  • entry_id (str, required): UUID of the entry to classify.

  • entry_type (str, required): Assigned type. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed]. Common intuitive aliases like "note" are NOT accepted but the error response includes a details.suggestion pointing to the canonical type (e.g. "note" -> "inbox").

  • confidence (float, required): Classification confidence (0-1). Entries below the configured threshold (default 0.6) go to pending_review.

  • reasoning (str, optional): Explanation of the classification decision.

  • suggested_tags (list[str], optional): Tags to merge onto the entry.

  • suggested_project (str, optional): Project to assign if entry has none.

RETURNS (success): { id: str, entry_type: str, status: str, ... } (full updated entry) RETURNS (error): { error: true, code: "NOT_FOUND" | "INVALID_PARAMS" | "INTERNAL", message: "...", details?: { field, provided, allowed, suggestion? } }

RELATED: distillery_resolve_review (to act on pending_review entries), distillery_list (with output_mode="review" to see the review queue)

Inputs: entry_id (required), confidence (required), entry_type (required), reasoning, +2 more.

distillery_configureA

Read or update a runtime configuration value.

USE WHEN: reading current thresholds/settings, or adjusting them at runtime without editing the config file directly.

PARAMS:

  • section (str, required): Config section path (dotted notation). Valid: [feeds, feeds.thresholds, defaults, classification].

  • key (str, required): Config key within the section. Valid keys by section: feeds: [user_agent]; feeds.thresholds: [alert, digest]; defaults: [dedup_threshold, dedup_limit, stale_days]; classification: [confidence_threshold, mode].

  • value (str | int | float | None, optional): New value. Omit to read the current value. When provided, must satisfy type and range constraints for the given key.

RETURNS (read): { section: str, key: str, value: any, message: str } RETURNS (write): { changed: bool, section: str, key: str, previous_value: any, new_value: any, disk_written: bool, message: str } RETURNS (error): { error: true, code: "INVALID_PARAMS" | "INTERNAL", message: "..." }

RELATED: distillery_watch (to manage feed sources), distillery_status (to review current system state)

Inputs: key (required), section (required), value.

distillery_correctA

Store a correction that supersedes an existing entry.

USE WHEN: an existing entry contains wrong information and you want to replace it with corrected content while preserving the audit trail.

PARAMS:

  • wrong_entry_id (str, required): UUID of the entry being corrected.

  • content (str, required): The corrected content.

  • entry_type (str, optional): Override type; inherited from original if omitted. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

  • author (str, optional): Override author; inherited from original if omitted.

  • project (str, optional): Override project; inherited from original if omitted.

  • tags (list[str], optional): Override tags; inherited from original if omitted.

  • metadata (dict, optional): Additional metadata for the correction entry.

RETURNS (success): { correction_entry_id: str, archived_entry_id: str } RETURNS (error): { error: true, code: "NOT_FOUND" | "INVALID_PARAMS" | "FORBIDDEN" | "INTERNAL", message: "..." }

RELATED: distillery_update (for non-breaking edits), distillery_relations (to view the 'corrects' relation)

Inputs: content (required), wrong_entry_id (required), tags, author, +3 more.

distillery_find_similarA

Find stored entries similar to the given text (cosine similarity).

USE WHEN: checking for duplicates or conflicts before storing, finding entries related to arbitrary text, or surfacing hidden connections to a known entry (entries that are similar but not yet linked via relations). Supports progressive disclosure modes.

PARAMS:

  • content (str, optional): Text to compare against stored entries. Required unless source_entry_id is provided. When both are set, content wins as the similarity probe.

  • threshold (float, optional, default=0.8): Cosine similarity cutoff (0-1).

  • limit (int, optional, default=10): Max results (1-200).

  • dedup_action (bool, optional, default=false): When true, includes dedup check with recommended action (create/skip/merge/link).

  • conflict_check (bool, optional, default=false): When true, includes conflict candidates with LLM evaluation prompts.

  • llm_responses (list[dict], optional): With conflict_check=true, evaluates LLM conflict verdicts. Each item: { entry_id: str, is_conflict: bool, reasoning: str }.

  • source_entry_id (str, optional): Anchor entry whose content is used as the similarity probe when content is omitted, and whose id is self-excluded from results. Required when exclude_linked=true. When set without content/dedup/conflict/accept_action, reuses the entry's STORED embedding (no re-embed, no embedding-budget spend).

  • source_entry_ids (list[str], optional): BATCH mode. Up to 50 seed ids. Reuses each seed's STORED embedding (no re-embed, no embedding-budget spend) and runs all similarity queries in ONE round-trip. Standalone — cannot be combined with content, source_entry_id, dedup_action, conflict_check, accept_action, or llm_responses (INVALID_PARAMS). Honours threshold, limit, and exclude_linked per seed; each seed always self-excludes.

  • exclude_linked (bool, optional, default=false): When true, filters out entries already linked to source_entry_id (or, in batch mode, to each seed) via entry_relations (any direction, any relation_type). Surfaces hidden connections.

  • accept_action (str, optional): When set, persists an entry_relations row from source_entry_id to each result above threshold. Valid: ['link' → related, 'merge' → merge_source, 'duplicate' → duplicate]. Requires source_entry_id. Idempotent via the unique (from_id, to_id, relation_type) index.

RETURNS (success, single/content): { results: [{ score: float, entry: {...} }], count: int, threshold: float, dedup?: { action: str, similar_entries: list }, conflict_candidates?: list, conflict_evaluation?: dict, excluded_linked_count?: int } Note: excluded_linked_count is present whenever source_entry_id is set or exclude_linked=true. It counts both linked-source exclusions (when exclude_linked=true) and the self-exclusion of source_entry_id itself (when source_entry_id == candidate); a non-zero value is therefore possible even with exclude_linked=false. RETURNS (success, batch / source_entry_ids): { results_by_seed: { "<seed_id>": { results: [{ score: float, entry: {...} }], count: int, excluded_count: int } }, seed_count: int, threshold: float } A seed with no stored embedding maps to an empty results list (not an error). excluded_count is best-effort (reported as 0 in batch mode). RETURNS (error): { error: true, code: "INVALID_PARAMS" | "NOT_FOUND" | "BUDGET_EXCEEDED" | "INTERNAL", message: "..." }

RELATED: distillery_store (stores with automatic dedup/conflict checks), distillery_search (for natural-language queries), distillery_relations (to inspect existing links between entries)

Inputs: limit, content, threshold, dedup_action, +6 more.

distillery_getA

Retrieve a single knowledge entry by its unique ID.

USE WHEN: fetching the full content and metadata of a specific entry (e.g. after finding its ID via search or list).

PARAMS:

  • entry_id (str, required): UUID of the entry to retrieve.

RETURNS (success): { id: str, content: str, entry_type: str, ... } RETURNS (error): { error: true, code: "NOT_FOUND" | "INTERNAL", message: "..." }

RELATED: distillery_search (to find entries by content), distillery_list (to browse entries by filters)

Inputs: entry_id (required).

distillery_gh_syncA

Sync GitHub issues and PRs into the knowledge base using a batched pipeline.

url: repository slug (owner/repo) or full GitHub URL. author: author field for created entries (default: gh-sync). project: optional project name to scope entries. background: when true, runs async and returns a job_id immediately.

Inputs: url (required), author, project, background.

distillery_ingest_docA

Ingest an arbitrary document (ADR, spec, decision, customer feedback).

USE WHEN: importing a standalone document — a markdown ADR/spec/RFC, a design decision, or a customer-feedback transcript/doc — so it becomes queryable knowledge with provenance. Distinct from distillery_store (single entry, semantic dedup) and from PreCompact transcripts: this chunks large text into multiple linked entries and deduplicates idempotently by content hash, so re-ingesting identical content adds no second entry.

PARAMS:

  • text (str, required): The full document text. Large text is split into multiple linked entries (relation_type="chunk").

  • author (str, required): Who is ingesting / owns this document.

  • doctype (str, optional, default="doc"): Document kind. Valid: [adr, spec, decision, feedback, doc]. Applied as both a "doctype/" tag and metadata.doctype for faceted retrieval.

  • source (str, optional): Provenance label (file path, Drive URL, etc.). Stored in metadata.source.

  • external_id (str, optional): Explicit dedup key. Defaults to the SHA-256 hash of text, making re-ingest idempotent.

  • title (str, optional): Human-readable title (stored in metadata.title).

  • project (str, optional): Project scope.

  • tags (list[str], optional): Extra namespaced tags.

  • metadata (dict, optional): Arbitrary extra metadata.

RETURNS (success): { entry_ids: list[str], count: int, doctype: str, external_id: str, chunked: bool, persisted: bool, dedup_action: "stored" | "skipped" } On re-ingest of identical content, persisted=false, dedup_action="skipped", and entry_ids points at the existing entries. RETURNS (error): { error: true, code: "INVALID_PARAMS" | "INTERNAL", message: "..." }

RELATED: distillery_store (single entry with semantic dedup), distillery_search (to retrieve ingested documents)

Inputs: text (required), author (required), tags, title, +5 more.

distillery_listA

List knowledge entries with optional filters and pagination (newest first).

USE WHEN: browsing or filtering entries without a semantic query. Use distillery_search instead when you have a natural-language question.

By default, only entries with status in (active, pending_review) are returned — archived entries are hidden. Pass status="archived" to list only archived entries, status="any" to include every status, or include_archived=true to add archived entries to the default view.

PARAMS:

  • entry_type (str | list[str], optional): Filter by type, or a list of types matched with OR (e.g. ["session", "reference"]) — pair with group_by to aggregate across several types in one call. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

  • author (str, optional): Filter by author.

  • project (str, optional): Filter by project scope.

  • tags (list[str], optional): Filter by tags (AND match).

  • status (str, optional): Filter by status. Valid: [active, pending_review, archived, any]. Default hides archived; use "any" to include all.

  • verification (str, optional): Filter by verification. Valid: [unverified, testing, verified].

  • source (str, optional): Filter by origin. Valid: [claude-code, manual, import, inference, documentation, external]. As a convenience, a URL-shaped value (starting with "http://" or "https://") is aliased to feed_url so source="https://hnrss.org/frontpage" matches feed items ingested from that source (same semantics as passing feed_url=...).

  • session_id (str, optional): Filter by session identifier.

  • date_from (str, optional): ISO 8601 lower bound on created_at.

  • date_to (str, optional): ISO 8601 upper bound on created_at.

  • limit (int, optional, default=20): Max entries to return (1-500).

  • offset (int, optional, default=0): Pagination offset.

  • tag_prefix (str, optional): Filter tags by namespace prefix.

  • output_mode (str, optional, default="summary"): Response shape. Valid: [full, summary, ids, review]. "summary" returns id/title/tags/project/ author/created_at plus a ~200-char content_preview (default — keeps responses small to conserve context). "full" returns entire content body. "ids" returns id/entry_type/created_at only. "review" filters to pending_review and enriches with confidence/classification_reasoning.

  • content_max_length (int, optional): Truncate content to N chars (full mode only).

  • stale_days (int, optional): Restrict to entries not accessed in N days (>= 1).

  • group_by (str, optional): Return grouped counts instead of entries. Valid: [entry_type, status, author, project, source, tags]. Mutually exclusive with output="stats".

  • output (str, optional): Set to "stats" for aggregate statistics. Mutually exclusive with group_by.

  • feed_url (str, optional): Filter to entries ingested from a registered feed source URL (matches metadata.source_url written by the poller). Use this to retrieve all items polled from e.g. "https://hnrss.org/frontpage".

  • include_archived (bool, optional, default=False): Include archived entries in the default view (same effect as status="any" when status is unset).

  • published_after (str, optional): ISO 8601 inclusive lower bound on metadata.published_at (the feed-item publication timestamp written by the poller). Use this to bound the /radar candidate set by the digest window.

  • published_before (str, optional): ISO 8601 inclusive upper bound on metadata.published_at.

  • include_evergreen (bool, optional, default=False): When False (default) and published_after/published_before is set, also drops entries flagged metadata.backfill=true so first-poll backfill items don't surface as "new intelligence". Set to True to surface older / evergreen items explicitly. See issue #444.

  • structural (list[str], optional): Surface entries with specific graph anomalies relative to entry_relations. Accepted values: ["orphans"] — entries that do not appear as either endpoint of any relation row. Unknown values yield INVALID_PARAMS. Combines (AND) with every other filter (project, tags, status, date range, stale_days, etc.) — orphans are first restricted by those filters, then the no-relations predicate is applied.

RETURNS (success): { entries: list, count: int, total_count: int, limit: int, offset: int, output_mode: str } — when structural is set, the payload additionally includes structural_filter (comma-joined applied filters, e.g. "orphans"). Existing fields are unchanged. RETURNS (error): { error: true, code: "INVALID_PARAMS" | "INTERNAL", message: "..." }

RELATED: distillery_search (for semantic search), distillery_status (for lightweight server health/metadata)

Inputs: tags, limit, author, offset, +20 more.

distillery_relationsA

Manage typed relations between knowledge entries.

USE WHEN: linking entries together (e.g. marking one as blocking another, citing a reference, or flagging duplicates), walking the relation graph from a seed entry to surface multi-hop neighbours, or computing graph metrics (bridges, communities) on the relations subgraph.

PARAMS:

  • action (str, required): Operation. Valid: [add, get, remove, traverse, metrics, promote_entities].

  • from_id (str, required for add): Source entry UUID.

  • to_id (str, required for add): Target entry UUID.

  • relation_type (str, required for add, optional for get/traverse): Relation type. Valid: [link, corrects, supersedes, related, blocks, depends_on, citation, duplicate, merge_source, sync_source, mentions, chunk].

  • weight (float, optional for add): Edge strength (e.g. interest/engagement magnitude). On a re-assert of an existing edge, supplied attributes are upserted.

  • valid_at / invalid_at (str ISO 8601, optional for add): Bi-temporal validity window — when the relationship became / stopped being true (invalid_at null = current).

  • metadata (object, optional for add): Arbitrary per-edge attributes (JSON).

  • entry_id (str, required for get/traverse, required for metrics scope='ego'): Entry UUID to query relations for (BFS root for traverse / ego-graph).

  • direction (str, optional for get/traverse, default="both"): Filter direction. Valid: [outgoing, incoming, both].

  • relation_id (str, required for remove): UUID of the relation to delete.

  • hops (int, optional for traverse, default=2): BFS depth, capped at [1, 3].

  • metric (str, required for metrics): Graph metric to compute. Valid: [bridges, communities, constraint, link_prediction, orphans]. Requires the [graph] optional extra.

  • scope (str, optional for metrics, default="global"): Subgraph scope. Valid: [global, ego]. "ego" requires entry_id.

  • limit (int, optional for metrics, default=10): top-k results. bridges = entries by betweenness centrality; communities = K largest communities; constraint = entries by lowest Burt constraint (strongest structural-hole brokers); link_prediction = top predicted edges by Adamic-Adar (pass entry_id to score adjacencies for one entry); orphans = sample (<=50) of entry IDs absent from the relations graph (unlinked entries — feeds a linking / gap-scan pass).

  • project / tags / date_from / date_to (optional, metrics global scope): restrict the entries whose relations participate in the graph.

RETURNS (success): { relation_id: str, from_id: str, to_id: str, relation_type: str, weight: float | null, valid_at: str | null, invalid_at: str | null, metadata: object | null } (add) or { entry_id: str, relations: list, count: int } (get) or { relation_id: str, removed: bool } (remove) or { action: "traverse", root: str, hops: int, direction: str, relation_type: str | null, nodes: [{id: str, depth: int}], edges: [{from_id, to_id, relation_type}], node_count: int, edge_count: int } (traverse) or { action: "metrics", metric: str, scope: str, node_count: int, edge_count: int, total_entries: int, graph_node_count: int, orphan_rate: float, results: list, count: int, computed_at: str, cache_hit: bool } (metrics). orphan_rate = 1 - graph_node_count/total_entries (graph-health signal; 0.0 when total_entries is 0). Or { action: "promote_entities", entities_created: int, entities_reused: int, mentions_created: int, threshold: int } (promote_entities). Scans entity/* and tech/* tags and promotes any canonical tag meeting the configured tags.entity_promotion_threshold to an ENTITY entry node, linking each tagged entry with a mentions edge. Idempotent. RETURNS (error): { error: true, code: "NOT_FOUND" | "INVALID_PARAMS" | "INTERNAL", message: "..." }

RELATED: distillery_correct (creates 'corrects' relations automatically), distillery_find_similar (to discover related entries)

Inputs: action (required), hops, tags, limit, +15 more.

distillery_resolve_reviewA

Resolve a pending-review entry by approving, reclassifying, or archiving it.

USE WHEN: acting on entries in the review queue (entries with status=pending_review from low-confidence classifications).

PARAMS:

  • entry_id (str, required): UUID of the pending-review entry.

  • action (str, required): Resolution action. Valid: [approve, reclassify, archive].

  • new_entry_type (str, optional): Required when action="reclassify". Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

  • reviewer (str, optional): Reviewer identity for audit metadata.

RETURNS (success): { id: str, status: str, ... } (full updated entry). When the requested action is a no-op (e.g. approve on an already-active entry), the response also includes { already_in_state: true } and the entry is returned unchanged (version is NOT bumped, reviewed_at / archived_at are NOT rewritten). RETURNS (error): { error: true, code: "NOT_FOUND" | "INVALID_PARAMS" | "FORBIDDEN" | "INTERNAL", message: "..." }

RELATED: distillery_classify (to classify entries), distillery_list (with output_mode="review" to see the queue)

Inputs: action (required), entry_id (required), reviewer, new_entry_type.

distillery_statusA

Return a lightweight in-protocol health/metadata probe.

USE WHEN: verifying MCP connectivity (e.g. from the /setup wizard) without relying on the HTTP-only /health endpoint. Works uniformly on stdio and HTTP transports.

PARAMS: (none)

RETURNS (success): { status: "ok", version: str, # distillery package version build_sha: str, # git SHA (or "dev") transport: "stdio" | "http" | "unknown", tool_count: int, # number of registered MCP tools store: { entry_count: int | null, db_size_bytes: int | null }, embedding_provider: str, # model name or provider class name last_feed_poll: { source_count: int, last_poll_at: str | null }, uptime_seconds?: int # seconds since server startup }

RELATED: distillery_list (for entry counts, filtering, and per-group aggregates), distillery_configure (to inspect/adjust runtime configuration)

Inputs: no named parameters.

distillery_storeA

Store a new knowledge entry and return its ID with dedup/conflict information.

USE WHEN: capturing a new piece of knowledge (session notes, bookmarks, meeting minutes, ideas, etc.) into the Distillery store.

PARAMS:

  • content (str, required): The knowledge content to store.

  • entry_type (str, required): Entry classification. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

  • author (str, required): Who authored this entry.

  • project (str, optional): Project scope for the entry.

  • tags (list[str], optional): Tags for categorisation; supports namespaced tags (e.g. "topic/ai").

  • metadata (dict, optional): Arbitrary key-value metadata. Some entry types REQUIRE specific metadata keys (TYPE_METADATA_SCHEMAS); omitting them returns INVALID_PARAMS naming the missing/invalid field:

    • person: expertise (list[str])

    • project: repo (str)

    • digest: period_start, period_end (str)

    • github: repo, ref_type, ref_number; ref_type in [issue, pr, discussion, release]

    • feed: source_url, source_type; source_type in [rss, github] Other types (session, bookmark, minutes, meeting, reference, idea, inbox) accept arbitrary metadata.

  • source (str, optional, default="claude-code"): Origin of the entry. Valid: [claude-code, manual, import, inference, documentation, external].

  • session_id (str, optional): Opaque session identifier for grouping related entries.

  • dedup_threshold (float, optional, default=config): Cosine similarity threshold (0-1) for near-duplicate warnings.

  • dedup_limit (int, optional, default=config): Max duplicates to report.

  • verification (str, optional, default="unverified"): Verification status. Valid: [unverified, testing, verified].

  • expires_at (str, optional): ISO 8601 datetime; entries past expiry appear in stale results.

  • output_mode (str, optional, default="full"): Response verbosity. Valid: [full, summary]. Use "summary" for bulk imports to skip dedup/conflict checks.

  • include_conflict_prompt (bool, optional, default=False): When true, each conflict candidate carries the ~1–2 KB conflict_prompt LLM template required to round-trip through distillery_find_similar(conflict_check=true). Defaults to false to keep store responses small (issue #348).

RETURNS (success): { entry_id: str, persisted: bool, dedup_action: str, conflicts?: list[{entry_id, content_preview, similarity_score, conflict_prompt?}], warnings?: list } RETURNS (error): { error: true, code: "INVALID_PARAMS" | "BUDGET_EXCEEDED" | "INTERNAL", message: "..." }

RELATED: distillery_find_similar (for pre-store dedup checks), distillery_correct (to supersede an existing entry)

Inputs: author (required), content (required), entry_type (required), tags, +10 more.

distillery_store_batchA

Batch-store multiple knowledge entries in one call (no dedup/conflict checks).

USE WHEN: bulk-importing entries (e.g. GitHub history sync, migration, backfill) where per-entry dedup is unnecessary and throughput matters.

PARAMS:

  • entries (list[dict], required): List of entry dicts. Each must have:

    • content (str, required): The knowledge content.

    • author (str, required): Who authored this entry.

    • entry_type (str, optional, default="inbox"): Entry classification. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

    • tags (list[str], optional): Tags for categorisation.

    • metadata (dict, optional): Arbitrary key-value metadata.

    • source (str, optional, default="claude-code"): Origin of the entry.

    • project (str, optional): Per-entry project override.

  • project (str, optional): Default project applied to entries lacking one.

RETURNS (success): { entry_ids: list[str | None], # per-item ids; null for failed items count: int, # number actually persisted results: list[dict], # per-item status preserving input order }

  • Successful items: { entry_id, persisted: true, dedup_action: "stored" }

  • Failed items: { entry_id: null, persisted: false, error: { code, message, details? } } Validation failures on individual items no longer abort the batch — valid entries are persisted and failures are reported per item in results (issue #364). Iterate results to discover failures. RETURNS (error): { error: true, code: "INVALID_PARAMS" | "BUDGET_EXCEEDED" | "INTERNAL", message: "..." } Top-level error is returned only for schema-level problems (entries not a list, budget exhaustion, persistence failure).

RELATED: distillery_store (single entry with dedup/conflict checks), distillery_watch (add feed sources with optional history sync)

Inputs: entries (required), project.

distillery_sync_statusA

Check the status of background sync jobs.

job_id: look up a specific job by ID. source_url: list jobs for a specific source URL. If neither is provided, lists all recent jobs.

Inputs: job_id, source_url.

distillery_updateA

Update one or more fields on an existing knowledge entry.

USE WHEN: modifying an entry's content, type, tags, status, or other mutable fields. At least one updatable field must be provided.

PARAMS:

  • entry_id (str, required): UUID of the entry to update.

  • content (str, optional): Replacement content.

  • entry_type (str, optional): New type. Valid: [session, bookmark, minutes, meeting, reference, idea, inbox, github, person, project, digest, feed].

  • author (str, optional): New author.

  • project (str, optional): New project scope.

  • tags (list[str], optional): Replacement tag list.

  • status (str, optional): New status. Valid: [active, pending_review, archived].

  • verification (str, optional): New verification. Valid: [unverified, testing, verified].

  • metadata (dict, optional): Replacement metadata dict.

  • session_id (str, optional): Session identifier for grouping.

  • expires_at (str, optional): ISO 8601 datetime; pass null to clear.

RETURNS (success): { id: str, content: str, entry_type: str, ... } (full updated entry) RETURNS (error): { error: true, code: "NOT_FOUND" | "INVALID_PARAMS" | "FORBIDDEN" | "INTERNAL", message: "..." }

RELATED: distillery_correct (to supersede rather than edit), distillery_get (to read before updating)

Inputs: entry_id (required), tags, author, status, +7 more.

distillery_watchA

Manage monitored feed sources for ambient intelligence.

USE WHEN: listing, adding, or removing RSS/GitHub feed sources that Distillery polls for new content.

PARAMS:

  • action (str, required): Operation to perform. Valid: [list, add, remove].

  • url (str, required for add/remove): Feed URL or GitHub owner/repo slug.

  • source_type (str, required for add): Feed type. Valid: [rss, github].

  • label (str, optional): Human-readable label for the source.

  • poll_interval_minutes (int, optional, default=60): Polling frequency in minutes.

  • trust_weight (float, optional, default=1.0): Source trust weight (0-1).

  • thresholds (object, optional): Per-source overrides for the global feeds.thresholds values. Mapping with optional float keys alert and/or digest in [0.0, 1.0] (when both set, digest <= alert). When omitted, the global cutoffs apply (pre-existing behaviour). Use this to raise the bar for noisy aggregators (HN/Lobsters/Reddit) since trust_weight only attenuates downward.

  • sync_history (bool, optional, default=false): When true and source_type is "github", kicks off an async background import of historical issues/PRs (returns immediately with job_id; use distillery_sync_status to check progress).

  • purge (bool, optional, default=false): When true and action is "remove", archives all entries from the removed source (soft-delete). Returns the count of archived entries in purged_entries.

  • probe (bool, optional, default=true): When adding, lightly probe the URL for reachability (HEAD with GET fallback, short timeout). Returns an INVALID_PARAMS error (with details.probe_failed=true) if the probe fails.

  • force (bool, optional, default=false): When adding, persist the source even if the reachability probe fails (useful for sites that block HEAD but work via the poller).

  • mode (str, optional, github only): Which content-bearing surface to poll. Valid: [releases, events]. Defaults to "releases" (one body-bearing entry per release). "events" is the opt-in contentless firehose.

RETURNS (success): { sources: list, count: int } (list) or { added: dict, sources: list, sync_job?: dict } (add) or { removed_url: str, removed: bool, sources: list, purged_entries?: int } (remove) RETURNS (error): { error: true, code: "INVALID_PARAMS" | "CONFLICT" | "INTERNAL", message: "..." }

RELATED: distillery_configure (to adjust feed thresholds), distillery_store_batch (for bulk entry ingestion)

Inputs: action (required), url, mode, force, +8 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 17 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: store vs store_batch vs ingest_doc handle different ingestion paths, get/update/correct handle modification, list/search/find_similar handle retrieval with different modes, and watch/gh_sync/relations cover external sources and graph operations. The descriptions explicitly call out when to use each over related tools.

    Naming Consistency4/5

    All tools share the 'distillery_' prefix and generally follow a verb-first pattern (store, get, update, list, search, watch, configure, status). Mixed single verbs and compound verb_noun forms (store_batch, ingest_doc, find_similar, resolve_review, gh_sync) are consistent in style; 'relations' is a noun-based command but remains clear. No camelCase or chaotic mixing.

    Tool Count5/5

    17 tools is slightly above the typical 3-15 range but fully justified given the server's broad scope: entry lifecycle, semantic search, duplicate detection, relation graph management, feed monitoring, GitHub sync, and configuration. Each tool addresses a distinct aspect of the domain without redundancy.

    Completeness4/5

    The surface covers create (store, ingest), read (get, list, search), update, and archival (via correct and resolve_review's archive action), plus relation management and feed/sync operations. The only notable gap is the lack of a direct hard-delete tool for entries; archiving effectively serves as a soft delete, but a permanent removal option is missing.

    Maintenance

    ActivityMaintained
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables semantic search and retrieval of personal and team knowledge from connected sources like Slack, Gmail, Google Drive, and Dropbox, with the ability to save new information for future recall.
      Apache 2.0
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI agents to capture, classify, synthesize, and retrieve persistent knowledge through MCP and REST APIs, so multiple agents can share memory and improve across sessions. It provides full-text, semantic, and hybrid search with token-optimized context retrieval from sources like Obsidian, code, transcripts, and terminal sessions.
      10 npm
      MIT