Skip to main content
FrontMCP v1.3 introduces a skills-only deployment model where the MCP surface is just four meta-tools (plus an optional legacy fifth tool for direct invocation), and every capability is reached through a skill. The agent loop becomes “discover the skill, describe its operations, execute AgentScript.” OpenAPI specs become a capability inventory rather than a flat tool catalog. This page is the conceptual overview. For the concrete pieces:

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.

The MCP surface — four meta-tools

A fifth tool — 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:
The 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:
  1. The capability inventory. What operations exist, what they take, what they return.
  2. 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.
  3. The type provider. Every operation’s input schema and output schema flow into the skill’s describe response so the agent gets full IntelliSense in AgentScript.
The classifier follows HTTP semantics with no per-spec assumptions: When the script in 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:
Inside any of those markdown files, OpenAPI operations are referenced in one of two equivalent forms — both detected by the deploy pipeline’s harvester and turned into the skill’s allowed-operations set:
The skill’s SKILL.md shrinks to almost-empty:
The build pipeline harvests op:// and [[op:...]] mentions across all the skill’s files and synthesizes:
  • The skill’s actions[] (one per unique operationId).
  • Each action’s input_schema and output_schema (from the OpenAPI definition).
  • The skill’s allowed-operations set (the harvested union; used as the runtime sandbox).
  • The MCP classification per operation (resource / tool / both, with notify semantics).
A skill that never mentions an operation is fine — it’s a knowledge skill: it shows up in 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 with alwaysLoad: true in the SKILL.md frontmatter and the runtime merges their bindings into every codecall:execute invocation regardless of which skills the agent listed:
You can also set the always-load list at the server level via the deploy manifest’s skills.alwaysLoad: [...] (see the deploy manifest reference).

The full deploy cycle

Concrete deploy details live on the Cloudflare Worker page and the deploy manifest reference.

What the runtime guarantees

  • Signature-verified bundles. The Worker rejects any envelope whose JWS signature doesn’t match a kid in signing.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 inside signing.replay.windowSeconds.
  • AST-validated AgentScript. Every codecall:execute script runs through @enclave-vm/ast at execute time (yes, every call) before it touches a binding. Disallowed identifiers (eval, Function, process, dynamic import, raw fetch, etc.) are rejected with a structured IllegalBuiltinAccess error.
  • 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

The agent never sees an OpenAPI operation directly. The agent never authors a raw HTTP call. The skill’s prose teaches the agent when to call each operation; the runtime enforces whether it’s allowed to.

Deploy manifest reference

The full frontmcp.deploy.yaml schema with examples.

Cloudflare Worker target

How the Worker boots and hot-reloads.