@logvault/mcp-router
v1.0.0
Published
MCP tool filter for Cursor. Stays under the 40-tool limit.
Readme
@logvault/mcp-router
Cursor caps MCP tools at 40. Run 4 MCPs and you hit that.
This sits between Cursor and your MCPs. Reads your query, picks relevant tools, drops the rest.
Install
npm i @logvault/mcp-routerSetup
~/.cursor/mcp.json:
{
"mcpServers": {
"router": {
"command": "npx",
"args": ["-y", "@logvault/mcp-router", "-c", "router.json"]
}
}
}router.json:
{
"maxTools": 35,
"backends": [
{
"name": "supabase",
"type": "stdio",
"command": "npx",
"args": ["-y", "@supabase/mcp-server-supabase@latest"],
"env": { "SUPABASE_ACCESS_TOKEN": "..." }
},
{
"name": "linear",
"type": "stdio",
"command": "npx",
"args": ["-y", "@linear/mcp-server@latest"],
"env": { "LINEAR_API_KEY": "..." }
},
{
"name": "vercel",
"type": "stdio",
"command": "npx",
"args": ["-y", "@vercel/mcp@latest"],
"env": { "VERCEL_TOKEN": "..." }
}
]
}How it works
Query: "Deploy to Vercel and create Linear issue"
- Detects intents:
deploy,issue - Scores tools (Vercel +10, Linear +10, others +0)
- Picks top 35
- Cursor sees filtered list
Intents
| Intent | Keywords | Boosts | | -------- | ------------------------------------- | -------------- | | deploy | deploy, vercel, railway, build | vercel, docker | | database | sql, query, supabase, table, postgres | supabase | | issue | issue, linear, bug, task, sprint | linear, github | | code | commit, pr, branch, merge, git | github | | test | test, playwright, e2e, vitest | browser | | debug | debug, console, error, screenshot | browser | | docs | doc, readme, markdown, guide | github |
Scoring
| Factor | Points | | ------------------ | ------ | | Intent match | +10 | | Tool name in query | +15 | | Keyword in name | +5 | | Keyword in desc | +3 | | Used recently | +2 |
Config options
| Option | Type | Default | Description |
| ------------------- | ----------- | -------- | ------------------------------------------ |
| maxTools | number | 35 | Hard cap on tools exposed to Cursor |
| minPerIntent | number | 3 | Minimum tools per detected intent |
| alwaysInclude | string[] | [] | Tool names to never filter out |
| backends | Backend[] | [] | MCP servers to connect |
| cacheTtl | number | 300000 | Tool list cache duration (ms) |
| debug | boolean | false | Enable logging to file |
| semanticThreshold | number | 0.8 | Use semantic routing below this confidence |
| plugins | string[] | [] | Built-in plugins to enable |
Example:
{
"maxTools": 35,
"minPerIntent": 3,
"alwaysInclude": ["mcp_supabase_execute_sql"],
"debug": true,
"backends": [...]
}Hybrid Semantic Routing (v0.3+)
For ambiguous queries, plug in an encoder for semantic matching:
import { HybridClassifier, type Encoder } from "@logvault/mcp-router";
// Bring your own encoder (fastembed, OpenAI, etc.)
const encoder: Encoder = {
name: "my-encoder",
dimension: 384,
async encode(texts: string[]): Promise<number[][]> {
// Your embedding logic here
return texts.map(() => new Array(384).fill(0));
},
};
const classifier = new HybridClassifier({
threshold: 0.8, // Use semantic when regex confidence < 80%
encoder,
});
await classifier.buildIndex(); // Index utterances once
const result = await classifier.classify("let the team know about deployment");
// Regex finds "deployment" → deploy intent
// Semantic finds "let the team know" ≈ "send message" → issue intent
// Merged: [deploy, issue] with combined scoresNo encoder? Falls back to regex-only (0.01ms, ~75% accuracy).
With encoder? Regex first, semantic fallback (1-5ms, ~95% accuracy).
Plugins (v0.4+)
Extend the router with hooks for filtering and tool calls:
import { MCPRouter, AuditPlugin, RateLimitPlugin } from "@logvault/mcp-router";
const router = new MCPRouter({ backends: [...] });
// Audit logging
router.use(new AuditPlugin());
// Rate limiting (per tool)
router.use(new RateLimitPlugin({
defaultLimit: 60, // 60 calls/min default
limits: {
"expensive_tool": 10, // Only 10/min for this tool
},
}));
await router.start();Built-in plugins
| Plugin | Purpose |
| ----------------- | ----------------------------------- |
| AuditPlugin | Logs filter/call events with timing |
| RateLimitPlugin | Token bucket rate limiting per tool |
Custom plugins
import type {
RouterPlugin,
FilterContext,
CallContext,
} from "@logvault/mcp-router";
const myPlugin: RouterPlugin = {
name: "my-plugin",
priority: 10, // Lower = runs first
async beforeFilter(ctx: FilterContext) {
// Modify query or tools before filtering
return ctx;
},
async afterFilter(ctx: FilterContext) {
// Inspect/modify filtered results
return ctx;
},
async beforeCall(ctx: CallContext) {
// Intercept tool calls (rate limit, validate, etc.)
return ctx;
},
async afterCall(ctx) {
// Log results, track metrics
},
};
router.use(myPlugin);Programmatic
import { classifyIntent, scoreTools, selectTools } from "@logvault/mcp-router";
// Classify
const match = classifyIntent("Deploy to Vercel");
// { intents: ["deploy"], keywords: ["deploy", "vercel"], confidence: Map }
// Score
const scores = scoreTools(tools, match, "Deploy to Vercel");
// [{ tool, score: 25, reasons: ["intent:deploy", "name:vercel"] }, ...]
// Select
const selected = selectTools(scores, match.intents, { maxTools: 35 });
// Top 35 tools, at least 3 per intentCLI
# Start router
npx @logvault/mcp-router
# With config
npx @logvault/mcp-router -c router.json
# Debug mode
npx @logvault/mcp-router -d
# or
MCP_ROUTER_DEBUG=1 npx @logvault/mcp-router
# Version
npx @logvault/mcp-router -vPerformance
| Operation | Time | | ------------ | ------ | | Classify | 0.01ms | | Score 100 | 0.1ms | | Score 500 | 0.5ms | | Backend list | cached |
License
MIT
