ctxjev-mcp
Integrates with GitHub Copilot agent mode in VS Code as an MCP host, providing relevance scoring and context pruning decisions for its agent's conversation and tool-call history.
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., "@ctxjev-mcpprune irrelevant context entries from my current session"
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.
ctxjev
Stop paying to re-read your own agent's history.
ctxjev scores every entry in a running AI agent's context for relevance with
Jev, TypeSafe AI's typed-decision model, and prunes what's no longer useful
before your host's own compaction has to summarize its way through it.
Why · How It Works · Quick Start · Packages · MCP: Claude Code / Codex / Copilot · Extra: Claude Code Plugin · Design Notes · Changelog
Why
Long-running agent loops, such as coding agents, browser agents, or anything with a growing tool-call history, accumulate context faster than it stays useful. Most of that history isn't hard to judge: "is this old tool result still relevant to the current task?" is exactly the kind of fast, cheap, structured decision Jev is built for, a decision model that returns typed judgments (a yes/no probability, a choice, a score) in about 100ms instead of writing a sentence about it.
ctxjev asks Jev that question continuously, so an agent's context stays close to what it
actually needs, before a host's own summarization has to compress its way through everything at
once and discard nuance along with the noise.
Jev can't see images, do arithmetic, or generate text.
ctxjevnever asks it to: token counting happens in code, and the keep/drop/summarize decision is a plain threshold applied to Jev's typed output. SeeCLAUDE.mdfor the full list of things this project deliberately never asks Jev to do.
Related MCP server: @yavdaanalytics/context-optimiser
How It Works
Every entry becomes its own question, and every question in a batch is evaluated in parallel against one shared state, so Jev's cost barely grows with the number of questions: scoring 50 tool-call entries costs about the same as scoring one.
import { pruneContext } from 'ctxjev-core'
const decisions = await pruneContext(
entries, // your agent's tool-call / message history
'Fix a bug where checkout charges customers twice on a slow network retry.',
)$ ctxjev analyze examples/sample-transcripts/checkout-bug.json
e1 bash summarize score 0.48 ran: npm test -- checkout.test.ts — 12 passed, 0 failed
e2 read summarize score 0.26 read package.json — saw the dependency list and script names
e3 grep keep score 0.89 grep "charge" in src/payments.ts — found chargeCustomer() c…
e4 bash drop score 0.14 ran: git log --oneline -5 — recent commits about unrelated …
e5 read keep score 0.93 read src/payments.ts — the retry handler re-calls chargeCus…
e6 assistant keep score 0.94 Found it: the retry path doesn't check for an in-flight or …
e7 bash drop score 0.14 ran: ls public/audio — unrelated, was checking something el…
3 kept, 2 summarized, 2 dropped (of 7 entries)
~60 / 154 tokens saved (39%)
Jev cost: 859 input tokens, 123 output tokens (free) — ~$0.000036This is real output against the sample transcript in this repo. Jev is probabilistic, so exact numbers vary slightly between runs. "Score" is Jev's relevance blended with each entry's recency within the batch, described further in Design Notes. The cost line is computed from what Jev's API actually reported for that request, not estimated.
The entries array above is the one shape every agent's history maps onto, regardless of which
host is running it: Claude Code, Codex, GitHub Copilot, or anything else that keeps a tool-call
history. ctxjev analyze also auto-detects one native format today, a real Claude Code session
.jsonl (transcript_path, or anything under ~/.claude/projects), and infers the goal from your
most recent chat message unless --goal overrides it. Any other host's history works the same
way once it's shaped into ctxjev's own plain JSON format ({ goal?, entries }), shown in
Quick Start below. See
examples/sample-transcripts/claude-code-session.jsonl
for a synthetic Claude Code transcript. Never point this at a real session log: real ones can
contain secrets pasted into chat, and entry content is sent to the live Jev API. See
CLAUDE.md.
Quick Start
npm install -g ctxjev-cli
export TYPESAFE_API_KEY=... # console.typesafe.ai/settings/keys (no waitlist)
ctxjev analyze transcript.jsonl --goal "Fix the checkout double-charge bug."Or from a clone, to run the exact sample transcript above:
git clone https://github.com/x96x64/ctxjev.git
cd ctxjev
pnpm install && pnpm build
export TYPESAFE_API_KEY=...
node packages/cli/dist/index.js analyze examples/sample-transcripts/checkout-bug.jsonPackages
This is a pnpm workspace monorepo: one host-agnostic engine, and a thin adapter for each place that engine gets used.
Package | What it is | Status |
The engine: | ✅ published | |
| ✅ published | |
MCP server exposing | ✅ published | |
Claude Code plugin: scores context with Jev at | ✅ working (not on npm; see below) |
Using It from an MCP Host
ctxjev-mcp speaks plain stdio MCP, so no per-host adapter is necessary. Every host below runs
the exact same binary (node packages/mcp-server/dist/index.js); only the config shape differs.
Claude Code ships with a project-level .mcp.json in this repo. In practice, that
scope requires an approval step that does not currently surface in the UI (tested against Claude
Code v2.1.278): claude mcp list silently omits the server, with no prompt and no error. claude mcp add at local scope works immediately with no friction:
claude mcp add ctxjev --env TYPESAFE_API_KEY=... -- node /absolute/path/to/ctxjev/packages/mcp-server/dist/index.jsIf you use this route in a repo that already has the project-level .mcp.json, claude mcp list
will warn that the same server is defined in two scopes. That warning concerns OAuth token
storage, which does not apply to a local stdio server, so it is safe to ignore, or run claude mcp remove ctxjev -s project to clear it.
Codex CLI (also shared with its VS Code extension and desktop app) needs only one command, with
no file to hand-edit. Verified against codex-cli v0.155.1: codex mcp get ctxjev confirms the
command, args, and env are registered correctly.
codex mcp add ctxjev --env TYPESAFE_API_KEY=... -- node /absolute/path/to/ctxjev/packages/mcp-server/dist/index.jsCodex also has its own plugin marketplace, separate from Claude Code's. This repo carries an
Agent Plugins-format bundle too, at
.agents/plugins/marketplace.json:
codex plugin marketplace add x96x64/ctxjev
codex plugin add ctxjev@ctxjev-pluginsAfterward, codex mcp list shows ctxjev registered with the exact npx ctxjev-mcp command and
environment the plugin bundle declares.
GitHub Copilot (VS Code, agent mode) uses .vscode/mcp.json. Note that the top-level key is
servers, not Claude Code's mcpServers:
{
"servers": {
"ctxjev": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/ctxjev/packages/mcp-server/dist/index.js"],
"env": { "TYPESAFE_API_KEY": "..." }
}
}
}It exposes two tools:
score_relevancetakes{ goal, entries, recencyWeight? }and returns a relevance/recency/combined score per entry plus Jev token usage, with no decision made. WrapsscoreEntries().prune_historytakes the same input plus{ dropBelow?, summarizeBelow? }and returns a decision (keep/drop/summarize) per entry, a savings report, and Jev token usage. WrapspruneContext().
Calling prune_history with two entries, one obviously relevant to the goal and one not, returns:
{
"decisions": [
{ "entryId": "a", "relevance": 0.96, "recency": 0, "combinedScore": 0.864, "action": "keep" },
{ "entryId": "b", "relevance": 0.04, "recency": 1, "combinedScore": 0.136, "action": "drop" }
],
"savings": {
"totalEntries": 2, "keptEntries": 1, "droppedEntries": 1, "summarizedEntries": 0,
"totalTokens": 13, "savedTokens": 5
},
"usage": { "inputTokens": 406, "outputTokens": 36 }
}This is a real response, captured against the live API through an in-process MCP client. The
exact call lives in server.live.test.ts.
The Claude Code Plugin
Everything above (the MCP server, and ctxjev-cli) already works the same way with Claude Code,
Codex, GitHub Copilot, or any other MCP-capable host — none of it is Claude Code-specific. This
section is the one extra, host-specific integration ctxjev currently ships, because Claude Code
happens to expose a lifecycle hook the others don't yet: a way to act right before and right after
its own compaction runs.
Claude Code hooks can read the conversation transcript but cannot rewrite it: there is no API
for a hook to remove old entries before compaction summarizes them away. ctxjev-claude works
within that constraint rather than around it, using the one pattern Claude Code supports: score
every entry at PreCompact, cache the highest-relevance ones, and re-inject a digest of them at
SessionStart (matcher: "compact"), the only documented way a hook can put content back into
context after compaction has already run.
PreCompact → score every entry with Jev, cache the top few to .ctxjev/preserved-context.json
(compaction happens, outside this plugin's control)
SessionStart (compact) → read that cache, print a digest; Claude Code adds it as a system reminderThe goal to score against is either set explicitly (/ctxjev:set-goal <text>, written to
.ctxjev/goal.txt) or, if you never set one, inferred from your most recent chat message.
/ctxjev:status shows the current goal and the last scoring pass without waiting for a real
compaction to trigger one.
Install it (Claude Code desktop app or CLI):
/plugin marketplace add x96x64/ctxjevThis repo carries a .claude-plugin/marketplace.json at its
root, pointing at the packages/claude-plugin subdirectory, so the desktop app can install it
directly with no local clone needed. Verified: /ctxjev:set-goal and /ctxjev:status show up as
available skills right after adding the marketplace.
ctxjev-claude isn't on npm; Claude Code plugins install through the marketplace mechanism above,
not npm install. This repo's own .mcp.json also wires ctxjev-mcp into any Claude
Code session opened here, so this project uses its own tools on its own repository.
Design Notes
Claude Code's transcript parser lives in
core, isolated, on purpose.claudeCodeTranscript.tsparses Claude Code's own internal session-log format, which is undocumented and not guaranteed stable across versions. It lives incorerather than inpackages/claude-pluginbecausectxjev-clineeds it too: both packages import the same function instead of each keeping, and drifting from, their own copy. Keeping every bit of that parsing in one module means a Claude Code format change is a one-file fix rather than a hunt across two packages. It's also whypackages/claude-plugindoesn't try to edit the transcript directly: hooks only get read access to it. A subagent's own private conversation (isSidechain: true) is excluded entirely rather than merged in, since that content already appears in the main thread as an ordinary tool call; merging it in would double-count content the parent session's compaction never actually operates on.No hand-rolled retry logic.
@typesafe-ai/sdk'sTypeSafeClientalready retries connection failures, timeouts, and 408/429/500-599 responses by default, so adding a custom retry layer would just be a worse copy of what the SDK already does correctly. SeejevClient.ts.Every cost claim here is measured, not estimated.
scoreEntries()/pruneContext()accept an optionalonUsagecallback, fired once per underlying Jev request with that request's real{ inputTokens, outputTokens }as reported by@typesafe-ai/sdk.ctxjev-cliand both MCP tools surface the total. Adding this as an optional callback rather than changing the return type kept it non-breaking.Scoring and deciding are two different functions, on purpose.
scoreEntries()calls Jev once per chunk of entries and returns arelevance/recency/combinedScoretriple per entry, with no opinion about what to do with it.pruneContext()isscoreEntries()plus a separate, pure decision step,decideAction(), that applies aPruningPolicy's thresholds. Splitting them means a threshold can be tuned, swapped for a different strategy, or applied to the same scores twice for comparison, all without re-querying Jev. It's also why the MCP server has two tools instead of one:score_relevancemaps ontoscoreEntries(),prune_historyontopruneContext(), and neither has to know the other exists.Recency is relative to the batch, not to
Date.now().computeRecency()normalizes each entry's timestamp to 0–1 within the entries it's given, oldest at 0 and newest at 1. Anchoring to wall-clock time instead would make every entry in a transcript replayed long after the fact, which is exactly whatctxjev-cli analyzeand the test fixtures do, read as maximally stale regardless of where it actually falls in the conversation. The same function has to give sensible answers for both a live agent's growing history and a static file analyzed after the fact, so it can't depend on when it happens to run.combinedScoreblends the two linearly, perPruningPolicy.recencyWeight:relevance * (1 - w) + recency * w, incombineScore(). A weight of0ignores recency entirely, and a weight of1ignores Jev entirely. The default (0.1) is backed by an actual sweep against two hand-labeled fixtures, one of them deliberately adversarial: a root-cause entry that's both early and critical. Accuracy ties fromw=0tow=0.2, but the adversarial fixture starts degrading right atw=0.2, as recency drags that entry's score down despite Jev rating it highly relevant.0.1sits safely on the near side of that cliff. SeerecencyWeight.live.test.ts, which turns that finding into a standing regression test. Two fixtures is still thin evidence for tuningdropBelow/summarizeBelowdirectly, so those thresholds keep their original, untuned defaults for now.Token counts are computed, not judged.
tokenEstimate.tsuses a real tokenizer,gpt-tokenizer, since Jev is explicitly bad at arithmetic and this project never asks it to count anything. Every "tokens saved" number in this README came from that tokenizer, not from Jev.The MCP server is verified two ways.
tools.live.test.tscovers the underlying logic directly, with no MCP framework involved.server.live.test.tsspins up the realMcpServeragainst an in-process client overInMemoryTransportto exercise the actual tool registration, zod schemas, and response shape. Both were also verified once as a real subprocess over stdio (StdioServerTransport↔StdioClientTransport), the same transport path a host like Claude Code uses, though that run is not part of the automated suite.Live tests are opt-in. Every test file ending in
.live.test.ts—core'sjevClient/recencyWeight,mcp-server'stools/server,claude-plugin'sselect(clihas none; its tests cover its own pure logic against a stubbed cache/transcript) — calls the real Jev API and is skipped automatically whenTYPESAFE_API_KEYisn't set. Cloning this repo and runningpnpm testwith no key still passes, on the pure-logic coverage alone, and CI never sets the key, so it exercises exactly that path on every push.Never run anything here against this repo's own real Claude Code session transcripts. They can contain secrets pasted into chat, and scoring sends entry content to the live Jev API. See the warning in
CLAUDE.md, and use a synthetic transcript instead.
Contributing
Issues and pull requests are welcome.
pnpm install && pnpm build && pnpm testpnpm test runs the full pure-logic suite with no API key and no network access. Tests that call
the live Jev API end in .live.test.ts and are skipped automatically unless TYPESAFE_API_KEY is
set.
Acknowledgments
Built on Jev, TypeSafe AI's System One model, via the official
@typesafe-ai/sdk. ctxjev-mcp is built on
Anthropic's @modelcontextprotocol/sdk.
ctxjev is an independent, unofficial project, not affiliated with or endorsed by TypeSafe AI or
Anthropic.
License
This project is released under the MIT license: free to use, modify, and distribute,
including in a commercial product, as long as the license text and copyright notice in
LICENSE ship with it. It comes with no warranty of any kind; see the license text
for the full disclaimer.
This choice matches every package ctxjev currently depends on, so there is nothing to reconcile
if you vendor or fork any of it:
Dependency | License |
MIT | |
MIT | |
MIT | |
MIT | |
ISC |
ISC and MIT are both short, permissive licenses with no material difference in what they let you do.
This server cannot be deployed
Maintenance
Related MCP Connectors
InfoLang semantic memory MCP — investigate, memorize, and recall compressed agent context.
Persistent memory for AI agents — log and recall conversation context over MCP.
Cross-tool persistent memory and context for AI assistants over MCP.
shared AI-context layer for teams — persistent memory your agents search and update over MCP
Related MCP Servers
- AlicenseAqualityCmaintenanceAn MCP server that helps AI agents reduce token usage by compressing, summarizing, and managing conversation/context data more efficiently.11MIT
- AlicenseNot gradedqualityBmaintenanceA context window optimizer and session rotator MCP server for agentic workflows that prevents LLMs from running out of context by compacting chat history and rotating sessions.5 npmMIT
- AlicenseNot gradedqualityCmaintenanceCompresses tool outputs, manages token budgets, deduplicates content, and filters by relevance to optimize context window usage for AI agents.MIT
- AlicenseNot gradedqualityCmaintenanceLocal context management, search engine, and memory for agentic AI via MCP, enabling efficient context retrieval and storage.31 npm4MIT