npm package: @openpets/medplum
Description: Template plugin demonstrating OpenPets best practices. Copy this directory to create your own pet with proper structure, test-connection tool, and graceful degradation.
Latest version: 1.0.2

# Pet Plugin Template

A comprehensive template demonstrating OpenPets best practices. Copy this folder and customize it to build your own pet.

## Best Practices Demonstrated

This template includes all required patterns:

1. **Test Connection Tool** - Every pet MUST have `{pet-name}-test-connection`
2. **Graceful Degradation** - Returns limited toolset when not configured
3. **Proper Tool Naming** - All tools follow `{pet-name}-{action}` pattern
4. **Structured Responses** - All tools return JSON with success/error status
5. **Environment Handling** - Uses `loadEnv()` for configuration
6. **Logging** - Uses `createLogger()` for debugging

## Quick Start

### 1. Copy This Template

```bash
# From the pets directory
cp -r _TEMPLATE_ my-pet
cd my-pet
```

### 2. Update package.json

Change these fields:

```json
{
  "name": "@openpets/my-pet",
  "title": "My Pet",
  "description": "What my pet does",
  "envVariables": {
    "required": [
      {
        "name": "MY_PET_API_KEY",
        "description": "API key from My Service"
      }
    ]
  },
  "queries": [
    "test my-pet connection",
    "list items from my-pet"
  ]
}
```

### 3. Update index.ts

```typescript
export const MyPetPlugin = async () => {
  const logger = createLogger("my-pet")
  const env = loadEnv("my-pet")  // Loads MY_PET_* variables
  
  const API_KEY = env.MY_PET_API_KEY
  const isConfigured = !!API_KEY
  
  if (!isConfigured) {
    // Return limited toolset when not configured
    return createPlugin([
      {
        name: "my-pet-test-connection",
        description: "Test my-pet connection status",
        schema: z.object({}),
        async execute() {
          return JSON.stringify({
            success: false,
            status: "not_configured",
            message: "Please set MY_PET_API_KEY"
          }, null, 2)
        }
      }
    ])
  }
  
  // Return full toolset when configured
  const tools: ToolDefinition[] = [
    {
      name: "my-pet-test-connection",
      // ... full implementation
    },
    {
      name: "my-pet-list-items",
      // ... your tools
    }
  ]
  
  return createPlugin(tools)
}
```

### 4. Update .env.example

```bash
# My Pet Configuration
MY_PET_API_KEY=your_api_key_here
```

### 5. Test Your Pet

```bash
bun install
opencode run "test my-pet connection" --print-logs
```

## Tool Naming Convention

All tools MUST follow the pattern: `{pet-name}-{action}`

| Good | Bad |
|------|-----|
| `jira-list-issues` | `list-issues` |
| `github-create-pr` | `create-pr` |
| `stripe-get-customer` | `getCustomer` |
| `my-pet-test-connection` | `test-connection` |

## Required: Test Connection Tool

Every pet MUST have a test-connection tool that:

1. Works even when the pet is not configured
2. Returns structured status information
3. Provides helpful error messages

```typescript
{
  name: "my-pet-test-connection",
  description: "Test connection and configuration status",
  schema: z.object({}),
  async execute() {
    return JSON.stringify({
      success: true,  // or false
      status: "connected",  // or "not_configured", "error"
      message: "Connected successfully",
      details: {
        // Add relevant details
      }
    }, null, 2)
  }
}
```

## Graceful Degradation

When your pet is not configured, return a limited toolset:

```typescript
if (!isConfigured) {
  logger.warn("Pet not configured - returning limited toolset")
  return createPlugin([
    // Only include test-connection tool
    testConnectionTool
  ])
}

// Full toolset when configured
return createPlugin(allTools)
```

## Response Format

Always return JSON strings with consistent structure:

```typescript
// Success response
return JSON.stringify({
  success: true,
  data: result,
  message: "Operation completed"
}, null, 2)

// Error response
return JSON.stringify({
  success: false,
  error: error.message,
  code: "INVALID_INPUT"  // optional error code
}, null, 2)

// List response
return JSON.stringify({
  success: true,
  total: items.length,
  items: items
}, null, 2)
```

## Schema Types (Zod)

Use these supported Zod types:

```typescript
schema: z.object({
  // Primitives
  name: z.string().describe("User name"),
  age: z.number().describe("User age"),
  active: z.boolean().describe("Is active"),
  
  // Optional with default
  limit: z.number().optional().default(50).describe("Max results"),
  
  // Enum
  status: z.enum(["active", "archived"]).describe("Status filter"),
  
  // Arrays (primitives only)
  tags: z.array(z.string()).describe("Tags list"),
  ids: z.array(z.number()).describe("ID list")
})
```

**NOT supported** (will cause errors):
- `z.array(z.object({...}))` - No nested objects in arrays
- `z.record()` - No record types
- `z.union()` with objects - No complex unions

For complex data, use JSON strings:

```typescript
schema: z.object({
  filtersJson: z.string().describe("JSON string of filter options")
}),
execute(args) {
  const filters = JSON.parse(args.filtersJson)
}
```

## Environment Variables

Use `loadEnv()` from openpets-sdk:

```typescript
const env = loadEnv("my-pet")

// This loads variables with the prefix:
// MY_PET_API_KEY -> env.MY_PET_API_KEY
// MY_PET_BASE_URL -> env.MY_PET_BASE_URL
```

Document in package.json:

```json
{
  "envVariables": {
    "required": [
      {
        "name": "MY_PET_API_KEY",
        "description": "API key from My Service",
        "provider": "My Service",
        "priority": 1
      }
    ],
    "optional": [
      {
        "name": "MY_PET_DEBUG",
        "description": "Enable debug logging",
        "provider": "Configuration",
        "priority": 2
      }
    ]
  }
}
```

## Logging

Use the logger for debugging:

```typescript
const logger = createLogger("my-pet")

logger.debug("Debug info", { context: data })
logger.info("Operation started")
logger.warn("Warning message")
logger.error("Error occurred", { error: err.message })
```

## Validation Checklist

Before publishing, verify:

- [ ] Package name is `@openpets/my-pet`
- [ ] Has `my-pet-test-connection` tool
- [ ] Test-connection works when NOT configured
- [ ] All tool names follow `my-pet-{action}` pattern
- [ ] All tools return JSON strings
- [ ] Environment variables documented in package.json
- [ ] .env.example has all variables
- [ ] queries array has example prompts
- [ ] `pets validate` passes

## Publishing

```bash
# Validate first
pets validate

# Preview what will be published
pets publish --preview

# Publish to npm
pets publish
```

## Examples

For real-world examples, check out:

- `pets/jira/` - Full-featured with 20+ tools
- `pets/github/` - GitHub API integration
- `pets/stripe/` - Payment processing
- `pets/wise/` - Financial services

## Need Help?

- [AGENTS.md](../../AGENTS.md) - Comprehensive development guide
- [docs/best-practices.md](../../docs/best-practices.md) - Best practices
- [docs/testing.md](../../docs/testing.md) - Testing guide