Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions posthog/event_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,16 @@ class EventSource(StrEnum):
POSTHOG_AI = "posthog_ai"
TERRAFORM = "terraform"
MCP = "mcp"
WIZARD = "wizard"


def get_event_source(request) -> EventSource:
"""Determine the source of an API request for analytics."""
user_agent = request.META.get("HTTP_USER_AGENT", "")
if "posthog/terraform-provider" in user_agent:
return EventSource.TERRAFORM
if "posthog/wizard" in user_agent:
return EventSource.WIZARD
if "posthog/mcp-server" in user_agent:
return EventSource.MCP
if isinstance(getattr(request, "successful_authenticator", None), SessionAuthentication):
Expand Down
5 changes: 3 additions & 2 deletions services/mcp/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

import { USER_AGENT } from '@/lib/constants'
import { getUserAgent } from '@/lib/constants'
import { ErrorCode } from '@/lib/errors'
import { getSearchParamsFromRecord } from '@/lib/utils.js'
import {
Expand Down Expand Up @@ -125,6 +125,7 @@ export type Result<T, E = Error> = { success: true; data: T } | { success: false
export interface ApiConfig {
apiToken: string
baseUrl: string
clientIdentifier?: string
}

type Endpoint = Record<string, any>
Expand Down Expand Up @@ -154,7 +155,7 @@ export class ApiClient {
// TODO: should we move rate limiting from `fetchWithSchema` to here?
const defaultHeaders: HeadersInit = {
Authorization: `Bearer ${this.config.apiToken}`,
'User-Agent': USER_AGENT,
'User-Agent': getUserAgent(this.config.clientIdentifier),
}
if (options?.body) {
defaultHeaders['Content-Type'] = 'application/json'
Expand Down
4 changes: 2 additions & 2 deletions services/mcp/src/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { USER_AGENT } from '@/lib/constants'
import { getUserAgent } from '@/lib/constants'

import type { ApiConfig } from './client'
import type { createApiClient } from './generated'
Expand All @@ -18,7 +18,7 @@ export const buildApiFetcher: (config: ApiConfig) => Parameters<typeof createApi

const headers = new Headers()
headers.set('Authorization', `Bearer ${config.apiToken}`)
headers.set('User-Agent', USER_AGENT)
headers.set('User-Agent', getUserAgent(config.clientIdentifier))

// Handle query parameters
if (input.urlSearchParams) {
Expand Down
7 changes: 7 additions & 0 deletions services/mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,19 @@ const handleRequest = async (
request.headers.get('x-posthog-organization-id') || url.searchParams.get('organization_id') || undefined
const projectId = request.headers.get('x-posthog-project-id') || url.searchParams.get('project_id') || undefined

// Extract posthog/foo identifier from the client's User-Agent (e.g. "posthog/wizard")
// so we can forward it in outgoing API requests for source attribution
const clientUserAgent = request.headers.get('User-Agent') || ''
const clientIdentifierMatch = clientUserAgent.match(/posthog\/(\S+)/)
const clientIdentifier = clientIdentifierMatch ? clientIdentifierMatch[0] : undefined

Object.assign(ctx.props, {
apiToken: token,
userHash: hash(token),
sessionId: sessionId || undefined,
organizationId,
projectId,
clientIdentifier,
})

// Search params are used to build up the list of available tools. If no features are provided, all tools are available.
Expand Down
7 changes: 7 additions & 0 deletions services/mcp/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import packageJson from '../../package.json'

export const USER_AGENT = `posthog/mcp-server; version: ${packageJson.version}`

export function getUserAgent(clientIdentifier?: string): string {
if (clientIdentifier) {
return `${USER_AGENT}; for ${clientIdentifier}`
}
return USER_AGENT
}

// Region-specific PostHog API base URLs
export const POSTHOG_US_BASE_URL = 'https://us.posthog.com'
export const POSTHOG_EU_BASE_URL = 'https://eu.posthog.com'
Expand Down
2 changes: 2 additions & 0 deletions services/mcp/src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type RequestProperties = {
version?: number
organizationId?: string
projectId?: string
clientIdentifier?: string
}

export class MCP extends McpAgent<Env> {
Expand Down Expand Up @@ -137,6 +138,7 @@ export class MCP extends McpAgent<Env> {
this._api = new ApiClient({
apiToken: this.requestProperties.apiToken,
baseUrl,
clientIdentifier: this.requestProperties.clientIdentifier,
})
}

Expand Down
Loading