vscode-agent-router
Cursor / VS Code dispatcher: any local model talks to Claude, Codex, Slack, and catalog peers over MCP, CLI, or API.
Hub
·
Docs
·
Use cases
·
MCP
·
Careers
www.splitin.net/tech-stack/open-internal-tools/vscode-agent-router
One Cursor agent. Official Claude, Codex, and Slack. No scraped UIs.
A Cursor (or VS Code) extension that routes work. The agent in this window — any model — calls small MCP tools. For Claude / Codex / ChatGPT, the router opens the official extension (vscode://splitin.agent-router/launch). It does not run claude -p, codex exec, or those vendors’ HTTP APIs. GitHub / Railway / Slack CLI stay as tools. Cursor Cloud API is available only when you pass runtime=cloud.
Built internally at SplitIn as the tech side of a product, then packaged so others can install the same dispatcher. A tool specialist owns this folder end to end. It can stay MIT and later also power a hosted SplitIn service. Every contributor would have % equity in that hosted service.
- Compose, do not rebuild: Slack CLI, Claude Code / Codex / ChatGPT extensions, Cursor Cloud Agents API (opt-in),
@modelcontextprotocol/sdk, tsup, vitest, @vscode/vsce.
- Indie-cheap: no extra daemon, no extra cloud, no extra seat. Secrets in env /
~/.agent-router, never in git.
- Out of the box: Marketplace / Open VSX install, F5, or a VSIX. Sidebar Peers + Jobs, Command Palette, editor/explorer handoff menus, URI handler.
- Version by version: catalog JSON adds peers without a new adapter class.
This does not merge Composer, Claude’s sidebar, and Codex into one transcript. It is a dispatcher, the same way ideation-loop-system routes Slack threads instead of inventing a new bot per vendor.
Cursor agent (any model)
→ MCP tools (list_peers, route, call_cli, slack_api, list_jobs)
→ AgentRouter
→ Claude: vscode://splitin.agent-router/launch → Claude Code URI (prefills, does not auto-submit)
→ Codex / ChatGPT: same URI → chatgpt.addToThread (openai.chatgpt)
→ Cursor: Composer command if present; otherwise you already are the agent
→ Slack: ~/.slack/bin/slack api | SLACK_BOT_TOKEN
→ GitHub / Railway / Vercel / Supabase / Stripe / Linear: allowlisted CLI or API
→ ideation-hq: POST /hooks/jobs (failed jobs stay on the Slack thread)
Table of contents
How this folder is built
open-internal-tools is a program repo: each folder is the tech of one product, MIT, install from that folder, one specialist owns it. Stack is chosen per job, not one language for the hub.
| Folder |
Language / runtime |
How it is built |
| mac-unlock-notify |
zsh on macOS |
LaunchAgent + ioreg + curl + Slack webhook. No Node. Secrets in ~/.config. |
| slack-agent-hq |
TypeScript on Node 20+ |
Slack Bolt + Slack CLI + YAML integrations. Official @Cursor / @Claude / @Codex apps, not a custom bot per vendor. |
| in-app-otp |
TypeScript |
tsup + vitest. Core + adapters (React, Express, Django, Supabase). |
| react-mobile-interactions |
TypeScript + React |
Package-only primitives, vitest, tsup. |
| vscode-agent-router (this folder) |
TypeScript on Node 20+ |
Cursor/VS Code extension + MCP stdio. Catalog JSON + adapters. Same compose rule: official CLIs and APIs. |
Shared defaults across those folders, followed here:
- Internal, then package — ship a VSIX /
dist/ others can load.
- Right tool for the job — extension host for
executeCommand / URI handoff; stdio MCP for any model; catalog/peers.json for generic CLIs.
- Pick existing OSS — do not scrape Claude or Codex UIs; do not persist API keys in
settings.json.
- One folder, one PR surface — keep Agent Router work inside
vscode-agent-router/.
- People and agents in sync — the product is that sync: Cursor agents call Claude/Codex/Slack instead of working in silos.
Getting started
git clone https://github.com/splitintech/open-internal-tools.git
cd open-internal-tools/vscode-agent-router
npm install
npm test
npm run build
Then F5 (Run Agent Router Extension) or install the VSIX (see Install). Work in sync with other contributors and agents. PRs stay in vscode-agent-router/.
Use cases
Ten ways developers integrate Agent Router:
- Consult Claude from Cursor — a Composer agent calls
route peer=claude action=consult runtime=ide (default). Agent Router opens Claude Code with a prefilled prompt. It does not auto-submit and does not run claude -p.
- Fan-out a fix — same prompt,
runtime=ide on claude, codex, and chatgpt; Jobs sidebar holds ids; poller can POST ideation HQ /hooks/jobs.
- Handoff a selection to Claude Code — editor context menu prefills
vscode://anthropic.claude-code/open?prompt=… (does not auto-submit).
- Add the current file to Codex — explorer or editor title uses
chatgpt.addFileToThread when openai.chatgpt is installed.
- Post a cloud result to Slack —
slack_api method=chat.postMessage or Agent Router: Post to Slack, using the public Slack CLI (~/.slack/bin/slack).
- Search Slack docs from the agent —
route peer=slack action=consult runs slack docs search.
- List GitHub PRs without a GitHub MCP —
call_cli peer=github argv=["pr","list","--limit","5"] (gh allowlist).
- Add Docker as a peer — Add Peer from Template writes
~/.agent-router/peers.json; call_cli enforces allow.
- Probe the laptop — Probe Transports / sidebar dots for which of
claude, codex, gh, railway, Slack CLI, and API keys are actually present.
- Status a Railway or Vercel service — catalog CLI rows, still one
call_cli tool, no per-vendor MCP explosion.
Install
VS Code Marketplace — search SplitIn Agent Router, or:
code --install-extension splitin.agent-router
https://marketplace.visualstudio.com/items?itemName=splitin.agent-router
Cursor (Open VSX) — Extensions panel → search SplitIn Agent Router (splitin.agent-router), or:
cursor --install-extension splitin.agent-router
https://open-vsx.org/extension/splitin/agent-router
From this folder (VSIX / F5)
npm run package
In Cursor: Extensions: Install from VSIX… → agent-router-0.1.0.vsix. Reload. Or F5 Run Agent Router Extension (.vscode/launch.json).
Publish a release (needs VSCE_PAT + OVSX_PAT in the environment):
npm run publish:marketplaces
On activate the extension registers MCP via Cursor cursor.mcp.registerServer when that API exists, otherwise it spawns dist/mcp.js. After install, a Cursor agent of any model can call the tools without a project mcp.json.
Keep the stdio binary for Claude Code or Codex MCP configs (and for the hub’s “reply as a peer” path):
{
"mcpServers": {
"agent-router": {
"command": "node",
"args": ["dist/mcp.js"]
}
}
}
Claude Code and Codex extensions are not hard dependencies. If they are missing, handoff commands prompt you to install them.
Transport preference is MCP → CLI → API. If Railway/Supabase/Linear is already on Cursor MCP, the agent should call those tools directly. route with transport=mcp tells you the same instead of proxying MCP inside MCP.
| Tool |
What it does |
list_peers |
Catalog ids, kinds, runtimes, transports |
probe_peers |
CLIs on PATH, API env vars, mcp.json, Slack CLI fingerprint |
route |
Opens the VS Code/Cursor extension for claude / codex / chatgpt / local cursor. params.promptId loads a catalog prompt; params.memoryPacket appends shared MEMORY. Platform peers still CLI/API. |
call_cli |
Allowlisted argv for a catalog CLI |
call_api |
HTTP to that peer’s baseUrl using authEnv |
slack_api |
slack api family.method key=value with HTTP fallback |
list_jobs |
Cloud launches recorded by the router |
job_status |
Get or refresh one job |
route peer=claude action=consult runtime=ide prompt="Summarize src/auth.ts"
route peer=chatgpt action=handoff params.promptId=chatgpt.plan params.memoryPacket="…"
route peer=codex action=consult runtime=ide prompt="Fix tests"
route peer=cursor action=consult runtime=ide prompt="Continue in Composer"
route peer=cursor action=launch runtime=cloud prompt="Add a README"
route peer=slack action=launch params.channel=C0123 text="Cloud job finished"
slack_api method=chat.postMessage channel=C0123 text="done"
call_cli peer=github argv=["pr","list","--limit","5"]
list_jobs
job_status jobId=ar-… refresh=true
runtime=local / cloud on claude, codex, and chatgpt is rejected (claude -p / codex exec / vendor HTTP). Default is runtime=ide. Local Cursor opens Composer if this build exposes a handoff command; otherwise you already are the agent. Cursor Cloud remains runtime=cloud.
Peers
Shipped in catalog/peers.json. User overlay: ~/.agent-router/peers.json.
| Peer |
MCP |
CLI |
API |
Cloud / IDE |
| cursor |
— |
— |
POST /v1/agents (opt-in runtime=cloud) |
default ide: Composer / you are the agent |
| claude |
— |
refused |
refused |
vscode://splitin.agent-router/launch → Claude Code URI (no auto-submit) |
| codex |
— |
refused |
refused |
same URI → chatgpt.addToThread |
| chatgpt |
— |
refused |
refused |
same URI → openai.chatgpt |
| ideation-hq |
— |
— |
POST /hooks/jobs |
link jobs into the Slack thread |
| slack |
optional |
slack api / docs / auth |
slack.com/api |
— |
| github |
optional |
gh |
api.github.com |
— |
| railway |
optional |
railway |
GraphQL |
— |
| vercel |
optional |
vercel |
api.vercel.com |
— |
| supabase |
optional |
supabase |
api.supabase.com |
— |
| stripe |
optional |
stripe |
api.stripe.com |
— |
| linear |
optional |
— |
GraphQL |
— |
Slack
Same rule as mac-unlock-notify and slack-agent-hq: use Slack’s own surface, not a homemade bot.
- Binary:
~/.slack/bin/slack (public CLI). Fingerprint d41d8cd98f00b204e9800998ecf8427e.
- Install:
curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
- Fallback:
SLACK_BOT_TOKEN → https://slack.com/api
- Commands: Slack Auth Status, Post to Slack, selection → Handoff Selection to Slack
- Settings:
agentRouter.slackTeamId, agentRouter.slackChannel (channel id, not a token)
slack api chat.postMessage channel=C0123456789 text="Hello from Agent Router"
Cloud jobs
Launches (action=launch) write a JobStore row. The extension polls every agentRouter.pollIntervalMs (default 15s) and POSTs ideation HQ POST {hqUrl}/hooks/jobs with x-agent-router-secret (AGENT_ROUTER_JOBS_SECRET) so failed jobs stay on the Slack thread.
Cursor Cloud: GET https://api.cursor.com/v1/agents/{id} (CURSOR_API_KEY) when you explicitly pass runtime=cloud.
Set agentRouter.hqUrl / AGENT_ROUTER_HQ_URL and optional agentRouter.projectId. Set agentRouter.notifySlackOnJobComplete and a channel to post when a job hits succeeded/failed.
Unpushed local files are not on the cloud clone. Push (or pass a remote SHA) first.
IDE handoff
MCP route (including the node dist/mcp.js child) cannot call vscode.commands.executeCommand. It opens vscode://splitin.agent-router/launch?peer=&prompt=. The extension host then:
- Claude:
vscode://anthropic.claude-code/open?prompt= — prefills a new tab, does not auto-submit, does not return a result.
- Codex / ChatGPT:
chatgpt.addToThread after ensureExtension("openai.chatgpt").
- Cursor local/ide: Composer command from
probeCursorHandoff if present; otherwise “you are already the agent”.
Add a peer
Agent Router: Add Peer from Template, or edit ~/.agent-router/peers.json. Example:
{
"version": 1,
"transportPreference": ["mcp", "cli", "api"],
"peers": [
{
"id": "docker",
"title": "Docker",
"kind": "platform",
"runtimes": ["local"],
"capabilities": ["api"],
"transports": {
"cli": { "bin": "docker", "allow": ["ps", "compose"] }
}
}
]
}
call_cli rejects argv that is not on allow. No new TypeScript adapter required.
Commands
| Command |
What it does |
| Agent Router: Probe Transports |
Refresh Peers tree + Output JSON |
| Agent Router: List Peers |
Catalog dump |
| Agent Router: Handoff to Claude Code |
Prefill Claude tab |
| Agent Router: Handoff to Codex |
chatgpt.addToThread |
| Agent Router: Add File to Codex |
chatgpt.addFileToThread |
| Agent Router: Slack Auth Status |
slack auth list |
| Agent Router: Post to Slack |
chat.postMessage |
| Agent Router: Add Peer from Template |
Write ~/.agent-router/peers.json |
| Agent Router: Open Job |
Open job URL |
Editor context (selection): Claude, Codex, Slack. Explorer file: Codex add-file.
Settings and secrets
Do not put tokens in settings.json or this repo.
| Setting / env |
Purpose |
CURSOR_API_KEY |
Cursor Cloud Agents (runtime=cloud only) |
SLACK_BOT_TOKEN |
HTTP fallback if Slack CLI is missing |
AGENT_ROUTER_HQ_URL / agentRouter.hqUrl |
Ideation HQ base URL for POST /hooks/jobs |
AGENT_ROUTER_JOBS_SECRET |
Shared secret header x-agent-router-secret |
AGENT_ROUTER_PROJECT_ID / agentRouter.projectId |
Default HQ project_id on job posts |
AGENT_ROUTER_PROMPTS_DIR / agentRouter.promptsDir |
Prompt catalog (default: sibling ../ideation-loop-system/prompts or ~/.agent-router/prompts) |
CURSOR_CLOUD_REPO_URL / agentRouter.cursorCloudRepoUrl |
Git remote for Cursor cloud |
agentRouter.slackTeamId / slackChannel |
Slack --team and default channel |
agentRouter.notifySlackOnJobComplete |
Slack on terminal job status |
agentRouter.pollIntervalMs |
Job poll interval |
agentRouter.timeoutMs |
CLI/API timeout |
agentRouter.catalogPath |
Extra catalog file; user file still merges |
Layout
catalog/peers.json shipped peers (MCP / CLI / API / cloud / ide)
src/core/ vscode-free: router, registry, jobs, poll, probe
src/adapters/ cursor, claude, codex, slack, generic
src/transports/ spawn (no shell), Slack CLI resolve, HTTP
src/mcp/ createMcpServer + stdio bootstrap
src/ext/ VS Code: MCP host, trees, handoff, poller
src/extension.ts activate
tests/ vitest, no vscode
Core stays vscode-free so npm test and node dist/mcp.js never load the extension host.
Confirm it works
npm test
npm run build
Then in Cursor:
- Probe Transports — sidebar Peers dots match CLIs on the machine
- Handoff a selection to Claude and Codex (those extensions installed)
- Slack Auth Status (or docs search if the CLI is present)
call_cli github with argv=["config"] → allowlist error
- Cloud launch (if keys exist) shows a row under Jobs
Careers
Own this dispatcher end to end — or explore SplitIn tech careers — at https://www.splitin.net/careers-requests.
License
MIT. See LICENSE. Program rules live with the hub: CONTRIBUTING.md.