Skip to content

Commit f8c2f2d

Browse files
MQ37jirispilka
andauthored
feat: swap call-actor with add-actor for actors tool category for supported clients (#299)
* feat: swap call-actor with add-actor for actors tool category for supported clients * use const instead of string literal * fix: update README.md * readme swap dxt to mcpb * bump mcp-client-capabilities --------- Co-authored-by: Jiri Spilka <[email protected]>
1 parent bddf067 commit f8c2f2d

File tree

11 files changed

+81
-16
lines changed

11 files changed

+81
-16
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ You can find detailed instructions for setting up the MCP server in the [Apify d
7171
To interact with the Apify MCP server, you can use various MCP clients, such as:
7272
- [Claude Desktop](https://claude.ai/download)
7373
- [Visual Studio Code](https://code.visualstudio.com/)
74-
- [LibreChat](https://www.librechat.ai/)
7574
- [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client)
7675
- Other clients at [https://modelcontextprotocol.io/clients](https://modelcontextprotocol.io/clients)
7776
- More clients at [https://glama.ai/mcp/clients](https://glama.ai/mcp/clients)
@@ -83,7 +82,7 @@ With MCP server integrated, you can ask your AI assistant things like:
8382
- "Provide a step-by-step guide on using the Model Context Protocol, including source URLs."
8483
- "What Apify Actors can I use?"
8584

86-
### Supported Clients Matrix
85+
### Supported clients matrix
8786

8887
The following table outlines the tested MCP clients and their level of support for key features.
8988

@@ -92,12 +91,19 @@ The following table outlines the tested MCP clients and their level of support f
9291
| **Claude.ai (web)** | ✅ Full | |
9392
| **Claude Desktop** | 🟡 Partial | Tools may need to be reloaded manually in the client. |
9493
| **VS Code (Genie)** | ✅ Full | |
95-
| **LibreChat** | ❓ Untested | |
9694
| **Apify Tester MCP Client** | ✅ Full | Designed for testing Apify MCP servers. |
9795

98-
*This matrix is a work in progress. If you have tested other clients, please consider contributing to this documentation.*
96+
Apify MCP Server is compatible with any MCP client that adheres to the [Model Context Protocol](https://modelcontextprotocol.org/), but the level of support for dynamic tool discovery and other features may vary between clients. Therefore, the server uses [mcp-client-capabilities](https://github.com/apify/mcp-client-capabilities) to detect client capabilities and adjust its behavior accordingly.
9997

100-
# 🪄 Try Apify MCP Instantly
98+
**Smart tool selection based on client capabilities:**
99+
100+
When the `actors` tool category is requested, the server intelligently selects the most appropriate Actor-related tools based on the client's capabilities:
101+
102+
- **Clients with dynamic tool support** (e.g., Claude.ai web, VS Code Genie): The server provides the `add-actor` tool instead of `call-actor`. This allows for a better user experience where users can dynamically discover and add new Actors as tools during their conversation.
103+
104+
- **Clients with limited dynamic tool support** (e.g., Claude Desktop): The server provides the standard `call-actor` tool along with other Actor category tools, ensuring compatibility while maintaining functionality.
105+
106+
# 🪄 Try Apify MCP instantly
101107

102108
Want to try Apify MCP without any setup?
103109

@@ -106,7 +112,7 @@ Check out [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-cli
106112
This interactive, chat-like interface provides an easy way to explore the capabilities of Apify MCP without any local setup.
107113
Just sign in with your Apify account and start experimenting with web scraping, data extraction, and automation tools!
108114

109-
Or use the Anthropic Desktop extension file (dxt) for one-click installation: [Apify MCP server dxt file](https://github.com/apify/apify-mcp-server/releases/latest/download/apify-mcp-server.dxt)
115+
Or use the MCP bundle file (formerly known as Anthropic Desktop extension file, or DXT) for one-click installation: [Apify MCP server MCPB file](https://github.com/apify/apify-mcp-server/releases/latest/download/apify-mcp-server.mcpb)
110116

111117
# 🛠️ Tools, resources, and prompts
112118

@@ -172,6 +178,8 @@ Here is an overview list of all the tools provided by the Apify MCP Server.
172178

173179
> **Note:**
174180
>
181+
> When using the `actors` tool category, clients that support dynamic tool discovery (like Claude.ai web and VS Code) automatically receive the `add-actor` tool instead of `call-actor` for enhanced Actor discovery capabilities.
182+
175183
> The `get-actor-output` tool is automatically included with any Actor-related tool, such as `call-actor`, `add-actor`, or any specific Actor tool like `apify-slash-rag-web-browser`. When you call an Actor - either through the `call-actor` tool or directly via an Actor tool (e.g., `apify-slash-rag-web-browser`) - you receive a preview of the output. The preview depends on the Actor's output format and length; for some Actors and runs, it may include the entire output, while for others, only a limited version is returned to avoid overwhelming the LLM. To retrieve the full output of an Actor run, use the `get-actor-output` tool (supports limit, offset, and field filtering) with the `datasetId` provided by the Actor call.
176184
177185
### Tools configuration

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"apify-client": "^2.12.6",
4848
"cheerio": "^1.1.2",
4949
"express": "^4.21.2",
50+
"mcp-client-capabilities": "^0.0.5",
5051
"to-json-schema": "^0.2.5",
5152
"turndown": "^7.2.0",
5253
"yargs": "^17.7.2",

src/actor/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { randomUUID } from 'node:crypto';
66

77
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
88
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
9+
import type { InitializeRequest } from '@modelcontextprotocol/sdk/types.js';
910
import type { Request, Response } from 'express';
1011
import express from 'express';
1112

@@ -154,7 +155,7 @@ export function createExpressApp(
154155
sessionIdGenerator: () => randomUUID(),
155156
enableJsonResponse: false, // Use SSE response mode
156157
});
157-
const mcpServer = new ActorsMcpServer({ setupSigintHandler: false });
158+
const mcpServer = new ActorsMcpServer({ setupSigintHandler: false, initializeRequestData: req.body as InitializeRequest });
158159

159160
// Load MCP server tools
160161
const apifyToken = process.env.APIFY_TOKEN as string;

src/mcp/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
66
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
77
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
8+
import type { InitializeRequest } from '@modelcontextprotocol/sdk/types.js';
89
import {
910
CallToolRequestSchema,
1011
CallToolResultSchema,
@@ -52,6 +53,7 @@ interface ActorsMcpServerOptions {
5253
* Switch to enable Skyfire agentic payment mode.
5354
*/
5455
skyfireMode?: boolean;
56+
initializeRequestData?: InitializeRequest;
5557
}
5658

5759
/**
@@ -230,7 +232,7 @@ export class ActorsMcpServer {
230232
* Used primarily for SSE.
231233
*/
232234
public async loadToolsFromUrl(url: string, apifyClient: ApifyClient) {
233-
const tools = await processParamsGetTools(url, apifyClient);
235+
const tools = await processParamsGetTools(url, apifyClient, this.options.initializeRequestData);
234236
if (tools.length > 0) {
235237
log.debug('Loading tools from query parameters');
236238
this.upsertTools(tools, false);

src/mcp/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createHash } from 'node:crypto';
22
import { parse } from 'node:querystring';
33

4+
import type { InitializeRequest } from '@modelcontextprotocol/sdk/types.js';
45
import type { ApifyClient } from 'apify-client';
56

67
import { processInput } from '../input.js';
@@ -41,9 +42,9 @@ export function getProxyMCPServerToolName(url: string, toolName: string): string
4142
* @param url
4243
* @param apifyToken
4344
*/
44-
export async function processParamsGetTools(url: string, apifyClient: ApifyClient) {
45+
export async function processParamsGetTools(url: string, apifyClient: ApifyClient, initializeRequestData?: InitializeRequest) {
4546
const input = parseInputParamsFromUrl(url);
46-
return await loadToolsFromInput(input, apifyClient);
47+
return await loadToolsFromInput(input, apifyClient, initializeRequestData);
4748
}
4849

4950
export function parseInputParamsFromUrl(url: string): Input {

src/stdio.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ async function main() {
117117
};
118118

119119
// Normalize (merges actors into tools for backward compatibility)
120-
const normalized = processInput(input);
120+
const normalizedInput = processInput(input);
121121

122122
const apifyClient = new ApifyClient({ token: process.env.APIFY_TOKEN });
123123
// Use the shared tools loading logic
124-
const tools = await loadToolsFromInput(normalized, apifyClient);
124+
const tools = await loadToolsFromInput(normalizedInput, apifyClient);
125125

126126
mcpServer.upsertTools(tools);
127127

src/utils/mcp-clients.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { InitializeRequest } from '@modelcontextprotocol/sdk/types';
2+
import mcpClients from 'mcp-client-capabilities';
3+
4+
/**
5+
* Determines if the MCP client supports dynamic tools based on the InitializeRequest data.
6+
*/
7+
export function doesMcpClientSupportDynamicTools(initializeRequestData?: InitializeRequest): boolean {
8+
const clientCapabilities = mcpClients[initializeRequestData?.params?.clientInfo?.name || ''];
9+
if (!clientCapabilities) return false;
10+
11+
const clientProtocolVersion = clientCapabilities.protocolVersion;
12+
const knownProtocolVersion = initializeRequestData?.params?.protocolVersion;
13+
14+
// Compare the protocolVersion to check if the client is up to date
15+
// We check for strict equality because if the versions differ, we cannot be sure about the capabilities
16+
if (clientProtocolVersion !== knownProtocolVersion) {
17+
// Client version is different from the known version, we cannot be sure about its capabilities
18+
return false;
19+
}
20+
21+
return clientCapabilities.tools?.listChanged === true;
22+
}

src/utils/tools-loader.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
* This eliminates duplication between stdio.ts and processParamsGetTools.
44
*/
55

6+
import type { InitializeRequest } from '@modelcontextprotocol/sdk/types.js';
67
import type { ValidateFunction } from 'ajv';
78
import type { ApifyClient } from 'apify';
89

910
import log from '@apify/log';
1011

11-
import { defaults } from '../const.js';
12+
import { defaults, HelperTools } from '../const.js';
1213
import { callActor } from '../tools/actor.js';
1314
import { getActorOutput } from '../tools/get-actor-output.js';
1415
import { addTool } from '../tools/helpers.js';
1516
import { getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from '../tools/index.js';
1617
import type { Input, InternalTool, InternalToolArgs, ToolCategory, ToolEntry } from '../types.js';
18+
import { doesMcpClientSupportDynamicTools } from './mcp-clients.js';
1719
import { getExpectedToolsByCategories } from './tools.js';
1820

1921
// Lazily-computed cache of internal tools by name to avoid circular init issues.
@@ -39,6 +41,7 @@ function getInternalToolByNameMap(): Map<string, ToolEntry> {
3941
export async function loadToolsFromInput(
4042
input: Input,
4143
apifyClient: ApifyClient,
44+
initializeRequestData?: InitializeRequest,
4245
): Promise<ToolEntry[]> {
4346
// Helpers for readability
4447
const normalizeSelectors = (value: Input['tools']): (string | ToolCategory)[] | undefined => {
@@ -68,6 +71,14 @@ export async function loadToolsFromInput(
6871
}
6972

7073
const categoryTools = toolCategories[selector as ToolCategory];
74+
75+
// Handler client capabilities logic for 'actors' category to swap call-actor for add-actor
76+
// if client supports dynamic tools.
77+
if (selector === 'actors' && doesMcpClientSupportDynamicTools(initializeRequestData)) {
78+
internalSelections.push(...categoryTools.filter((t) => t.tool.name !== HelperTools.ACTOR_CALL));
79+
internalSelections.push(addTool);
80+
continue;
81+
}
7182
if (categoryTools) {
7283
internalSelections.push(...categoryTools);
7384
continue;

tests/helpers.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface McpClientOptions {
1212
enableAddingActors?: boolean;
1313
tools?: (ToolCategory | string)[]; // Tool categories, specific tool or Actor names to include
1414
useEnv?: boolean; // Use environment variables instead of command line arguments (stdio only)
15+
clientName?: string; // Client name for identification
1516
}
1617

1718
export async function createMcpSseClient(
@@ -45,7 +46,7 @@ export async function createMcpSseClient(
4546
);
4647

4748
const client = new Client({
48-
name: 'sse-client',
49+
name: options?.clientName || 'sse-client',
4950
version: '1.0.0',
5051
});
5152
await client.connect(transport);
@@ -84,7 +85,7 @@ export async function createMcpStreamableClient(
8485
);
8586

8687
const client = new Client({
87-
name: 'streamable-http-client',
88+
name: options?.clientName || 'streamable-http-client',
8889
version: '1.0.0',
8990
});
9091
await client.connect(transport);
@@ -134,7 +135,7 @@ export async function createMcpStdioClient(
134135
env,
135136
});
136137
const client = new Client({
137-
name: 'stdio-client',
138+
name: options?.clientName || 'stdio-client',
138139
version: '1.0.0',
139140
});
140141
await client.connect(transport);

0 commit comments

Comments
 (0)