Skip to content

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.


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.


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 });
}
};

Terminal window
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

Terminal window
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.


Terminal window
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.


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

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
});

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.


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.


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