SDK Tool Generation
This guide explains how to automatically generate OpenPets-compatible tools from TypeScript SDK packages, rather than manually defining each tool.
Overview
Many services provide official TypeScript SDKs (like @polar-sh/sdk, @stripe/stripe-node, etc.) that expose their APIs as typed methods. Instead of manually writing tool definitions for each API endpoint, you can use bun scripts/generate-from-sdk.ts --pet <pet-name> to generate tools from these SDKs.
This approach is similar to how hey-api generates TypeScript clients from OpenAPI specs - you configure the SDK, and tools are generated automatically.
Note: There is no
pets generate-sdkCLI command registered in the pets CLI. Use the script directly withbun scripts/generate-from-sdk.ts --pet <pet-name>from the repository root.
When to Use SDK Generation
Use SDK tool generation when:
- The service provides an official TypeScript SDK
- You want to quickly bootstrap a pet with many tools
- The SDK is well-maintained with proper TypeScript types
- You prefer direct SDK integration over MCP servers
Consider MCP generation or manual tool definitions when:
- The service provides an MCP server (may have better authentication)
- You need custom logic beyond what the SDK provides
- You want more control over tool behavior
- The SDK doesn't follow predictable patterns
How It Works
The SDK generator:
- Reads configuration from
package.jsonorsdk-config.json - Generates tool definitions for each SDK method
- Creates a
client.tsfile with typed tool exports - Supports read-only mode filtering
Step-by-Step Guide
Step 1: Add SDK Generator Config
Add an sdkGenerator section to your pet's package.json:
{
"name": "@openpets/my-service",
"sdkGenerator": {
"sdkPackage": "@my-service/sdk",
"petName": "my-service",
"mainClassName": "MyService",
"tokenEnvVar": "MY_SERVICE_TOKEN",
"description": "My Service SDK integration",
"namespaces": [
{
"name": "users",
"methods": ["list", "get", "create", "update", "delete"],
"readOnlyMethods": ["list", "get"]
},
{
"name": "projects",
"methods": ["list", "get", "create"],
"readOnlyMethods": ["list", "get"]
}
]
}
}Or create a separate sdk-config.json file:
{
"sdkPackage": "@my-service/sdk",
"petName": "my-service",
"mainClassName": "MyService",
"tokenEnvVar": "MY_SERVICE_TOKEN",
"description": "My Service SDK integration",
"namespaces": [
{
"name": "users",
"methods": ["list", "get", "create", "update", "delete"],
"readOnlyMethods": ["list", "get"]
}
]
}Step 2: Set Up Authentication
Set the access token environment variable in your .env file:
MY_SERVICE_TOKEN=your-token-hereStep 3: Generate Tools
Run the SDK generator from the repository root:
# Preview what will be generated
bun scripts/generate-from-sdk.ts --pet my-service --dry-run --verbose
# Generate the client.ts file
bun scripts/generate-from-sdk.ts --pet my-serviceYou'll see output like:
SDK Package: @my-service/sdk
Pet Name: my-service
Main Class: MyService
Namespaces: 2
Generated 8 tool definitions from 2 namespaces
Tools:
[read] my-service-users-list
[read] my-service-users-get
[write] my-service-users-create
[write] my-service-users-update
[write] my-service-users-delete
[read] my-service-projects-list
[read] my-service-projects-get
[write] my-service-projects-create
Generated 8 tools (5 read-only, 3 write) to client.tsStep 4: Import SDK Tools in Your Plugin
Update your index.ts to import and include the generated tools:
import { z, createPlugin, type ToolDefinition, createLogger, loadEnv } from "openpets-sdk"
import { createMyServiceTools } from "./client"
export const MyServicePlugin = async () => {
const logger = createLogger("my-service")
const env = loadEnv("my-service")
const ACCESS_TOKEN = env.MY_SERVICE_TOKEN
const isConfigured = !!ACCESS_TOKEN
if (!isConfigured) {
logger.warn("Plugin not configured - returning limited toolset")
return createPlugin([
{
name: "my-service-test-connection",
description: "Test connection status",
schema: z.object({}),
async execute() {
return JSON.stringify({
success: false,
status: "not_configured",
message: "Please set MY_SERVICE_TOKEN environment variable."
}, null, 2)
}
}
])
}
// Create SDK tools with the access token
const sdkTools = createMyServiceTools({ accessToken: ACCESS_TOKEN })
return createPlugin(sdkTools)
}
export default MyServicePluginStep 5: Test the Integration
# Test the connection
bun test:pet my-service --query "test connection"Configuration Reference
SDKGeneratorConfig
| Property | Type | Required | Description |
|---|---|---|---|
sdkPackage | string | Yes | NPM package name (e.g., "@polar-sh/sdk") |
petName | string | Yes | Pet name for tool naming (e.g., "polar") |
mainClassName | string | Yes | Main class exported by SDK (e.g., "Polar") |
tokenEnvVar | string | Yes | Env var name for the token (e.g., "POLAR_TOKEN") |
description | string | No | Description of the SDK |
orgIdEnvVar | string | No | Optional org ID env var |
namespaces | array | Yes | Array of namespace configurations |
SDKNamespaceConfig
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Namespace name (e.g., "products", "users") |
methods | string[] | Yes | Method names to generate tools for |
readOnlyMethods | string[] | No | Methods that are safe in read-only mode |
Method Name Patterns
The generator automatically creates appropriate schemas based on method names:
| Pattern | Schema Generated | Description |
|---|---|---|
list, list* | { organizationId?, limit? } | Paginated listing |
get, get* | { id } | Get by ID |
create | { dataJson } | Create with JSON data |
update, update* | { id, dataJson } | Update by ID |
delete, revoke | { id } | Delete by ID |
validate | { key, organizationId } | Validate a key |
export | {} | Export data |
| Other | {} | Empty schema |
Generated File Structure
The client.ts file includes:
- SDK Client Initialization - Creates the SDK client with access token
- Read-Only Mode Support - Filters write tools when read-only is enabled
- Tool Definitions - Auto-generated ToolDefinition array with:
- Kebab-case names prefixed with pet name (e.g.,
polar-products-list) - Descriptions based on method patterns
- Zod schemas for input validation
- Execute functions that call SDK methods
- Kebab-case names prefixed with pet name (e.g.,
Example generated tool:
{
name: "polar-products-get",
description: "Get a product by ID",
schema: z.object({
id: z.string().describe("Product ID")
}),
async execute(args) {
try {
const result = await client.products.get(args)
return JSON.stringify(result, null, 2)
} catch (error: any) {
return JSON.stringify({ success: false, error: error.message }, null, 2)
}
}
}Handling Paginated Methods
List methods automatically iterate through all pages:
async execute(args) {
try {
const items: any[] = []
const result = await client.products.list(args)
for await (const page of result) {
if (page?.items) items.push(...page.items)
else if (page?.result?.items) items.push(...page.result.items)
else if (Array.isArray(page)) items.push(...page)
}
return JSON.stringify({ total: items.length, items }, null, 2)
} catch (error: any) {
return JSON.stringify({ success: false, error: error.message }, null, 2)
}
}Read-Only Mode and Tool Classification
The SDK generator implements a two-layer approach to classify tools as read-only or write operations:
Layer 1: Configuration-Based Classification
When defining namespaces, you explicitly mark which methods are read-only:
{
"namespaces": [
{
"name": "products",
"methods": ["list", "get", "create", "update", "delete"],
"readOnlyMethods": ["list", "get"]
}
]
}This generates a READ_ONLY_TOOLS array in the client:
// Read-only tool names (safe to use in read-only mode)
const READ_ONLY_TOOLS = [
"polar-products-list",
"polar-products-get",
"polar-customers-list",
"polar-customers-get",
// ... all tools from readOnlyMethods
]Layer 2: Pattern-Based Filtering
At runtime, the filterToolsForReadOnly function filters tools based on name patterns:
// Write operation patterns for filtering
const WRITE_PATTERNS = ['create', 'update', 'delete', 'add', 'remove', 'revoke', 'archive']
// Filter applied when read-only mode is enabled
const tools = filterToolsForReadOnly(allTools, WRITE_PATTERNS, readOnlyMode, logger)How Read-Only Mode Gets Enabled
Users can enable read-only mode in three ways (priority order):
Environment variable (highest priority):
bashexport POLAR_READ_ONLY=trueCLI command:
bashpets read-only polar onConfig file (
.pets/config.json):json{ "petConfig": { "polar": { "readOnly": true } } }
Runtime Behavior
When a user runs with read-only mode enabled:
$ pets read-only polar on
$ bun test:pet polar --query "list polar products"
# The plugin logs:
[polar-client] READ-ONLY MODE - write operations disabled
# Only read tools are available:
# ✅ polar-products-list
# ✅ polar-products-get
# ❌ polar-products-create (filtered out)
# ❌ polar-products-update (filtered out)
# ❌ polar-products-delete (filtered out)Verbose Dry-Run Output
The generator shows tool classification during generation:
$ bun scripts/generate-from-sdk.ts --pet my-service --dry-run --verbose
Tools:
[read] polar-products-list
[read] polar-products-get
[write] polar-products-create
[write] polar-products-update
[write] polar-products-delete
Generated 88 tools (45 read-only, 43 write) to client.tsReal-World Example: Polar SDK
The Polar SDK is a built-in example. With @polar-sh/sdk as a dependency, the generator auto-detects it:
{
"name": "@openpets/polar",
"dependencies": {
"@polar-sh/sdk": "latest"
}
}Run generation:
# Run from repository root
bun scripts/generate-from-sdk.ts --pet polar --verboseThis generates 88 tools (45 read-only, 43 write) covering:
- Organizations (list, get, create, update)
- Products (list, get, create, update, updateBenefits)
- Customers (list, get, create, update, delete, getExternal, updateExternal, deleteExternal, getState, getStateExternal)
- Orders (list, get, update, generateInvoice, invoice, export)
- Subscriptions (list, get, create, update, revoke, export)
- Benefits (list, get, create, update, delete, grants)
- Checkouts (list, get, create, update)
- Checkout Links (list, get, create, update)
- Discounts (list, get, create, update, delete)
- License Keys (list, get, create, update, getActivation, validate)
- Metrics (get, limits)
- Webhooks (listWebhookEndpoints, getWebhookEndpoint, createWebhookEndpoint, updateWebhookEndpoint, deleteWebhookEndpoint, listWebhookDeliveries)
- Files (list, create, update, delete, upload)
- Payments (list, get)
- Refunds (list)
- Meters (list, get, create, update, quantities)
- Customer Meters (list, get)
- Custom Fields (list, get, create, update, delete)
CLI Reference
# Basic usage (from repository root)
bun scripts/generate-from-sdk.ts --pet my-service
# Custom output file
bun scripts/generate-from-sdk.ts --pet my-service --output pets/my-service/sdk-tools.ts
# Preview without writing
bun scripts/generate-from-sdk.ts --pet my-service --dry-run
# Verbose output showing generated tools
bun scripts/generate-from-sdk.ts --pet my-service --verbose
# Combined
bun scripts/generate-from-sdk.ts --pet my-service --dry-run --verboseRegenerating Tools
When the SDK updates with new methods:
# Run from repository root
bun scripts/generate-from-sdk.ts --pet my-serviceThe client.ts file will be regenerated with the latest tools.
Troubleshooting
"No sdkGenerator configuration found"
Add an sdkGenerator section to your package.json or create an sdk-config.json file.
"Cannot find module"
Install the SDK package:
cd pets/my-service
bun add @my-service/sdkGenerated tools have wrong types
Check that your namespaces configuration matches the SDK's actual namespace and method names. Look at the SDK's README or TypeScript definitions for the correct names.
Paginated methods not collecting all items
The generator tries multiple patterns for pagination. If your SDK uses a different pattern, you may need to customize the generated code.
Comparing SDK vs MCP Generation
| Aspect | SDK Generation | MCP Generation |
|---|---|---|
| Source | TypeScript SDK package | MCP server (local or remote) |
| Authentication | Environment variable token | Token or OAuth (browser) |
| Speed | Very fast (no network) | Slower (connects to server) |
| Schema accuracy | Based on config patterns | Extracted from MCP server |
| Customization | Edit config, regenerate | Limited to MCP server tools |
| Dependencies | SDK package only | MCP client + server package |
Adding Support for New SDKs
To add built-in support for a new SDK, edit src/core/sdk-generator.ts and add a new config function similar to createPolarSDKConfig().
For SDKs not built-in, create an sdk-config.json file with the full configuration.