MCP ServersFunction-Backed Skills

Function-Backed Skills

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

// src/invoice-extractor/index.ts
export default {
  async fetch(request: Request, env: Env & { AI: Ai }): Promise<Response> {
    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

aerostack functions push src/invoice-extractor/index.ts \
  --name "invoice-extractor"
 
aerostack functions publish <returned-id>
# → Deployed as fn--{id} on Cloudflare edge
# → Returns: function ID abc-123

Step 3: Create the Skill Wrapper

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 SkillsNew SkillBacked by Function.


Step 4: Install and Test

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)

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

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

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

Multi-Tool Functions

One function can handle multiple tools:

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    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:

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