Give Your Agent Access to Every Tool
Add two tools to your AI SDK agent and instantly access thousands of tools from the TPMJS registry. No configuration, no manual imports—just dynamic tool discovery and execution.
Quick Start
Install the packages
npm install @tpmjs/registry-search @tpmjs/registry-executepnpm add @tpmjs/registry-search @tpmjs/registry-executeAdd to your agent
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecuteTool } from '@tpmjs/registry-execute';
const result = streamText({
model: openai('gpt-4.1-mini'),
tools: {
// Your existing tools
weather: weatherTool,
database: databaseTool,
// TPMJS registry access
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
system: `You have access to thousands of tools via the TPMJS registry.
Use registrySearch to find tools, then registryExecute to run them.`,
prompt: 'Search for web scraping tools and scrape https://example.com',
});That's it!
Your agent can now discover and execute any tool from the registry. Here's what happens when a user asks for something:
registrySearch({ query: "web search" })@exalabs/ai-sdk::webSearchregistryExecute({ toolId: "@exalabs/ai-sdk::webSearch", params: {...} })How It Works
Search the TPMJS registry to find tools for any task. Returns metadata including the toolId needed for execution.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | Search query (keywords, tool names, descriptions) |
| category | string | No | Filter by category |
| limit | number | No | Max results (1-20, default 5) |
Categories
Return Value
{
"query": "web scraping",
"matchCount": 3,
"tools": [
{
"toolId": "@firecrawl/ai-sdk::scrapeTool",
"name": "scrapeTool",
"package": "@firecrawl/ai-sdk",
"description": "Scrape any website into clean markdown",
"category": "web-scraping",
"requiredEnvVars": ["FIRECRAWL_API_KEY"],
"healthStatus": "HEALTHY",
"qualityScore": 0.9
}
]
}Execute any tool from the registry by its toolId. Tools run in a secure sandbox—no local installation required.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| toolId | string | Yes | Tool identifier (format: package::name) |
| params | object | Yes | Parameters to pass to the tool |
| env | object | No | Environment variables (API keys) |
Example
// Execute a web search tool
const result = await registryExecuteTool.execute({
toolId: '@exalabs/ai-sdk::webSearch',
params: { query: 'latest AI news' },
env: { EXA_API_KEY: 'your-api-key' },
});
// Result:
// {
// toolId: '@exalabs/ai-sdk::webSearch',
// executionTimeMs: 1234,
// output: { results: [...] }
// }Return Value
{
"toolId": "@exalabs/ai-sdk::webSearch",
"executionTimeMs": 1234,
"output": { ... }
}Environment Variables
Both packages support self-hosted registries via environment variables. This is useful for enterprise deployments or running your own tool registry.
| Variable | Default | Description |
|---|---|---|
| TPMJS_API_URL | https://tpmjs.com | Base URL for the registry API |
| TPMJS_EXECUTOR_URL | https://executor.tpmjs.com | URL for the sandbox executor |
Self-Hosted Example
# Use your own TPMJS registry
export TPMJS_API_URL=https://registry.mycompany.com
export TPMJS_EXECUTOR_URL=https://executor.mycompany.comPassing API Keys
Many tools require API keys (e.g., Firecrawl, Exa). The recommended approach is to wrap registryExecuteTool with your pre-configured keys.
Create a Wrapper (Recommended)
import { tool } from 'ai';
import { registryExecuteTool } from '@tpmjs/registry-execute';
// Pre-configure your API keys
const API_KEYS: Record<string, string> = {
FIRECRAWL_API_KEY: process.env.FIRECRAWL_API_KEY!,
EXA_API_KEY: process.env.EXA_API_KEY!,
};
// Create a wrapped version that auto-injects keys
export const registryExecute = tool({
description: registryExecuteTool.description,
parameters: registryExecuteTool.parameters,
execute: async ({ toolId, params }) => {
return registryExecuteTool.execute({ toolId, params, env: API_KEYS });
},
});Use the Wrapped Tool
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecute } from './tools'; // Your wrapped version
const result = streamText({
model: openai('gpt-4.1-mini'),
tools: {
registrySearch: registrySearchTool,
registryExecute, // Keys are auto-injected
},
system: `You have access to the TPMJS tool registry.
Use registrySearch to find tools, then registryExecute to run them.`,
prompt: 'Scrape https://example.com and summarize the content',
});How It Works
registrySearchreturnsrequiredEnvVarsfor each tool (e.g.,["FIRECRAWL_API_KEY"])- Your wrapper automatically passes all configured keys to the executor
- The executor injects matching keys as environment variables in the sandbox
- Tools without required keys work with or without the wrapper
Security
Sandboxed Execution
All tools run in an isolated Deno runtime on Railway. They cannot access your local filesystem or environment.
API Key Isolation
API keys are passed per-request and never stored. Each execution is stateless and isolated.
Registry-Only Execution
Only tools registered in TPMJS can be executed. No arbitrary code execution is possible.
Health Monitoring
Every tool is continuously health-checked. Broken tools are flagged and filtered from search results.
The Vision
We're building the npm for AI tools. Just as npm changed how developers share JavaScript packages, TPMJS aims to do the same for AI agent tools—a universal ecosystem where agents discover and use tools on-demand.
The registrySearch and registryExecute tools are just the beginning. Here's what's coming:
Collections
Pre-configured tool bundles for specific domains. Think of them as “skill packs” for your AI agent.
// Future API concept
const tools = await tpmjs.loadCollection('web-scraping');
// Includes: scrapeTool, crawlTool, extractTool, searchTool...
const tools = await tpmjs.loadCollection('data-analysis');
// Includes: csvParser, jsonTransform, statistics, plotting...
// Or create your own private collections
const tools = await tpmjs.loadCollection('my-company/internal-tools');API Keys & Rate Limiting
Personal API keys for authentication, usage tracking, and rate limiting. Enterprise features for teams including usage analytics and billing.
Tool Versioning
Pin specific tool versions in your agent configuration. Automatic compatibility checking and migration guides when tools update.
Private Registries
Run your own TPMJS instance for internal tools. Connect multiple registries (public + private) in a single agent. Enterprise SSO and access controls.
Streaming Execution
Stream tool outputs for long-running operations. Real-time progress updates and partial results for better UX.
MCP Server Integration
TPMJS supports the Model Context Protocol (MCP), allowing you to use your tool collections directly in Claude Desktop, Cursor, VS Code, and other MCP-compatible clients.
Create a Collection
Sign in to tpmjs.com and create a collection of tools. Add the tools you want your agent to have access to, and configure any required API keys.
Get Your MCP URL
Your collection has a unique MCP endpoint URL:
https://tpmjs.com/api/mcp/{username}/{collection-slug}/httpReplace {username} with your username and {collection-slug} with your collection's slug.
Configure Your Client
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"tpmjs": {
"command": "npx",
"args": [
"mcp-remote",
"https://tpmjs.com/api/mcp/username/my-tools/http",
"--header",
"Authorization: Bearer YOUR_API_KEY"
]
}
}
}Cursor / VS Code
Add to your .cursor/mcp.json or VS Code MCP settings:
{
"mcpServers": {
"tpmjs": {
"command": "npx",
"args": [
"mcp-remote",
"https://tpmjs.com/api/mcp/username/my-tools/http",
"--header",
"Authorization: Bearer YOUR_API_KEY"
]
}
}
}Get an API Key
Generate an API key from your account settings. API keys authenticate your MCP requests and enable access to your private collections.
REST API Reference
For advanced integrations, you can use the TPMJS REST API directly. All endpoints are available at https://tpmjs.com/api.
/api/tools
List all tools with filtering, sorting, and pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| q | string | Search query (package name, description) |
| category | string | Filter by category |
| official | boolean | Filter by official status |
| limit | number | Results per page (1-1000, default 20) |
| offset | number | Pagination offset (default 0) |
Example
curl "https://tpmjs.com/api/tools?category=web-scraping&limit=10"/api/tools/search
Semantic search using BM25 algorithm. Better for natural language queries.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| q | string | Search query (natural language) |
| category | string | Filter by category |
| limit | number | Max results (1-100, default 10) |
Example
curl "https://tpmjs.com/api/tools/search?q=scrape%20website%20to%20markdown"Response
{
"success": true,
"query": "scrape website to markdown",
"results": {
"total": 5,
"tools": [
{
"id": "clx...",
"name": "scrapeTool",
"description": "Scrape any website into clean markdown",
"package": {
"npmPackageName": "@firecrawl/ai-sdk",
"category": "web-scraping",
"env": ["FIRECRAWL_API_KEY"]
}
}
]
}
}/api/tools/execute/{package}/{tool}
Execute a tool in the secure sandbox. Returns the tool output.
Request Body
{
"params": {
"url": "https://example.com"
},
"env": {
"FIRECRAWL_API_KEY": "your-api-key"
}
}Example
curl -X POST "https://tpmjs.com/api/tools/execute/@firecrawl/ai-sdk/scrapeTool" \
-H "Content-Type: application/json" \
-d '{
"params": { "url": "https://example.com" },
"env": { "FIRECRAWL_API_KEY": "your-key" }
}'Response
{
"success": true,
"result": {
"markdown": "# Example Domain\n\nThis domain is for use...",
"metadata": {
"title": "Example Domain",
"url": "https://example.com"
}
},
"executionTimeMs": 1234
}Building an Agent Like Omega
Omega is our flagship AI agent that demonstrates dynamic tool discovery at scale. Here's how to build something similar.
Architecture Overview
Omega uses a two-tier tool discovery pattern:
- Automatic discovery — Every message triggers a BM25 search to find relevant tools
- Agent-driven search — The agent can explicitly search for more tools using
registrySearchTool
Complete Implementation
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecuteTool } from '@tpmjs/registry-execute';
// Pre-configure API keys for tool execution
const API_KEYS: Record<string, string> = {
FIRECRAWL_API_KEY: process.env.FIRECRAWL_API_KEY!,
EXA_API_KEY: process.env.EXA_API_KEY!,
OPENAI_API_KEY: process.env.OPENAI_API_KEY!,
};
// Wrapped execute tool with pre-configured keys
const registryExecute = tool({
description: registryExecuteTool.description,
parameters: registryExecuteTool.parameters,
execute: async ({ toolId, params }) => {
return registryExecuteTool.execute({ toolId, params, env: API_KEYS });
},
});
// System prompt for Omega-like behavior
const SYSTEM_PROMPT = `You are an AI assistant with access to thousands of tools via the TPMJS registry.
## Available Tools
- registrySearch: Search the registry to find tools for any task
- registryExecute: Execute any tool by its toolId
## Workflow
1. When given a task, first search for relevant tools
2. Review the results - each tool has: toolId, name, description, requiredEnvVars
3. Execute tools with appropriate parameters
4. Synthesize results into a helpful response
## Best Practices
- Search first when unsure what tools exist
- Execute tools to get real results (not just descriptions)
- Handle errors gracefully - suggest alternatives if a tool fails
- Be efficient - don't search repeatedly for the same thing`;
// Auto-discover tools based on user message
async function discoverTools(message: string) {
const response = await fetch(
`https://tpmjs.com/api/tools/search?q=${encodeURIComponent(message)}&limit=10`
);
const data = await response.json();
return data.results?.tools || [];
}
// Main agent function
async function runAgent(userMessage: string) {
// Step 1: Auto-discover relevant tools
const discoveredTools = await discoverTools(userMessage);
console.log(`Found ${discoveredTools.length} relevant tools`);
// Step 2: Create dynamic tool context for the prompt
const toolContext = discoveredTools.length > 0
? `\n\n## Pre-discovered Tools\nBased on your request, these tools may be helpful:\n${
discoveredTools.map((t: { toolId: string; description: string }) =>
`- ${t.toolId}: ${t.description}`
).join('\n')
}`
: '';
// Step 3: Run the agent with tool access
const result = await streamText({
model: openai('gpt-4.1-mini'),
tools: {
registrySearch: registrySearchTool,
registryExecute,
},
maxSteps: 10,
system: SYSTEM_PROMPT + toolContext,
prompt: userMessage,
});
// Step 4: Stream the response
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
return result;
}
// Usage
await runAgent('Scrape https://example.com and summarize the content');Streaming with Server-Sent Events
For real-time UI updates, stream tool execution status via SSE:
// API Route: POST /api/chat
export async function POST(request: Request) {
const { message } = await request.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Emit tool discovery event
const tools = await discoverTools(message);
controller.enqueue(encoder.encode(
`event: tools.discovered\ndata: ${JSON.stringify({ tools })}\n\n`
));
// Run agent and stream events
const result = await streamText({
model: openai('gpt-4.1-mini'),
tools: { registrySearch: registrySearchTool, registryExecute },
maxSteps: 10,
prompt: message,
onStepFinish: ({ stepType, toolCalls, toolResults }) => {
if (stepType === 'tool-result') {
controller.enqueue(encoder.encode(
`event: tool.completed\ndata: ${JSON.stringify({ toolCalls, toolResults })}\n\n`
));
}
},
});
// Stream text chunks
for await (const chunk of result.textStream) {
controller.enqueue(encoder.encode(
`event: message.delta\ndata: ${JSON.stringify({ content: chunk })}\n\n`
));
}
controller.enqueue(encoder.encode(`event: done\ndata: {}\n\n`));
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}Ready to Get Started?
Give your AI agent access to thousands of tools in minutes.