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
9 changes: 5 additions & 4 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"dependencies": {
"@apify/datastructures": "^2.0.3",
"@apify/log": "^2.5.16",
"@modelcontextprotocol/sdk": "^1.13.2",
"@modelcontextprotocol/sdk": "^1.17.4",
"@types/turndown": "^5.0.5",
"ajv": "^8.17.1",
"algoliasearch": "^5.31.0",
Expand Down
11 changes: 11 additions & 0 deletions src/mcp/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
export const MAX_TOOL_NAME_LENGTH = 64;
export const SERVER_ID_LENGTH = 8;
export const EXTERNAL_TOOL_CALL_TIMEOUT_MSEC = 120_000; // 2 minutes

export const LOG_LEVEL_MAP: Record<string, number> = {
debug: 0,
info: 1,
notice: 2,
warning: 3,
error: 4,
critical: 5,
alert: 6,
emergency: 7,
};
31 changes: 30 additions & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ListToolsRequestSchema,
McpError,
ServerNotificationSchema,
SetLevelRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import type { ValidateFunction } from 'ajv';
import { type ActorCallOptions, ApifyApiError } from 'apify-client';
Expand All @@ -31,7 +32,7 @@ import type { ActorMcpTool, ActorTool, HelperTool, ToolEntry } from '../types.js
import { createProgressTracker } from '../utils/progress.js';
import { getToolPublicFieldOnly } from '../utils/tools.js';
import { connectMCPClient } from './client.js';
import { EXTERNAL_TOOL_CALL_TIMEOUT_MSEC } from './const.js';
import { EXTERNAL_TOOL_CALL_TIMEOUT_MSEC, LOG_LEVEL_MAP } from './const.js';
import { processParamsGetTools } from './utils.js';

type ToolsChangedHandler = (toolNames: string[]) => void;
Expand All @@ -44,6 +45,7 @@ export class ActorsMcpServer {
public readonly tools: Map<string, ToolEntry>;
private toolsChangedHandler: ToolsChangedHandler | undefined;
private sigintHandler: (() => Promise<void>) | undefined;
private currentLogLevel = 'info';

constructor(setupSigintHandler = true) {
this.server = new Server(
Expand All @@ -59,8 +61,10 @@ export class ActorsMcpServer {
},
},
);
this.setupLoggingProxy();
this.tools = new Map();
this.setupErrorHandling(setupSigintHandler);
this.setupLoggingHandlers();
this.setupToolHandlers();
this.setupPromptHandlers();
}
Expand Down Expand Up @@ -264,6 +268,31 @@ export class ActorsMcpServer {
}
}

private setupLoggingProxy(): void {
// Store original sendLoggingMessage
const originalSendLoggingMessage = this.server.sendLoggingMessage.bind(this.server);

// Proxy sendLoggingMessage to filter logs
this.server.sendLoggingMessage = async (params: { level: string; data?: unknown; [key: string]: unknown }) => {
const messageLevelValue = LOG_LEVEL_MAP[params.level] ?? -1; // Unknown levels get -1, discard
const currentLevelValue = LOG_LEVEL_MAP[this.currentLogLevel] ?? LOG_LEVEL_MAP.info; // Default to info if invalid
if (messageLevelValue >= currentLevelValue) {
await originalSendLoggingMessage(params as Parameters<typeof originalSendLoggingMessage>[0]);
}
};
}

private setupLoggingHandlers(): void {
this.server.setRequestHandler(SetLevelRequestSchema, (request) => {
const { level } = request.params;
if (LOG_LEVEL_MAP[level] !== undefined) {
this.currentLogLevel = level;
}
// Sending empty result based on MCP spec
return {};
});
}

/**
* Sets up MCP request handlers for prompts.
*/
Expand Down