Agent skill

agent-tool

Define tools for the support agent. Use when adding new capabilities like refund processing, license transfer, knowledge lookup, or any agent action.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/agent-tool

SKILL.md

Agent Tool Definition

Tools are the agent's hands. Every action the agent can take is a tool with defined parameters, approval requirements, and execution logic.

Agent Core (Mastra)

typescript
import { Agent } from '@mastra/core'
import { supportTools } from './tools'

export const supportAgent = new Agent({
  name: 'support-agent',
  instructions: `
    You are a support agent for Skill Recordings products.
    Your goal is to resolve customer issues quickly and empathetically.

    ## Authority Levels

    AUTO-APPROVE (do immediately):
    - Magic link requests
    - Password reset requests
    - Refunds within 30 days of purchase
    - Transfers within 14 days of purchase
    - Email/name updates

    REQUIRE-APPROVAL (draft action, wait for human):
    - Refunds 30-45 days after purchase
    - Transfers after 14 days
    - Bulk seat management
    - Account deletions

    ALWAYS-ESCALATE (flag for human, do not act):
    - Angry/frustrated customers (detect sentiment)
    - Legal language (lawsuit, lawyer, etc.)
    - Repeated failed interactions
    - Anything you're uncertain about

    ## Response Style
    - Be personal, use first names
    - Be concise, not verbose
    - Don't apologize excessively
    - Focus on resolution, not explanation
  `,
  model: { provider: 'anthropic', name: 'claude-sonnet-4-[PHONE]' },
  tools: supportTools,
})

Tool Definition Pattern

typescript
import { createTool } from './create-tool'
import { z } from 'zod'
import { getApp } from '@skillrecordings/core/services/app-registry'
import { IntegrationClient } from '@skillrecordings/sdk/client'

export const processRefund = createTool({
  name: 'process_refund',
  description: 'Process a refund for a purchase. Use only within policy.',

  // Zod schema for parameters
  parameters: z.object({
    purchaseId: z.string(),
    appId: z.string(),
    reason: z.string(),
  }),

  // Dynamic approval requirement based on context
  requiresApproval: (params, context) => {
    const purchase = context.purchases.find(p => p.id === params.purchaseId)
    const daysSincePurchase = daysBetween(purchase.purchasedAt, new Date())
    return daysSincePurchase > 30  // Auto-approve within 30 days
  },

  // Execution logic - THROW errors, don't return {success: false}
  execute: async ({ purchaseId, appId, reason }, context) => {
    // Get app config from registry (5-min TTL cache)
    const app = await getApp(appId)
    if (!app) {
      throw new Error(`App not found: ${appId}`)  // ← THROW, don't return error
    }

    // Process via Stripe Connect
    const stripeRefund = await stripe.refunds.create({
      charge: purchase.stripeChargeId,
    }, {
      stripeAccount: app.stripe_account_id,
    })

    // Revoke access via IntegrationClient (signed request)
    const client = new IntegrationClient({
      baseUrl: app.integration_base_url,
      webhookSecret: app.webhook_secret,
    })

    const revokeResult = await client.revokeAccess({
      purchaseId,
      reason,
      refundId: stripeRefund.id,
    })

    if (!revokeResult.success) {
      throw new Error(revokeResult.message || 'Failed to revoke access')
    }

    // Return data only - wrapper adds {success: true, data: ...}
    return { refundId: stripeRefund.id, amountRefunded: stripeRefund.amount }
  },
})

Error Handling Pattern

CRITICAL: The createTool wrapper handles success/error wrapping automatically.

typescript
// ❌ WRONG - Don't return success/error objects
execute: async (params) => {
  if (!app) {
    return { success: false, error: 'App not found' }  // BAD
  }
  return { success: true, data: result }  // BAD
}

// ✅ CORRECT - Throw errors, return plain data
execute: async (params) => {
  if (!app) {
    throw new Error('App not found')  // GOOD - wrapper catches this
  }
  return { refundId, amount }  // GOOD - wrapper wraps as {success: true, data: ...}
}

The wrapper produces:

  • Success: { success: true, data: <your return value> }
  • Error: { success: false, error: { code: 'EXECUTION_ERROR', message: '...' } }

## Standard Tools

| Tool | Description | Approval |
|------|-------------|----------|
| `lookup_user` | Get user details and purchase history | Never |
| `process_refund` | Issue a refund via Stripe Connect | >30 days |
| `generate_magic_link` | Create login link for user | Never |
| `transfer_purchase` | Move purchase to another user | >14 days |
| `draft_response` | Create draft reply in Front | Never |
| `escalate_to_human` | Flag for human review | Never |

## Tool Examples

### Lookup User (No Approval)
```typescript
import { getApp } from '@skillrecordings/core/services/app-registry'
import { IntegrationClient } from '@skillrecordings/sdk/client'

export const lookupUser = createTool({
  name: 'lookup_user',
  description: 'Look up a user by email to get their account details and purchase history',
  parameters: z.object({
    email: z.string().email(),
    appId: z.string(),
  }),
  execute: async ({ email, appId }) => {
    const app = await getApp(appId)
    if (!app) {
      return { found: false, error: `App not found: ${appId}` }
    }

    const client = new IntegrationClient({
      baseUrl: app.integration_base_url,
      webhookSecret: app.webhook_secret,
    })

    const user = await client.lookupUser(email)
    if (!user) {
      return { found: false, user: null, purchases: [] }
    }

    const purchases = await client.getPurchases(user.id)
    return { found: true, user, purchases }
  },
})

Escalate to Human

typescript
export const escalateToHuman = createTool({
  name: 'escalate_to_human',
  description: 'Escalate this conversation to a human support agent',
  parameters: z.object({
    conversationId: z.string(),
    reason: z.string(),
    urgency: z.enum(['low', 'medium', 'high']),
  }),
  execute: async ({ conversationId, reason, urgency }) => {
    await front.conversations.addTag(conversationId, 'needs-human')

    await slack.postMessage({
      channel: SUPPORT_CHANNEL,
      text: `Escalation needed`,
      blocks: [
        {
          type: 'section',
          text: { type: 'mrkdwn', text: `*Reason:* ${reason}\n*Urgency:* ${urgency}` },
        },
        {
          type: 'actions',
          elements: [
            { type: 'button', text: { type: 'plain_text', text: 'Open in Front' }, url: frontConversationUrl },
          ],
        },
      ],
    })

    return { escalated: true }
  },
})

File Locations

File Purpose
packages/core/src/agent/config.ts Agent definition with tool bindings
packages/core/src/tools/ Individual tool implementations
packages/core/src/tools/create-tool.ts Tool factory with success/error wrapping
packages/core/src/services/app-registry.ts App config lookup (5-min TTL cache)
packages/sdk/src/client.ts IntegrationClient for SDK calls

Reference Docs

For full details, see:

  • docs/support-app-prd/64-agent-tools.md

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results