npm package: @logvault/mcp-router
Description: MCP tool filter for Cursor. Stays under the 40-tool limit.
Author: LogVault
Latest version: 1.0.0

# @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

```bash
npm i @logvault/mcp-router
```

## Setup

`~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "router": {
      "command": "npx",
      "args": ["-y", "@logvault/mcp-router", "-c", "router.json"]
    }
  }
}
```

`router.json`:

```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"

1. Detects intents: `deploy`, `issue`
2. Scores tools (Vercel +10, Linear +10, others +0)
3. Picks top 35
4. 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:

```json
{
  "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:

```typescript
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 scores
```

**No 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:

```typescript
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

```typescript
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

```typescript
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 intent
```

## CLI

```bash
# 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 -v
```

## Performance

| Operation    | Time   |
| ------------ | ------ |
| Classify     | 0.01ms |
| Score 100    | 0.1ms  |
| Score 500    | 0.5ms  |
| Backend list | cached |

## License

MIT