Cloudflare Worker target
Host the whole runtime on a Worker; hot-reload on every git push.
frontmcp.deploy.yaml
The declarative manifest the GitHub Action ingests.
Skill-Based Workflows
The underlying skill primitive this model builds on.
MCP Tasks Spec
Upstream MCP
tools/call, resources/updated, and searchSkills semantics.Why this model
The flat “every operation becomes a tool” approach hits two walls fast: catalog explosion (a 200-operation OpenAPI dumps 200 tools and agents drown) and curation drift (the description of how to use the API lives outside the tool surface). Skills-only deployment collapses six surfaces (openapi, skills, tools, prompts, resources, data) into one — the skill — with one execution path (CodeCall/AgentScript). OpenAPI stays the source of truth for what operations exist, what they take, what they return — but it’s reachable only through a skill that’s been granted access.| Concern | Flat tools | Skills-only |
|---|---|---|
| Catalog size at the MCP surface | 200+ tools | 3–4 meta-tools |
| Agent loop | tools/list → tools/call | search → describe → execute |
| OpenAPI role | Source of every tool | Capability inventory; never exposed directly |
| Curation point | Tool name + description per op | Skill markdown (prose + op mentions) |
| Authority boundary | Per-tool | Per-skill (allowlist derived from markdown) |
| Hot-reload unit | Per-tool registration | Skill bundle envelope |
The MCP surface — four meta-tools
| Tool | Purpose |
|---|---|
codecall:searchSkills | Find executable skills (skills that mention OpenAPI operations or declare tools). Returns name, description, tags, and the operation IDs inline so the agent can author AgentScript from one search response. |
codecall:searchKnowledge | Find knowledge-only skills (pure references / examples / prose). Use to load domain context before deciding what to do. |
codecall:describe | Full skill content: instructions + references + every operation’s request/response schema. Use when search wasn’t enough. |
codecall:execute | Run agent-authored AgentScript in a capability-bound sandbox. The active skills’ OpenAPI operations are pre-bound as namespaces. |
codecall:invoke — stays for the rare case where the agent wants to call a tool by name without the AgentScript path.
AgentScript — namespaced bindings from OpenAPI
The agent authors TypeScript/JavaScript at runtime; the runtime AST-validates and executes it in a capability-bound sandbox. Each OpenAPI spec the active skill references becomes a typed namespace on the script’s scope object:acme.* namespace is generated at boot from the OpenAPI operation IDs (acme.getOrder, acme.issueRefund). Operations not in the active skill’s allowlist are not present on the namespace object at all — there’s no acme.deleteUser to call accidentally because the binding layer never exposed it.
For backward compatibility the older shape still works:
How the namespace is built
OperationToolFactory in plugin-skilled-openapi already names tools ${specId}.${operationId}. The runtime parses the dot, partitions by prefix, and hands the script a frozen object of { acme: { getOrder, issueRefund }, billing: { ... } }. Each method is a thin wrapper that delegates to the same callTool closure used for the non-namespaced path, so all security checks (self-reference, whitelist, flow execution, audit, quota) run uniformly. Tools whose namespace prefix isn’t a valid JavaScript identifier (e.g. acme-api.getUser) stay reachable via callTool but are skipped from namespace surfacing — the lint output explains why.
Reserved prefixes —
console, Math, JSON, Object, Promise, Map, Set, globalThis, callTool, getTool, mcpLog, mcpNotify, etc. — never become namespaces, even if a spec is named after them. The tools remain callable via callTool.OpenAPI as capability inventory
OpenAPI specs ship to the project but are never directly exposed to MCP clients. They serve three purposes:- The capability inventory. What operations exist, what they take, what they return.
- The classification source. HTTP semantics decide whether each operation surfaces as an MCP resource, a tool, or both — and what
notifications/resources/*event should fire on a successful call. - The type provider. Every operation’s input schema and output schema flow into the skill’s
describeresponse so the agent gets full IntelliSense in AgentScript.
| OpenAPI shape | MCP surface | Notify on success |
|---|---|---|
GET /users/{id} | Resource template + tool | — |
GET /users | Resource (collection) | — |
POST /users (matching GET) | Tool | resources/list_changed on /users |
POST /users/{id} (matching GET) | Tool | resources/updated on /users/{id} |
POST /users/{id}/reset-password (no matching GET) | Tool | resources/updated on parent /users/{id} |
PUT /users/{id} / PATCH /users/{id} | Tool | resources/updated on /users/{id} |
DELETE /users/{id} | Tool | resources/list_changed on parent /users |
codecall:execute calls acme.issueRefund({ orderId: '42' }) and that maps to POST /orders/{orderId}/refund (a “no matching GET” action endpoint), the runtime auto-emits notifications/resources/updated for mcp+op://acme/orders/42 once on success — no extra code, no manual wiring.
The notification fires once per call regardless of which skill’s binding made the call. The notification is a property of the underlying operation + args, not the skill. Two skills calling the same
PUT /users/{id} → still one notification.Skills as the only unit
A skill is a directory:SKILL.md shrinks to almost-empty:
op:// and [[op:...]] mentions across all the skill’s files and synthesizes:
- The skill’s
actions[](one per unique operationId). - Each action’s
input_schemaandoutput_schema(from the OpenAPI definition). - The skill’s
allowed-operationsset (the harvested union; used as the runtime sandbox). - The MCP classification per operation (resource / tool / both, with notify semantics).
searchKnowledge, contributes its references/examples to context when the agent calls describe, and has no callable actions.
Always-loaded skills
Some skills are a per-server “standard library” — authentication helpers, observability shims, common utilities. Mark them withalwaysLoad: true in the SKILL.md frontmatter and the runtime merges their bindings into every codecall:execute invocation regardless of which skills the agent listed:
skills.alwaysLoad: [...] (see the deploy manifest reference).
The full deploy cycle
What the runtime guarantees
- Signature-verified bundles. The Worker rejects any envelope whose JWS signature doesn’t match a
kidinsigning.trustRoots. Local dev mode can bypass this with a loud warning. - Replay protection. Every bundle carries a timestamp + nonce. The Worker tracks nonces in KV (
bindings.kvNamespaces[]configured for the purpose) and rejects re-submissions insidesigning.replay.windowSeconds. - AST-validated AgentScript. Every
codecall:executescript runs through@enclave-vm/astat execute time (yes, every call) before it touches a binding. Disallowed identifiers (eval,Function,process, dynamicimport, rawfetch, etc.) are rejected with a structuredIllegalBuiltinAccesserror. - Capability-bound scope. The script’s globals are a frozen object containing only the active skills’ generated namespaces plus
callTool/getTool/mcpLog/mcpNotify. There is no way to reach an operation the skill didn’t allow. - Resource-change notifications. Auto-derived from OpenAPI semantics; one emit per call regardless of which skill made it.
Mental model summary
Deploy manifest reference
The full
frontmcp.deploy.yaml schema with examples.Cloudflare Worker target
How the Worker boots and hot-reloads.