# How the PostHog MCP Server Exposes Tools: Registration Flow and HTTP Endpoints

> Discover how the PostHog MCP server exposes tools through its registration flow and HTTP endpoints. Learn about the REST API for accessing tools.

- Repository: [PostHog/posthog](https://github.com/PostHog/posthog)
- Tags: how-to-guide
- Published: 2026-04-25

---

**The PostHog MCP server exposes tools by discovering available definitions during the worker's `init()` phase, registering normalized tool metadata with the underlying `McpServer` via `registerTool`, and automatically exposing each tool as a callable REST endpoint at `/api/environments/<env>/mcp_tools/<tool-name>/`.**

The Model Context Protocol (MCP) implementation in PostHog enables AI models to interact with product analytics through standardized tool definitions. This article examines how the **MCP server exposes tools** by tracing the registration flow from YAML manifest discovery to HTTP endpoint generation in the `PostHog/posthog` repository.

## Tool Discovery and Catalog Building

During initialization, the MCP worker gathers available tools based on feature flags and access controls. The `getToolsFromContext` function (lines 447-456 in [`services/mcp/src/mcp.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/mcp.ts)) collects tools that satisfy client-specific rules, filtering by read-only requirements and exclusion lists.

Static tool definitions—including titles, descriptions, input schemas, and UI metadata—are loaded via `getToolDefinition` from generated JSON/YAML files located in [`services/mcp/src/tools/toolDefinitions.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/toolDefinitions.ts) (lines 65-71). Product-level manifests in `products/*/mcp/tools.yaml` drive this generation, ensuring tool catalogs remain synchronized with product capabilities.

## The Registration Pipeline

The `registerTool` method in [`services/mcp/src/mcp.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/mcp.ts) (lines 364-498) serves as the core mechanism for exposing tools to the MCP ecosystem.

### Normalizing UI Metadata

Before registration, the system processes the `_meta` object to ensure backward compatibility. If a tool definition includes `ui.resourceUri` without the legacy `RESOURCE_URI_META_KEY`, the code enriches the metadata at lines 329-337:

```typescript
let normalizedMeta = tool._meta
if (tool._meta?.ui?.resourceUri && !tool._meta[RESOURCE_URI_META_KEY]) {
    normalizedMeta = { ...tool._meta, [RESOURCE_URI_META_KEY]: tool._meta.ui.resourceUri }
}

```

This normalization ensures that UI resource identifiers remain accessible to legacy clients while supporting modern metadata structures.

### Handler Wrapping and Validation

The user-provided handler undergoes wrapping to enforce validation, analytics tracking, and error conversion. The wrapped handler (lines 364-426) performs context-switching for analytics, handles UI-resource specific logic, and converts errors into standardized `ToolResult` payloads before returning to the caller.

The final registration call passes the normalized metadata to the underlying SDK:

```typescript
this.server.registerTool(
    tool.name,
    {
        title: tool.title,
        description: tool.description,
        inputSchema: tool.schema.shape,
        annotations: tool.annotations,
        ...(normalizedMeta ? { _meta: normalizedMeta } : {}),
    },
    wrappedHandler as unknown as ToolCallback<TSchema['shape']>
)

```

## Server Initialization and Endpoint Exposure

The MCP server initializes with a default prompt template before recreating the instance with the final tool catalog. Lines 66-67 in [`services/mcp/src/mcp.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/mcp.ts) construct the initial server:

```typescript
server = new McpServer({ name: 'PostHog', version: '1.0.0' }, { instructions: INSTRUCTIONS_TEMPLATE_V1 })

```

After tool discovery and prompt generation complete, the server reinstantiates with the updated instructions (lines 93-95).

Each registered tool automatically becomes available at a dedicated REST endpoint. The URL construction logic in [`services/mcp/src/tools/posthogAiTools/invokeTool.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/posthogAiTools/invokeTool.ts) (line 23) builds the path:

```typescript
const url = `${context.api.baseUrl}/api/environments/${projectId}/mcp_tools/${toolName}/`

```

This endpoint routes requests to the correct durable object instance, validates incoming payloads against the tool's Zod schema, and executes the wrapped handler.

## Practical Implementation Examples

### Registering a Custom Tool

Developers can expose new functionality by creating tool definitions and registering them during worker initialization:

```typescript
import { z } from 'zod'
import { Tool } from '@/tools/types'

const myTool: Tool<typeof z.object({ query: z.string() })> = {
    name: 'my-custom-query',
    title: 'Run a custom query',
    description: 'Executes a bespoke SQL query against the data warehouse.',
    schema: z.object({ query: z.string() }),
    handler: async (context, { query }) => {
        return { rows: await context.api.sql.run(query) }
    },
}
mcp.registerTool(myTool, myTool.handler)

```

Once registered, the tool appears in the MCP system prompt and becomes callable at `/api/environments/<env>/mcp_tools/my-custom-query/`.

### Invoking Tools via HTTP

Clients interact with exposed tools through standardized HTTP POST requests:

```bash
curl -X POST "https://mcp.posthog.com/api/environments/123/mcp_tools/execute-sql/" \
     -H "Authorization: Bearer <api-token>" \
     -H "Content-Type: application/json" \
     -d '{"query":"SELECT * FROM events LIMIT 10"}'

```

The server validates the request body against the tool's input schema, executes the handler, and returns a JSON payload containing either `structuredContent` for UI applications or plain text responses.

## Summary

- **Tool discovery** occurs during the worker's `init()` phase via `getToolsFromContext` in [`services/mcp/src/mcp.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/mcp.ts), filtering tools by feature flags and access rules.
- **Registration** happens through the `registerTool` method (lines 364-498), which normalizes UI metadata and wraps handlers for validation and analytics.
- **HTTP endpoints** generate automatically at `/api/environments/<env>/mcp_tools/<tool-name>/` as implemented in [`services/mcp/src/tools/posthogAiTools/invokeTool.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/posthogAiTools/invokeTool.ts).
- **Metadata normalization** ensures backward compatibility by mapping `ui.resourceUri` to legacy keys before SDK registration.
- **Source definitions** reside in product-level YAML manifests (`products/*/mcp/tools.yaml`) and are consumed by [`services/mcp/src/tools/toolDefinitions.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/toolDefinitions.ts).

## Frequently Asked Questions

### How does the MCP server determine which tools to expose?

The server calls `getToolsFromContext` during initialization to gather tools matching the client's feature flags, read-only constraints, and exclusion rules. Static definitions are loaded from generated JSON files via `getToolDefinition` in [`services/mcp/src/tools/toolDefinitions.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/toolDefinitions.ts), ensuring only authorized tools appear in the catalog.

### What is the URL pattern for exposed MCP tools?

Each tool exposes a REST endpoint at `/api/environments/{projectId}/mcp_tools/{tool-name}/` as constructed in [`services/mcp/src/tools/posthogAiTools/invokeTool.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/tools/posthogAiTools/invokeTool.ts) (line 23). This pattern routes requests to the specific tool handler registered with the `McpServer` instance.

### How does the server handle UI-specific metadata in tool definitions?

The `registerTool` method normalizes the `_meta` object by checking for `ui.resourceUri` and injecting the legacy `RESOURCE_URI_META_KEY` if absent (lines 329-337 in [`services/mcp/src/mcp.ts`](https://github.com/PostHog/posthog/blob/main/services/mcp/src/mcp.ts)). This ensures backward compatibility while supporting modern UI resource annotations.

### Can custom tools be registered dynamically?

Yes. Developers can define tools using Zod schemas and the `Tool` interface, then pass them to `mcp.registerTool()` along with their handler functions. The tool immediately becomes available in the system prompt and at its dedicated HTTP endpoint without requiring server restarts.