On this page12 sections
- Prerequisites
- 1. Start pgvector
- 2. Wire vector storage into the bookshelf
- 3. Boot the bookshelf with the big dataset
- 4. One strategy at a time
- 5. Several strategies in one pass
- 5.5 Switch to the graph dataset for the GraphRAG strategies
- 5.6 Graph-aware sampling with composable stratifiers
- 5.7 Path chains and semantic record ranking
- 6. Recall by category
- 7. Teardown
- Where to go next
Analysis Quickstart
The hands-on entry to chapter IX. Part 2 of the Quickstart — once the bookshelf is running with its 5,000-book dataset, this guide brings up postgres+pgvector, points the analysis tools at it, and walks all nine built-in summary strategies end to end — five field-level strategies on the large dataset, then four GraphRAG-aware strategies on the graph dataset (500 books with author + genre + intentional gaps).
If you prefer the architecture first, read Retrieval & GraphRAG overview (next in the chapter) and come back here when you’re ready to run something.
You’ll spend about twenty minutes: ~3 on infrastructure, ~7 on the field-level tour, ~7 on the GraphRAG tour, ~3 on graph-aware sampling and teardown.
Prerequisites
- Part 1 of the Quickstart running locally — specifically the bookshelf template scaffolded as
bookshelf-demo(rune new bookshelf-demo --template bookshelf), which ships the 5,000-book dataset this guide leans on. - Docker (for one container) and a free port on
5432.
The analysis tools (analysis_ingest, analysis_summarize, analysis_query, analysis_act, analysis_clear, analysis_store) are gated by requiresVectorStorage. Without pgvector, they don’t show up in tools/list — that’s by design (see Analysis Memories).
1. Start pgvector
Drop this docker-compose.yml into bookshelf-demo/ (next to src/server.ts) and run docker compose up -d:
services:
pgvector:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: bookshelf
POSTGRES_PASSWORD: bookshelf
POSTGRES_DB: bookshelf
ports:
- '5432:5432'
Then apply mcp-rune’s migrations against the new database. The framework ships migrations as data. (ANALYSIS_ENABLED below is only an input to this snippet’s own filter expression — the framework itself never reads it.)
DATABASE_URL=postgres://bookshelf:bookshelf@localhost:5432/bookshelf \
ANALYSIS_ENABLED=true \
npx tsx -e "
import { Pool } from 'pg';
import { migrations } from '@mcp-rune/mcp-rune/db/migrations';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const needed = migrations.filter(m => m.feature === 'core' || process.env.ANALYSIS_ENABLED === 'true');
for (const m of needed) { await pool.query(m.up); console.log('applied', m.name); }
await pool.end();
"
That creates the three tables the feature relies on: analysis_memories (vectors + JSONB), ingested_records (raw rows + optional per-record embeddings), and ingested_edges (the relationship graph).
2. Wire vector storage into the bookshelf
Open bookshelf-demo/src/server.ts and add the storage init before createServer is called:
import { Pool } from 'pg'
import { vectorStorage } from '@mcp-rune/mcp-rune/runtime'
import { createPgvectorAdapter } from '@mcp-rune/mcp-rune/runtime/integrations/pgvector'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
vectorStorage.initVectorStorage({
adapter: createPgvectorAdapter({ pool }),
serviceName: 'bookshelf-mcp',
version: '1.0.0'
})import { Pool } from 'pg'
import { vectorStorage } from '@mcp-rune/mcp-rune/runtime'
import { createPgvectorAdapter } from '@mcp-rune/mcp-rune/runtime/integrations/pgvector'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
vectorStorage.initVectorStorage({
adapter: createPgvectorAdapter({ pool }),
serviceName: 'bookshelf-mcp',
version: '1.0.0'
})Storage init is only half the gate. The bookshelf template registers just the data and form-strategy tool classes, so the analysis tools also need to be spread into the registry’s toolClasses, with analysisStorageEnabled telling the registry the analysis adapter is live. In the same src/server.ts, extend the existing new ToolRegistry({ … }) call:
import {
ToolRegistry,
DATA_TOOL_CLASSES,
FORM_STRATEGY_TOOL_CLASSES,
ANALYSIS_TOOL_CLASSES
} from '@mcp-rune/mcp-rune/tools'
import { vectorStorage } from '@mcp-rune/mcp-rune/runtime'
const toolRegistry = new ToolRegistry({
toolClasses: {
...DATA_TOOL_CLASSES,
...FORM_STRATEGY_TOOL_CLASSES,
...ANALYSIS_TOOL_CLASSES // add: the analysis tools must be in the class map
},
// …keep the template's existing config here (models, serverContext, createApiClient)…
analysisStorageEnabled: vectorStorage.isVectorStorageEnabled() // add: lets requiresAnalysisStorage tools register
})import {
ToolRegistry,
DATA_TOOL_CLASSES,
FORM_STRATEGY_TOOL_CLASSES,
ANALYSIS_TOOL_CLASSES
} from '@mcp-rune/mcp-rune/tools'
import { vectorStorage } from '@mcp-rune/mcp-rune/runtime'
const toolRegistry = new ToolRegistry({
toolClasses: {
...DATA_TOOL_CLASSES,
...FORM_STRATEGY_TOOL_CLASSES,
...ANALYSIS_TOOL_CLASSES // add: the analysis tools must be in the class map
},
// …keep the template's existing config here (models, serverContext, createApiClient)…
analysisStorageEnabled: vectorStorage.isVectorStorageEnabled() // add: lets requiresAnalysisStorage tools register
})When the adapter is missing — say you forget DATABASE_URL and skip init — initVectorStorage returns false, isVectorStorageEnabled() stays false, and the six analysis tools simply stay out of tools/list. No crash, no noisy error path.
Behind on migrations? If
DATABASE_URLis set but the analysis migrations haven’t been applied (e.g. after upgrading mcp-rune), the analysis tools register but fail at call time withcolumn "embedding" does not exist. Guard against it by callingassertMigrationsCurrent(pool, { features: ['core', 'analysis'] })beforeinitVectorStorage— it fails fast at startup with the pending list andRun: npm run db:migrate. See Database setup → Detecting drift at startup.
3. Boot the bookshelf with the big dataset
DATABASE_URL=postgres://bookshelf:bookshelf@localhost:5432/bookshelf \
BOOKSHELF_DATASET=large \
npx @modelcontextprotocol/inspector -- npx tsx src/server.ts
Inside the Inspector, confirm the analysis tools are now visible — analysis_ingest, analysis_summarize, analysis_query, analysis_act, analysis_clear, analysis_store should all appear alongside the CRUD tools. If they don’t, double-check initVectorStorage was called with a live adapter and that the registry got both halves from step 2: the ANALYSIS_TOOL_CLASSES spread and analysisStorageEnabled.
4. One strategy at a time
analysis_ingest downloads records once and writes a per-page summary through whichever strategy you pick. After the first ingest, every later call goes through analysis_summarize, which re-runs strategies against the already-stored rows without hitting the API again — the whole point of the two-tool split.
distribution (the default)
analysis_ingest({
analysis_id: "tour",
model: "book",
ingest_all: true,
per_page: 50
})
// → "Stored 5000 record(s) (8 fields per record) across 100 page(s)."
// → 100 memories written with category "page_summary:distribution".
coverage
analysis_summarize({
analysis_id: "tour",
strategy: "coverage",
max_records: 5000
})
// → category "page_summary:coverage", flags `notes` and `rating`
// on pages where the missing rate crosses 50%.
anomaly
analysis_summarize({
analysis_id: "tour",
strategy: "anomaly",
max_records: 5000
})
// → category "page_summary:anomaly", surfaces the ~1% of records
// with extreme `pages` values (|z| > 2) plus rare enum values.
temporal
analysis_summarize({
analysis_id: "tour",
strategy: "temporal",
max_records: 5000
})
// → category "page_summary:temporal", buckets `created_at` by week,
// reports the recency window, and flags the 60-day gap baked into
// the generator.
entity-extraction
analysis_summarize({
analysis_id: "tour",
strategy: "entity-extraction",
max_records: 5000
})
// → category "page_summary:entity-extraction", top-N `genre_id`
// crosswalk for the page.
5. Several strategies in one pass
The same lens menu is available at ingest time. Run all five at once on a fresh analysis_id to see what a multi-strategy ingest looks like end to end:
analysis_ingest({
analysis_id: "tour-multi",
model: "book",
ingest_all: true,
per_page: 50,
summary_strategies: [
"distribution",
"coverage",
"anomaly",
"temporal",
"entity-extraction"
]
})
// → Stored 5000 records across 100 pages.
// → ~500 memories written: one per page per strategy that passed its
// `appliesTo` check (anomaly needs ≥4 records per page; temporal
// needs ≥1 ISO-date field; entity-extraction needs ≥1 *_id field).
summary_strategy (singular) and summary_strategies (plural) are mutually exclusive — passing both fails fast at validation time.
5.5 Switch to the graph dataset for the GraphRAG strategies
The remaining four strategies — relationship-coverage, concept-touch, rule-violation, semantic-cluster — read from edges, embeddings, and the domain registry. None of those exist on the flat large dataset. Restart the bookshelf with the graph dataset, which adds author and genre models with proper foreign keys, two DomainConcepts (reading-pipeline, catalogue), and two BusinessRules (completed-books-need-rating, books-need-author). The graph generator deliberately leaves ~5% of books without an author_id and ~15% of completed books without a rating so every GraphRAG strategy has real signal to surface.
Stop the running Inspector, then:
DATABASE_URL=postgres://bookshelf:bookshelf@localhost:5432/bookshelf \
BOOKSHELF_DATASET=graph \
npx @modelcontextprotocol/inspector -- npx tsx src/server.ts
In the Inspector, run a fresh ingest. The new args are hop_depth: 1 (follow declared belongsTo associations) and embed_records: true (the default — embeddings are needed for semantic-cluster):
analysis_ingest({
analysis_id: "graphrag",
model: "book",
ingest_all: true,
hop_depth: 1,
embed_records: true,
summary_strategies: [
"relationship-coverage",
"concept-touch",
"rule-violation",
"semantic-cluster"
]
})
// → Stored 500 record(s) across 10 page(s). Hopped: 10 author, 6 genre.
// → ~40 memories written (one per page per strategy, modulo appliesTo gates).
Four strategy lenses, walked one at a time:
relationship-coverage
analysis_query({
analysis_id: "graphrag",
mode: "semantic",
category: "page_summary:relationship-coverage",
query: "missing author edge"
})
// → top finding names belongsTo:author coverage % per page (around 94%)
// and the 1–3 gap IDs per page that lack an author edge.
The relationship-coverage guide explains the per-edge-type stats; the takeaway here is that the gap records surface directly in the finding text so the LLM can analysis_query mode:"filter" where:{id:[...]} to inspect them.
concept-touch
analysis_query({
analysis_id: "graphrag",
mode: "semantic",
category: "page_summary:concept-touch",
query: "catalogue gap"
})
// → finding contains "catalogue → [author, genre]: 47/50 (94%);
// per-target author=47, genre=50". The 3-record gap matches the
// bookshelf's missing-author rate.
reading-pipeline (book + genre) lands at 100% because every book has a genre. catalogue (book + author + genre) lands lower because of the missing-author records. See the concept-touch guide.
rule-violation
analysis_query({
analysis_id: "graphrag",
mode: "semantic",
category: "page_summary:rule-violation",
query: "completed books missing rating"
})
// → finding names the failing rule + the first few IDs per page:
// "completed-books-need-rating (warning): N/50 failed (e.g. b127, b223)".
// "books-need-author (error): 2/50 failed (e.g. b34, b48)".
See the rule-violation guide.
semantic-cluster
analysis_query({
analysis_id: "graphrag",
mode: "semantic",
category: "page_summary:semantic-cluster",
query: "natural book groupings"
})
// → finding names the cluster representatives by title hint:
// "cluster 1 (size 14, mean dist 0.18): rep b3 \"Clean Patterns #3\""
// …
See the semantic-cluster guide.
5.6 Graph-aware sampling with composable stratifiers
With edges + embeddings + concepts in play, analysis_query mode:"sample" can balance a sample across multiple graph dimensions at once. The stratifiers param accepts up to 3 entries that compose with the existing where / proximity / stratify_by partitioning:
analysis_query({
mode: "describe",
analysis_id: "graphrag"
})
// → A "Graph dimensions available" section lists registered concepts,
// observed edge types, and embedding coverage %. (describe takes no
// model param — it documents the whole session, listing every model.)
A three-stratifier sample, pre-filtered to completed books:
analysis_query({
mode: "sample",
analysis_id: "graphrag",
sample_size: 12,
where: { status: "completed" },
stratifiers: [
{ kind: "concept", concept: "catalogue" },
{ kind: "edge", edge_type: "belongsTo:genre", bucket: "present" },
{ kind: "cluster", k: 3 }
],
model: "book"
})
// → 12 completed-status books, balanced across:
// - concept (catalogue touched vs not)
// - edge presence (has genre edge vs not — uniform on bookshelf)
// - semantic cluster (3 anchor-nearest groups)
When kind: "cluster" is requested on a session that was ingested with embed_records: false, the tool auto-back-fills missing embeddings before sampling. No re-ingest required.
5.7 Path chains and semantic record ranking
The hop-expanded session unlocks the two newest query surfaces. path mode walks the typed edge graph between two records — ask how two books are connected (swap in any two ids you saw in earlier results):
analysis_query({
analysis_id: "graphrag",
mode: "path",
from_model: "book",
from_id: "b34",
to_model: "book",
to_id: "b17",
max_depth: 4
})
// → "1 path(s) between book/b34 and book/b17 (shortest: 2 edge(s)):
// - book/b34 —belongsTo:author→ author/a7 ←belongsTo:author— book/b17"
Traversal is undirected over the directed edges, and each step renders with the direction it used — the chain above reads “b34 and b17 share author a7”. When nothing connects within max_depth (default 4, capped at 6), the tool says so and suggests re-ingesting with a deeper hop_depth.
semantic with target: "records" ranks the ingested rows themselves by meaning — and composes with where for hybrid semantic + filter retrieval in one SQL statement. Because graphrag holds three models, the model param is required (the error would name the candidates):
analysis_query({
analysis_id: "graphrag",
mode: "semantic",
target: "records",
model: "book",
query: "space exploration epics",
where: { status: "completed" },
min_similarity: 0.3,
top_k: 5
})
// → "Top 5 record(s) by similarity to \"space exploration epics\":
// - [61.2%] {\"id\":\"b201\",\"title\":\"Voyages Beyond #12\",\"status\":\"completed\",…}"
// …plus an embedding-coverage footer when not every record is embedded.
Only the query string pays for an embed. Because the §5.5 ingest ran with embed_records: true (the default), every record was embedded at write time; on a session ingested with embed_records: false, each records-targeted query back-fills up to 500 missing embeddings before ranking. min_similarity is an optional floor — omit it for rank-only results.
6. Recall by category
Every memory is written with category: "page_summary:<strategy>", which lets analysis_query mode: semantic filter cleanly:
analysis_query({
analysis_id: "tour-multi",
mode: "semantic",
category: "page_summary:anomaly",
query: "outlier page count"
})
// → only the anomaly memories rank, no distribution noise in the results.
Drop the category to cross-cut all strategies:
analysis_query({
analysis_id: "tour-multi",
mode: "semantic",
query: "missing metadata"
})
// → ranks coverage memories highest, with relevant distribution and
// anomaly memories trailing.
7. Teardown
analysis_clear cascade-deletes both tables for the session:
analysis_clear({ analysis_id: "tour" })
analysis_clear({ analysis_id: "tour-multi" })
// → "Cleared 5000 ingested record(s) and N finding(s) …"
docker compose down -v drops the container and the volume — by design the analysis loop is opt-in infrastructure, not a permanent dependency.
Where to go next
- Summary Strategies — full catalog with links to each strategy’s deep-dive guide, the
SummaryStrategycontract, and a walk-through for shipping your own strategy via anApiExtension. - Per-strategy guides linked from the Summary Strategies catalog — one file each for the nine built-ins; explains the algorithm, inputs consumed, output shape, and edge cases.
- Analysis Memories — the full feature reference: ingest modes, the six-mode
analysis_queryAPI, stratified sampling,analysis_act, lifecycle and retention. - Proximity Sampling — date-windowed bucketed sampling for the
analysis_query mode: samplepath. - Domain Knowledge Guide — how
DomainConceptandBusinessRulefeedconcept-touchandrule-violation.