Environment awareness
The same server can run on a laptop, in a Docker container, on a serverless platform or inside another program. FrontMCP works out where it's running the first time something asks, and uses the answer in two places: availableWhen, which takes an entry out of the server where it can't work, and this.runtimeContext, which lets code inside a call branch on it.
@Tool({ name: "export_tickets", availableWhen: { runtime: ["node", "bun"], env: ["production"] } })
class ExportTickets extends ToolContext {
async execute() {
this.runtimeContext; // { os, platform, runtime, deployment, provider, target, env }
this.isEnv("production");
}
}
Reference
availableWhen
An option of @Tool, @Resource, @ResourceTemplate, @Prompt and @Skill. It takes an object of arrays; each array lists the values a field may have.
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({
name: "export_tickets",
description: "Write every open ticket to a CSV file on the server's disk",
inputSchema: {},
availableWhen: { runtime: ["node", "bun"], deployment: ["standalone"] },
})
export class ExportTickets extends ToolContext {
async execute() {
return { path: "/var/exports/tickets.csv" };
}
}Fields
| Field | Matches | Values FrontMCP detects |
|---|---|---|
os | The operating system, as Node's process.platform names it. | "darwin", "linux", "win32", "freebsd", …, and "browser" in FrontMCP's browser build |
platform | The same as os. Deprecated: use os. | |
runtime | The JavaScript runtime. | "node", "bun", "deno", "edge", and "browser" in FrontMCP's browser build |
deployment | How the server is deployed. | "standalone", "serverless", "distributed" |
provider | The hosting provider. | "bare", "docker", "vercel", "lambda", "cloudflare", "netlify", "azure", "gcp", "fly", "render", "railway" |
target | The build target: what frontmcp build --target made, or FRONTMCP_BUILD_TARGET. | "unknown", "node", "distributed", "cli", "vercel", "lambda", "cloudflare", "browser", "sdk", "mcpb" |
env | NODE_ENV. | "development" when it isn't set, or whatever it's set to: "production", "test", … |
surface | Where the call comes from, not the machine: "mcp", "cli", "agent", "job", "http-trigger" or "webmcp". See Surfaces. | "mcp" for MCP clients and createDirect(); "cli" for the commands of a server built with frontmcp build --target cli; "agent" for an agent's calls; "job" for a job's this.callTool(); "http-trigger" for a channel's this.callTool() while it handles a webhook; "webmcp" for an agent in the user's browser, through @frontmcp/plugin-webmcp. |
How each value is detected is below. The types also list "browser" for deployment, but FrontMCP never reports it: its browser build says "standalone", as in the Playground.
Matching
- Every field you set must match.
{ runtime: ["node"], env: ["production"] }needs both. - Within a field, any one value matches.
runtime: ["node", "bun"]matches either. - A field you leave out matches everything. No
availableWhenmeans available everywhere. - An empty array matches nothing: the entry is never available.
- Values aren't checked. A misspelled one, like
runtime: ["nodejs"], never matches, so the entry is silently never available. A misspelled field fails at startup withUnrecognized key.
What happens where it doesn't match
| Entry | Listed? | Usable? |
|---|---|---|
@Tool | No: left out of tools/list. | No: a call fails with ENTRY_UNAVAILABLE, from a client or from this.callTool(). |
@Resource, @ResourceTemplate | No: left out of resources/list and resources/templates/list. | No: resources/read fails with -32003. |
@Prompt | No: left out of prompts/list. | No: prompts/get fails with -32003. |
@Skill | No: left out of skills/list, skills/search and the skill:// resources. | No: skills/load can't find it. See @Skill. |
@Agent | No: its invoke_<name> tool is left out of tools/list. | No: a call fails with ENTRY_UNAVAILABLE, as for a tool. |
@Channel | No: the channel isn't registered, so a two-way channel's channel-reply tool isn't listed either. | No. FrontMCP logs Channel "…" is not available in this runtime; skipped at verbose. |
@Job, @Workflow | Not an option. |
Before FrontMCP 1.8.3, a resource or prompt that didn't match was only left out of its list and could still be read by its URI or name, and availableWhen on an @Agent was ignored. Before 1.8.4, a skill that didn't match was still listed in skills/list and skills/search.
Two tools may share a name when their availableWhen never match at the same time. Clients see the one that matches: see one tool, a version per platform.
Surfaces
surface is about who is calling, not where the server runs. An entry whose surface leaves out the caller's surface is treated as if it didn't exist: it's left out of every list, and a call, read or get answers as for an unknown name: Tool "…" not found, Resource not found: …, Prompt not found: …. invoke_<agent> tools work the same way, and so do skills in skills/list and skills/load. Keeping a tool from MCP clients shows it.
| Caller | Surface |
|---|---|
An MCP client, over HTTP or stdio, and createDirect() | "mcp" |
The commands of a server built with frontmcp build --target cli | "cli" |
An agent: the tools its model is offered and calls, and this.callTool() in the agent's own code | "agent" |
A job's this.callTool() | "job" |
this.callTool() in a tool, resource or prompt | None: surface isn't checked, except surface: [], which nothing matches. |
A channel's this.callTool() while it handles a request to its webhook source | "http-trigger" |
An agent in the user's browser, calling a tool of a server that runs in the page, through @frontmcp/plugin-webmcp | "webmcp": ["webmcp"] offers a tool only to those agents, ["mcp"] keeps it from them. See server.registerTool(). |
So surface: ["agent"] hides a tool from every MCP client while an agent can still call it, and surface: ["mcp"] keeps a tool from agents and jobs: an agent's model isn't offered it, and a job's this.callTool() fails with Tool "…" not found, as does a webhook channel's. Keeping a tool from agents and jobs shows it. Webhook routes are served by the Node server only: through createFetchHandler(), and so in the Playground, the webhook's path answers 404, which is why "http-trigger" was checked in Node. "webmcp", new in 1.9.0, is the surface of calls from an agent in the user's browser through @frontmcp/plugin-webmcp. Changed in 1.8.5: webhook sources weren't served, so no call carried "http-trigger". Changed in 1.8.4: agents and jobs had no surface, so surface didn't keep a tool from them. Before FrontMCP 1.8.3, surface wasn't checked at all, and only surface: [] had an effect.
How FrontMCP detects the environment
The first time anything asks (usually when the server builds its tool list), FrontMCP detects each field and keeps the result for the life of the process, except env, which it reads from NODE_ENV every time. Changing any other environment variable afterwards changes nothing; changing NODE_ENV changes env from then on: see Changing NODE_ENV while the server runs.
| Field | How it's detected, first match wins |
|---|---|
os, platform | process.platform. |
runtime | "bun" or "deno" when their global exists; "edge" when the global scope looks like an edge runtime, or when both EDGE_RUNTIME and VERCEL_ENV are set; otherwise "node". FrontMCP's browser build, which a bundler picks with the browser condition, always says "browser". |
deployment | FRONTMCP_DEPLOYMENT_MODE when it's "serverless" or "distributed"; "serverless" when any of VERCEL, NETLIFY, CF_PAGES, AWS_LAMBDA_FUNCTION_NAME, AZURE_FUNCTIONS_ENVIRONMENT, K_SERVICE, RAILWAY_ENVIRONMENT, RENDER or FLY_APP_NAME is set; otherwise "standalone". |
provider | FRONTMCP_PROVIDER, when it's one of the known names (anything else is ignored); then VERCEL → "vercel", AWS_LAMBDA_FUNCTION_NAME → "lambda", CF_PAGES → "cloudflare", NETLIFY → "netlify", AZURE_FUNCTIONS_ENVIRONMENT → "azure", K_SERVICE → "gcp", FLY_APP_NAME → "fly", RENDER → "render", RAILWAY_ENVIRONMENT → "railway"; a /.dockerenv file → "docker"; otherwise "bare". |
target | The target a bundle from frontmcp build --target was built for, which it sets when it starts (since 1.9.0); else FRONTMCP_BUILD_TARGET, when it's one of the known names; otherwise "unknown", as for a server started with frontmcp dev or tsx. |
env | NODE_ENV, or "development" when it isn't set, each time it's read. (Before 1.8.7 it was read once, like the rest.) |
frontmcp build does set FRONTMCP_DEPLOYMENT_MODE in the bundles it makes: "serverless" for the Vercel, Lambda and Cloudflare targets, "distributed" for the distributed target. FRONTMCP_SERVERLESS=1, which makes @FrontMcp serve a handler instead of listening, doesn't change deployment.
When any entry has availableWhen, each registry logs a summary at startup, at info level, and each entry it filtered at verbose:
[ToolRegistry] availability: 5 total, 4 with availableWhen constraint, 3 available, 1 filtered [ctx: platform=darwin, runtime=node, deployment=standalone, env=development]
In the Playground
A Playground on this site runs FrontMCP's browser build in your browser (since FrontMCP 1.9, the SDK ships one), and that build doesn't detect anything: it always says { os: "browser", platform: "browser", runtime: "browser", deployment: "standalone", provider: "bare", target: "browser" }. Its env follows NODE_ENV, as in Node: the page's process.env.NODE_ENV when there is one, else the value a bundler built in, else "development". Error details follow the same value. The Playground sets NODE_ENV to "development". (Before 1.9.2 the browser build's env was always "production", while its errors were formatted for development.) When the same examples run in Node, as they do in this site's tests and under frontmcp test, os is your operating system, runtime is "node" and target is "unknown". So the examples on this page restrict by fields that are the same in both, like deployment or env, or show both answers. Before 1.9, the Playground's build said runtime: "node" and target: "unknown".
this.runtimeContext and the checks
Every context class has them: tools, resources, prompts, agents, jobs and channels.
| Member | Returns | Description |
|---|---|---|
this.runtimeContext | RuntimeContext | The detected environment: { os, platform, runtime, deployment, provider, target, env }. The same object for every call. |
this.isEnv(env) | boolean | runtimeContext.env === env. |
this.isRuntime(runtime) | boolean | runtimeContext.runtime === runtime. |
this.isDeployment(deployment) | boolean | runtimeContext.deployment === deployment. |
this.isPlatform(os) | boolean | runtimeContext.platform === os. |
There's no isProvider() or isTarget(): compare this.runtimeContext.provider and .target.
Runtime modes
How a server is started is a separate thing from where it runs, and runtimeContext doesn't tell the ways apart:
| Mode | Started by | What runtimeContext says |
|---|---|---|
| HTTP server | @FrontMcp (the default serve: true) or FrontMcpInstance.bootstrap() | deployment: "standalone", or "distributed" with FRONTMCP_DEPLOYMENT_MODE=distributed. |
| stdio | FRONTMCP_STDIO=1, or FrontMcpInstance.runStdio() | The same as for HTTP. |
| Request handler | FRONTMCP_SERVERLESS=1, FrontMcpInstance.createHandler() or createFetchHandler() | "serverless" only when a platform variable or FRONTMCP_DEPLOYMENT_MODE says so. |
| In your process | create(), createDirect() or connect() | The process's environment, like any other mode. |
If a tool needs to know the mode, set a variable of your own where you start the server and read it with ConfigPlugin.
Caveats
- Everything but
envis detected once per process, so in one process every server sees the sameos,runtime,deployment,providerandtarget.envfollowsNODE_ENV: change it while a server runs, andthis.runtimeContext.envand the entriesavailableWhen: { env }allows change with it. availableWhenis about the machine and, withsurface, the kind of caller, never about who the caller is. To decide which users may call something, useauthorities.- The
ENTRY_UNAVAILABLEmessage, and the-32003error of a resource or prompt, tell any client the server's OS, runtime, deployment, provider, target andNODE_ENV, in production too. If that's more than callers should know, leave the entry out of the server where it can't run.
Usage
Offering a tool only where it works
A tool that needs something only some environments have, like a disk that keeps its files or a Node API, says so with availableWhen. Where it doesn't match, the model never sees it, so it never tries it. Here export_tickets writes to the disk of a long-running server; upload_tickets is for serverless deployments, whose functions don't keep files from one call to the next:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({
name: "export_tickets",
description: "Write every open ticket to a CSV file on the server's disk",
inputSchema: {},
availableWhen: { deployment: ["standalone"] },
})
export class ExportTickets extends ToolContext {
async execute() {
return { path: "/var/exports/tickets.csv", rows: 42 };
}
}
@Tool({
name: "upload_tickets",
description: "Upload every open ticket as a CSV file to object storage",
inputSchema: {},
availableWhen: { deployment: ["serverless"] },
})
export class UploadTickets extends ToolContext {
async execute() {
return { url: "https://files.desk.example/tickets.csv", rows: 42 };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The same works for resources and prompts, which then don't appear in their lists, and can't be read by a client that knows their URI or name.
One tool, a version per platform
When the same capability needs different code on different platforms, write one class per platform with the same name and a different availableWhen. The model sees a single export_tickets tool, and calls reach whichever version matches. Here the server runtimes write to disk, and the runtimes without a file system, the browser and edge runtimes, upload instead:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({
name: "export_tickets",
description: "Export every open ticket as a CSV file and return where it is",
inputSchema: {},
availableWhen: { runtime: ["node", "bun", "deno"] },
})
export class ExportTicketsToDisk extends ToolContext {
async execute() {
return { location: "/var/exports/tickets.csv" };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
In your browser the Call tab shows the upload's location; run the same server in Node and the disk version answers.
Keep the name, description and inputSchema the same in every version, so the model's view of the tool doesn't depend on where the server runs. Make sure the availableWhen of the versions don't overlap: when two match, clients see and call only the first one registered, and FrontMCP reports no conflict.
Tools for development only
env restricts a tool to some values of NODE_ENV. A tool that resets demo data should never reach production:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({
name: "reset_demo_data",
description: "Delete every ticket and load the demo tickets again",
inputSchema: {},
availableWhen: { env: ["development", "test"] },
})
export class ResetDemoData extends ToolContext {
async execute() {
return { tickets: 3 };
}
}
@Tool({ name: "where", description: "Where the server is running", inputSchema: {} })
export class Where extends ToolContext {
async execute() {
return { env: this.runtimeContext.env };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The Playground runs as development, so the Call tab shows "env": "development" and the tools list has reset_demo_data. Start the server with NODE_ENV=production and it's gone; Changing NODE_ENV while the server runs tests both.
Keeping a tool from MCP clients
surface restricts who an entry is offered to. A tool for operators, meant for the commands of the server's CLI build, shouldn't reach the model: with surface: ["cli"], MCP clients don't see it, and a call by its name fails as if it didn't exist. A tool's own code isn't a surface, so a tool that calls it with this.callTool() still runs it:
import { Tool, ToolContext, z } from "@frontmcp/sdk";
@Tool({
name: "purge_closed_tickets",
description: "Delete every closed ticket for good",
inputSchema: {},
availableWhen: { surface: ["cli"] },
})
export class PurgeClosedTickets extends ToolContext {
async execute() {
return { purged: 12 };
}
}
@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
async execute({ id }: { id: string }) {
return { id, status: "closed" };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
surface decides what clients are offered, not what your code may do. Keep tools that call a restricted one restricted too, and to decide which users may call something, use authorities.
Keeping a tool from agents and jobs
surface works the other way too. A tool that only a person should run, through their MCP client, gets surface: ["mcp"]: an agent's model isn't offered it even when it's in the agent's tools, and a job can't call it. Here the triage agent may read tickets but not delete them, and neither may the spam job. get_ticket reports the surface it was called on, with getCallSurface():
import { Tool, ToolContext, getCallSurface, z } from "@frontmcp/sdk";
@Tool({ name: "get_ticket", description: "Get a support ticket by its id", inputSchema: { id: z.string() } })
export class GetTicket extends ToolContext {
async execute({ id }: { id: string }) {
return { id, title: "Charged twice", calledFrom: getCallSurface() };
}
}
@Tool({
name: "delete_ticket",
description: "Delete a support ticket for good",
inputSchema: { id: z.string() },
availableWhen: { surface: ["mcp"] }, // people, through their MCP client
})
export class DeleteTicket extends ToolContext {
async execute({ id }: { id: string }) {
return { id, deleted: true };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
An agent's model only learns about the tools it's offered, so it never sees delete_ticket; the Call tab shows what it got when it asked for it anyway. this.callTool() in the agent's own code is on "agent" too, so it can't reach delete_ticket either.
Offering an agent or a channel only where it works
availableWhen works on an @Agent as on a tool: its invoke_<name> tool is left out of tools/list where it doesn't match, and a call fails with ENTRY_UNAVAILABLE. On a @Channel, the channel isn't registered at all, so a two-way channel's channel-reply tool is gone too:
import { Agent, AgentContext, App, Channel, ChannelContext, FrontMcp, z, type ChannelNotification } from "@frontmcp/sdk";
@Agent({
name: "triage",
description: "Sort a support ticket into a queue",
inputSchema: { ticket: z.string() },
availableWhen: { runtime: ["deno"] },
llm: { adapter: { async completion() { return { content: "billing", finishReason: "stop" }; } } },
})
class Triage extends AgentContext {}
@Channel({
name: "deploys",
description: "Deploys of the help desk, as they happen",
source: { type: "app-event", event: "deploy" },
twoWay: true,
availableWhen: { runtime: ["deno"] },
})
class DeploysChannel extends ChannelContext {
async onEvent(payload: unknown): Promise<ChannelNotification> {
return { content: `deployed ${(payload as { version: string }).version}` };
}
async onReply() {}
}
@App({ id: "help-desk", name: "Help Desk", agents: [Triage], channels: [DeploysChannel] })
class HelpDeskApp {}
@FrontMcp({ info: { name: "help-desk", version: "1.0.0" }, apps: [HelpDeskApp], channels: { enabled: true } })
export default class Server {}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Remove availableWhen from the channel, and channel-reply is listed. Before 1.9.2, availableWhen on a channel was ignored; before 1.8.3, on an agent too.
Branching inside a call
When most of a tool works everywhere and only one step differs, check this.runtimeContext inside execute() instead of writing two tools. Here the tool keeps its cache in memory on a single long-running server, and in a shared store when the server runs as short-lived functions that don't share memory:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({ name: "cache_status", description: "Where search results are cached", inputSchema: {} })
export class CacheStatus extends ToolContext {
async execute() {
const { runtime, deployment, provider } = this.runtimeContext;
const cache = this.isDeployment("serverless") ? "shared store" : "memory";
return { cache, runtime, deployment, provider };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Resources and prompts have the same members. Context classes has an example that adds debugging details outside production.
Setting the environment for a deployment
Detection covers the common platforms. Where it can't tell, set the variables yourself, before the server starts, for example in a Dockerfile:
ENV NODE_ENV=production
ENV FRONTMCP_PROVIDER=fly
ENV FRONTMCP_DEPLOYMENT_MODE=distributed
ENV FRONTMCP_BUILD_TARGET=nodeThese can't be shown in a Playground: it runs in one process, whose environment is fixed. Configuration files lists every environment variable FrontMCP reads.
Troubleshooting
Tool "…" is not available in the current environment
A client called a tool whose availableWhen doesn't match where the server runs. The result is a tool error with the code ENTRY_UNAVAILABLE, and the message says what the tool requires, what the server has, and which fields didn't match:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({ name: "run_on_deno", description: "Only works on Deno", inputSchema: {}, availableWhen: { runtime: ["deno"] } })
export class RunOnDeno extends ToolContext {
async execute() {
return { ok: true };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The client had the tool's name from an older list, or guessed it; the model won't see it in tools/list. If the tool should run here, fix its availableWhen. A tool called with this.callTool() fails the same way, by throwing an EntryUnavailableError.
A tool is missing, and nothing says why
Its availableWhen doesn't match, most often because of a misspelled value ("nodejs", "prod"), an empty array, or a surface without "mcp"; none is an error. Look for the startup line [ToolRegistry] availability: … filtered, and set the log level to Verbose to see filtered: "<name>" with the constraint and the detected value. Compare with what this.runtimeContext returns in a tool that has no availableWhen.
Tool "…" not found for a tool that has surface
Since FrontMCP 1.8.3, surface is checked. A tool whose surface leaves out "mcp" isn't offered to MCP clients or to createDirect(), and a call to it fails with TOOL_NOT_FOUND, as for a tool that doesn't exist; a resource or prompt answers Resource not found: … or Prompt not found: …. Before 1.8.3 such a tool was listed and callable. Add "mcp" to its surface if clients should reach it, or remove surface.
Since 1.8.4, agents and jobs are surfaces too. An agent whose tool's surface leaves out "agent" isn't offered that tool, and when its model calls it anyway, the model gets Tool "…" not found in agent "…"; a job's this.callTool() of a tool without "job" throws Tool "…" not found. Add the caller's surface, or remove surface. See Surfaces, Keeping a tool from MCP clients and Keeping a tool from agents and jobs.
Resource "…" is not available in the current environment
A client read a resource, or got a prompt, whose availableWhen doesn't match where the server runs. Both are left out of their lists, and since FrontMCP 1.8.3 reading or getting them fails too, with JSON-RPC error -32003 and the same kind of message as for a tool. Before 1.8.3 they could still be read by a client that knew the URI or the name:
import { Prompt, PromptContext, Resource, ResourceContext } from "@frontmcp/sdk";
@Resource({ name: "deno-config", uri: "desk://deno-config", mimeType: "application/json", availableWhen: { runtime: ["deno"] } })
export class DenoConfig extends ResourceContext {
async execute(uri: string) {
return { contents: [{ uri, text: JSON.stringify({ permissions: ["--allow-net"] }) }] };
}
}
@Prompt({ name: "deno-review", description: "Review a Deno module", arguments: [], availableWhen: { runtime: ["deno"] } })
export class DenoReview extends PromptContext {
async execute() {
return { messages: [{ role: "user" as const, content: { type: "text" as const, text: "Review this Deno module." } }] };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The client had the URI or the name from elsewhere; the model won't see either in a list. If the resource or prompt should work here, fix its availableWhen.
Unrecognized key in availableWhen
The server fails to start with a Zod error: Unrecognized key: "environment", path availableWhen. The fields are os, platform, runtime, deployment, provider, target, env and surface:
// 🚩 Not a field
availableWhen: { environment: ["production"] }
// ✅
availableWhen: { env: ["production"] }
target is "unknown"
The server didn't start from a bundle that frontmcp build --target … made: frontmcp dev and tsx start your source. (The Playground says "browser".) A bundle sets its target when it starts, since FrontMCP 1.9.0; node dist/node/<name>.bundle.js reports target: "node". Elsewhere, set FRONTMCP_BUILD_TARGET in the environment, or restrict by deployment or provider, which are detected. Before 1.9.0, frontmcp build didn't pass the target to the bundle, so it was always "unknown".
Changing NODE_ENV while the server runs
env is read from NODE_ENV each time, so a tool that's availableWhen: { env: ["production"] } appears, and one that's ["development"] disappears, as soon as NODE_ENV becomes production, and this.runtimeContext.env says so too. That is useful in a test and surprising in a server: set NODE_ENV before the server starts, in the shell, the Dockerfile or the platform's settings, and leave it. A .env file only works if something loads it before FrontMCP starts, such as frontmcp dev: see Configuration files. Every other field is detected once, and changing its variable afterwards does nothing. Before 1.8.7 env was fixed too, so NODE_ENV set after the first read changed nothing. The Playground reads its own NODE_ENV the same way, so the tests pass in your browser and in Node.
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({ name: "reset_demo_data", description: "Delete every ticket and load the demo tickets again", inputSchema: {}, availableWhen: { env: ["development"] } })
export class ResetDemoData extends ToolContext {
async execute() {
return { tickets: 3 };
}
}
@Tool({ name: "export_audit_log", description: "Write the audit log for the auditors", inputSchema: {}, availableWhen: { env: ["production"] } })
export class ExportAuditLog extends ToolContext {
async execute() {
return { rows: 120 };
}
}
@Tool({ name: "where", description: "Where the server is running", inputSchema: {} })
export class Where extends ToolContext {
async execute() {
return { env: this.runtimeContext.env };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.