MCP Server Tool Generation
This guide explains how to automatically generate OpenPets-compatible tools from existing MCP (Model Context Protocol) servers, rather than manually defining each tool.
Overview
Many services now provide official MCP servers that expose their APIs as standardized tools. Instead of manually writing tool definitions for each API endpoint, you can use the pets generate-mcp command to automatically discover and generate tools from these servers.
This approach is similar to how hey-api generates TypeScript clients from OpenAPI specs - you point to a spec/server, and tools are generated automatically.
When to Use MCP Generation
Use MCP tool generation when:
- The service provides an official MCP server (e.g., Sentry, Asana, Atlassian, Polar)
- You want to quickly bootstrap a pet with 20+ tools
- The service's MCP server is well-maintained and up-to-date
- You need OAuth-based authentication that the MCP server handles
Consider manual tool definitions when:
- The service doesn't have an MCP server
- You need custom logic beyond what the MCP server provides
- You want more control over tool naming, descriptions, or behavior
- The MCP server doesn't expose all the functionality you need
Transport Modes
MCP servers support two main authentication/transport modes:
1. Stdio Transport (Access Token)
Used when the MCP server is distributed as an npm package that runs locally and accepts an access token.
Example: Sentry
{
"mcpServer": {
"url": "https://mcp.sentry.dev/mcp",
"name": "sentry",
"npmPackage": "@sentry/mcp-server@latest"
}
}Authentication: Requires SENTRY_ACCESS_TOKEN environment variable.
2. SSE Remote Transport (OAuth)
Used when the MCP server is hosted remotely and uses OAuth 2.0 for authentication. Uses mcp-remote as a proxy to handle the OAuth flow automatically.
How it works:
- The generator spawns
bunx mcp-remote <server-url>as a subprocess mcp-remoteinitiates OAuth 2.0 authorization- A browser window opens automatically for you to log in
- After authorization, credentials are cached in
~/.mcp-auth/ - Subsequent connections use cached credentials (no browser popup)
Example: Asana
{
"mcpServer": {
"url": "https://mcp.asana.com/sse",
"name": "asana",
"transport": "sse-remote"
}
}Example: Atlassian (Jira & Confluence)
{
"mcpServer": {
"url": "https://mcp.atlassian.com/v1/sse",
"name": "atlassian",
"transport": "sse-remote"
}
}Example: Polar
{
"mcpServer": {
"url": "https://mcp.polar.sh/mcp/polar-mcp",
"name": "polar",
"transport": "sse-remote"
}
}Authentication: On first use, a browser window opens for OAuth login. Credentials are cached in ~/.mcp-auth/.
Important Notes for OAuth servers:
- No environment variable is required - authentication is handled via browser
- The first connection will timeout if you don't complete OAuth in the browser within ~60 seconds
- After first successful auth, subsequent connections are automatic
- To re-authenticate, delete the cached credentials:
rm -rf ~/.mcp-auth/
Step-by-Step Guide
Step 1: Add MCP Server Config to package.json
Add an mcpServer section to your pet's package.json:
For Stdio Transport (npm package with access token):
{
"name": "@openpets/my-service",
"mcpServer": {
"url": "https://mcp.my-service.com/mcp",
"name": "my-service",
"npmPackage": "@my-service/mcp-server@latest"
},
"envVariables": {
"required": [
{
"name": "MY_SERVICE_ACCESS_TOKEN",
"description": "API access token from My Service",
"provider": "My Service",
"priority": 1
}
]
}
}For SSE Remote Transport (OAuth):
{
"name": "@openpets/my-service",
"mcpServer": {
"url": "https://mcp.my-service.com/sse",
"name": "my-service",
"transport": "sse-remote"
}
}Step 2: Set Up Authentication
For Stdio Transport:
Set the access token environment variable:
# In your .env file
MY_SERVICE_ACCESS_TOKEN=your-token-hereFor SSE Remote Transport (OAuth):
No environment variable needed. OAuth will be triggered during generation and on first tool invocation.
Important: For SSE Remote (OAuth) servers, the generation process requires interactive browser authentication:
- When you run
pets generate-mcp, a browser window will open automatically - Log in to the service (e.g., Atlassian, Asana) and authorize the MCP connection
- After successful authorization, return to the terminal - generation will continue
- Credentials are cached in
~/.mcp-auth/for future use
Step 3: Generate Tools
Run the MCP generator from your pet's directory:
cd pets/my-service
# Preview what will be generated (for token-based auth)
pets generate-mcp --dry-run --verbose
# Generate the mcp.ts file
pets generate-mcpFor OAuth-based servers (SSE Remote):
The generation process is interactive:
cd pets/my-service
# This will open a browser for OAuth
pets generate-mcp --verboseYou'll see output like:
Transport: sse-remote
Connecting to remote MCP server via OAuth: https://mcp.atlassian.com/v1/sse
🔐 OAuth authentication required. A browser window will open...
Please authorize this client by visiting:
https://mcp.atlassian.com/v1/authorize?response_type=code&client_id=...
Browser opened automatically.Complete these steps:
- The browser opens automatically (or copy/paste the URL if it doesn't)
- Log in to the service and authorize the connection
- Return to the terminal - generation will continue automatically
- The
mcp.tsfile is created with all discovered tools
Alternative: Using bun directly
If the pets generate-mcp command isn't available, you can run the generator directly:
cd pets/my-service
# Using the SDK function directly
bun -e "
import { generateMCPTools } from '../../src/core/mcp-generator'
const result = await generateMCPTools({ verbose: true })
console.log(result.message)
"This creates an mcp.ts file with all discovered tools.
Step 4: Import MCP 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 mcpTools from "./mcp"
export const MyServicePlugin = async () => {
const logger = createLogger("my-service")
const env = loadEnv("my-service")
const ACCESS_TOKEN = env.MY_SERVICE_ACCESS_TOKEN
const isConfigured = !!ACCESS_TOKEN
// For OAuth-based MCP tools, they work even without an access token
// because authentication is handled by mcp-remote
if (!isConfigured) {
logger.info("Using MCP tools only (OAuth-based)")
// Return MCP tools + basic test connection
return createPlugin([
{
name: "my-service-test-connection",
description: "Test connection status",
schema: z.object({}),
async execute() {
return JSON.stringify({
success: true,
status: "mcp_only",
message: "Running with MCP tools. OAuth will prompt on first use.",
mcpToolsLoaded: mcpTools.length
}, null, 2)
}
},
...mcpTools
])
}
// If configured, you can add additional custom tools alongside MCP tools
const customTools: ToolDefinition[] = [
// Your custom tools that use the access token directly
]
return createPlugin([
...mcpTools,
...customTools
])
}
export default MyServicePluginStep 5: Test the Integration
# Test the connection
bun test:pet my-service --query "test connection"
# For OAuth, a browser window will open on first useGenerated File Structure
The mcp.ts file includes:
- MCP Client Management - Lazy-initialized client connection
- Tool Caller - Wrapper function that calls MCP tools and returns JSON
- Tool Definitions - Auto-generated ToolDefinition array with:
- Kebab-case names prefixed with pet name (e.g.,
asana-get-task) - Descriptions extracted from MCP tool metadata
- Zod schemas converted from JSON Schema
- Execute functions that delegate to the MCP server
- Kebab-case names prefixed with pet name (e.g.,
Example generated tool:
{
name: "asana-get-task",
description: `Returns the complete task record for a single task.`,
schema: z.object({
task_gid: z.string().describe("Globally unique identifier for the task."),
opt_fields: z.string().optional().describe("Comma-separated list of optional fields")
}),
async execute(args) {
return callMCPTool("get_task", args)
}
}Handling Schema Limitations
OpenPets runtime schemas have compatibility limits (no nested objects in arrays, no deeply nested objects). The generator automatically handles this by:
- Converting complex array items to
z.string()with a "JSON array" description - Converting nested objects to
z.string()with a "JSON object" description
The MCP tool will still receive the proper JSON when you pass a JSON string.
Combining MCP Tools with Custom Tools
You can mix auto-generated MCP tools with custom tools:
const tools: ToolDefinition[] = [
// Auto-generated from MCP server
...mcpTools,
// Custom tools with additional logic
{
name: "my-service-custom-workflow",
description: "Custom workflow combining multiple operations",
schema: z.object({
input: z.string().describe("Workflow input")
}),
async execute(args) {
// Call multiple MCP tools or add custom logic
const step1 = await callMCPTool("tool_a", { id: args.input })
const step2 = await callMCPTool("tool_b", { data: step1 })
return JSON.stringify({ result: step2 }, null, 2)
}
}
]Regenerating Tools
When the MCP server updates with new tools or changed schemas:
cd pets/my-service
pets generate-mcpThe mcp.ts file will be regenerated with the latest tools.
Real-World Examples
Sentry (Stdio Transport)
// pets/sentry/package.json
{
"mcpServer": {
"url": "https://mcp.sentry.dev/mcp",
"name": "sentry",
"npmPackage": "@sentry/mcp-server@latest"
}
}Generates 20 tools including: sentry-list-issues, sentry-get-issue-details, sentry-list-projects, etc.
Asana (SSE Remote / OAuth)
// pets/asana/package.json
{
"mcpServer": {
"url": "https://mcp.asana.com/sse",
"name": "asana",
"transport": "sse-remote"
}
}Generates 44 tools including: asana-get-task, asana-create-task, asana-search-tasks, etc.
Atlassian / Jira (SSE Remote / OAuth)
// pets/jira/package.json
{
"mcpServer": {
"url": "https://mcp.atlassian.com/v1/sse",
"name": "atlassian",
"transport": "sse-remote"
}
}The Atlassian MCP server provides access to both Jira and Confluence. On first use:
- Browser opens to Atlassian's OAuth page
- Select your Atlassian Cloud site
- Authorize the MCP connection
- Credentials are cached for future use
Generates tools for Jira (issues, projects, boards, sprints) and Confluence (pages, spaces, search).
Polar (SSE Remote / OAuth)
// pets/polar/package.json
{
"mcpServer": {
"url": "https://mcp.polar.sh/mcp/polar-mcp",
"name": "polar",
"transport": "sse-remote"
}
}Generates tools for managing products, customers, orders, and subscriptions.
Authentication Requirements Summary
| Transport | Auth Method | Environment Variable | Notes |
|---|---|---|---|
| stdio | Access Token | {PET_NAME}_ACCESS_TOKEN | Token passed to npm package |
| sse-remote | OAuth 2.0 | None (browser popup) | Credentials cached in ~/.mcp-auth/ |
OAuth Credential Management
For SSE Remote (OAuth) servers, credentials are cached automatically:
# View cached OAuth credentials
ls -la ~/.mcp-auth/
# Clear all cached credentials (forces re-authentication)
rm -rf ~/.mcp-auth/
# Clear credentials for specific server
rm ~/.mcp-auth/<server-hash>CLI Reference
# Basic usage (from pet directory)
pets generate-mcp
# Custom output file
pets generate-mcp --output tools.ts
# Preview without writing
pets generate-mcp --dry-run
# Verbose output showing discovered tools
pets generate-mcp --verbose
# Combined
pets generate-mcp --dry-run --verboseTroubleshooting
"No mcpServer configuration found"
Add an mcpServer section to your package.json.
"Missing ACCESS_TOKEN environment variable"
For stdio transport, set the token in .env:
MY_SERVICE_ACCESS_TOKEN=your-token-here"Failed to connect to MCP server"
- Check that the npm package name is correct
- Ensure you have network access to the MCP server URL
- Verify the access token is valid
OAuth popup doesn't appear
For sse-remote transport:
- Check that your default browser can open automatically
- Look for the authorization URL in the terminal output and open it manually
- Ensure
mcp-remoteis installed:bunx mcp-remote --version
"Request timed out" during OAuth
This means you didn't complete the OAuth flow in time (default ~60 seconds):
- Run the generator again
- Complete the browser authorization quickly
- Return to the terminal to see the results
# Try again with verbose output
pets generate-mcp --verboseOAuth authentication issues
If OAuth keeps failing:
# Clear cached credentials and try again
rm -rf ~/.mcp-auth/
# Verify mcp-remote works standalone
bunx mcp-remote https://mcp.atlassian.com/v1/sse"Browser opened automatically" but nothing happens
Some systems don't support automatic browser opening:
- Copy the authorization URL from the terminal
- Paste it into your browser manually
- Complete the OAuth flow
- Return to the terminal
Tools not working after generation
- Check that
mcp.tsis imported inindex.ts - Verify the tools are added to the
createPlugin()call - Check logs for connection errors
- For OAuth servers, verify credentials are cached:
ls ~/.mcp-auth/
mcp-remote version conflicts
If you see npm override errors with mcp-remote:
# Use a specific version
bunx mcp-remote@0.1.13 https://mcp.atlassian.com/v1/sse
# Or install globally
bun add -g mcp-remoteAdvanced: Manual OAuth Flow
For SSE Remote servers where automatic OAuth doesn't work, you can perform the OAuth flow manually:
# Step 1: Start mcp-remote in a separate terminal
bunx mcp-remote https://mcp.atlassian.com/v1/sse
# Step 2: Complete OAuth in browser when prompted
# Step 3: Once authenticated, run the generator in another terminal
# The cached credentials will be used automatically
cd pets/jira
pets generate-mcp --verboseKnown SSE Remote MCP Servers
| Service | URL | Notes |
|---|---|---|
| Asana | https://mcp.asana.com/sse | Task and project management |
| Atlassian | https://mcp.atlassian.com/v1/sse | Jira + Confluence |
| Polar | https://mcp.polar.sh/mcp/polar-mcp | Payments and subscriptions |
These servers all require OAuth authentication via browser popup on first use.