Skip to content

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

json
{
  "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:

  1. The generator spawns bunx mcp-remote <server-url> as a subprocess
  2. mcp-remote initiates OAuth 2.0 authorization
  3. A browser window opens automatically for you to log in
  4. After authorization, credentials are cached in ~/.mcp-auth/
  5. Subsequent connections use cached credentials (no browser popup)

Example: Asana

json
{
  "mcpServer": {
    "url": "https://mcp.asana.com/sse",
    "name": "asana",
    "transport": "sse-remote"
  }
}

Example: Atlassian (Jira & Confluence)

json
{
  "mcpServer": {
    "url": "https://mcp.atlassian.com/v1/sse",
    "name": "atlassian",
    "transport": "sse-remote"
  }
}

Example: Polar

json
{
  "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):

json
{
  "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):

json
{
  "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:

bash
# In your .env file
MY_SERVICE_ACCESS_TOKEN=your-token-here

For 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:

  1. When you run pets generate-mcp, a browser window will open automatically
  2. Log in to the service (e.g., Atlassian, Asana) and authorize the MCP connection
  3. After successful authorization, return to the terminal - generation will continue
  4. Credentials are cached in ~/.mcp-auth/ for future use

Step 3: Generate Tools

Run the MCP generator from your pet's directory:

bash
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-mcp

For OAuth-based servers (SSE Remote):

The generation process is interactive:

bash
cd pets/my-service

# This will open a browser for OAuth
pets generate-mcp --verbose

You'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:

  1. The browser opens automatically (or copy/paste the URL if it doesn't)
  2. Log in to the service and authorize the connection
  3. Return to the terminal - generation will continue automatically
  4. The mcp.ts file 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:

bash
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:

typescript
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 MyServicePlugin

Step 5: Test the Integration

bash
# Test the connection
bun test:pet my-service --query "test connection"

# For OAuth, a browser window will open on first use

Generated File Structure

The mcp.ts file includes:

  1. MCP Client Management - Lazy-initialized client connection
  2. Tool Caller - Wrapper function that calls MCP tools and returns JSON
  3. 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

Example generated tool:

typescript
{
  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:

  1. Converting complex array items to z.string() with a "JSON array" description
  2. 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:

typescript
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:

bash
cd pets/my-service
pets generate-mcp

The mcp.ts file will be regenerated with the latest tools.

Real-World Examples

Sentry (Stdio Transport)

json
// 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)

json
// 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)

json
// 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:

  1. Browser opens to Atlassian's OAuth page
  2. Select your Atlassian Cloud site
  3. Authorize the MCP connection
  4. Credentials are cached for future use

Generates tools for Jira (issues, projects, boards, sprints) and Confluence (pages, spaces, search).

Polar (SSE Remote / OAuth)

json
// 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

TransportAuth MethodEnvironment VariableNotes
stdioAccess Token{PET_NAME}_ACCESS_TOKENToken passed to npm package
sse-remoteOAuth 2.0None (browser popup)Credentials cached in ~/.mcp-auth/

OAuth Credential Management

For SSE Remote (OAuth) servers, credentials are cached automatically:

bash
# 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

bash
# 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 --verbose

Troubleshooting

"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:

bash
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:

  1. Check that your default browser can open automatically
  2. Look for the authorization URL in the terminal output and open it manually
  3. Ensure mcp-remote is installed: bunx mcp-remote --version

"Request timed out" during OAuth

This means you didn't complete the OAuth flow in time (default ~60 seconds):

  1. Run the generator again
  2. Complete the browser authorization quickly
  3. Return to the terminal to see the results
bash
# Try again with verbose output
pets generate-mcp --verbose

OAuth authentication issues

If OAuth keeps failing:

bash
# 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:

  1. Copy the authorization URL from the terminal
  2. Paste it into your browser manually
  3. Complete the OAuth flow
  4. Return to the terminal

Tools not working after generation

  1. Check that mcp.ts is imported in index.ts
  2. Verify the tools are added to the createPlugin() call
  3. Check logs for connection errors
  4. For OAuth servers, verify credentials are cached: ls ~/.mcp-auth/

mcp-remote version conflicts

If you see npm override errors with mcp-remote:

bash
# Use a specific version
bunx mcp-remote@0.1.13 https://mcp.atlassian.com/v1/sse

# Or install globally
bun add -g mcp-remote

Advanced: Manual OAuth Flow

For SSE Remote servers where automatic OAuth doesn't work, you can perform the OAuth flow manually:

bash
# 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 --verbose

Known SSE Remote MCP Servers

ServiceURLNotes
Asanahttps://mcp.asana.com/sseTask and project management
Atlassianhttps://mcp.atlassian.com/v1/sseJira + Confluence
Polarhttps://mcp.polar.sh/mcp/polar-mcpPayments and subscriptions

These servers all require OAuth authentication via browser popup on first use.