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
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
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 (theactionIdis whatload_skilllists underactions[]). It resolves the operation across all loaded skills, runs it through the full pipeline below, and returns the action’s responsedata— or throws if the action is unknown / unauthorized / fails validation.MathandJSONare available.- The script’s final
return <value>becomes the workflow’svalue.
example: chain two actions in one round-trip
Per-callTool pipeline
Each callTool(actionId, input) runs the same authorized/validated path a single direct action call would:
- Resolve
actionIdfrom the plugin-privateHiddenOpRegistryacross loaded skills. Unknown action throwsunknown action "..." — run search_skill then load_skill to discover available actions. - Pin the operation descriptor at call entry — a hot bundle-swap mid-call doesn’t change the descriptor the in-flight call uses.
- Authorize: evaluate the action’s
requiredAuthorities(if any) against the caller’sauthInfovia@frontmcp/auth’sAuthoritiesEngine. Denial throwsauthority denied: .... - Validate input strictly against the action’s JSON Schema. A validation failure throws
input validation failed: .... - Build the outbound HTTP request through
@frontmcp/adapters/openapi’sbuildRequest— path interpolation, header injection defenses, body serialization, security context resolution all flow through the adapter. - SSRF gate: scheme allowlist (
https:by default), hostname allowlist (built from the active bundle’sservices[].baseUrl), post-DNS IPv4/IPv6 blocklist (RFC 1918, link-local incl. cloud metadata, loopback, ULA), cloud-metadata hostname blocklist. - Fetch with timeout (
op.timeoutMs ?? defaultTimeoutMs) and response-size cap (op.maxResponseBytes ?? defaultMaxResponseBytes). - Parse the response through
parseResponse, validate the JSON body against the action’soutputSchema, and return the responsedatainto the script.
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 callawait callTool(actionId, input)for each loaded action — you can chain several calls in one round-trip. End withreturn <value>. EachcallToolauto-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 withskills_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/listresponse - Are NOT directly callable from MCP clients via
tools/call - Always live in the plugin’s
HiddenOpRegistryso arun_workflowscript’scallToolcan find and run them with the ABAC checkpoint
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.