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
Section titled “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 LLMThe function receives the tool name and input. It returns a standard MCP content response.
Step 1: Write the Function
Section titled “Step 1: Write the Function”Your function receives a JSON body with { tool, input } and returns { content }:
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
Section titled “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-123Step 3: Create the Skill Wrapper
Section titled “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 Skills → New Skill → Backed by Function.
Step 4: Install and Test
Section titled “Step 4: Install and Test”aerostack skill install yourusername/invoice-extractor# ✓ extract_invoice now available in your gatewayAsk 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
Section titled “Function Input/Output Contract”Input (what your function receives)
Section titled “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)
Section titled “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
Section titled “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
Section titled “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 AIenv.AI.run('@cf/...')
// Use database (if bound)env.DB.prepare('SELECT ...').all()
// Use cacheenv.KV.get('key')
// Use object storageenv.R2.get('object-key')Set function environment variables in the Admin dashboard → My Functions → Environment Variables.
When to Use External URL Instead
Section titled “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