Skip to content
Merged
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
6 changes: 5 additions & 1 deletion dist/src/clients/postman.js

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

43 changes: 22 additions & 21 deletions dist/src/index.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/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.

6 changes: 6 additions & 0 deletions dist/src/telemetry/srvTrace.js

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

1 change: 1 addition & 0 deletions dist/src/telemetry/types.js

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

7 changes: 6 additions & 1 deletion src/clients/postman.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ export class PostmanAPIClient implements IPostmanAPIClient {
private readonly baseUrl: string;
private readonly apiKey?: string;
private readonly serverContext?: ServerContext;
private readonly srvTraceId?: string;
private static instance: PostmanAPIClient | null = null;

constructor(
apiKey?: string,
baseUrl: string = env.POSTMAN_API_BASE_URL,
serverContext?: ServerContext
serverContext?: ServerContext,
srvTraceId?: string
) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.serverContext = serverContext;
this.srvTraceId = srvTraceId;
}

/**
Expand Down Expand Up @@ -127,6 +130,7 @@ export class PostmanAPIClient implements IPostmanAPIClient {
'host',
'accept-encoding',
'keep-alive',
'x-srv-trace',
]);
const extra = Object.fromEntries(
Object.entries(options.headers ?? {}).filter(([k]) => !disallowed.has(k.toLowerCase()))
Expand All @@ -139,6 +143,7 @@ export class PostmanAPIClient implements IPostmanAPIClient {
...extra,
'x-api-key': currentApiKey,
'user-agent': userAgentHeader,
...(this.srvTraceId ? { 'x-srv-trace': this.srvTraceId } : {}),
};

const { headers: _ignored, ...optionsWithoutHeaders } = options;
Expand Down
80 changes: 36 additions & 44 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,8 @@ 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';
import { generateSrvTrace } from './telemetry/srvTrace.js';

const SUPPORTED_REGIONS = {
us: 'https://api.postman.com',
Expand Down Expand Up @@ -206,20 +201,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 @@ -286,9 +277,6 @@ async function run() {
const errorsDir = join(__dirname, './views/errors');
const renderErrorTemplate = createErrorTemplateRenderer(errorsDir);

// Create a client instance with the API key and server context for STDIO mode
const client = new PostmanAPIClient(apiKey, undefined, serverContext);

log('info', 'Registering tools with McpServer');

// Register all tools using the McpServer registerTool method
Expand All @@ -308,6 +296,13 @@ async function run() {
const start = Date.now();
const paginationUsed = detectPaginationUsed(args as Record<string, unknown>);
const metaRaw = extra?._meta ? JSON.stringify(extra._meta) : '';

// One correlation id per tool call: sent as the x-srv-trace header on
// every outbound API request and recorded on the telemetry event.
const srvTraceId = generateSrvTrace();
// Create a client instance with the API key, server context, and
// this call's trace id for STDIO mode.
const client = new PostmanAPIClient(apiKey, undefined, serverContext, srvTraceId);
try {
const result = await tool.handler(args, {
client,
Expand All @@ -334,14 +329,13 @@ 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,
srvTraceId,
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 +354,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 @@ -381,17 +373,15 @@ async function run() {
isError: true,
authMethod: 'api_key',
paginationUsed,
srvTraceId,
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 Expand Up @@ -432,6 +422,7 @@ async function run() {
isError: true,
authMethod: 'api_key',
paginationUsed,
srvTraceId,
errorType: 'tool',
errorCode: 'UPSTREAM_ERROR',
errorStage: 'upstream',
Expand All @@ -452,6 +443,7 @@ async function run() {
isError: true,
authMethod: 'api_key',
paginationUsed,
srvTraceId,
errorType: 'protocol',
errorCode: 'INTERNAL_ERROR',
errorStage: '',
Expand Down
Loading
Loading