Skip to content

Commit b25d639

Browse files
authored
fix: Refactor usage metadata and expose run costs in MCP response (#456)
fix: Refactor MCP response code to centralize usage metadata logic with `buildUsageMeta` function. Fix naming.
1 parent 192f043 commit b25d639

5 files changed

Lines changed: 33 additions & 22 deletions

File tree

src/mcp/server.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import type {
7070
} from '../types.js';
7171
import { buildActorResponseContent } from '../utils/actor-response.js';
7272
import { logHttpError, redactSkyfirePayId } from '../utils/logging.js';
73-
import { buildMCPResponse } from '../utils/mcp.js';
73+
import { buildMCPResponse, buildUsageMeta } from '../utils/mcp.js';
7474
import { createProgressTracker } from '../utils/progress.js';
7575
import { getServerInstructions } from '../utils/server-instructions.js';
7676
import { validateSkyfirePayId } from '../utils/skyfire.js';
@@ -878,12 +878,11 @@ Please verify the server URL is correct and accessible, and ensure you have a va
878878
}
879879

880880
const { content, structuredContent } = buildActorResponseContent(tool.actorFullName, callResult);
881+
const _meta = buildUsageMeta(callResult);
881882
return {
882883
content,
883884
structuredContent,
884-
...(callResult.usageTotalUsd !== undefined && {
885-
_meta: { apifyUsageTotalUsd: callResult.usageTotalUsd, apifyUsageUsd: callResult.usageUsd },
886-
}),
885+
...(_meta && { _meta }),
887886
};
888887
} finally {
889888
if (progressTracker) {
@@ -1076,12 +1075,11 @@ Please verify the tool name and ensure the tool is properly registered.`;
10761075
result = {};
10771076
} else {
10781077
const { content, structuredContent } = buildActorResponseContent(tool.actorFullName, callResult);
1078+
const _meta = buildUsageMeta(callResult);
10791079
result = {
10801080
content,
10811081
structuredContent,
1082-
...(callResult.usageTotalUsd !== undefined && {
1083-
_meta: { apifyUsageTotalUsd: callResult.usageTotalUsd, apifyUsageUsd: callResult.usageUsd },
1084-
}),
1082+
...(_meta && { _meta }),
10851083
};
10861084
}
10871085

src/tools/actor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { ensureOutputWithinCharLimit, getActorDefinitionStorageFieldNames, getAc
3535
import { buildActorResponseContent } from '../utils/actor-response.js';
3636
import { ajv, compileSchema } from '../utils/ajv.js';
3737
import { logHttpError, redactSkyfirePayId } from '../utils/logging.js';
38-
import { buildMCPResponse } from '../utils/mcp.js';
38+
import { buildMCPResponse, buildUsageMeta } from '../utils/mcp.js';
3939
import type { ProgressTracker } from '../utils/progress.js';
4040
import type { JsonSchemaProperty } from '../utils/schema-generation.js';
4141
import { generateSchemaFromItems } from '../utils/schema-generation.js';
@@ -697,12 +697,11 @@ Do NOT proactively poll using ${HelperTools.ACTOR_RUNS_GET}. Wait for the widget
697697

698698
const { content, structuredContent } = buildActorResponseContent(actorName, callResult, previewOutput);
699699

700+
const _meta = buildUsageMeta(callResult);
700701
return {
701702
content,
702703
structuredContent,
703-
...(callResult.usageTotalUsd !== undefined && {
704-
_meta: { apifyUsageTotalUsd: callResult.usageTotalUsd, apifyUsageUsd: callResult.usageUsd },
705-
}),
704+
...(_meta && { _meta }),
706705
};
707706
} catch (error) {
708707
logHttpError(error, 'Failed to call Actor', { actorName, async: async ?? (apifyMcpServer.options.uiMode === 'openai') });

src/tools/run.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getWidgetConfig, WIDGET_URIS } from '../resources/widgets.js';
88
import type { InternalToolArgs, ToolEntry, ToolInputSchema } from '../types.js';
99
import { compileSchema } from '../utils/ajv.js';
1010
import { logHttpError } from '../utils/logging.js';
11-
import { buildMCPResponse } from '../utils/mcp.js';
11+
import { buildMCPResponse, buildUsageMeta } from '../utils/mcp.js';
1212
import { generateSchemaFromItems } from '../utils/schema-generation.js';
1313
import { getActorRunOutputSchema } from './structured-output-schemas.js';
1414

@@ -138,15 +138,13 @@ USAGE EXAMPLES:
138138
: `Actor run ${parsed.runId} status: ${run.status}. A progress widget has been rendered.`;
139139

140140
const widgetConfig = getWidgetConfig(WIDGET_URIS.ACTOR_RUN);
141+
const usageMeta = buildUsageMeta(run);
141142
return buildMCPResponse({
142143
texts: [statusText],
143144
structuredContent,
144145
_meta: {
145146
...widgetConfig?.meta,
146-
...(run.usageTotalUsd !== undefined && {
147-
apifyUsageTotalUsd: run.usageTotalUsd,
148-
apifyUsageUsd: run.usageUsd,
149-
}),
147+
...usageMeta,
150148
},
151149
});
152150
}
@@ -158,10 +156,7 @@ USAGE EXAMPLES:
158156
return buildMCPResponse({
159157
texts,
160158
structuredContent,
161-
_meta: run.usageTotalUsd !== undefined ? {
162-
apifyUsageTotalUsd: run.usageTotalUsd,
163-
apifyUsageUsd: run.usageUsd as Record<string, number> | undefined,
164-
} : undefined,
159+
_meta: buildUsageMeta(run),
165160
});
166161
} catch (error) {
167162
logHttpError(error, 'Failed to get Actor run', { runId: parsed.runId });

src/tools/structured-output-schemas.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ export const callActorOutputSchema = {
223223
description: 'Dataset items from the Actor run (sync mode only, may be truncated due to size limits)',
224224
},
225225
instructions: { type: 'string', description: 'Instructions for the LLM on how to process or retrieve additional data' },
226-
usageTotalUsd: { type: 'number', description: 'Total cost of the Actor run in USD (sync mode only)' },
227226
},
228227
required: ['runId'],
229228
};
@@ -244,7 +243,6 @@ export const getActorRunOutputSchema = {
244243
type: 'object' as const,
245244
description: 'Run statistics (compute units, memory, duration, etc.)',
246245
},
247-
usageTotalUsd: { type: 'number', description: 'Total cost of the Actor run in USD' },
248246
dataset: {
249247
type: 'object' as const,
250248
description: 'Dataset information (only for completed runs with results)',

src/utils/mcp.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
import type { ToolStatus } from '../types.js';
22

3+
/**
4+
* Builds usage metadata for MCP response from a source object containing Apify run costs.
5+
* Uses MCP-compliant key names with com.apify/ prefix as per MCP specification.
6+
* @param source - Object containing usage cost information
7+
* @param source.usageTotalUsd - Total cost in USD (optional)
8+
* @param source.usageUsd - Breakdown of costs by resource type (optional)
9+
* @returns Usage metadata object or undefined if no usage data is available
10+
*/
11+
export function buildUsageMeta(source: {
12+
usageTotalUsd?: number;
13+
usageUsd?: unknown;
14+
}): Record<string, unknown> | undefined {
15+
const { usageTotalUsd, usageUsd } = source;
16+
return usageTotalUsd !== undefined
17+
? {
18+
'com.apify/usageTotalUsd': usageTotalUsd,
19+
'com.apify/usageUsd': usageUsd,
20+
}
21+
: undefined;
22+
}
23+
324
/**
425
* Helper to build a response for MCP from an array of text strings.
526
* @param options - Object containing response configuration

0 commit comments

Comments
 (0)