Skip to main content
The plugin exposes exactly three tools through standard tools/list. Every per-operation OpenAPI tool is hidden behind these — run_workflow is the only path to actually invoke an upstream operation. It runs a short AgentScript program in an enclave sandbox, and each callTool(actionId, input) inside that script is the sole authorization checkpoint for that action.

search_skill

Search the registry by free-form query.
input
output
The score comes from the SDK’s SkillRegistry.search() (TF-IDF). bundleVersion is included when the skill was registered from a bundle — polling clients can use it to detect bundle swaps without depending on notifications/skills/list_changed.

load_skill

Load the full markdown instructions and the executable actions for one skill.
input
output
The LLM should call load_skill once per skill it intends to use, then keep the action schemas in context. Schemas inside actions[] are the same schemas the OpenAPI spec declared; the executor validates input against them on every callTool a run_workflow script issues. The actionId values listed here are exactly what you pass to callTool(actionId, input). Throws an MCP-protocol error when the skillId is unknown.

run_workflow

Execute a task by running a short AgentScript program inside a secure enclave sandbox. This is the EXECUTE step: after search_skill and load_skill, the LLM writes a script that calls the loaded skill’s actions to accomplish the user’s task — chaining several calls in ONE round-trip.
input
output (success)
output (failure)

Inside the script

The script runs in the dependency-free @enclave-vm interpreter — no host access and no network except callTool:
  • await callTool(actionId, input) invokes a loaded skill’s action (the actionId is what load_skill lists under actions[]). It resolves the operation across all loaded skills, runs it through the full pipeline below, and returns the action’s response data — or throws if the action is unknown / unauthorized / fails validation.
  • Math and JSON are available.
  • The script’s final return <value> becomes the workflow’s value.
example: chain two actions in one round-trip
The sandbox caps a single workflow’s step count, tool-call count (25), and wall time (8s).

Per-callTool pipeline

Each callTool(actionId, input) runs the same authorized/validated path a single direct action call would:
  1. Resolve actionId from the plugin-private HiddenOpRegistry across loaded skills. Unknown action throws unknown action "..." — run search_skill then load_skill to discover available actions.
  2. Pin the operation descriptor at call entry — a hot bundle-swap mid-call doesn’t change the descriptor the in-flight call uses.
  3. Authorize: evaluate the action’s requiredAuthorities (if any) against the caller’s authInfo via @frontmcp/auth’s AuthoritiesEngine. Denial throws authority denied: ....
  4. Validate input strictly against the action’s JSON Schema. A validation failure throws input validation failed: ....
  5. Build the outbound HTTP request through @frontmcp/adapters/openapi’s buildRequest — path interpolation, header injection defenses, body serialization, security context resolution all flow through the adapter.
  6. SSRF gate: scheme allowlist (https: by default), hostname allowlist (built from the active bundle’s services[].baseUrl), post-DNS IPv4/IPv6 blocklist (RFC 1918, link-local incl. cloud metadata, loopback, ULA), cloud-metadata hostname blocklist.
  7. Fetch with timeout (op.timeoutMs ?? defaultTimeoutMs) and response-size cap (op.maxResponseBytes ?? defaultMaxResponseBytes).
  8. Parse the response through parseResponse, validate the JSON body against the action’s outputSchema, and return the response data into the script.
A callTool failure (auth, schema, network, SSRF) throws inside the script. If the script doesn’t catch it, the workflow returns { success: false, error, stats }run_workflow itself never throws to the MCP client; every outcome is a structured envelope. (If the enclave isn’t installed, run_workflow returns { success: false, error: "workflow execution unavailable: ..." }.)

Prompts to give the LLM

The exact prompt depends on your agent harness, but these phrasings work well in practice (cribbed from the tool descriptions the plugin ships):
  • For search_skill: “Use this tool first to discover what skills exist for the user’s request.”
  • For load_skill: “Call this tool once per skill you intend to use. The instructions field is markdown — read it carefully before invoking any action.”
  • For run_workflow: “This is the only way to invoke upstream operations. Write a short AgentScript program and call await callTool(actionId, input) for each loaded action — you can chain several calls in one round-trip. End with return <value>. Each callTool auto-validates input and applies authority checks; an unauthorized or failing call throws inside your script. The result comes back as { success, value, error, stats }.”

Skills-only mode

If your FrontMCP server is configured with skills_only mode (set via ?mode=skills_only on the MCP transport URL), the meta-tools stay visible — they’re the only way to use skills, and the plugin treats them as exempt from the skills-only filter. See Skills for the skills-only contract.

What about hidden tools?

By default (exposeOperationsAsInternalTools: true), per-operation tools are registered in scope.tools with internal visibility. This means they:
  • Are still callable via the SDK / direct client and can participate in DI, hooks, and observability
  • Do NOT appear in the public MCP tools/list response
  • Are NOT directly callable from MCP clients via tools/call
  • Always live in the plugin’s HiddenOpRegistry so a run_workflow script’s callTool can find and run them with the ABAC checkpoint
If you set exposeOperationsAsInternalTools: false, the per-operation tools live only in HiddenOpRegistry (not in scope.tools) and the only way to invoke them is a callTool inside a run_workflow script. Use this stricter mode when you need to guarantee that nothing — not even other plugins or in-process tooling — can bypass the ABAC checkpoint.