Skip to main content

Overview

Every tool handler receives a ToolContext as its second argument. It carries the current session ID, resolved authentication headers, provider token, and an abort signal for cancellation.
handler: async (args: z.infer<TSchema>, context: ToolContext) => { ... }

Properties

sessionId
string
The current MCP session ID. Generated by the server on the initialize handshake. Use this to correlate tool calls to a specific client session in logs or external systems.
signal
AbortSignal | undefined
An AbortSignal for cooperative cancellation. When the MCP client sends a notifications/cancelled message, the server aborts the signal. Pass this to fetch or other async operations so they stop early when the client cancels.
handler: async (args, context) => {
  const res = await fetch("https://api.example.com/data", {
    signal: context.signal,
  });
  return res.json();
},
meta
{ progressToken?: string | number; requestId?: string } | undefined
Request metadata forwarded from the MCP JSON-RPC layer.
  • progressToken — opaque token the client uses to track progress notifications.
  • requestId — the JSON-RPC request ID for this call.
authStrategy
'oauth' | 'bearer' | 'api_key' | 'custom' | 'none' | undefined
The active authentication strategy for this server instance.
ValueDescription
oauthFull OAuth 2.1 PKCE flow; providerToken is the mapped provider access token
bearerStatic bearer token from BEARER_TOKEN env var
api_keyStatic API key from API_KEY env var
customArbitrary headers from CUSTOM_HEADERS env var
noneNo authentication configured
providerToken
string | undefined
The resolved access token for the authenticated user.
  • OAuth: the provider access token mapped from the RS token (e.g., a Google or GitHub token).
  • Bearer: the value of the BEARER_TOKEN environment variable.
  • API key: the value of the API_KEY environment variable.
  • Custom / none: undefined.
Use resolvedHeaders for making API calls instead of constructing the Authorization header yourself.
provider
ProviderInfo | undefined
OAuth provider details. Only populated when authStrategy is oauth.
interface ProviderInfo {
  accessToken: string;
  refreshToken?: string;
  expiresAt?: number;
  scopes?: string[];
}
resolvedHeaders
Record<string, string> | undefined
Ready-to-use HTTP headers for forwarding authentication to external APIs. The exact headers depend on the active strategy:
StrategyHeader
oauthAuthorization: Bearer <provider-token>
bearerAuthorization: Bearer <BEARER_TOKEN>
api_key<API_KEY_HEADER>: <API_KEY> (default header: x-api-key)
customAll headers from CUSTOM_HEADERS env var
Spread or pass this object directly to fetch:
handler: async (args, context) => {
  const response = await fetch("https://api.example.com/me", {
    headers: context.resolvedHeaders,
  });
  return response.json();
},
authHeaders
Record<string, string> | undefined
deprecated
Raw authorization headers from the incoming MCP request (before resolution). Use resolvedHeaders instead — it contains the correct auth headers for forwarding to external services regardless of the active strategy.

AuthenticatedToolContext

AuthenticatedToolContext extends ToolContext and guarantees that providerToken is a non-optional string. The dispatcher populates this automatically for tools that declare requiresAuth: true.
interface AuthenticatedToolContext extends ToolContext {
  providerToken: string;
}
You can also use assertProviderToken to narrow the type manually:
import { assertProviderToken } from "@phake/mcp";

handler: async (_args, context) => {
  assertProviderToken(context);
  // context.providerToken is now string, not string | undefined
},

Examples

Forward auth to an external API

import { defineTool } from "@phake/mcp";
import { z } from "zod";

export const getProfileTool = defineTool({
  name: "get_profile",
  description: "Fetch the authenticated user's profile",
  inputSchema: z.object({}),
  requiresAuth: true,
  handler: async (_args, context) => {
    const response = await fetch("https://api.example.com/me", {
      headers: context.resolvedHeaders,
      signal: context.signal,
    });
    if (!response.ok) {
      return { error: `HTTP ${response.status}` };
    }
    return response.json();
  },
});

Use session ID for logging

handler: async (args, context) => {
  console.log(`[${context.sessionId}] Calling search with query: ${args.query}`);
  // ...
},

Inspect auth strategy

handler: async (args, context) => {
  if (context.authStrategy === "none") {
    return { error: "Authentication is not configured on this server" };
  }
  // continue with authenticated logic
},