Host on Aerostack
Build a custom MCP server and deploy it to Cloudflare’s edge network. Your server gets a permanent HTTPS URL, runs globally with zero cold starts, and scales to zero when idle. No ngrok, no VPS, no Docker.
Hosted MCP servers are stored with hosted=1 and a worker_url pointing to your deployed Cloudflare Worker.
When to Host on Aerostack
Section titled “When to Host on Aerostack”- You want to build a custom integration (internal API, proprietary data source, business logic)
- You want zero infrastructure — no servers, no SSL, no scaling config
- You want to publish to the Hub for others to install
- You want native platform bindings (Database, Cache, Storage, AI, Vector Search)
If you already have an MCP server running on your own infrastructure, see Proxy Existing MCP instead.
Project Structure
Section titled “Project Structure”An Aerostack-hosted MCP server follows this structure:
my-mcp-server/ src/ index.ts # MCP server entry point aerostack.json # Aerostack MCP configuration aerostack.toml # Cloudflare Worker configuration package.json tsconfig.jsonaerostack.json
Section titled “aerostack.json”This is the MCP server manifest. It tells Aerostack about your server’s metadata, required secrets, and publishing info:
{ "name": "my-mcp-server", "slug": "my-mcp-server", "version": "1.0.0", "description": "A custom MCP server for internal APIs", "category": "developer-tools", "tags": ["crm", "support", "internal-tools"], "secrets": ["INTERNAL_API_KEY"], "visibility": "team"}aerostack.toml
Section titled “aerostack.toml”This is the Cloudflare Worker configuration. Any [vars] you declare here become the config schema — Aerostack reads them to auto-generate the Docs tab auth section, so users know what secrets to provide when installing your MCP:
name = "my-mcp-server"main = "src/index.ts"compatibility_date = "2024-01-01"
[vars]INTERNAL_API_KEY = ""When users install your MCP from the Hub, they see these env var names in the Docs tab and know exactly what secrets to configure.
src/index.ts
Section titled “src/index.ts”The MCP server entry point. This is a standard Cloudflare Worker that handles MCP protocol requests:
import { McpServer } from '@aerostack/mcp';
const server = new McpServer({ name: 'my-mcp-server', version: '1.0.0',});
server.tool('lookup_customer', async ({ email }, env) => { const response = await fetch(`https://api.internal.yourcompany.com/customers?email=${email}`, { headers: { 'Authorization': `Bearer ${env.INTERNAL_API_KEY}` }, }); const customer = await response.json();
return { content: [{ type: 'text', text: JSON.stringify(customer, null, 2), }], };});
server.tool('create_ticket', async ({ customer_email, subject, body, priority }, env) => { const response = await fetch('https://api.internal.yourcompany.com/tickets', { method: 'POST', headers: { 'Authorization': `Bearer ${env.INTERNAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ customer_email, subject, body, priority: priority || 'medium', }), }); const ticket = await response.json();
return { content: [{ type: 'text', text: `Ticket #${ticket.id} created: ${ticket.subject}`, }], };});
export default server;Build and Deploy
Section titled “Build and Deploy”-
Scaffold the project
Terminal window aerostack mcp init my-mcp-servercd my-mcp-servernpm install -
Write your tool handlers
Edit
src/index.tswith your tool logic. Each tool receives the input arguments and the Workerenvobject (for secrets and bindings). -
Test locally
Terminal window npm run dev# -> MCP server running at http://localhost:8787You can test with any MCP client pointed at
http://localhost:8787/sse. -
Deploy to Cloudflare edge
Terminal window aerostack deploy mcp# -> Deploying my-mcp-server to Cloudflare edge...# -> Worker URL: https://mcp-my-mcp-server.yourname.workers.dev# -> MCP registered in Aerostack (hosted=1) -
Set secrets
Terminal window aerostack secrets set my-mcp-server INTERNAL_API_KEY "sk-your-api-key"# -> Secret stored (AES-GCM encrypted). -
Add to your workspace
Terminal window aerostack mcp install my-mcp-server --workspace my-workspaceYour tools are now available:
my-mcp-server__lookup_customermy-mcp-server__create_ticket
What Happens on Deploy
Section titled “What Happens on Deploy”When you run aerostack deploy mcp, the platform does much more than just upload your Worker:
Auto-Generated Capability Manifest
Section titled “Auto-Generated Capability Manifest”After tools are fetched, Aerostack generates a capability manifest — a structured summary that powers intelligent discovery:
{ "capabilities": ["crud", "search", "webhooks"], "data_types": ["customers", "tickets", "support-cases"], "auth_required": "secret_headers", "triggers_available": ["ticket.created", "ticket.updated"], "pairs_with": ["slack-mcp", "email-mcp"], "best_for": ["customer support automation", "ticket triage"], "not_suitable_for": ["real-time streaming", "file storage"]}This is generated using Workers AI (Llama 3.1 8B) with a heuristic fallback. You don’t need to write this manually — it’s derived from your tool names, descriptions, README, category, and tags.
Hourly Tool Refresh
Section titled “Hourly Tool Refresh”Aerostack runs an hourly cron that re-fetches tools/list from all hosted MCPs. This means:
- If you add a new tool and redeploy, it appears in the catalog within an hour (or immediately on deploy)
- If your MCP becomes unreachable, it retries up to 5 times before pausing
- Function-backed and first-party MCPs are excluded (they don’t need refresh)
Release Checklist
Section titled “Release Checklist”Before publishing your MCP to the Hub, ensure your project includes:
| Requirement | Why | How |
|---|---|---|
| Good tool descriptions | AI agents use descriptions to decide which tool to call | Write clear, specific description fields on every tool |
aerostack.toml with [vars] | Powers the Docs tab auth section | List all required API keys/tokens as [vars] entries |
description in aerostack.json | Shown in Hub search results | Keep it under 500 chars, explain what the MCP does |
category | Hub filtering and discovery | Use standard categories: developer-tools, communication, productivity, analytics, ai-ml, data, commerce, security |
tags | Fine-grained search | Up to 20 tags, lowercase, relevant keywords |
| README.md | Long-form docs, linked from aerostack.json | Include setup instructions, examples, and limitations |
inputSchema on tools | Validation + auto-generated forms | Use JSON Schema with required fields and description on every property |
Convert an Existing stdio MCP Server
Section titled “Convert an Existing stdio MCP Server”If you have an MCP server that runs as a local stdio process (from npm, GitHub, or a local directory), you can convert it to a Cloudflare Worker and host it on Aerostack.
aerostack mcp convert --package @notionhq/notion-mcp-server --deploy --slug notion-mcp# -> Analyzed tool definitions# -> Generated Cloudflare Worker wrapper (HTTP SSE <-> stdio)# -> Deployed to edge# -> Registered as notion-mcpaerostack mcp convert --github https://github.com/user/my-mcp-server --deploy --slug my-mcpaerostack mcp convert --dir ./my-local-mcp --slug my-mcpaerostack deploy mcpThe conversion process:
- Downloads and analyzes the MCP server source
- Extracts tool definitions and required environment variables
- Generates a Cloudflare Worker wrapper that translates HTTP SSE to stdio
- Flags incompatible Node.js APIs (file system, child processes, etc.)
Deploy via CI/CD
Section titled “Deploy via CI/CD”Automate deployments from GitHub Actions:
name: Deploy MCP Serveron: push: branches: [main] paths: ['src/**', 'aerostack.json']
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx aerostack deploy mcp env: AEROSTACK_API_KEY: ${{ secrets.AEROSTACK_API_KEY }} CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}Publish to the Hub
Section titled “Publish to the Hub”Once your MCP server is working, you can publish it for the community:
# Set visibility to publicaerostack mcp update my-mcp-server --visibility public
# Publish to Hubaerostack mcp publish my-mcp-server# -> Published to Hub at aerostack.dev/hub/yourname/my-mcp-serverInclude a README in your aerostack.json:
{ "readme": "README.md", "tags": ["crm", "support", "internal-tools"], "license": "MIT"}Platform Bindings
Section titled “Platform Bindings”Hosted MCP servers have access to the full Aerostack platform:
server.tool('query_database', async ({ sql }, env) => { // Database query const result = await env.DB.prepare(sql).all(); return { content: [{ type: 'text', text: JSON.stringify(result.results) }] };});
server.tool('store_document', async ({ key, content }, env) => { // Object storage await env.R2.put(key, content); return { content: [{ type: 'text', text: `Stored document at ${key}` }] };});
server.tool('cache_lookup', async ({ key }, env) => { // Cache lookup const value = await env.KV.get(key); return { content: [{ type: 'text', text: value || 'Not found' }] };});
server.tool('summarize', async ({ text }, env) => { // Workers AI const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', { messages: [{ role: 'user', content: `Summarize: ${text}` }], }); return { content: [{ type: 'text', text: result.response }] };});Configure bindings in aerostack.toml:
[[d1_databases]]binding = "DB"database_name = "my-database"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
[[r2_buckets]]binding = "R2"bucket_name = "my-bucket"
[[kv_namespaces]]binding = "KV"id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
[ai]binding = "AI"Next Steps
Section titled “Next Steps”- Proxy an existing MCP server instead of hosting
- Configure secrets for your hosted server
- Add team members to your workspace
- Back a skill with an edge function for simpler single-tool use cases