# Function-Backed Skills

> Write TypeScript business logic as an Aerostack function, then expose it as an AI-callable skill on the edge. No server required.

Every other MCP skill marketplace requires you to run and maintain an HTTPS server. Aerostack doesn't.

Write your skill's logic as a **community function** — TypeScript code that runs on Cloudflare edge. Then wrap it as a **skill** with a tool definition. The gateway calls your function directly when an LLM invokes the tool.

No cold starts. No server costs. No SSL certs. No infrastructure.

---

## How It Works

```
LLM calls tool via gateway
       ↓
Gateway receives: tools/call { name: "extract_invoice", input: { pdf_url: "..." } }
       ↓
Skill is backed by function fn--abc123 (Cloudflare dispatch namespace)
       ↓
Gateway calls: fn--abc123 → { tool: "extract_invoice", input: { pdf_url: "..." } }
       ↓
Function runs TypeScript logic (PDF parse, AI extraction, database query...)
       ↓
Function returns: { content: [{ type: "text", text: "..." }] }
       ↓
Gateway returns result to LLM
```

The function receives the tool name and input. It returns a standard MCP content response.

---

## Step 1: Write the Function

Your function receives a JSON body with `{ tool, input }` and returns `{ content }`:

```typescript
// src/invoice-extractor/index.ts

  async fetch(request: Request, env: Env & { AI: Ai }): Promise {
    const { tool, input } = await request.json() as {
      tool: string;
      input: Record<string, unknown>;
    };

    if (tool === 'extract_invoice') {
      const pdfUrl = input.pdf_url as string;

      // Download and parse the PDF
      const pdfResponse = await fetch(pdfUrl);
      const pdfBuffer = await pdfResponse.arrayBuffer();

      // Use Workers AI to extract structured data
      const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
        messages: [
          {
            role: 'system',
            content: 'Extract invoice data as JSON: { vendor, amount, date, line_items[] }'
          },
          {
            role: 'user',
            content: `PDF content: [binary data, ${pdfBuffer.byteLength} bytes]`
          }
        ]
      });

      return Response.json({
        content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
      });
    }

    return Response.json({ error: 'Unknown tool' }, { status: 400 });
  }
};
```

---

## Step 2: Deploy the Function

```bash
aerostack functions push src/invoice-extractor/index.ts \
  --name "invoice-extractor"

aerostack functions publish <returned-id>
# → Returns: function ID abc-123
```

---

## Step 3: Create the Skill Wrapper

```bash
aerostack skill publish \
  --name "Invoice Extractor" \
  --description "Extract structured data from invoice PDFs using AI" \
  --function abc-123 \
  --tools '[
    {
      "name": "extract_invoice",
      "description": "Extract invoice data (vendor, amount, date, line items) from a PDF URL",
      "inputSchema": {
        "type": "object",
        "properties": {
          "pdf_url": {
            "type": "string",
            "description": "HTTPS URL of the invoice PDF"
          }
        },
        "required": ["pdf_url"]
      }
    }
  ]'
```

Or use the Admin dashboard → **My Skills** → **New Skill** → **Backed by Function**.

---

## Step 4: Install and Test

```bash
aerostack skill install yourusername/invoice-extractor
# ✓ extract_invoice now available in your gateway
```

Ask your LLM: *"Extract the invoice data from https://example.com/invoice-jan.pdf"*

The agent calls `extract_invoice({ pdf_url: "..." })` → your function runs on Cloudflare edge → structured data returned.

---

## Function Input/Output Contract

### Input (what your function receives)

```typescript
{
  tool: string;           // The tool name being called (e.g. "extract_invoice")
  input: {                // The arguments the LLM passed
    [key: string]: unknown;
  };
}
```

### Output (what your function must return)

MCP-standard content response:

```typescript
{
  content: Array<
    | { type: 'text'; text: string }
    | { type: 'image'; data: string; mimeType: string }
    | { type: 'resource'; resource: { uri: string; text?: string; blob?: string } }
  >;
  isError?: boolean;  // Set to true if the tool call failed
}
```

For errors:

```typescript
return Response.json({
  content: [{ type: 'text', text: 'Failed to parse PDF: invalid format' }],
  isError: true
});
```

---

## Multi-Tool Functions

One function can handle multiple tools:

```typescript

  async fetch(request: Request, env: Env): Promise {
    const { tool, input } = await request.json();

    switch (tool) {
      case 'extract_invoice':
        return handleExtractInvoice(input, env);

      case 'validate_invoice':
        return handleValidateInvoice(input, env);

      case 'export_to_csv':
        return handleExportToCsv(input, env);

      default:
        return Response.json({ error: `Unknown tool: ${tool}` }, { status: 400 });
    }
  }
};
```

Declare all tools in your skill definition — they all route to the same function.

---

## Environment Variables and Bindings

Your function runs in the Cloudflare worker environment with all the same capabilities:

```typescript
// Access environment variables (set in Admin → Functions → Environment)
env.MY_API_KEY

// Use Workers AI
env.AI.run('@cf/...')

// Use database (if bound)
env.DB.prepare('SELECT ...').all()

// Use cache
env.KV.get('key')

// Use object storage
env.R2.get('object-key')
```

Set function environment variables in the Admin dashboard → **My Functions** → **Environment Variables**.

---

## When to Use External URL Instead

Function-backed skills are ideal for:
- Custom business logic
- AI pipelines
- Database queries
- Data transformation

Use an **external URL** (standard MCP server) when:
- You already have a running server
- You need persistent connections (WebSockets)
- You require a language other than TypeScript/JavaScript
- You need local file system access

---

## Next Steps

- [Use your function-backed skill with any LLM →](/mcp/cross-llm)
- [Make your skill private for your team →](/mcp/team-skills)
