Skip to content

Commit 0b32b43

Browse files
committed
Merge remote-tracking branch 'origin/master' into feature/default-enable-dynamic-actor-loading
2 parents d8bf3f4 + 8733c0d commit 0b32b43

File tree

6 files changed

+39
-7
lines changed

6 files changed

+39
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
99

1010
- Explicitly clear resources ([#136](https://github.com/apify/actors-mcp-server/pull/136)) ([779d2ba](https://github.com/apify/actors-mcp-server/commit/779d2ba2407bcd5fbdd89d3201463a784e67c931)) by [@jirispilka](https://github.com/jirispilka)
1111
- Readme Actors list ([#141](https://github.com/apify/actors-mcp-server/pull/141)) ([dc0a332](https://github.com/apify/actors-mcp-server/commit/dc0a332c8dbe450290d4acb5a19759545edf3c32)) by [@MQ37](https://github.com/MQ37)
12+
- Notifications ([#145](https://github.com/apify/actors-mcp-server/pull/145)) ([d96c427](https://github.com/apify/actors-mcp-server/commit/d96c42775db86f563c1012285c4a42f12fc23a19)) by [@MQ37](https://github.com/MQ37)
1213

1314

1415
<!-- git-cliff-unreleased-end -->

src/actor/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export function createExpressApp(
130130
// New initialization request - use JSON response mode
131131
transport = new StreamableHTTPServerTransport({
132132
sessionIdGenerator: () => randomUUID(),
133-
enableJsonResponse: true, // Enable JSON response mode
133+
enableJsonResponse: false, // Use SSE response mode
134134
});
135135
// Load MCP server tools
136136
await loadToolsAndActors(mcpServer, req.url, process.env.APIFY_TOKEN as string);

src/mcp/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,10 @@ export class ActorsMcpServer {
344344
/**
345345
* Handles the request to call a tool.
346346
* @param {object} request - The request object containing tool name and arguments.
347+
* @param {object} extra - Extra data given to the request handler, such as sendNotification function.
347348
* @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
348349
*/
349-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
350+
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
350351
// eslint-disable-next-line prefer-const
351352
let { name, arguments: args } = request.params;
352353
const apifyToken = (request.params.apifyToken || process.env.APIFY_TOKEN) as string;
@@ -414,6 +415,7 @@ export class ActorsMcpServer {
414415
const internalTool = tool.tool as HelperTool;
415416
const res = await internalTool.call({
416417
args,
418+
extra,
417419
apifyMcpServer: this,
418420
mcpServer: this.server,
419421
apifyToken,

src/tools/helpers.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const addTool: ToolEntry = {
7878
ajvValidate: ajv.compile(zodToJsonSchema(addToolArgsSchema)),
7979
// TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool
8080
call: async (toolArgs) => {
81-
const { apifyMcpServer, mcpServer, apifyToken, args } = toolArgs;
81+
const { apifyMcpServer, apifyToken, args, extra: { sendNotification } } = toolArgs;
8282
const parsed = addToolArgsSchema.parse(args);
8383
if (apifyMcpServer.listAllToolNames().includes(parsed.actorName)) {
8484
return {
@@ -90,7 +90,7 @@ export const addTool: ToolEntry = {
9090
}
9191
const tools = await getActorsAsTools([parsed.actorName], apifyToken);
9292
const toolsAdded = apifyMcpServer.upsertTools(tools, true);
93-
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
93+
await sendNotification({ method: 'notifications/tools/list_changed' });
9494

9595
return {
9696
content: [{
@@ -121,13 +121,13 @@ export const removeTool: ToolEntry = {
121121
ajvValidate: ajv.compile(zodToJsonSchema(removeToolArgsSchema)),
122122
// TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool
123123
call: async (toolArgs) => {
124-
const { apifyMcpServer, mcpServer, args } = toolArgs;
124+
const { apifyMcpServer, args, extra: { sendNotification } } = toolArgs;
125125
const parsed = removeToolArgsSchema.parse(args);
126126
// Check if tool exists before attempting removal
127127
if (!apifyMcpServer.tools.has(parsed.toolName)) {
128128
// Send notification so client can update its tool list
129129
// just in case the client tool list is out of sync
130-
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
130+
await sendNotification({ method: 'notifications/tools/list_changed' });
131131
return {
132132
content: [{
133133
type: 'text',
@@ -136,7 +136,7 @@ export const removeTool: ToolEntry = {
136136
};
137137
}
138138
const removedTools = apifyMcpServer.removeToolsByName([parsed.toolName], true);
139-
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
139+
await sendNotification({ method: 'notifications/tools/list_changed' });
140140
return { content: [{ type: 'text', text: `Tools removed: ${removedTools.join(', ')}` }] };
141141
},
142142
} as InternalTool,

src/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
3+
import type { Notification, Request } from '@modelcontextprotocol/sdk/types.js';
24
import type { ValidateFunction } from 'ajv';
35
import type { ActorDefaultRunOptions, ActorDefinition } from 'apify-client';
46

@@ -80,6 +82,13 @@ export interface ActorTool extends ToolBase {
8082
export type InternalToolArgs = {
8183
/** Arguments passed to the tool */
8284
args: Record<string, unknown>;
85+
/** Extra data given to request handlers.
86+
*
87+
* Can be used to send notifications from the server to the client.
88+
*
89+
* For more details see: https://github.com/modelcontextprotocol/typescript-sdk/blob/f822c1255edcf98c4e73b9bf17a9dd1b03f86716/src/shared/protocol.ts#L102
90+
*/
91+
extra: RequestHandlerExtra<Request, Notification>;
8392
/** Reference to the Apify MCP server instance */
8493
apifyMcpServer: ActorsMcpServer;
8594
/** Reference to the MCP server instance */

tests/integration/suite.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
2+
import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
23
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
34

45
import { defaults, HelperTools } from '../../src/const.js';
@@ -330,5 +331,24 @@ export function createIntegrationTestsSuite(
330331
expect(notificationCount).toBe(1);
331332
await client.close();
332333
});
334+
335+
it('should notify client about tool list changed', async () => {
336+
const client = await createClientFn({ enableAddingActors: true });
337+
338+
// This flag is set to true when a 'notifications/tools/list_changed' notification is received,
339+
// indicating that the tool list has been updated dynamically.
340+
let hasReceivedNotification = false;
341+
client.setNotificationHandler(ToolListChangedNotificationSchema, async (notification) => {
342+
if (notification.method === 'notifications/tools/list_changed') {
343+
hasReceivedNotification = true;
344+
}
345+
});
346+
// Add Actor dynamically
347+
await client.callTool({ name: HelperTools.ACTOR_ADD, arguments: { actorName: ACTOR_PYTHON_EXAMPLE } });
348+
349+
expect(hasReceivedNotification).toBe(true);
350+
351+
await client.close();
352+
});
333353
});
334354
}

0 commit comments

Comments
 (0)