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 LLMThe 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-123Step 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
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
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 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