Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 15 additions & 20 deletions dist/src/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion dist/src/telemetry/client.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion dist/src/telemetry/session.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 24 additions & 41 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@ import { ServerContext } from './tools/utils/toolHelpers.js';
import { env } from './env.js';
import { createTemplateRenderer } from './tools/utils/templateRenderer.js';
import { createErrorTemplateRenderer } from './tools/utils/errorTemplateRenderer.js';
import {
createTelemetryClient,
detectPaginationUsed,
parseTelemetryFlag,
TelemetrySession,
type ITelemetryClient,
} from './telemetry/index.js';
import { createTelemetryClient, detectPaginationUsed, parseTelemetryFlag, TelemetrySession, type ITelemetryClient } from './telemetry/index.js';

const SUPPORTED_REGIONS = {
us: 'https://api.postman.com',
Expand Down Expand Up @@ -206,20 +200,16 @@ async function run() {
.sort(toolSorter);

// Determine region for telemetry
const region: 'us' | 'eu' =
regionIndex !== -1 && regionIndex + 1 < args.length && isValidRegion(args[regionIndex + 1])
? (args[regionIndex + 1] as 'us' | 'eu')
: 'us';
const region: 'us' | 'eu' = (regionIndex !== -1 && regionIndex + 1 < args.length && isValidRegion(args[regionIndex + 1]))
? args[regionIndex + 1] as 'us' | 'eu'
: 'us';

// Telemetry is OFF by default for the open-source STDIO server. Users must
// opt in explicitly with POSTMAN_MCP_TELEMETRY=true; any other value (or
// unset) keeps telemetry disabled.
const telemetryEnabled = parseTelemetryFlag(process.env.POSTMAN_MCP_TELEMETRY) ?? false;
if (telemetryEnabled) {
log(
'info',
'Telemetry enabled (POSTMAN_MCP_TELEMETRY=true). Set POSTMAN_MCP_TELEMETRY=false to disable.'
);
log('info', 'Telemetry enabled (POSTMAN_MCP_TELEMETRY=true). Set POSTMAN_MCP_TELEMETRY=false to disable.');
}
const telemetry: ITelemetryClient = createTelemetryClient({
telemetryEnabled,
Expand Down Expand Up @@ -334,14 +324,12 @@ async function run() {
isError: result.isError ?? false,
authMethod: 'api_key',
paginationUsed,
meta: extra?._meta
? {
trigger: (extra._meta as any).trigger ?? '',
conversation_id: (extra._meta as any).conversation_id ?? '',
task_type: (extra._meta as any).task_type ?? '',
model_name: (extra._meta as any).model_name ?? '',
}
: undefined,
meta: extra?._meta ? {
trigger: (extra._meta as any).trigger ?? '',
conversation_id: (extra._meta as any).conversation_id ?? '',
task_type: (extra._meta as any).task_type ?? '',
model_name: (extra._meta as any).model_name ?? '',
} : undefined,
metaRaw,
});

Expand All @@ -360,14 +348,12 @@ async function run() {
logBoth(server, 'error', `Tool invocation failed: ${toolName}: ${errMsg}`, { toolName });

const errDurationMs = Date.now() - start;
const errorMeta = extra?._meta
? {
trigger: (extra._meta as any).trigger ?? '',
conversation_id: (extra._meta as any).conversation_id ?? '',
task_type: (extra._meta as any).task_type ?? '',
model_name: (extra._meta as any).model_name ?? '',
}
: undefined;
const errorMeta = extra?._meta ? {
trigger: (extra._meta as any).trigger ?? '',
conversation_id: (extra._meta as any).conversation_id ?? '',
task_type: (extra._meta as any).task_type ?? '',
model_name: (extra._meta as any).model_name ?? '',
} : undefined;

if (error instanceof McpError) {
const httpStatus = (error.data as Record<string, unknown>)?.httpStatus;
Expand All @@ -382,16 +368,13 @@ async function run() {
authMethod: 'api_key',
paginationUsed,
errorType: 'tool',
errorCode:
typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403)
? 'AUTH_ERROR'
: typeof httpStatus === 'number' && httpStatus === 429
? 'RATE_LIMITED'
: 'UPSTREAM_ERROR',
errorStage:
typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403)
? 'auth'
: 'upstream',
errorCode: typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403)
? 'AUTH_ERROR'
: typeof httpStatus === 'number' && httpStatus === 429
? 'RATE_LIMITED'
: 'UPSTREAM_ERROR',
errorStage: typeof httpStatus === 'number' && (httpStatus === 401 || httpStatus === 403)
? 'auth' : 'upstream',
errorUpstream: 'postman-api',
rateLimited: typeof httpStatus === 'number' && httpStatus === 429,
meta: errorMeta,
Expand Down
23 changes: 17 additions & 6 deletions src/telemetry/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { TelemetrySession } from './session.js';
import type { TelemetryEvent, SessionInitEvent, ToolCallEvent } from './types.js';
import type {
TelemetryEvent,
SessionInitEvent,
ToolCallEvent,
} from './types.js';

/**
* Options for constructing a TelemetryClient.
Expand Down Expand Up @@ -124,7 +128,7 @@ export class TelemetryClient {
params: {
clientCapabilities: string[];
serverCapabilities: string[];
}
},
): void {
const event: SessionInitEvent = {
...this.buildBaseEvent(session, 'session_init'),
Expand Down Expand Up @@ -209,7 +213,7 @@ export class TelemetryClient {
private buildBaseEvent<T extends 'session_init' | 'tool_call'>(
session: TelemetrySession,
eventType: T,
conversationId?: string
conversationId?: string,
): {
event_type: T;
timestamp: string;
Expand Down Expand Up @@ -263,7 +267,10 @@ export class TelemetryClient {
this.resetIdleTimer();

// Auto-flush if thresholds exceeded
if (this.buffer.length >= MAX_BUFFER_COUNT || this.bufferSizeBytes >= MAX_BUFFER_BYTES) {
if (
this.buffer.length >= MAX_BUFFER_COUNT ||
this.bufferSizeBytes >= MAX_BUFFER_BYTES
) {
// Fire-and-forget flush
void this.flush();
}
Expand All @@ -277,9 +284,13 @@ export class TelemetryClient {
* a single flush batch are assumed to share a region (true in practice —
* a single pod targets a single region).
*/
private async send(events: TelemetryEvent[], attempt: number): Promise<void> {
private async send(
events: TelemetryEvent[],
attempt: number,
): Promise<void> {
const region = events[0]?.region ?? 'us';
const baseUrl = process.env[GATEWAY_BASE_URL_OVERRIDE_ENV] ?? GATEWAY_BASE_URL[region];
const baseUrl =
process.env[GATEWAY_BASE_URL_OVERRIDE_ENV] ?? GATEWAY_BASE_URL[region];
const ingestUrl = `${baseUrl}${TELEMETRY_INGEST_PATH}`;

try {
Expand Down
2 changes: 1 addition & 1 deletion src/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type { ToolCallParams } from './client.js';
export interface ITelemetryClient {
sessionInit(
session: TelemetrySession,
params: { clientCapabilities: string[]; serverCapabilities: string[] }
params: { clientCapabilities: string[]; serverCapabilities: string[] },
): void;
toolCall(session: TelemetrySession, params: ToolCallParams): void;
flush(): Promise<void>;
Expand Down
2 changes: 1 addition & 1 deletion src/telemetry/noop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { TelemetrySession } from './session.js';
export class NoopTelemetryClient {
sessionInit(
_session: TelemetrySession,
_params: { clientCapabilities: string[]; serverCapabilities: string[] }
_params: { clientCapabilities: string[]; serverCapabilities: string[] },
): void {}
toolCall(_session: TelemetrySession, _params: ToolCallParams): void {}
async flush(): Promise<void> {}
Expand Down
9 changes: 7 additions & 2 deletions src/telemetry/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ export class TelemetrySession {
}

/** Stores client metadata received during the MCP initialize handshake. */
setClientInfo(clientInfo: { name: string; version: string }, protocolVersion: string): void {
setClientInfo(
clientInfo: { name: string; version: string },
protocolVersion: string,
): void {
this.clientName = clientInfo.name;
this.clientVersion = clientInfo.version;
this.protocolVersion = protocolVersion;
Expand All @@ -155,7 +158,9 @@ export class TelemetrySession {
setToolNames(toolNames: string[]): void {
this.toolListCount = toolNames.length;
const sorted = [...toolNames].sort();
const hash = createHash('sha256').update(sorted.join(',')).digest('hex');
const hash = createHash('sha256')
.update(sorted.join(','))
.digest('hex');
this.toolsetSnapshotId = hash.substring(0, 12);
}

Expand Down
Loading