feat: add @tpmjs/registrySearch and @tpmjs/registryExecute SDK packages

- Create @tpmjs/registrySearch package for searching tool registry
- Create @tpmjs/registryExecute package for executing tools via sandbox
- Support self-hosted registries via TPMJS_API_URL and TPMJS_EXECUTOR_URL env vars
- Add /sdk documentation page with usage examples and architecture
- Add SDK link to navigation menu
- Include design doc for registry SDK architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-12 07:29:06 +10:00
parent e85bbe8638
commit ee825aa97c
12 changed files with 1626 additions and 4 deletions

View file

@ -0,0 +1,558 @@
import { Button } from '@tpmjs/ui/Button/Button';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Container } from '@tpmjs/ui/Container/Container';
import Link from 'next/link';
import { AppHeader } from '~/components/AppHeader';
export const metadata = {
title: 'SDK - Registry Tools | TPMJS',
description:
'Add two tools to your AI agent and instantly access thousands of tools from the TPMJS registry',
};
export default function SDKPage(): React.ReactElement {
return (
<div className="min-h-screen flex flex-col bg-background">
<AppHeader />
<main className="flex-1 py-16">
<Container size="lg" padding="lg">
{/* Hero */}
<div className="text-center mb-16">
<div className="inline-flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-primary/10 text-primary rounded-full">
New
</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold mb-4 text-foreground">
Give Your Agent Access to Every Tool
</h1>
<p className="text-xl text-foreground-secondary max-w-3xl mx-auto">
Add two tools to your AI SDK agent and instantly access thousands of tools from the
TPMJS registry. No configuration, no manual importsjust dynamic tool discovery and
execution.
</p>
</div>
{/* Quick Start */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">Quick Start</h2>
<div className="space-y-6">
{/* Install */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-3 mb-4">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-bold">
1
</span>
<h3 className="text-xl font-semibold text-foreground">Install the packages</h3>
</div>
<CodeBlock
language="bash"
code={`npm install @tpmjs/registrySearch @tpmjs/registryExecute
# or
pnpm add @tpmjs/registrySearch @tpmjs/registryExecute`}
/>
</div>
{/* Add to agent */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-3 mb-4">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-bold">
2
</span>
<h3 className="text-xl font-semibold text-foreground">Add to your agent</h3>
</div>
<CodeBlock
language="typescript"
code={`import { Agent } from 'ai';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
instructions: \`You have access to thousands of tools via the TPMJS registry.
Use registrySearch to find tools, then registryExecute to run them.\`,
tools: {
// Your existing tools
weather: weatherTool,
database: databaseTool,
// TPMJS registry access
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});`}
/>
</div>
{/* That's it */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-3 mb-4">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-bold">
3
</span>
<h3 className="text-xl font-semibold text-foreground">That&apos;s it!</h3>
</div>
<p className="text-foreground-secondary mb-4">
Your agent can now discover and execute any tool from the registry. Here&apos;s
what happens when a user asks for something:
</p>
<div className="p-4 bg-background rounded border border-border font-mono text-sm">
<div className="text-foreground-secondary">
<span className="text-primary">User:</span> &quot;Search the web for AI news and
summarize it&quot;
</div>
<div className="mt-3 text-foreground-secondary">
<span className="text-primary">Agent:</span>
</div>
<div className="ml-4 mt-1 space-y-1 text-foreground-tertiary">
<div>
1. Calls <code className="text-primary">registrySearch</code>
{`({ query: "web search" })`}
</div>
<div>
2. Finds <code className="text-foreground">@exalabs/ai-sdk::webSearch</code>
</div>
<div>
3. Calls <code className="text-primary">registryExecute</code>
{`({ toolId: "@exalabs/ai-sdk::webSearch", params: {...} })`}
</div>
<div>4. Returns results to user</div>
</div>
</div>
</div>
</div>
</section>
{/* How It Works */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">How It Works</h2>
<div className="p-8 border border-border rounded-lg bg-surface font-mono text-sm overflow-x-auto">
<pre className="text-foreground-secondary whitespace-pre">
{`┌─────────────────────────────────────────────────────────────────┐
Your AI Agent
Your Tools registrySearch registryExecute
TPMJS Registry Sandbox Executor
tpmjs.com/api executor.tpmjs.com
Tool Metadata Secure Deno Runtime
1000+ tools Isolated execution
`}
</pre>
</div>
</section>
{/* registrySearchTool */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">registrySearchTool</h2>
<p className="text-lg text-foreground-secondary mb-6">
Search the TPMJS registry to find tools for any task. Returns metadata including the{' '}
<code className="text-primary">toolId</code> needed for execution.
</p>
<div className="space-y-6">
{/* Parameters */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Parameters</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 pr-4 text-foreground">Name</th>
<th className="text-left py-2 pr-4 text-foreground">Type</th>
<th className="text-left py-2 pr-4 text-foreground">Required</th>
<th className="text-left py-2 text-foreground">Description</th>
</tr>
</thead>
<tbody className="text-foreground-secondary">
<tr className="border-b border-border/50">
<td className="py-2 pr-4 font-mono text-primary">query</td>
<td className="py-2 pr-4">string</td>
<td className="py-2 pr-4">Yes</td>
<td className="py-2">Search query (keywords, tool names, descriptions)</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-2 pr-4 font-mono text-primary">category</td>
<td className="py-2 pr-4">string</td>
<td className="py-2 pr-4">No</td>
<td className="py-2">Filter by category</td>
</tr>
<tr>
<td className="py-2 pr-4 font-mono text-primary">limit</td>
<td className="py-2 pr-4">number</td>
<td className="py-2 pr-4">No</td>
<td className="py-2">Max results (1-20, default 5)</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Categories */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Categories</h3>
<div className="flex flex-wrap gap-2">
{[
'web-scraping',
'data-processing',
'file-operations',
'communication',
'database',
'api-integration',
'image-processing',
'text-analysis',
'automation',
'ai-ml',
'security',
'monitoring',
].map((category) => (
<span
key={category}
className="px-3 py-1 text-sm bg-background border border-border rounded-full text-foreground-secondary"
>
{category}
</span>
))}
</div>
</div>
{/* Return Value */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Return Value</h3>
<CodeBlock
language="json"
code={`{
"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
}
]
}`}
/>
</div>
</div>
</section>
{/* registryExecuteTool */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">registryExecuteTool</h2>
<p className="text-lg text-foreground-secondary mb-6">
Execute any tool from the registry by its <code className="text-primary">toolId</code>
. Tools run in a secure sandboxno local installation required.
</p>
<div className="space-y-6">
{/* Parameters */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Parameters</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 pr-4 text-foreground">Name</th>
<th className="text-left py-2 pr-4 text-foreground">Type</th>
<th className="text-left py-2 pr-4 text-foreground">Required</th>
<th className="text-left py-2 text-foreground">Description</th>
</tr>
</thead>
<tbody className="text-foreground-secondary">
<tr className="border-b border-border/50">
<td className="py-2 pr-4 font-mono text-primary">toolId</td>
<td className="py-2 pr-4">string</td>
<td className="py-2 pr-4">Yes</td>
<td className="py-2">
Tool identifier (format: <code>package::exportName</code>)
</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-2 pr-4 font-mono text-primary">params</td>
<td className="py-2 pr-4">object</td>
<td className="py-2 pr-4">Yes</td>
<td className="py-2">Parameters to pass to the tool</td>
</tr>
<tr>
<td className="py-2 pr-4 font-mono text-primary">env</td>
<td className="py-2 pr-4">object</td>
<td className="py-2 pr-4">No</td>
<td className="py-2">Environment variables (API keys)</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Example */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Example</h3>
<CodeBlock
language="typescript"
code={`// 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: [...] }
// }`}
/>
</div>
{/* Return Value */}
<div className="p-6 border border-border rounded-lg bg-surface">
<h3 className="text-xl font-semibold mb-4 text-foreground">Return Value</h3>
<CodeBlock
language="json"
code={`{
"toolId": "@exalabs/ai-sdk::webSearch",
"executionTimeMs": 1234,
"output": { ... }
}`}
/>
</div>
</div>
</section>
{/* Environment Variables */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">Environment Variables</h2>
<p className="text-lg text-foreground-secondary mb-6">
Both packages support self-hosted registries via environment variables. This is useful
for enterprise deployments or running your own tool registry.
</p>
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 pr-4 text-foreground">Variable</th>
<th className="text-left py-2 pr-4 text-foreground">Default</th>
<th className="text-left py-2 text-foreground">Description</th>
</tr>
</thead>
<tbody className="text-foreground-secondary">
<tr className="border-b border-border/50">
<td className="py-2 pr-4 font-mono text-primary">TPMJS_API_URL</td>
<td className="py-2 pr-4 font-mono">https://tpmjs.com</td>
<td className="py-2">Base URL for the registry API</td>
</tr>
<tr>
<td className="py-2 pr-4 font-mono text-primary">TPMJS_EXECUTOR_URL</td>
<td className="py-2 pr-4 font-mono">https://executor.tpmjs.com</td>
<td className="py-2">URL for the sandbox executor</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-6">
<h4 className="font-semibold mb-2 text-foreground">Self-Hosted Example</h4>
<CodeBlock
language="bash"
code={`# Use your own TPMJS registry
export TPMJS_API_URL=https://registry.mycompany.com
export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
/>
</div>
</div>
</section>
{/* Security */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">Security</h2>
<div className="grid md:grid-cols-2 gap-6">
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="text-2xl mb-2">🏝</div>
<h3 className="font-semibold mb-2 text-foreground">Sandboxed Execution</h3>
<p className="text-sm text-foreground-secondary">
All tools run in an isolated Deno runtime on Railway. They cannot access your
local filesystem or environment.
</p>
</div>
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="text-2xl mb-2">🔐</div>
<h3 className="font-semibold mb-2 text-foreground">API Key Isolation</h3>
<p className="text-sm text-foreground-secondary">
API keys are passed per-request and never stored. Each execution is stateless and
isolated.
</p>
</div>
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="text-2xl mb-2"></div>
<h3 className="font-semibold mb-2 text-foreground">Registry-Only Execution</h3>
<p className="text-sm text-foreground-secondary">
Only tools registered in TPMJS can be executed. No arbitrary code execution is
possible.
</p>
</div>
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="text-2xl mb-2">🏥</div>
<h3 className="font-semibold mb-2 text-foreground">Health Monitoring</h3>
<p className="text-sm text-foreground-secondary">
Every tool is continuously health-checked. Broken tools are flagged and filtered
from search results.
</p>
</div>
</div>
</section>
{/* Vision & Future */}
<section className="mb-16">
<h2 className="text-3xl font-bold mb-6 text-foreground">The Vision</h2>
<div className="prose max-w-none text-foreground-secondary text-lg space-y-4 mb-8">
<p>
We&apos;re building the{' '}
<span className="text-foreground font-semibold">npm for AI tools</span>. Just as npm
revolutionized JavaScript package sharing, TPMJS aims to create a universal
ecosystem where AI agents can discover, share, and execute tools seamlessly.
</p>
<p>
The <code className="text-primary">registrySearch</code> and{' '}
<code className="text-primary">registryExecute</code> tools are just the beginning.
Here&apos;s what&apos;s coming:
</p>
</div>
<div className="space-y-6">
{/* Collections */}
<div className="p-6 border-2 border-primary/20 rounded-lg bg-primary/5">
<div className="flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-primary/20 text-primary rounded-full">
Coming Soon
</span>
<h3 className="text-xl font-semibold text-foreground">Collections</h3>
</div>
<p className="text-foreground-secondary mb-4">
Pre-configured tool bundles for specific domains. Think of them as &ldquo;skill
packs&rdquo; for your AI agent.
</p>
<CodeBlock
language="typescript"
code={`// 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');`}
/>
</div>
{/* API Keys */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-foreground-tertiary/20 text-foreground-secondary rounded-full">
Planned
</span>
<h3 className="text-xl font-semibold text-foreground">
API Keys & Rate Limiting
</h3>
</div>
<p className="text-foreground-secondary">
Personal API keys for authentication, usage tracking, and rate limiting.
Enterprise features for teams including usage analytics and billing.
</p>
</div>
{/* Tool Versioning */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-foreground-tertiary/20 text-foreground-secondary rounded-full">
Planned
</span>
<h3 className="text-xl font-semibold text-foreground">Tool Versioning</h3>
</div>
<p className="text-foreground-secondary">
Pin specific tool versions in your agent configuration. Automatic compatibility
checking and migration guides when tools update.
</p>
</div>
{/* Private Registries */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-foreground-tertiary/20 text-foreground-secondary rounded-full">
Planned
</span>
<h3 className="text-xl font-semibold text-foreground">Private Registries</h3>
</div>
<p className="text-foreground-secondary">
Run your own TPMJS instance for internal tools. Connect multiple registries
(public + private) in a single agent. Enterprise SSO and access controls.
</p>
</div>
{/* Streaming */}
<div className="p-6 border border-border rounded-lg bg-surface">
<div className="flex items-center gap-2 mb-4">
<span className="px-3 py-1 text-sm font-semibold bg-foreground-tertiary/20 text-foreground-secondary rounded-full">
Planned
</span>
<h3 className="text-xl font-semibold text-foreground">Streaming Execution</h3>
</div>
<p className="text-foreground-secondary">
Stream tool outputs for long-running operations. Real-time progress updates and
partial results for better UX.
</p>
</div>
</div>
</section>
{/* CTA */}
<section className="text-center py-12 border border-border rounded-lg bg-surface">
<h2 className="text-3xl font-bold mb-4 text-foreground">Ready to Get Started?</h2>
<p className="text-lg text-foreground-secondary mb-8 max-w-2xl mx-auto">
Give your AI agent access to thousands of tools in minutes.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<a
href="https://www.npmjs.com/package/@tpmjs/registrySearch"
target="_blank"
rel="noopener noreferrer"
>
<Button size="lg" variant="default">
View on npm
</Button>
</a>
<Link href="/playground">
<Button size="lg" variant="outline">
Try in Playground
</Button>
</Link>
<Link href="/tool/tool-search">
<Button size="lg" variant="outline">
Browse Tools
</Button>
</Link>
</div>
</section>
</Container>
</main>
</div>
);
}

View file

@ -43,6 +43,11 @@ export function AppHeader(): React.ReactElement {
Spec
</Button>
</Link>
<Link href="/sdk">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
SDK
</Button>
</Link>
<a
href="https://github.com/tpmjs/tpmjs"
target="_blank"

438
docs/REGISTRY_SDK_DESIGN.md Normal file
View file

@ -0,0 +1,438 @@
# TPMJS Registry SDK Design
This document describes the design for `@tpmjs/registrySearch` and `@tpmjs/registryExecute` packages that allow any AI SDK v4+ project to access the entire TPMJS tool registry.
## Overview
The goal is to let developers add two tools to their existing AI SDK agent and instantly gain access to thousands of tools from the TPMJS registry:
```typescript
import { weatherTool } from './tools/weather';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
export const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
instructions: 'You are a helpful assistant with access to many tools.',
tools: {
weather: weatherTool, // Their own tools
registrySearch: registrySearchTool, // Search TPMJS registry
registryExecute: registryExecuteTool, // Execute any registry tool
},
});
```
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ User's Agent │
│ ┌─────────────┐ ┌────────────────┐ ┌─────────────────────┐ │
│ │ weatherTool │ │ registrySearch │ │ registryExecute │ │
│ └─────────────┘ └───────┬────────┘ └──────────┬──────────┘ │
└───────────────────────────┼──────────────────────┼──────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────┐
│ TPMJS Search │ │ Railway Executor │
│ API │ │ (Sandboxed Deno) │
└─────────────────┘ └─────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────┐
│ Tool Registry │ │ esm.sh (CDN) │
│ (Postgres) │ │ Dynamic imports │
└─────────────────┘ └─────────────────────────┘
```
## Package Design
Two separate packages so users can install only what they need:
```
@tpmjs/registrySearch - Search the TPMJS registry for tools
@tpmjs/registryExecute - Execute any tool from the registry
```
## Environment Variables
Both packages support self-hosted registries via environment variables:
| 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 |
This allows users to run their own TPMJS registry and executor.
## Tool Definitions
### 1. `@tpmjs/registrySearch`
```typescript
import { tool } from 'ai';
import { z } from 'zod';
const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com';
export const registrySearchTool = tool({
description: `Search the TPMJS tool registry to discover available tools.
Use this when you need to find a tool for a specific task.
Returns tool metadata including name, description, required env vars, and how to execute it.`,
parameters: z.object({
query: z.string().describe('Search query describing what you want to do'),
category: z.enum([
'search',
'code',
'data',
'media',
'communication',
'productivity',
'finance',
'ai-ml',
'devops',
'other'
]).optional().describe('Filter by category'),
limit: z.number().min(1).max(20).default(5).describe('Max results to return'),
}),
execute: async ({ query, category, limit }) => {
const params = new URLSearchParams({
q: query,
limit: String(limit),
...(category && { category }),
});
const response = await fetch(`${TPMJS_API_URL}/api/tools/search?${params}`);
const data = await response.json();
return {
query,
matchCount: data.data.length,
tools: data.data.map((tool: any) => ({
// Unique identifier for registryExecuteTool
toolId: `${tool.package.npmPackageName}::${tool.exportName}`,
// Human-readable info
name: tool.exportName,
package: tool.package.npmPackageName,
description: tool.description,
category: tool.category,
// Execution requirements
requiredEnvVars: tool.env?.filter((e: any) => e.required).map((e: any) => e.name) || [],
// Quality indicators
healthStatus: tool.executionHealth,
qualityScore: tool.qualityScore,
})),
};
},
});
```
### 2. `@tpmjs/registryExecute`
```typescript
import { tool } from 'ai';
import { z } from 'zod';
const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com';
const TPMJS_EXECUTOR_URL = process.env.TPMJS_EXECUTOR_URL || 'https://executor.tpmjs.com';
export const registryExecuteTool = tool({
description: `Execute a tool from the TPMJS registry.
Use registrySearchTool first to find the toolId, then call this with the toolId and parameters.
The tool runs in a secure sandbox - you don't need to install anything.`,
parameters: z.object({
toolId: z.string().describe('Tool identifier from registrySearchTool (format: "package::exportName")'),
params: z.record(z.any()).describe('Parameters to pass to the tool'),
env: z.record(z.string()).optional().describe('Environment variables (API keys) if required'),
}),
execute: async ({ toolId, params, env }) => {
const [packageName, exportName] = toolId.split('::');
if (!packageName || !exportName) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
}
// Get tool metadata to find version and importUrl
const metaResponse = await fetch(
`${TPMJS_API_URL}/api/tools?package=${encodeURIComponent(packageName)}&export=${encodeURIComponent(exportName)}`
);
const metaData = await metaResponse.json();
const toolMeta = metaData.data?.[0];
if (!toolMeta) {
throw new Error(`Tool not found: ${toolId}`);
}
// Execute via sandbox executor
const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
version: toolMeta.package.npmVersion,
importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`,
params,
env: env || {},
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Tool execution failed');
}
return {
toolId,
executionTimeMs: result.executionTimeMs,
output: result.output,
};
},
});
```
## Usage Examples
### Basic Usage
```typescript
import { Agent } from 'ai';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
tools: {
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});
// User: "Search the web for latest AI news"
// Agent:
// 1. Calls registrySearch({ query: "web search" })
// 2. Finds @exalabs/ai-sdk::webSearch
// 3. Calls registryExecute({
// toolId: "@exalabs/ai-sdk::webSearch",
// params: { query: "latest AI news" },
// env: { EXA_API_KEY: "..." }
// })
// 4. Returns results to user
```
### With Their Own Tools
```typescript
import { Agent } from 'ai';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
import { weatherTool } from './tools/weather';
import { databaseTool } from './tools/database';
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
instructions: `You are a helpful assistant.
For common tasks, use the built-in tools (weather, database).
For anything else, search the TPMJS registry to find appropriate tools.`,
tools: {
// Their custom tools
weather: weatherTool,
database: databaseTool,
// TPMJS registry access
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});
```
### Self-Hosted Registry
```typescript
// Set environment variables for your own registry
// TPMJS_API_URL=https://registry.mycompany.com
// TPMJS_EXECUTOR_URL=https://executor.mycompany.com
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
// Tools will automatically use your self-hosted URLs
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
tools: {
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});
```
## API Endpoints Required
### 1. Search API (existing, may need updates)
```
GET https://tpmjs.com/api/tools/search?q=web+search&limit=5&category=search
```
Response:
```json
{
"success": true,
"data": [
{
"id": "...",
"exportName": "webSearch",
"description": "Search the web...",
"category": "search",
"executionHealth": "HEALTHY",
"qualityScore": 0.9,
"env": [
{ "name": "EXA_API_KEY", "required": true }
],
"package": {
"npmPackageName": "@exalabs/ai-sdk",
"npmVersion": "1.0.5"
},
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5"
}
]
}
```
### 2. Execute API (existing Railway executor)
```
POST https://executor.tpmjs.com/execute-tool
```
Request:
```json
{
"packageName": "@exalabs/ai-sdk",
"exportName": "webSearch",
"version": "1.0.5",
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5",
"params": { "query": "latest AI news" },
"env": { "EXA_API_KEY": "..." }
}
```
Response:
```json
{
"success": true,
"output": { ... },
"executionTimeMs": 1234
}
```
## Package Structure
### @tpmjs/registrySearch
```
packages/registrySearch/
├── package.json
├── tsconfig.json
├── src/
│ └── index.ts # Exports registrySearchTool
└── README.md
```
### @tpmjs/registryExecute
```
packages/registryExecute/
├── package.json
├── tsconfig.json
├── src/
│ └── index.ts # Exports registryExecuteTool
└── README.md
```
### package.json (@tpmjs/registrySearch)
```json
{
"name": "@tpmjs/registrySearch",
"version": "0.1.0",
"description": "Search the TPMJS tool registry from any AI SDK agent",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "search"],
"peerDependencies": {
"ai": "^4.0.0",
"zod": "^3.0.0"
}
}
```
### package.json (@tpmjs/registryExecute)
```json
{
"name": "@tpmjs/registryExecute",
"version": "0.1.0",
"description": "Execute tools from the TPMJS registry in any AI SDK agent",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "execute"],
"peerDependencies": {
"ai": "^4.0.0",
"zod": "^3.0.0"
}
}
```
## Security Considerations
1. **Sandboxed Execution**: All tools run in the Railway Deno sandbox, not in the user's process
2. **No Code Injection**: Users can't execute arbitrary code, only registered tools
3. **API Key Isolation**: Keys are passed per-request, not stored
4. **Health Checks**: Only HEALTHY tools should be returned in search results by default
## Next Steps
1. [ ] Create `packages/registrySearch` directory
2. [ ] Create `packages/registryExecute` directory
3. [ ] Implement `registrySearchTool`
4. [ ] Implement `registryExecuteTool`
5. [ ] Add search API endpoint if not exists
6. [ ] Write README with examples for each package
7. [ ] Publish to npm
8. [ ] Create demo agent using the SDK
## Future Enhancements
1. **Tool Recommendations**: Based on conversation context, proactively suggest tools
2. **Tool Chaining**: Pre-built workflows combining multiple tools
3. **Local Caching**: Cache tool metadata for faster searches
4. **Type Generation**: Generate TypeScript types for popular tools
5. **Usage Analytics**: Track which tools are used most (anonymized)
6. **Rate Limiting**: Add rate limiting when needed

View file

@ -0,0 +1,102 @@
# @tpmjs/registryExecute
Execute tools from the TPMJS registry in any AI SDK agent. Tools run in a secure sandbox - no local installation required.
## Installation
```bash
npm install @tpmjs/registryExecute
# or
pnpm add @tpmjs/registryExecute
```
## Usage
```typescript
import { Agent } from 'ai';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
tools: {
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});
// The agent can now:
// 1. Search for tools: registrySearch({ query: "web scraping" })
// 2. Execute found tools: registryExecute({ toolId: "@firecrawl/ai-sdk::scrapeTool", params: { url: "..." } })
```
## Tool: registryExecuteTool
Execute a tool from the TPMJS registry by its toolId.
### Parameters
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `toolId` | string | Yes | Tool identifier (format: `package::exportName`) |
| `params` | object | Yes | Parameters to pass to the tool |
| `env` | object | No | Environment variables (API keys) if required |
### Example
```typescript
// 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: [...] }
// }
```
### Returns
```json
{
"toolId": "@exalabs/ai-sdk::webSearch",
"executionTimeMs": 1234,
"output": { ... }
}
```
## Environment Variables
| 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 Registry
To use your own TPMJS registry and executor:
```bash
export TPMJS_API_URL=https://registry.mycompany.com
export TPMJS_EXECUTOR_URL=https://executor.mycompany.com
```
## Security
- All tools run in a sandboxed Deno environment on Railway
- API keys are passed per-request, never stored
- Only registered tools can be executed (no arbitrary code)
## Related
- [@tpmjs/registrySearch](https://www.npmjs.com/package/@tpmjs/registrySearch) - Find tools to execute
- [TPMJS Registry](https://tpmjs.com) - Browse all available tools
## License
MIT

View file

@ -0,0 +1,72 @@
{
"name": "@tpmjs/registryExecute",
"version": "0.1.0",
"description": "Execute tools from the TPMJS registry in any AI SDK agent",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"type-check": "tsc --noEmit"
},
"keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "execute"],
"dependencies": {
"ai": "6.0.0-beta.124"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"typescript": "^5.7.2"
},
"files": ["dist", "README.md"],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/ajaxdavis/tpmjs.git",
"directory": "packages/tools/registryExecute"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "api-integration",
"frameworks": ["vercel-ai"],
"tools": [
{
"exportName": "registryExecuteTool",
"description": "Execute a tool from the TPMJS registry by toolId. Use registrySearchTool first to find toolIds.",
"parameters": [
{
"name": "toolId",
"type": "string",
"description": "Tool identifier from registrySearchTool (format: 'package::exportName')",
"required": true
},
{
"name": "params",
"type": "object",
"description": "Parameters to pass to the tool",
"required": true
},
{
"name": "env",
"type": "object",
"description": "Environment variables (API keys) if required by the tool",
"required": false
}
],
"returns": {
"type": "object",
"description": "Tool execution result with output and timing"
},
"aiAgent": {
"useCase": "Use after finding a tool with registrySearchTool. Execute the tool by its toolId with the required parameters.",
"examples": [
"Execute '@exalabs/ai-sdk::webSearch' with query parameter",
"Execute '@firecrawl/ai-sdk::scrapeTool' with url parameter"
]
}
}
]
}
}

View file

@ -0,0 +1,120 @@
import { jsonSchema, tool } from 'ai';
const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com';
const TPMJS_EXECUTOR_URL = process.env.TPMJS_EXECUTOR_URL || 'https://executor.tpmjs.com';
/**
* Input type for Registry Execute Tool
*/
type RegistryExecuteInput = {
toolId: string;
params: Record<string, unknown>;
env?: Record<string, string>;
};
/**
* AI SDK tool for executing tools from the TPMJS registry
*
* Use registrySearchTool first to find the toolId, then execute with this tool.
* Tools run in a secure sandbox - no local installation required.
*
* Supports self-hosted registries via environment variables:
* - TPMJS_API_URL: Registry API (default: https://tpmjs.com)
* - TPMJS_EXECUTOR_URL: Sandbox executor (default: https://executor.tpmjs.com)
*/
export const registryExecuteTool = tool({
description:
'Execute a tool from the TPMJS registry. Use registrySearchTool first to find the toolId. Tools run in a secure sandbox.',
inputSchema: jsonSchema<RegistryExecuteInput>({
type: 'object',
properties: {
toolId: {
type: 'string',
description: "Tool identifier from registrySearchTool (format: 'package::exportName')",
},
params: {
type: 'object',
description: 'Parameters to pass to the tool',
additionalProperties: true,
},
env: {
type: 'object',
description: 'Environment variables (API keys) if required by the tool',
additionalProperties: {
type: 'string',
},
},
},
required: ['toolId', 'params'],
additionalProperties: false,
}),
async execute({ toolId, params, env }) {
// Parse toolId format: "package::exportName"
const separatorIndex = toolId.lastIndexOf('::');
if (separatorIndex === -1) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
}
const packageName = toolId.substring(0, separatorIndex);
const exportName = toolId.substring(separatorIndex + 2);
if (!packageName || !exportName) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
}
// Fetch tool metadata to get version and importUrl
const metaParams = new URLSearchParams({
q: exportName,
limit: '10',
});
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
if (!metaResponse.ok) {
throw new Error(`Failed to fetch tool metadata: ${metaResponse.statusText}`);
}
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
const metaData = (await metaResponse.json()) as any;
const toolsArray = metaData.results?.tools || [];
// Find the exact tool match
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
const toolMeta = toolsArray.find(
(t: any) => t.package.npmPackageName === packageName && t.exportName === exportName
);
if (!toolMeta) {
throw new Error(`Tool not found: ${toolId}`);
}
const version = toolMeta.package.npmVersion;
const importUrl = `https://esm.sh/${packageName}@${version}`;
// Execute via sandbox executor
const response = await fetch(`${TPMJS_EXECUTOR_URL}/execute-tool`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
version,
importUrl,
params,
env: env || {},
}),
});
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
const result = (await response.json()) as any;
if (!result.success) {
throw new Error(result.error || 'Tool execution failed');
}
return {
toolId,
executionTimeMs: result.executionTimeMs,
output: result.output,
};
},
});

View file

@ -0,0 +1,12 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,102 @@
# @tpmjs/registrySearch
Search the TPMJS tool registry from any AI SDK agent. Discover thousands of tools dynamically.
## Installation
```bash
npm install @tpmjs/registrySearch
# or
pnpm add @tpmjs/registrySearch
```
## Usage
```typescript
import { Agent } from 'ai';
import { registrySearchTool } from '@tpmjs/registrySearch';
import { registryExecuteTool } from '@tpmjs/registryExecute';
const agent = new Agent({
model: 'anthropic/claude-sonnet-4-20250514',
tools: {
registrySearch: registrySearchTool,
registryExecute: registryExecuteTool,
},
});
// The agent can now:
// 1. Search for tools: registrySearch({ query: "web scraping" })
// 2. Execute found tools: registryExecute({ toolId: "@firecrawl/ai-sdk::scrapeTool", params: { url: "..." } })
```
## Tool: registrySearchTool
Search the TPMJS registry to find AI SDK tools.
### 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
- `web-scraping`
- `data-processing`
- `file-operations`
- `communication`
- `database`
- `api-integration`
- `image-processing`
- `text-analysis`
- `automation`
- `ai-ml`
- `security`
- `monitoring`
### Returns
```json
{
"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
}
]
}
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `TPMJS_API_URL` | `https://tpmjs.com` | Base URL for the registry API |
### Self-Hosted Registry
To use your own TPMJS registry:
```bash
export TPMJS_API_URL=https://registry.mycompany.com
```
## Related
- [@tpmjs/registryExecute](https://www.npmjs.com/package/@tpmjs/registryExecute) - Execute tools found with this package
- [TPMJS Registry](https://tpmjs.com) - Browse all available tools
## License
MIT

View file

@ -0,0 +1,73 @@
{
"name": "@tpmjs/registrySearch",
"version": "0.1.0",
"description": "Search the TPMJS tool registry from any AI SDK agent",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"type-check": "tsc --noEmit"
},
"keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "search"],
"dependencies": {
"ai": "6.0.0-beta.124"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"typescript": "^5.7.2"
},
"files": ["dist", "README.md"],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/ajaxdavis/tpmjs.git",
"directory": "packages/tools/registrySearch"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "api-integration",
"frameworks": ["vercel-ai"],
"tools": [
{
"exportName": "registrySearchTool",
"description": "Search the TPMJS tool registry to find AI SDK tools. Returns tool metadata including toolId for use with registryExecuteTool.",
"parameters": [
{
"name": "query",
"type": "string",
"description": "Search query (keywords, tool names, descriptions)",
"required": true
},
{
"name": "category",
"type": "string",
"description": "Filter by category (optional)",
"required": false
},
{
"name": "limit",
"type": "number",
"description": "Maximum results (1-20, default 5)",
"required": false
}
],
"returns": {
"type": "object",
"description": "Search results with tool metadata including toolId, packageName, exportName, description, requiredEnvVars"
},
"aiAgent": {
"useCase": "Use when you need to find a tool for a specific task. Search returns toolIds that can be executed with registryExecuteTool.",
"examples": [
"Search for 'web scraping' to find scraping tools",
"Search for 'weather' to find weather API tools",
"Search for 'database' to find database tools"
]
}
}
]
}
}

View file

@ -0,0 +1,102 @@
import { jsonSchema, tool } from 'ai';
const TPMJS_API_URL = process.env.TPMJS_API_URL || 'https://tpmjs.com';
/**
* Input type for Registry Search Tool
*/
type RegistrySearchInput = {
query: string;
category?: string;
limit?: number;
};
/**
* AI SDK tool for searching the TPMJS tool registry
*
* This tool enables agents to discover tools dynamically from the TPMJS registry.
* Search results include toolIds that can be executed with @tpmjs/registryExecute.
*
* Supports self-hosted registries via TPMJS_API_URL environment variable.
*/
export const registrySearchTool = tool({
description:
'Search the TPMJS tool registry to find AI SDK tools. Use this to discover tools for any task. Returns toolIds that can be executed with registryExecuteTool.',
inputSchema: jsonSchema<RegistrySearchInput>({
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query (keywords, tool names, descriptions)',
},
category: {
type: 'string',
description: 'Filter by tool category (optional)',
enum: [
'web-scraping',
'data-processing',
'file-operations',
'communication',
'database',
'api-integration',
'image-processing',
'text-analysis',
'automation',
'ai-ml',
'security',
'monitoring',
],
},
limit: {
type: 'number',
description: 'Maximum number of results (1-20, default 5)',
minimum: 1,
maximum: 20,
},
},
required: ['query'],
additionalProperties: false,
}),
async execute({ query, category, limit = 5 }) {
const params = new URLSearchParams({
q: query,
limit: String(limit),
...(category && { category }),
});
const url = `${TPMJS_API_URL}/api/tools/search?${params}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
// biome-ignore lint/suspicious/noExplicitAny: API response types vary
const data = (await response.json()) as any;
const toolsArray = data.results?.tools || [];
return {
query,
matchCount: toolsArray.length,
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
tools: toolsArray.map((t: any) => ({
// Unique identifier for registryExecuteTool
toolId: `${t.package.npmPackageName}::${t.exportName}`,
// Human-readable info
name: t.exportName,
package: t.package.npmPackageName,
description: t.description,
category: t.package.category,
// Execution requirements
requiredEnvVars:
t.package.env?.filter((e: any) => e.required).map((e: any) => e.name) || [],
// Quality indicators
healthStatus: t.executionHealth,
qualityScore: t.qualityScore,
})),
};
},
});

View file

@ -0,0 +1,12 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

34
pnpm-lock.yaml generated
View file

@ -131,10 +131,10 @@ importers:
version: 10.4.22(postcss@8.5.6)
eslint:
specifier: ^9.39.1
version: 9.39.1(jiti@2.6.1)
version: 9.39.1(jiti@1.21.7)
eslint-config-next:
specifier: ^16.0.4
version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
postcss:
specifier: ^8.5.1
version: 8.5.6
@ -249,10 +249,10 @@ importers:
version: 10.4.22(postcss@8.5.6)
eslint:
specifier: ^9.39.1
version: 9.39.1(jiti@1.21.7)
version: 9.39.1(jiti@2.6.1)
eslint-config-next:
specifier: ^16.0.4
version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
postcss:
specifier: ^8.5.1
version: 8.5.6
@ -600,6 +600,32 @@ importers:
specifier: ^5.9.3
version: 5.9.3
packages/tools/registryExecute:
dependencies:
ai:
specifier: 6.0.0-beta.124
version: 6.0.0-beta.124(effect@3.18.4)(zod@4.1.13)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../config/tsconfig
typescript:
specifier: ^5.7.2
version: 5.9.3
packages/tools/registrySearch:
dependencies:
ai:
specifier: 6.0.0-beta.124
version: 6.0.0-beta.124(effect@3.18.4)(zod@4.1.13)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../config/tsconfig
typescript:
specifier: ^5.7.2
version: 5.9.3
packages/tools/search-registry:
dependencies:
ai: