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.
ConcernFlat toolsSkills-only
Catalog size at the MCP surface200+ tools3–4 meta-tools
Agent looptools/listtools/callsearchdescribeexecute
OpenAPI roleSource of every toolCapability inventory; never exposed directly
Curation pointTool name + description per opSkill markdown (prose + op mentions)
Authority boundaryPer-toolPer-skill (allowlist derived from markdown)
Hot-reload unitPer-tool registrationSkill bundle envelope

The MCP surface — four meta-tools

ToolPurpose
codecall:searchSkillsFind 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:searchKnowledgeFind knowledge-only skills (pure references / examples / prose). Use to load domain context before deciding what to do.
codecall:describeFull skill content: instructions + references + every operation’s request/response schema. Use when search wasn’t enough.
codecall:executeRun agent-authored AgentScript in a capability-bound sandbox. The active skills’ OpenAPI operations are pre-bound as namespaces.
A fifth tool — codecall:invoke — stays for the rare case where the agent wants to call a tool by name without the AgentScript path.
// codecall:searchSkills returns
{
  "skills": [
    {
      "name": "refund",
      "description": "Issue a refund for an order. Use when a customer has confirmed cancellation.",
      "tags": ["billing"],
      "operations": [
        { "spec": "acme", "operationId": "getOrder" },
        { "spec": "acme", "operationId": "issueRefund" }
      ],
      "tools": [],
      "relevanceScore": 0.86,
      "matchedQueries": ["refund order"],
      "source": "local"
    }
  ],
  "warnings": [],
  "totalExecutableSkills": 18
}

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:
// codecall:execute payload
const order = await acme.getOrder({ id: orderId });
if (!order.refundable) return { ok: false };
const refund = await acme.issueRefund({ orderId, amount: order.total });
return { ok: true, refundId: refund.id };
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:
const order = await callTool('acme.getOrder', { id: orderId });

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:
OpenAPI shapeMCP surfaceNotify on success
GET /users/{id}Resource template + tool
GET /usersResource (collection)
POST /users (matching GET)Toolresources/list_changed on /users
POST /users/{id} (matching GET)Toolresources/updated on /users/{id}
POST /users/{id}/reset-password (no matching GET)Toolresources/updated on parent /users/{id}
PUT /users/{id} / PATCH /users/{id}Toolresources/updated on /users/{id}
DELETE /users/{id}Toolresources/list_changed on parent /users
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:
skills/
  refund/
    SKILL.md           ← frontmatter + agent-facing instructions
    references/        ← markdown the agent pulls into context
      refund-policy.md
    examples/          ← prose narratives that reference operations
      partial-refund.md
    data/              ← static lookup data (eligibility matrices, etc.)
      refund-codes.json
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:
1. URI form (renders as a link in any markdown viewer):
   [Fetch the order](op://acme/getOrder)

2. Wikilink form (familiar from Obsidian / Foam):
   When the order has shipped, look up the customer via [[op:acme/getCustomer]].
The skill’s SKILL.md shrinks to almost-empty:
---
name: refund
description: Issue a refund for an order.
tags: [billing]
---

When a customer requests a refund, first fetch the order via
[[op:acme/getOrder]] and check eligibility against
[[ref:refund-policy.md]]. For full refunds, call
[[op:acme/issueRefund]] with `amount` omitted...
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:
---
name: auth-helpers
description: Helpers every agent needs to reach our auth boundary.
alwaysLoad: true
hideFromDiscovery: true   # optional — load without showing in search
---
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

GitHub repo (specs/ + skills/ + frontmcp.deploy.yaml)

         │  on push: frontmcp/deploy-action@v1

   ┌────────────────────────────────────────────────────────┐
   │ 1. Parse + cross-validate the deploy manifest          │
   │ 2. Apply environments.<env> overlay                    │
   │ 3. Walk skills/; run the op-reference harvester        │
   │ 4. Run the OpenAPI → MCP classifier                    │
   │ 5. Inline & content-address all artifacts              │
   │ 6. Sign the envelope with the project key (Ed25519)    │
   │ 7. POST to the Cloudflare Worker's /_frontmcp/resync   │
   └────────────────────────────────────────────────────────┘


   Cloudflare Worker
         │  - Verifies the signature against TRUSTED_KEYS
         │  - Replay-guards via KV nonce dedupe
         │  - Atomically swaps the in-memory skill registry
         │  - Emits notifications/{skills,tools,resources,prompts}/list_changed

   MCP clients see the new catalog without reconnecting.
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

                ┌─────────────────────┐
                │   MCP client / LLM  │
                └──────────┬──────────┘
                           │ tools/call
              ┌────────────┼────────────┐
              │            │            │
         searchSkills   describe     execute
        searchKnowledge                │
                                       │ agentscript

                          ┌─────────────────────────┐
                          │  AST-validated sandbox  │
                          │  (Worker isolate)       │
                          └────────────┬────────────┘

                          ┌─────────────────────────┐
                          │  Capability bindings:   │
                          │   acme.getOrder(...)    │  ← generated from OpenAPI
                          │   billing.postLedger... │     for the active skill's
                          │                         │     allowed-operations set
                          └────────────┬────────────┘
                                       │ HTTPS

                              ┌────────────────┐
                              │ Upstream APIs  │
                              └────────────────┘
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.