Escape Hatches
Most of FrontMCP is declarative. You describe a tool's name, schema and handler, and FrontMCP does the rest: it validates arguments, shapes results, and speaks the protocol over HTTP. Now and then a server has to step outside that model: to look at the request itself, to reach a service on the other side of the network, or to run somewhere FrontMCP's own server doesn't. Most servers never need these. When yours does, this chapter shows how, and what to watch out for.
In this chapter
Reading the request
A tool gets its arguments in execute(). Everything else about the call is on this: this.context holds an id for the request and its trace, and this.auth the caller your authentication established. The request id is what ties a failure the user reports to the line in your logs:
import { Tool, ToolContext } from "@frontmcp/sdk";
@Tool({
name: "describe_call",
description: "Describe the request this call arrived in. For debugging.",
inputSchema: {},
})
export class DescribeCall extends ToolContext {
async execute() {
return {
requestId: this.context.requestId,
traceId: this.context.traceContext.traceId,
caller: this.auth.user.sub,
};
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Call it again and all three change: the Playground has no authentication, so every request is a new anonymous caller. Only the caller comes from something your server checked. The arguments, the client's name and its headers are whatever the client chose to send.
Ready to learn this topic?
Learn what this.context and this.auth hold, how to tag logs and errors with the request id, where the caller's identity should come from, and why client info and arguments prove nothing.
Calling other services
this.fetch() is fetch with the current request attached: it tells the service which request is calling, and gives up after 30 seconds. The rest is up to the tool. It sends the service's own credentials rather than the caller's, and passes on only what the model needs:
import { Tool, ToolContext, z } from "@frontmcp/sdk";
type Invoice = { id: string; status: string; total_cents: number; currency: string; due_date: string };
@Tool({
name: "get_invoice",
description: "Get an invoice from the billing service: its status, total and due date.",
inputSchema: { id: z.string().regex(/^INV-\d+$/).describe("Invoice id, like INV-1") },
annotations: { readOnlyHint: true, openWorldHint: true },
})
export class GetInvoice extends ToolContext {
async execute({ id }: { id: string }) {
const res = await this.fetch(`https://billing.example/invoices/${id}`, {
headers: { Authorization: "Bearer bk_test_7Hq2" }, // billing's key, from your secrets in a real server
signal: AbortSignal.timeout(2_000),
});
const invoice = (await res.json()) as Invoice;
return {
id: invoice.id,
status: invoice.status,
total: `${(invoice.total_cents / 100).toFixed(2)} ${invoice.currency}`,
due: invoice.due_date,
};
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The billing service here is played by billing.example.ts, because the Playground can't reach the internet. Try INV-9 or INV-7: this version still passes billing's errors off as invoices, which is one of the things the lesson fixes.
Ready to learn this topic?
Learn what this.fetch() adds to a request, why it keeps the caller's token to itself, and how to turn failures and timeouts into errors the model can act on.
Running FrontMCP anywhere
@FrontMcp normally starts an HTTP server for you. Underneath it, FrontMcpInstance.createFetchHandler() turns your server into a function from Request to Response, for Cloudflare Workers and any other runtime that speaks the web's fetch API. It's what runs this Playground. Open the Tests tab:
import { FrontMcpInstance } from "@frontmcp/sdk";
import { HelpDesk } from "./help-desk.app";
const handler = FrontMcpInstance.createFetchHandler({ info: { name: "help-desk", version: "1.0.0" }, apps: [HelpDesk] });
export default {
async fetch(request: Request): Promise<Response> {
return (await handler)(request);
},
};Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Two more entry points skip HTTP altogether: connect() gives your own code an MCP client connected in memory, and createDirect() runs tools with no client at all, for scripts and jobs.
Ready to learn this topic?
Learn when to take over from @FrontMcp, how to serve MCP from any web-standard runtime, and how connect() and createDirect() differ from a real client in identity, errors and formatting.
What's next?
Start the chapter with Reading the Request. This is the last chapter of Learn. From here, the Reference covers each API in detail.