This enables platform-specific tools (e.g., Apple Notes on macOS only), runtime-specific resources (e.g., Node.js file system), and environment-gated features (e.g., debug tools in development only).
The availableWhen Option
Add availableWhen to any entry’s metadata to constrain its availability:
Matching Semantics
- AND across fields — all specified fields must match
- OR within arrays — at least one value in an array must match
- Omitted fields — unconstrained (matches everything)
- Empty array — matches nothing (entry is never available)
- No
availableWhen— always available (default)
Available Fields
os (replaces platform)
Operating system, matching process.platform values. Renamed from platform in issue #417 — the old name still works as a deprecated alias.
runtime
JavaScript runtime:
deployment
Coarse deployment mode:
provider (issue #417)
Discriminated deploy provider. Lets @Tool({ availableWhen: { provider: ['vercel'] } }) express provider-specific rules where deployment: ['serverless'] is too coarse.
Override the detection with
FRONTMCP_PROVIDER=<name> (useful for tests, Docker images without a discriminating env var, etc.).
target (issue #417)
Build target produced by frontmcp build --target <x>. Always 'unknown' in dev (frontmcp dev).
Resolution order:
globalThis.FRONTMCP_BUILD_TARGET → process.env.FRONTMCP_BUILD_TARGET → 'unknown'.
Every artifact frontmcp build produces sets globalThis.FRONTMCP_BUILD_TARGET before your code runs: the
node, cli and mcpb bundles in a preamble, the vercel, lambda, cloudflare and distributed
builds in their generated setup module, and the browser / sdk library bundles at load. The first one to
run wins, so a --target cli binary that loads its server bundle reports 'cli', and a host application
built for node that imports a library built with --target sdk reports 'node'.
surface (issue #417)
Per-call axis — set by the transport adapter / dispatcher on the request ctx to discriminate “who is calling this tool.” Unlike the other axes (which are constant for the process lifetime), surface varies per request.
An entry whose
surface excludes the caller’s is left out of that caller’s listings and answers like an entry that doesn’t exist (Tool "x" not found, Resource not found, Prompt not found, no completions, a refused resources/subscribe), so the caller can’t tell it exists. An agent’s model isn’t offered its tools whose surface leaves out 'agent', and a call it makes to one anyway fails as a call to a tool the agent doesn’t have. Skills follow the same rule on every path that serves them, for every axis (a skill runtime, env or another process-wide axis excludes is absent too): skills/list, skills/search, skills/load, the skill:// resources (including each skill’s skill://<path>/SKILL.md entry in resources/list), the server instructions and the skills HTTP endpoints (/skills, /llm.txt, /llm_full.txt), which count as 'mcp'. CodeCall acts for the client that called it, so codecall:* search, describe, invoke and execute apply that client’s surface too.
Code running for a call reads its surface with getCallSurface(): in a tool, a resource read, a prompt and a completion, it is the surface of the call being served ('job' in a job, 'http-trigger' in a channel handling a webhook). A tool, resource or prompt calling this.callTool() is in-process dispatch: that call carries no surface and isn’t restricted by this axis. A task a detached CLI worker runs keeps the surface of the call that created it.
Example — block external MCP calls but allow agent dispatch:
env
NODE_ENV value:
It is the
NODE_ENV the process runs with, read live: a value the deployment sets at run time (a Cloudflare Worker’s [vars] NODE_ENV) wins over the constant a bundler inlines for process.env.NODE_ENV at build time (wrangler dev inlines "development"). With no value set at run time it falls back to that constant, then to 'development'. getNodeEnv() from @frontmcp/utils reads it the same way.
Structured errors
When a tool, resource, resource template or prompt exists but a process-wide axis (os, runtime, deployment, provider, target, env) excludes it, tools/call, resources/read and prompts/get throw EntryUnavailableError with a missingAxes array in data (issue #417). Clients can surface “this tool isn’t reachable because provider=vercel / …” without parsing prose. A surface mismatch answers like an unknown entry instead (see surface above).
Supported Entry Types
availableWhen works on all five entry types:
- Tool
- Resource
- Prompt
- Skill
- Agent
Runtime Context API
Insideexecute() methods, use runtime context helpers for imperative checks:
Available Methods
RuntimeContext exposes the operating system via the os property; platform remains as a deprecated alias for backward compatibility (issue #417) and resolves to the same value. New code should read this.runtimeContext.os directly.
These methods are available on
ToolContext, ResourceContext, PromptContext, and AgentContext.
Multi-Platform Pattern
When building tools that serve the same purpose across platforms, use separate files withavailableWhen in each:
Error Handling
When a client tries to call a tool that exists but is unavailable in the current environment, the SDK returns anEntryUnavailableError (HTTP 403) with both the constraint and the current context. The data payload includes a missingAxes array so clients can show a precise reason for the failure without parsing the message string:
ToolNotFoundError (404), helping clients understand why a tool is inaccessible. See the Structured errors reference (if present) for the full schema.
How It Works: Registry-Level Filtering
Key differences:
availableWhenis a hard constraint — filtered entries cannot be listed OR called- It runs at registry initialization, not in HTTP flows — no per-request overhead
- The runtime context (OS, runtime, deployment, NODE_ENV) is detected once and cached
- Results are logged at boot time for operational visibility
Boot-Time Logging
When entries haveavailableWhen constraints, the SDK logs a summary at startup: