Skip to main content
Glama
josephruocco

posthog-context-mcp

by josephruocco

posthog-context-mcp

An MCP server that hands PostHog's JavaScript SDK docs to a coding agent, plus an eval that scores what comes back.

Ask it how to capture a custom event in React. It returns around 680 tokens of deduplicated, cited passages. Wire a retriever straight to a tool and dump the top five chunks, and the same question costs about 2,650 tokens. Both find a correct doc every time.

the CLI comparing a naive dump against the assembled block

naive vs engineered

metric

naive top-5

naive (budgeted)

engineered

better

Hit rate

100%

79%

100%

higher

Precision

61%

69%

88%

higher

Restraint (1-source cases)

21%

57%

86%

higher

Wrong sources per question

1.14

0.25

0.29

lower

Mean context tokens

2,655

461

678

lower

28 hand-labelled questions. naive top-5 concatenates the top five chunks. naive (budgeted) takes that same ranking and stops at the 800-token ceiling, so the engineered config gets no credit for being allowed to stop. Truncating early costs it 21% of the answers.

Restraint is the metric worth explaining. Fourteen of the questions have exactly one doc that answers them. On those, I score whether the system returned that doc alone. Bringing a second source counts as a failure even when hit rate and precision look fine. Any eval that only rewards finding the answer will tell you a system returning the whole index is perfect.

How it works

Ingest (posthog_context/ingest.py) sparse-clones PostHog/posthog.com and reads the MDX. PostHog composes their docs from shared _snippets/ fragments, so a section like "Capturing events" is two lines on disk: an import and <WebSendEvents />. The loader resolves that import graph recursively before it chunks anything. Skip that step and you index empty sections where the useful content should be.

Retrieval (retrieval.py) runs BM25 over two fields, heading and body, scored separately and summed. Tokens get stemmed and camelCase gets split, so usePostHog matches a query about posthog and "Capturing custom events" matches someone asking how to capture a custom event. No vector database.

Assembly (assemble.py) is where the work is. Seven steps, and only the first one adds anything: retrieve 24 candidates, re-score them for task fit, cut everything below 42% of the top passage, fold near-duplicates together, cap the answer at four distinct docs, fill the token budget in value order, then sort into reading order with a citation on every passage.

Server (server.py) exposes three tools over stdio.

Eval (eval/) runs 28 cases through three configs and writes the chart.

Related MCP server: mobile-docs-mcp

Setup

python3 -m venv .venv && source .venv/bin/activate && pip install -e .
python -m posthog_context.ingest

The ingest pulls about 11MB of markdown into data/, which is gitignored, and builds the index. PostHog's /contents/ directory is MIT licensed and the rest of that repo is not, so this reads only /contents/ and vendors nothing.

The tools

tool

what it's for

how_do_i(task, token_budget=800)

The one that matters. Give it a task in plain language and it returns assembled, cited, budgeted context.

search_posthog_docs(query, k=5)

Ranked snippets with no assembly, for when an agent wants to see what documentation exists.

get_posthog_doc(path_or_slug)

A whole page, for when it genuinely needs all of it.

Connect it to an agent

Claude Desktop reads ~/Library/Application Support/Claude/claude_desktop_config.json on macOS. Cursor reads .cursor/mcp.json in your project. Same shape either way:

{
  "mcpServers": {
    "posthog-context": {
      "command": "/absolute/path/to/posthog-mcp-mini/.venv/bin/python",
      "args": ["-m", "posthog_context.server"],
      "cwd": "/absolute/path/to/posthog-mcp-mini"
    }
  }
}

Use absolute paths, and run the ingest first. The server refuses to start without an index rather than quietly serving an empty one.

Run the eval

python -m eval.run

It prints the table, names every case that failed, and writes eval/out/comparison.png.

The loader asserts that the number of parsed cases matches the number the file declares, and that every gold label points at a doc the index actually contains. A loader that silently skips a malformed case still gives you a confident number, just for a smaller experiment you didn't run, and nothing downstream can tell you that happened.

Each module also checks itself:

python -m posthog_context.ingest      # 6 assertions on chunk quality
python -m posthog_context.retrieval   # ranking sanity
python -m posthog_context.assemble    # budgets are never exceeded

Scope

The JavaScript and Web SDK, plus client-side capture. Installation, custom events, identify, person properties, autocapture, configuration. 185 chunks from 15 docs.

Feature flags, session replay, experiments and the server-side SDKs are all left out on purpose. Widening the index makes it vaguer, and the argument here is about depth.

What's still wrong with it

28 cases is a small eval, and I tuned constants while watching it. I kept myself honest by only making changes I could argue from principle, so the length penalty became concave because a 28-token stub can't answer anything, rather than because 0.38 happened to score better. A held-out set is the next step.

The token counter estimates four characters per token. Every number here inherits that approximation. It routes through one function, so swapping in a real tokenizer is a one-line change.

Two restraint failures survive. Ask how to remove a stored super property and you get the right passage plus a passage about removing person properties. Those two read almost identically and they're different APIs. BM25 can't separate them, and neither can my re-scoring. That specific problem is the honest case for adding embeddings.

NOTES.md has the full decision log, including four bugs that produced output looking completely fine.

Available Tools

3 tools
get_posthog_docA

Fetch a full PostHog doc by path or slug, e.g. 'libraries/js/usage', '/docs/product-analytics/identify', or a full posthog.com URL. Returns the complete markdown — this can be large, so prefer how_do_i unless you need the whole page.

ParametersJSON Schema
NameRequiredDescriptionDefault
path_or_slugYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It discloses that the return is complete markdown and can be large, which is important behavioral context. It doesn't cover auth or side effects, but for a simple fetch tool this is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no wasted words. The purpose is front-loaded, and the guidance is compact yet informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one parameter and no output schema, the description covers input format, output type, and size warning. It gives the agent everything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Despite 0% schema coverage, the description adds meaningful meaning with three format examples (path, slug, URL). This helps the agent understand what to pass for path_or_slug.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Fetch a full PostHog doc') and the resource ('by path or slug'), with concrete examples. It distinguishes itself from sibling tools by the 'prefer how_do_i' note.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says to prefer 'how_do_i' unless the whole page is needed, providing clear when-to-use guidance. The examples of valid paths/slugs/URLs further clarify usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

how_do_iA

Answer a 'how do I X with PostHog?' task with an assembled context block: the minimal set of deduplicated, ordered, cited passages that answer it, within a token budget. Prefer this over search for any implementation question — it returns roughly a quarter the tokens of a raw top-k dump with materially higher precision. Pass the task in natural language, and mention the framework if you know it ('in React') so the right variant is selected.

ParametersJSON Schema
NameRequiredDescriptionDefault
taskYes
token_budgetNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that the tool returns a deduplicated, ordered, cited set of passages within a token budget, and explains its efficiency advantage over a raw top-k dump. It does not discuss side effects, but for a retrieval-style tool this is sufficiently transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, front-loaded with purpose, then comparative advantage, then usage guidance. Every sentence earns its place with no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description adequately explains what the tool returns (assembled context block of cited passages) and when to use it. It could be more explicit about the token_budget parameter, but overall it provides a strong context for an agent to select and invoke the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It explains the 'task' parameter (natural language, mention framework), but 'token_budget' is only implied via 'within a token budget' and is never explicitly named or described. This leaves some ambiguity about the parameter's meaning and impact.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states it answers 'how do I X with PostHog?' tasks with an assembled context block. The verb 'Answer' and the specific resource (the task) are clear, and it explicitly distinguishes from search_posthog_docs by recommending this tool over search for implementation questions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says 'Prefer this over search for any implementation question', which is a clear alternative. It also tells the agent to pass the task in natural language and mention the framework (e.g., 'in React') so the right variant is selected, providing actionable usage instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_posthog_docsA

Search PostHog's JS/Web SDK docs and return ranked snippets. Pure retrieval with no assembly — use this to discover what documentation exists. For 'how do I X' questions, use how_do_i instead.

ParametersJSON Schema
NameRequiredDescriptionDefault
kNo
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of disclosing behavior. It states 'Pure retrieval with no assembly,' which is a key behavioral trait indicating no synthesis. However, it does not mention potential limitations such as result count limits or the meaning of the 'k' parameter, though the output schema likely clarifies return structure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the core purpose, then adding usage guidance. Every sentence earns its place—there is no fluff or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity and the presence of an output schema, the description provides sufficient context for selection and invocation. It explains purpose, output, and usage, but falls slightly short by not addressing the 'k' parameter. Overall, it is complete enough for a search tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 0%, so the description must compensate. It does not explain the 'query' or 'k' parameters at all. While 'query' is implied by the verb 'search', 'k' (which likely controls the number of results) is entirely unexplained, leaving the agent without sufficient information to use parameters correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: 'Search PostHog's JS/Web SDK docs and return ranked snippets.' It specifies the resource (JS/Web SDK docs), the action (search), and the output (ranked snippets). It also distinguishes from sibling tools by noting 'Pure retrieval with no assembly' and explicitly contrasting with 'how_do_i'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit usage guidance: 'use this to discover what documentation exists' and 'For "how do I X" questions, use `how_do_i` instead.' This clearly states when to use this tool versus an alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 3 tool updatesv0.1.0
    • First observedget_posthog_doc
    • First observedhow_do_i
    • First observedsearch_posthog_docs

TDQS

A4.3/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct role: search for discovery, get for full-page retrieval, and how_do_i for assembled answers. Descriptions explicitly disambiguate overlapping use cases, such as directing 'how do I' questions to how_do_i instead of search.

Naming Consistency3/5

The first two tools follow a verb_noun pattern (search_posthog_docs, get_posthog_doc), but how_do_i breaks the convention with a question-phrase name. This is a minor inconsistency that still leaves the tools readable.

Tool Count4/5

With only 3 tools, the set is lean but each tool earns its place, covering discovery, full retrieval, and synthesized answers. The count is slightly on the low side but appropriate for a focused documentation context server.

Completeness4/5

The tool surface covers the core needs of searching, reading, and getting direct answers from PostHog docs. Minor gaps exist, such as no ability to list or browse doc categories, but the main workflows are well-supported.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers