Skip to content

Commit 6329c4c

Browse files
authored
fix: migrate custom request parameters to _meta structure for SDK compatibility (#379)
- Move mcpSessionId, apifyToken, and userRentedActorIds into params._meta object - Newer MCP SDK versions filter out unknown top-level parameters, so _meta is used as standard container - Add ApifyRequestParams type definition for proper typing of _meta structure - Update parameter extraction in tool handlers and task handlers to read from _meta - Refactor parameter injection to use nullish coalescing assignment (??=) for clarity - Allow _meta underscore in ESLint config as standard MCP protocol field - Remove unnecessary parameter deletion after extraction since they're now in _meta - Enable MCP Session ID error throwing since injection is now guaranteed FIXES: Parameters being stripped by newer MCP SDK versions causing task store errors
1 parent fe796e1 commit 6329c4c

File tree

6 files changed

+70
-33
lines changed

6 files changed

+70
-33
lines changed

eslint.config.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ export default [
4747
],
4848
},
4949
],
50+
// Allow _meta as it's a standard MCP protocol field for metadata
51+
'no-underscore-dangle': [
52+
'error',
53+
{
54+
allow: ['_meta'],
55+
},
56+
],
5057
},
5158
languageOptions: {
5259
// Use ES modules (import/export syntax)

src/actor/server.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import log from '@apify/log';
1515

1616
import { ApifyClient } from '../apify-client.js';
1717
import { ActorsMcpServer } from '../mcp/server.js';
18+
import type { ApifyRequestParams } from '../types.js';
1819
import { parseBooleanFromString } from '../utils/generic.js';
1920
import { getHelpMessage, HEADER_READINESS_PROBE, Routes, TransportType } from './const.js';
2021
import { getActorRunData } from './utils.js';
@@ -117,8 +118,9 @@ export function createExpressApp(
117118
const msgRecord = message as Record<string, unknown>;
118119
// Inject session ID into all requests with params
119120
if (msgRecord.params) {
120-
const params = msgRecord.params as Record<string, unknown>;
121-
params.mcpSessionId = mcpSessionId;
121+
const params = msgRecord.params as ApifyRequestParams;
122+
params._meta ??= {};
123+
params._meta.mcpSessionId = mcpSessionId;
122124
}
123125
// Call the original onmessage handler
124126
if (originalOnMessage) {
@@ -194,8 +196,9 @@ export function createExpressApp(
194196
// Reuse existing transport
195197
transport = transports[sessionId];
196198
// Inject session ID into request params for existing sessions
197-
if (req.body && req.body.params) {
198-
req.body.params.mcpSessionId = sessionId;
199+
if (req.body?.params) {
200+
req.body.params._meta ??= {};
201+
req.body.params._meta.mcpSessionId = sessionId;
199202
}
200203
} else if (!sessionId && isInitializeRequest(req.body)) {
201204
// New initialization request
@@ -253,8 +256,9 @@ export function createExpressApp(
253256
}
254257

255258
// Inject session ID into request params for all requests
256-
if (req.body && req.body.params && sessionId) {
257-
req.body.params.mcpSessionId = sessionId;
259+
if (req.body?.params && sessionId) {
260+
req.body.params._meta ??= {};
261+
req.body.params._meta.mcpSessionId = sessionId;
258262
}
259263

260264
// Handle the request with existing transport - no need to reconnect

src/mcp/server.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import type {
5757
ActorMcpTool,
5858
ActorsMcpServerOptions,
5959
ActorTool,
60+
ApifyRequestParams,
6061
HelperTool,
6162
TelemetryEnv,
6263
ToolCallTelemetryProperties,
@@ -525,8 +526,9 @@ export class ActorsMcpServer {
525526
// List tasks
526527
this.server.setRequestHandler(ListTasksRequestSchema, async (request) => {
527528
// mcpSessionId is injected at transport layer for session isolation in task stores
528-
const params = (request.params || {}) as { cursor?: string; mcpSessionId?: string };
529-
const { cursor, mcpSessionId } = params;
529+
const params = (request.params || {}) as ApifyRequestParams & { cursor?: string };
530+
const { cursor } = params;
531+
const mcpSessionId = params._meta?.mcpSessionId;
530532
log.debug('[ListTasksRequestSchema] Listing tasks', { mcpSessionId });
531533
const result = await this.taskStore.listTasks(cursor, mcpSessionId);
532534
return { tasks: result.tasks, nextCursor: result.nextCursor };
@@ -535,8 +537,9 @@ export class ActorsMcpServer {
535537
// Get task status
536538
this.server.setRequestHandler(GetTaskRequestSchema, async (request) => {
537539
// mcpSessionId is injected at transport layer for session isolation in task stores
538-
const params = (request.params || {}) as { taskId: string; mcpSessionId?: string };
539-
const { taskId, mcpSessionId } = params;
540+
const params = (request.params || {}) as ApifyRequestParams & { taskId: string };
541+
const { taskId } = params;
542+
const mcpSessionId = params._meta?.mcpSessionId;
540543
log.debug('[GetTaskRequestSchema] Getting task status', { taskId, mcpSessionId });
541544
const task = await this.taskStore.getTask(taskId, mcpSessionId);
542545
if (task) return task;
@@ -549,8 +552,9 @@ export class ActorsMcpServer {
549552
// Get task result payload
550553
this.server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
551554
// mcpSessionId is injected at transport layer for session isolation in task stores
552-
const params = (request.params || {}) as { taskId: string; mcpSessionId?: string };
553-
const { taskId, mcpSessionId } = params;
555+
const params = (request.params || {}) as ApifyRequestParams & { taskId: string };
556+
const { taskId } = params;
557+
const mcpSessionId = params._meta?.mcpSessionId;
554558
log.debug('[GetTaskPayloadRequestSchema] Getting task result', { taskId, mcpSessionId });
555559
const task = await this.taskStore.getTask(taskId, mcpSessionId);
556560
if (!task) {
@@ -573,8 +577,9 @@ export class ActorsMcpServer {
573577
// Cancel task
574578
this.server.setRequestHandler(CancelTaskRequestSchema, async (request) => {
575579
// mcpSessionId is injected at transport layer for session isolation in task stores
576-
const params = (request.params || {}) as { taskId: string; mcpSessionId?: string };
577-
const { taskId, mcpSessionId } = params;
580+
const params = (request.params || {}) as ApifyRequestParams & { taskId: string };
581+
const { taskId } = params;
582+
const mcpSessionId = params._meta?.mcpSessionId;
578583
log.debug('[CancelTaskRequestSchema] Cancelling task', { taskId, mcpSessionId });
579584

580585
const task = await this.taskStore.getTask(taskId, mcpSessionId);
@@ -622,22 +627,19 @@ export class ActorsMcpServer {
622627
* @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
623628
*/
624629
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
630+
const params = request.params as ApifyRequestParams & { name: string; arguments?: Record<string, unknown> };
625631
// eslint-disable-next-line prefer-const
626-
let { name, arguments: args, _meta: meta } = request.params;
627-
const { progressToken } = meta || {};
628-
const apifyToken = (request.params.apifyToken || this.options.token || process.env.APIFY_TOKEN) as string;
629-
const userRentedActorIds = request.params.userRentedActorIds as string[] | undefined;
632+
let { name, arguments: args, _meta: meta } = params;
633+
const progressToken = meta?.progressToken;
634+
const metaApifyToken = meta?.apifyToken;
635+
const apifyToken = (metaApifyToken || this.options.token || process.env.APIFY_TOKEN) as string;
636+
const userRentedActorIds = meta?.userRentedActorIds;
630637
// mcpSessionId was injected upstream it is important and required for long running tasks as the store uses it and there is not other way to pass it
631-
const mcpSessionId = typeof request.params.mcpSessionId === 'string' ? request.params.mcpSessionId : undefined;
638+
const mcpSessionId = meta?.mcpSessionId;
632639
if (!mcpSessionId) {
633640
log.error('MCP Session ID is missing in tool call request. This should never happen.');
634-
// TEMP: do not throw for now as it causes issues with stdio transport
635-
// throw new Error('MCP Session ID is required for tool calls');
641+
throw new Error('MCP Session ID is required for tool calls');
636642
}
637-
// Remove apifyToken from request.params just in case
638-
delete request.params.apifyToken;
639-
// Remove other custom params passed from apify-mcp-server
640-
delete request.params.userRentedActorIds;
641643

642644
// Validate token
643645
if (!apifyToken && !this.options.skyfireMode && !this.options.allowUnauthMode) {
@@ -690,7 +692,7 @@ Please provide the required arguments for this tool. Check the tool's input sche
690692
}
691693
// Decode dot property names in arguments before validation,
692694
// since validation expects the original, non-encoded property names.
693-
args = decodeDotPropertyNames(args);
695+
args = decodeDotPropertyNames(args as Record<string, unknown>) as Record<string, unknown>;
694696
log.debug('Validate arguments for tool', { toolName: tool.name, input: args });
695697
if (!tool.ajvValidate(args)) {
696698
const errors = tool?.ajvValidate.errors || [];

src/stdio.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { DEFAULT_TELEMETRY_ENV, TELEMETRY_ENV } from './const.js';
3333
import { processInput } from './input.js';
3434
import { ActorsMcpServer } from './mcp/server.js';
3535
import { getTelemetryEnv } from './telemetry.js';
36-
import type { Input, TelemetryEnv, ToolSelector } from './types.js';
36+
import type { ApifyRequestParams, Input, TelemetryEnv, ToolSelector } from './types.js';
3737
import { parseCommaSeparatedList } from './utils/generic.js';
3838
import { loadToolsFromInput } from './utils/tools-loader.js';
3939

@@ -201,8 +201,9 @@ async function main() {
201201
// Inject session ID into all requests for task isolation and session tracking.
202202
// CRITICAL: Always create params object if missing (some requests like listTasks/getTasks don't have params),
203203
// otherwise mcpSessionId injection fails, breaking session isolation in multi-node setups.
204-
const params = (msgRecord.params || {}) as Record<string, unknown>;
205-
params.mcpSessionId = mcpSessionId;
204+
const params = (msgRecord.params || {}) as ApifyRequestParams;
205+
params._meta ??= {};
206+
params._meta.mcpSessionId = mcpSessionId;
206207
msgRecord.params = params;
207208

208209
// Call the original onmessage handler

src/tools/actor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,9 @@ Actor description: ${definition.description}`;
206206
openWorldHint: true,
207207
},
208208
// Allow long running tasks for Actor tools, make it optional for now
209-
// TEMP: disable for now as it causes issues with session id error for stdio transport
210-
// execution: {
211-
// taskSupport: 'optional',
212-
// },
209+
execution: {
210+
taskSupport: 'optional',
211+
},
213212
});
214213
}
215214
return tools;

src/types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,27 @@ export type StructuredActorCard = {
411411
modifiedAt?: string;
412412
isDeprecated: boolean;
413413
}
414+
415+
/**
416+
* MCP request parameters with Apify-specific extensions.
417+
* Extends the standard MCP params object with Apify custom fields in the _meta object.
418+
*/
419+
export type ApifyRequestParams = {
420+
/**
421+
* Metadata object for MCP and Apify-specific fields.
422+
*/
423+
_meta?: {
424+
/** Session ID for tracking MCP requests across the Apify server */
425+
mcpSessionId?: string;
426+
/** Apify API token for authentication */
427+
apifyToken?: string;
428+
/** List of Actor IDs that the user has rented */
429+
userRentedActorIds?: string[];
430+
/** Progress token for out-of-band progress notifications (standard MCP) */
431+
progressToken?: string | number;
432+
/** Allow other metadata fields */
433+
[key: string]: unknown;
434+
};
435+
/** Allow any other request parameters */
436+
[key: string]: unknown;
437+
};

0 commit comments

Comments
 (0)