@apify/actors-mcp-server 0.1.0 → 0.1.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +6 -2
- package/.actor/Dockerfile +0 -57
- package/.actor/actor.json +0 -9
- package/.actor/input_schema.json +0 -35
- package/.dockerignore +0 -19
- package/.editorconfig +0 -9
- package/.env.example +0 -3
- package/.github/scripts/before-beta-release.cjs +0 -32
- package/.github/workflows/check.yaml +0 -31
- package/.github/workflows/pre_release.yaml +0 -103
- package/.github/workflows/pre_release_ci.yaml +0 -101
- package/.github/workflows/release.yaml +0 -119
- package/CHANGELOG.md +0 -19
- package/eslint.config.mjs +0 -16
- package/src/actorDefinition.ts +0 -87
- package/src/const.ts +0 -20
- package/src/examples/clientSse.ts +0 -100
- package/src/examples/clientStdio.ts +0 -85
- package/src/examples/clientStdioChat.ts +0 -227
- package/src/examples/client_sse.py +0 -48
- package/src/index.ts +0 -34
- package/src/input.ts +0 -16
- package/src/logger.ts +0 -5
- package/src/main.ts +0 -106
- package/src/server.ts +0 -153
- package/src/types.ts +0 -20
- package/tsconfig.eslint.json +0 -4
- package/tsconfig.json +0 -16
package/src/actorDefinition.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Ajv } from 'ajv';
|
|
2
|
-
import { ApifyClient } from 'apify-client';
|
|
3
|
-
|
|
4
|
-
import { log } from './logger.js';
|
|
5
|
-
import type { ActorDefinitionWithDesc, Tool } from './types';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Get actor input schema by actor name.
|
|
9
|
-
* First, fetch the actor details to get the default build tag and buildId.
|
|
10
|
-
* Then, fetch the build details and return actorName, description, and input schema.
|
|
11
|
-
* @param {string} actorFullName - The full name of the actor.
|
|
12
|
-
* @returns {Promise<ActorDefinitionWithDesc | null>} - The actor definition with description or null if not found.
|
|
13
|
-
*/
|
|
14
|
-
async function fetchActorDefinition(actorFullName: string): Promise<ActorDefinitionWithDesc | null> {
|
|
15
|
-
if (!process.env.APIFY_TOKEN) {
|
|
16
|
-
log.error('APIFY_TOKEN is required but not set. Please set it as an environment variable');
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
|
|
20
|
-
const actorClient = client.actor(actorFullName);
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// Fetch actor details
|
|
24
|
-
const actor = await actorClient.get();
|
|
25
|
-
if (!actor) {
|
|
26
|
-
log.error(`Failed to fetch input schema for actor: ${actorFullName}. Actor not found.`);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// fnesveda: The default build is not necessarily tagged, you can specify any build number as default build.
|
|
31
|
-
// There will be a new API endpoint to fetch a default build.
|
|
32
|
-
// For now, we'll use the tagged build, it will work for 90% of Actors. Later, we can update this.
|
|
33
|
-
const tag = actor.defaultRunOptions?.build || '';
|
|
34
|
-
const buildId = actor.taggedBuilds?.[tag]?.buildId || '';
|
|
35
|
-
|
|
36
|
-
if (!buildId) {
|
|
37
|
-
log.error(`Failed to fetch input schema for actor: ${actorFullName}. Build ID not found.`);
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
// Fetch build details and return the input schema
|
|
41
|
-
const buildDetails = await client.build(buildId).get();
|
|
42
|
-
if (buildDetails?.actorDefinition) {
|
|
43
|
-
const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc;
|
|
44
|
-
actorDefinitions.description = actor.description || '';
|
|
45
|
-
actorDefinitions.name = actorFullName;
|
|
46
|
-
return actorDefinitions;
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
log.error(`Failed to fetch input schema for actor: ${actorFullName} with error ${error}.`);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Fetches actor input schemas by actor full names and creates MCP tools.
|
|
57
|
-
*
|
|
58
|
-
* This function retrieves the input schemas for the specified actors and compiles them into MCP tools.
|
|
59
|
-
* It uses the AJV library to validate the input schemas.
|
|
60
|
-
*
|
|
61
|
-
* Tool name can't contain /, so it is replaced with _
|
|
62
|
-
*
|
|
63
|
-
* @param {string[]} actors - An array of actor full names.
|
|
64
|
-
* @returns {Promise<Tool[]>} - A promise that resolves to an array of MCP tools.
|
|
65
|
-
*/
|
|
66
|
-
export async function getActorsAsTools(actors: string[]): Promise<Tool[]> {
|
|
67
|
-
// Fetch input schemas in parallel
|
|
68
|
-
const ajv = new Ajv({ coerceTypes: 'array', strict: false });
|
|
69
|
-
const results = await Promise.all(actors.map(fetchActorDefinition));
|
|
70
|
-
const tools = [];
|
|
71
|
-
for (const result of results) {
|
|
72
|
-
if (result) {
|
|
73
|
-
try {
|
|
74
|
-
tools.push({
|
|
75
|
-
name: result.name.replace('/', '_'),
|
|
76
|
-
actorName: result.name,
|
|
77
|
-
description: result.description,
|
|
78
|
-
inputSchema: result.input || {},
|
|
79
|
-
ajvValidate: ajv.compile(result.input || {}),
|
|
80
|
-
});
|
|
81
|
-
} catch (validationError) {
|
|
82
|
-
log.error(`Failed to compile AJV schema for actor: ${result.name}. Error: ${validationError}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return tools;
|
|
87
|
-
}
|
package/src/const.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export const SERVER_NAME = 'apify-mcp-server';
|
|
2
|
-
export const SERVER_VERSION = '0.1.0';
|
|
3
|
-
|
|
4
|
-
export const defaults = {
|
|
5
|
-
actors: [
|
|
6
|
-
'apify/instagram-scraper',
|
|
7
|
-
'apify/rag-web-browser',
|
|
8
|
-
'lukaskrivka/google-maps-with-contact-details',
|
|
9
|
-
],
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 2_000;
|
|
13
|
-
export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.`
|
|
14
|
-
+ ` There is no reason to call this tool again!`;
|
|
15
|
-
|
|
16
|
-
export enum Routes {
|
|
17
|
-
ROOT = '/',
|
|
18
|
-
SSE = '/sse',
|
|
19
|
-
MESSAGE = '/message',
|
|
20
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/**
|
|
3
|
-
* Connect to the MCP server using SSE transport and call a tool.
|
|
4
|
-
* The Actors MCP Server will load default Actors.
|
|
5
|
-
*
|
|
6
|
-
* !!! This example needs to be fixed as it does not work !!!
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
13
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
14
|
-
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
-
import dotenv from 'dotenv';
|
|
16
|
-
import { EventSource } from 'eventsource';
|
|
17
|
-
|
|
18
|
-
// Resolve dirname equivalent in ES module
|
|
19
|
-
const filename = fileURLToPath(import.meta.url);
|
|
20
|
-
const dirname = path.dirname(filename);
|
|
21
|
-
|
|
22
|
-
dotenv.config({ path: path.resolve(dirname, '../../.env') });
|
|
23
|
-
|
|
24
|
-
const SERVER_URL = 'https://actors-mcp-server/sse';
|
|
25
|
-
// We need to change forward slash / to underscore _ in the tool name as Anthropic does not allow forward slashes in the tool name
|
|
26
|
-
const SELECTED_TOOL = 'apify_rag-web-browser';
|
|
27
|
-
|
|
28
|
-
if (!process.env.APIFY_TOKEN) {
|
|
29
|
-
console.error('APIFY_TOKEN is required but not set in the environment variables.');
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (typeof globalThis.EventSource === 'undefined') {
|
|
34
|
-
globalThis.EventSource = EventSource as unknown as typeof globalThis.EventSource;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function main(): Promise<void> {
|
|
38
|
-
const transport = new SSEClientTransport(
|
|
39
|
-
new URL(SERVER_URL),
|
|
40
|
-
{
|
|
41
|
-
requestInit: {
|
|
42
|
-
headers: {
|
|
43
|
-
authorization: `Bearer ${process.env.APIFY_TOKEN}`,
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
eventSourceInit: {
|
|
47
|
-
// The EventSource package augments EventSourceInit with a "fetch" parameter.
|
|
48
|
-
// You can use this to set additional headers on the outgoing request.
|
|
49
|
-
// Based on this example: https://github.com/modelcontextprotocol/typescript-sdk/issues/118
|
|
50
|
-
async fetch(input: Request | URL | string, init?: RequestInit) {
|
|
51
|
-
const headers = new Headers(init?.headers || {});
|
|
52
|
-
headers.set('authorization', `Bearer ${process.env.APIFY_TOKEN}`);
|
|
53
|
-
return fetch(input, { ...init, headers });
|
|
54
|
-
},
|
|
55
|
-
// We have to cast to "any" to use it, since it's non-standard
|
|
56
|
-
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
const client = new Client(
|
|
60
|
-
{ name: 'example-client', version: '1.0.0' },
|
|
61
|
-
{ capabilities: {} },
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
// Connect to the MCP server
|
|
66
|
-
await client.connect(transport);
|
|
67
|
-
|
|
68
|
-
// List available tools
|
|
69
|
-
const tools = await client.listTools();
|
|
70
|
-
console.log('Available tools:', tools);
|
|
71
|
-
|
|
72
|
-
if (tools.tools.length === 0) {
|
|
73
|
-
console.log('No tools available');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const selectedTool = tools.tools.find((tool) => tool.name === SELECTED_TOOL);
|
|
78
|
-
if (!selectedTool) {
|
|
79
|
-
console.error(`The specified tool: ${selectedTool} is not available. Exiting.`);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Call a tool
|
|
84
|
-
console.log('Calling actor ...');
|
|
85
|
-
const result = await client.callTool(
|
|
86
|
-
{ name: SELECTED_TOOL, arguments: { query: 'web browser for Anthropic' } },
|
|
87
|
-
CallToolResultSchema,
|
|
88
|
-
);
|
|
89
|
-
console.log('Tool result:', JSON.stringify(result, null, 2));
|
|
90
|
-
} catch (error: unknown) {
|
|
91
|
-
if (error instanceof Error) {
|
|
92
|
-
console.error('Error:', error.message);
|
|
93
|
-
console.error(error.stack);
|
|
94
|
-
} else {
|
|
95
|
-
console.error('An unknown error occurred:', error);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await main();
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/**
|
|
3
|
-
* Connect to the MCP server using stdio transport and call a tool.
|
|
4
|
-
* This script uses a selected tool without LLM involvement.
|
|
5
|
-
* You need to provide the path to the MCP server and `APIFY_TOKEN` in the `.env` file.
|
|
6
|
-
* You can choose actors to run in the server, for example: `apify/rag-web-browser`.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
|
|
13
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
14
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
15
|
-
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
-
import dotenv from 'dotenv';
|
|
17
|
-
|
|
18
|
-
// Resolve dirname equivalent in ES module
|
|
19
|
-
const filename = fileURLToPath(import.meta.url);
|
|
20
|
-
const dirname = path.dirname(filename);
|
|
21
|
-
|
|
22
|
-
dotenv.config({ path: path.resolve(dirname, '../../.env') });
|
|
23
|
-
const SERVER_PATH = path.resolve(dirname, '../../dist/index.js');
|
|
24
|
-
const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which node').toString().trim();
|
|
25
|
-
|
|
26
|
-
const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details';
|
|
27
|
-
const SELECTED_TOOL = 'apify_rag-web-browser'; // We need to change / to _ in the tool name
|
|
28
|
-
|
|
29
|
-
if (!process.env.APIFY_TOKEN) {
|
|
30
|
-
console.error('APIFY_TOKEN is required but not set in the environment variables.');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Create server parameters for stdio connection
|
|
35
|
-
const transport = new StdioClientTransport({
|
|
36
|
-
command: NODE_PATH,
|
|
37
|
-
args: [SERVER_PATH, '--actors', TOOLS],
|
|
38
|
-
env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' },
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Create a new client instance
|
|
42
|
-
const client = new Client(
|
|
43
|
-
{ name: 'example-client', version: '0.1.0' },
|
|
44
|
-
{ capabilities: {} },
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Main function to run the example client
|
|
48
|
-
async function run() {
|
|
49
|
-
try {
|
|
50
|
-
// Connect to the MCP server
|
|
51
|
-
await client.connect(transport);
|
|
52
|
-
|
|
53
|
-
// List available tools
|
|
54
|
-
const tools = await client.listTools();
|
|
55
|
-
console.log('Available tools:', tools);
|
|
56
|
-
|
|
57
|
-
if (tools.tools.length === 0) {
|
|
58
|
-
console.log('No tools available');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Example: Call the first available tool
|
|
63
|
-
const selectedTool = tools.tools.find((tool) => tool.name === SELECTED_TOOL);
|
|
64
|
-
|
|
65
|
-
if (!selectedTool) {
|
|
66
|
-
console.error(`The specified tool: ${selectedTool} is not available. Exiting.`);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Call a tool
|
|
71
|
-
console.log('Calling actor ...');
|
|
72
|
-
const result = await client.callTool(
|
|
73
|
-
{ name: SELECTED_TOOL, arguments: { query: 'web browser for Anthropic' } },
|
|
74
|
-
CallToolResultSchema,
|
|
75
|
-
);
|
|
76
|
-
console.log('Tool result:', JSON.stringify(result));
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('Error:', error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
run().catch((error) => {
|
|
83
|
-
console.error('Unhandled error:', error);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
});
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/**
|
|
3
|
-
* Create a simple chat client that connects to the Model Context Protocol server using the stdio transport.
|
|
4
|
-
* Based on the user input, the client sends a query to the MCP server, retrieves results and processes them.
|
|
5
|
-
*
|
|
6
|
-
* You can expect the following output:
|
|
7
|
-
*
|
|
8
|
-
* MCP Client Started!
|
|
9
|
-
* Type your queries or 'quit|q|exit' to exit.
|
|
10
|
-
* You: Find to articles about AI agent and return URLs
|
|
11
|
-
* [internal] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents
|
|
12
|
-
* and provide you with a summary."},{"type":"tool_use","id":"tool_01He9TkzQfh2979bbeuxWVqM","name":"search",
|
|
13
|
-
* "input":{"query":"what are AI agents definition capabilities applications","maxResults":2}}]
|
|
14
|
-
* [internal] Calling tool: {"name":"search","arguments":{"query":"what are AI agents definition ...
|
|
15
|
-
* I can help analyze the provided content about AI agents.
|
|
16
|
-
* This appears to be crawled content from AWS and IBM websites explaining what AI agents are.
|
|
17
|
-
* Let me summarize the key points:
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { execSync } from 'child_process';
|
|
21
|
-
import path from 'path';
|
|
22
|
-
import * as readline from 'readline';
|
|
23
|
-
import { fileURLToPath } from 'url';
|
|
24
|
-
|
|
25
|
-
import { Anthropic } from '@anthropic-ai/sdk';
|
|
26
|
-
import type { Message, ToolUseBlock, MessageParam } from '@anthropic-ai/sdk/resources/messages';
|
|
27
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
28
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
29
|
-
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
30
|
-
import dotenv from 'dotenv';
|
|
31
|
-
|
|
32
|
-
const filename = fileURLToPath(import.meta.url);
|
|
33
|
-
const dirname = path.dirname(filename);
|
|
34
|
-
|
|
35
|
-
dotenv.config({ path: path.resolve(dirname, '../../.env') });
|
|
36
|
-
|
|
37
|
-
const REQUEST_TIMEOUT = 120_000; // 2 minutes
|
|
38
|
-
const MAX_TOKENS = 2048; // Maximum tokens for Claude response
|
|
39
|
-
|
|
40
|
-
// const CLAUDE_MODEL = 'claude-3-5-sonnet-20241022'; // the most intelligent model
|
|
41
|
-
// const CLAUDE_MODEL = 'claude-3-5-haiku-20241022'; // a fastest model
|
|
42
|
-
const CLAUDE_MODEL = 'claude-3-haiku-20240307'; // a fastest and most compact model for near-instant responsiveness
|
|
43
|
-
const DEBUG = true;
|
|
44
|
-
const DEBUG_SERVER_PATH = path.resolve(dirname, '../../dist/index.js');
|
|
45
|
-
|
|
46
|
-
const NODE_PATH = execSync('which node').toString().trim();
|
|
47
|
-
|
|
48
|
-
dotenv.config(); // Load environment variables from .env
|
|
49
|
-
|
|
50
|
-
export type Tool = {
|
|
51
|
-
name: string;
|
|
52
|
-
description: string | undefined;
|
|
53
|
-
input_schema: unknown;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
class MCPClient {
|
|
57
|
-
private anthropic: Anthropic;
|
|
58
|
-
private client = new Client(
|
|
59
|
-
{
|
|
60
|
-
name: 'example-client',
|
|
61
|
-
version: '0.1.0',
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
capabilities: {}, // Optional capabilities
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
private tools: Tool[] = [];
|
|
69
|
-
|
|
70
|
-
constructor() {
|
|
71
|
-
this.anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Start the server using node and provided server script path.
|
|
76
|
-
* Connect to the server using stdio transport and list available tools.
|
|
77
|
-
*/
|
|
78
|
-
async connectToServer(serverArgs: string[]) {
|
|
79
|
-
const transport = new StdioClientTransport({
|
|
80
|
-
command: NODE_PATH,
|
|
81
|
-
args: serverArgs,
|
|
82
|
-
env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' },
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
await this.client.connect(transport);
|
|
86
|
-
const response = await this.client.listTools();
|
|
87
|
-
|
|
88
|
-
this.tools = response.tools.map((x) => ({
|
|
89
|
-
name: x.name,
|
|
90
|
-
description: x.description,
|
|
91
|
-
input_schema: x.inputSchema,
|
|
92
|
-
}));
|
|
93
|
-
console.log('Connected to server with tools:', this.tools.map((x) => x.name));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Process LLM response and check whether it contains any tool calls.
|
|
98
|
-
* If a tool call is found, call the tool and return the response and save the results to messages with type: user.
|
|
99
|
-
* If the tools response is too large, truncate it to the limit.
|
|
100
|
-
*/
|
|
101
|
-
async processMsg(response: Message, messages: MessageParam[]): Promise<MessageParam[]> {
|
|
102
|
-
for (const content of response.content) {
|
|
103
|
-
if (content.type === 'text') {
|
|
104
|
-
messages.push({ role: 'assistant', content: content.text });
|
|
105
|
-
} else if (content.type === 'tool_use') {
|
|
106
|
-
await this.handleToolCall(content, messages);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return messages;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Call the tool and return the response.
|
|
114
|
-
*/
|
|
115
|
-
private async handleToolCall(content: ToolUseBlock, messages: MessageParam[], toolCallCount = 0): Promise<MessageParam[]> {
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
-
const params = { name: content.name, arguments: content.input as any };
|
|
118
|
-
console.log(`[internal] Calling tool (count: ${toolCallCount}): ${JSON.stringify(params)}`);
|
|
119
|
-
let results;
|
|
120
|
-
try {
|
|
121
|
-
results = await this.client.callTool(params, CallToolResultSchema, { timeout: REQUEST_TIMEOUT });
|
|
122
|
-
if (results.content instanceof Array && results.content.length !== 0) {
|
|
123
|
-
const text = results.content.map((x) => x.text);
|
|
124
|
-
messages.push({ role: 'user', content: `Tool result: ${text.join('\n\n')}` });
|
|
125
|
-
} else {
|
|
126
|
-
messages.push({ role: 'user', content: `No results retrieved from ${params.name}` });
|
|
127
|
-
}
|
|
128
|
-
} catch (error) {
|
|
129
|
-
messages.push({ role: 'user', content: `Error calling tool: ${params.name}, error: ${error}` });
|
|
130
|
-
}
|
|
131
|
-
// Get next response from Claude
|
|
132
|
-
const nextResponse: Message = await this.anthropic.messages.create({
|
|
133
|
-
model: CLAUDE_MODEL,
|
|
134
|
-
max_tokens: MAX_TOKENS,
|
|
135
|
-
messages,
|
|
136
|
-
tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
for (const c of nextResponse.content) {
|
|
140
|
-
if (c.type === 'text') {
|
|
141
|
-
messages.push({ role: 'assistant', content: c.text });
|
|
142
|
-
} else if (c.type === 'tool_use' && toolCallCount < 3) {
|
|
143
|
-
return await this.handleToolCall(c, messages, toolCallCount + 1);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return messages;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Process user query by sending it to the server and returning the response.
|
|
152
|
-
* Also, process any tool calls.
|
|
153
|
-
*/
|
|
154
|
-
async processQuery(query: string, messages: MessageParam[]): Promise<MessageParam[]> {
|
|
155
|
-
messages.push({ role: 'user', content: query });
|
|
156
|
-
const response: Message = await this.anthropic.messages.create({
|
|
157
|
-
model: CLAUDE_MODEL,
|
|
158
|
-
max_tokens: MAX_TOKENS,
|
|
159
|
-
messages,
|
|
160
|
-
tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
161
|
-
});
|
|
162
|
-
console.log('[internal] Received response from Claude:', JSON.stringify(response.content));
|
|
163
|
-
return await this.processMsg(response, messages);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Create a chat loop that reads user input from the console and sends it to the server for processing.
|
|
168
|
-
*/
|
|
169
|
-
async chatLoop() {
|
|
170
|
-
const rl = readline.createInterface({
|
|
171
|
-
input: process.stdin,
|
|
172
|
-
output: process.stdout,
|
|
173
|
-
prompt: 'You: ',
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
console.log("MCP Client Started!\nType your queries or 'quit|q|exit' to exit.");
|
|
177
|
-
rl.prompt();
|
|
178
|
-
|
|
179
|
-
let lastPrintMessage = 0;
|
|
180
|
-
const messages: MessageParam[] = [];
|
|
181
|
-
rl.on('line', async (input) => {
|
|
182
|
-
const v = input.trim().toLowerCase();
|
|
183
|
-
if (v === 'quit' || v === 'q' || v === 'exit') {
|
|
184
|
-
rl.close();
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
await this.processQuery(input, messages);
|
|
189
|
-
for (let i = lastPrintMessage + 1; i < messages.length; i++) {
|
|
190
|
-
if (messages[i].role === 'assistant') {
|
|
191
|
-
console.log('CLAUDE:', messages[i].content);
|
|
192
|
-
} else if (messages[i].role === 'user') {
|
|
193
|
-
console.log('USER:', messages[i].content.slice(0, 500), '...');
|
|
194
|
-
} else {
|
|
195
|
-
console.log('CLAUDE[thinking]:', messages[i].content);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
lastPrintMessage += messages.length;
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error('Error processing query:', error);
|
|
201
|
-
}
|
|
202
|
-
rl.prompt();
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function main() {
|
|
208
|
-
const client = new MCPClient();
|
|
209
|
-
|
|
210
|
-
if (process.argv.length < 3) {
|
|
211
|
-
if (DEBUG) {
|
|
212
|
-
process.argv.push(DEBUG_SERVER_PATH);
|
|
213
|
-
} else {
|
|
214
|
-
console.error('Usage: node <path_to_server_script>');
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
await client.connectToServer(process.argv.slice(2));
|
|
221
|
-
await client.chatLoop();
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error('Error:', error);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
main().catch(console.error);
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Test Apify MCP Server using SSE client
|
|
3
|
-
|
|
4
|
-
It is using python client as the typescript one does not support custom headers when connecting to the SSE server.
|
|
5
|
-
|
|
6
|
-
Install python dependencies (assumes you have python installed):
|
|
7
|
-
> pip install requests python-dotenv mcp
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import asyncio
|
|
11
|
-
import os
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
import requests
|
|
15
|
-
from dotenv import load_dotenv
|
|
16
|
-
from mcp.client.session import ClientSession
|
|
17
|
-
from mcp.client.sse import sse_client
|
|
18
|
-
|
|
19
|
-
load_dotenv(Path(__file__).resolve().parent.parent.parent / ".env")
|
|
20
|
-
|
|
21
|
-
MCP_SERVER_URL = "https://actors-mcp-server.apify.actor"
|
|
22
|
-
|
|
23
|
-
HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_TOKEN')}"}
|
|
24
|
-
|
|
25
|
-
async def run() -> None:
|
|
26
|
-
|
|
27
|
-
print("Start MCP Server with Actors")
|
|
28
|
-
r = requests.get(MCP_SERVER_URL, headers=HEADERS)
|
|
29
|
-
print("MCP Server Response:", r.json(), end="\n\n")
|
|
30
|
-
|
|
31
|
-
async with sse_client(url=f"{MCP_SERVER_URL}/sse", timeout=60, headers=HEADERS) as (read, write):
|
|
32
|
-
async with ClientSession(read, write) as session:
|
|
33
|
-
await session.initialize()
|
|
34
|
-
|
|
35
|
-
tools = await session.list_tools()
|
|
36
|
-
print("Available Tools:", tools, end="\n\n")
|
|
37
|
-
|
|
38
|
-
if hasattr(tools, "tools") and not tools.tools:
|
|
39
|
-
print("No tools available!")
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
result = await session.call_tool("apify/rag-web-browser", { "query": "example.com", "maxResults": 3 })
|
|
43
|
-
print("Tools Call Result:")
|
|
44
|
-
|
|
45
|
-
for content in result.content:
|
|
46
|
-
print(content)
|
|
47
|
-
|
|
48
|
-
asyncio.run(run())
|
package/src/index.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This script initializes and starts the Apify MCP server using the Stdio transport.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* node <script_name> --actors=<actor1,actor2,...>
|
|
6
|
-
*
|
|
7
|
-
* Command-line arguments:
|
|
8
|
-
* --actors - A comma-separated list of actor full names to add to the server.
|
|
9
|
-
*
|
|
10
|
-
* Example:
|
|
11
|
-
* node index.js --actors=apify/google-search-scraper,apify/instagram-scraper
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
-
import minimist from 'minimist';
|
|
16
|
-
|
|
17
|
-
import { ApifyMcpServer } from './server.js';
|
|
18
|
-
|
|
19
|
-
const argv = minimist(process.argv.slice(2));
|
|
20
|
-
const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || [];
|
|
21
|
-
|
|
22
|
-
async function main() {
|
|
23
|
-
const server = new ApifyMcpServer();
|
|
24
|
-
await (argActors.length !== 0
|
|
25
|
-
? server.addToolsFromActors(argActors)
|
|
26
|
-
: server.addToolsFromDefaultActors());
|
|
27
|
-
const transport = new StdioServerTransport();
|
|
28
|
-
await server.connect(transport);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
main().catch((error) => {
|
|
32
|
-
console.error('Server error:', error); // eslint-disable-line no-console
|
|
33
|
-
process.exit(1);
|
|
34
|
-
});
|
package/src/input.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Input } from './types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Process input parameters, split actors string into an array
|
|
5
|
-
* @param originalInput
|
|
6
|
-
* @returns input
|
|
7
|
-
*/
|
|
8
|
-
export async function processInput(originalInput: Partial<Input>): Promise<Input> {
|
|
9
|
-
const input = originalInput as Input;
|
|
10
|
-
|
|
11
|
-
// actors can be a string or an array of strings
|
|
12
|
-
if (input.actors && typeof input.actors === 'string') {
|
|
13
|
-
input.actors = input.actors.split(',').map((format: string) => format.trim()) as string[];
|
|
14
|
-
}
|
|
15
|
-
return input;
|
|
16
|
-
}
|