npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mcp-ts/tool-router

v0.4.0

Published

ToolRouter lets agents discover tools, load schemas on-demand, and route tool calls across multiple MCP servers.

Readme

@mcp-ts/tool-router

Dynamically search, fetch schemas, and route tool calls across multiple MCP servers to optimize LLM context.

@mcp-ts/tool-router lets an agent work with multiple MCP servers without loading every tool definition into the model context. Instead, the model receives a small set of meta-tools, searches for relevant tools, fetches schemas on demand, and calls tools through the router.


Why Use It

When you have many tools, sending all schemas to the LLM is expensive and can exceed context limits. ToolRouter acts as an intermediary, keeping the active context small while preserving access to the full catalog.

Use it to:

  • Index and search tools across multiple MCP servers or custom adapters.
  • Expose a small set of meta-tools for dynamic schema loading.
  • Control tool calls with allow/deny rules and approval gates.
  • Integrate with Vercel AI SDK.

Installation

npm install @mcp-ts/tool-router

Core Concepts

ToolServer

Anything that can list and call tools can be adapted into a ToolServer.

import { createToolServer } from "@mcp-ts/tool-router";

const github = createToolServer({
  id: "github",
  name: "GitHub",
  listTools: async () => ({
    tools: [
      {
        name: "list_pull_requests",
        description: "List pull requests for a repository",
        inputSchema: {
          type: "object",
          properties: {
            owner: { type: "string" },
            repo: { type: "string" }
          },
          required: ["owner", "repo"]
        }
      }
    ]
  }),
  callTool: async (name, args) => {
    return callYourMcpClient(name, args);
  }
});

Meta-Tools

The router exposes four meta-tools to LLMs:

  • search_tools: Search the tool index without fetching full schemas.
  • list_servers: List registered servers and tool counts.
  • get_tool_schemas: Fetch input schemas for one or more specific tools.
  • call_tool: Invoke a tool on a registered server.

Meta-tool calls return text content for compatibility and structured data in structuredContent for clients that can consume typed payloads.

Pinned Tools

Use pinnedTools when a small number of tools should remain directly visible alongside the meta-tools. Prefer canonical ids such as github.help; legacy bare tool names such as help are still supported, but canonical ids avoid ambiguity when multiple servers expose the same tool name.

The router owns canonical ids: server ids are normalized to lowercase slug-style ids before they appear in tool ids. For example, u2tsgODpOrlF.toolname is treated as u2tsgodporlf.toolname.

Deferred Tools

Use deferredTools for tools that should stay out of the initial client-visible tool list while remaining indexed for search_tools, schema lookup, and call_tool.

This is useful when you want an LLM to discover a tool only on demand instead of feeding it upfront. In practice:

  • pinnedTools are directly visible
  • deferredTools are meta-tool only
  • excludeTools are removed entirely

Servers can also declare deferred-by-default tools with custom metadata:

{
  name: "workflow_list",
  description: "List workflows",
  _meta: {
    toolRouter: {
      deferred: true
    }
  }
}

Router config still takes precedence, so you can pin a deferred-by-default tool when a specific client should see it directly.

Excluded Tools

Use excludeTools to omit tools from the router catalog entirely. Excluded tools are not indexed, not searchable, not pinnable, and not callable through router meta-tools.

excludeTools supports the same matching ergonomics as pinnedTools:

  • Canonical ids such as exa.web_search_exa exclude a specific tool.
  • Bare names such as web_search_exa exclude matching tool names across all servers.
  • Wildcards work in both forms, for example exa.deep_* or crawling_*.

Basic Usage

import { createToolRouter } from "@mcp-ts/tool-router";

const router = await createToolRouter({
  servers: [github, linear, slack],
  metaToolNames: {
    searchTools: "find_tools",
    listServers: "servers",
    getToolSchemas: "tool_schemas",
    callTool: "run_tool"
  }
});

// Search tools
const results = await router.searchTools({
  query: "github open pull requests"
});

// Get tool input schema
const [schema] = router.getToolSchemas({
  toolIds: ["github.list_pull_requests"]
});

// Invoke tool
const pullRequests = await router.callTool({
  toolId: "github.list_pull_requests",
  args: {
    owner: "zonlabs",
    repo: "mcp-ts"
  }
});

AI SDK Integration

Use createAISDKTools to expose the router's meta-tools to the Vercel AI SDK:

import { generateText } from "ai";
import { createToolRouter, createAISDKTools } from "@mcp-ts/tool-router";

const router = await createToolRouter({
  servers: [github, slack]
});

const tools = await createAISDKTools(router);

const result = await generateText({
  model,
  tools,
  prompt: "Find open GitHub PRs about authentication."
});

The model only sees the meta-tools rather than the entire tool catalog at start.


AI SDK Agent Example (Exa + grep)

This mirrors the pattern used in examples/next/app/agent/agent.ts.

import { ToolLoopAgent, stepCountIs } from "ai";
import { createMCPClient } from "@ai-sdk/mcp";
import { createDeepSeek } from "@ai-sdk/deepseek";
import { createToolRouter, createAISDKTools, mcpServer } from "@mcp-ts/tool-router";

const EXA_MCP_URL =
  "https://mcp.exa.ai/mcp?tools=web_search_exa,deep_search_exa,get_code_context_exa,crawling_exa";
const GREP_MCP_URL = "https://mcp.grep.app";

const instructions = `
You are an expert assistant that helps users with tasks using available MCP tools.
Use this flow:
1) list_servers
2) search_tools
3) get_tool_schemas
4) call_tool
Always search first before calling.
`;

async function createAgent() {
  const [exaClient, grepClient] = await Promise.all([
    createMCPClient({ transport: { type: "http", url: EXA_MCP_URL } }),
    createMCPClient({ transport: { type: "http", url: GREP_MCP_URL } })
  ]);

  const router = await createToolRouter({
    servers: [
      mcpServer("exa", exaClient),
      mcpServer("grep", grepClient)
    ],
    maxSearchResults: 8
  });

  const tools = await createAISDKTools(router);

  return new ToolLoopAgent({
    model: createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY })("deepseek-chat"),
    instructions,
    tools: tools as any,
    stopWhen: stepCountIs(20)
  });
}

MCP Client Adapters

Wrap any compatible tool client with mcpServer:

import { createToolRouter, mcpServer, mcpServers } from "@mcp-ts/tool-router";

const router = await createToolRouter({
  servers: [
    mcpServer("github", githubMcpClient),
    mcpServer("linear", linearMcpClient)
  ]
});

If you have a client provider that manages multiple active clients:

const router = await createToolRouter({
  servers: mcpServers(multiSessionClient)
});

Using Vercel AI SDK MCP clients (@ai-sdk/mcp):

import { createMCPClient } from "@ai-sdk/mcp";
import { createToolRouter, mcpServer } from "@mcp-ts/tool-router";

const exa = await createMCPClient({ transport: { type: "http", url: "https://mcp.exa.ai/mcp" } });
const grep = await createMCPClient({ transport: { type: "http", url: "https://mcp.grep.app" } });

const router = await createToolRouter({
  servers: [mcpServer("exa", exa), mcpServer("grep", grep)]
});

Policy Gates

Restrict tool execution with policies:

const router = await createToolRouter({
  servers,
  excludeTools: ["grep.internal_*"],
  policy: {
    allowTools: ["github.*", "linear.*"],
    denyTools: ["github.delete_*"],
    denyDestructiveTools: true,
    approveToolCall: async ({ tool, args }) => {
      // Custom approval logic
      return tool.annotations?.destructiveHint !== true;
    }
  }
});

API Reference

Main exports:

  • createToolRouter(options): Create and initialize a ToolRouter.
  • createToolServer(server): Helper to type-check custom tool adapters.
  • createAISDKTools(router): Expose meta-tools as Vercel AI SDK tools.
  • mcpServer(id, client, name?): Wrap a ToolClient, including @ai-sdk/mcp clients, as a ToolServer.
  • mcpServers(provider): Convert a ToolClientProvider into ToolServer[].

License

MIT License.