Skip to content

Commit ef300c8

Browse files
committed
Add Origin/mcp-server to user-agent. Truncate inputSchema description to 200 characters. Limit enums to 50 values.
1 parent 07d2e8e commit ef300c8

File tree

6 files changed

+83
-4
lines changed

6 files changed

+83
-4
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,15 +301,21 @@ npm run build
301301
You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
302302

303303
```bash
304-
npx @modelcontextprotocol/inspector node /path/to/actor-mcp-server/dist/index.js --env APIFY_TOKEN=your-apify-token
304+
npx @modelcontextprotocol/inspector node @apify/actors-mcp-server --env APIFY_TOKEN=your-apify-token
305305
```
306306

307307
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
308308

309+
## ⓘ Limitations and feedback
310+
311+
To limit the context size the properties in the `input schema` are pruned and description is truncated to 200 characters.
312+
Enum fields and titles are truncated to max 50 options.
313+
314+
If you need other features or have any feedback, please [submit an issue](https://console.apify.com/actors/3ox4R101TgZz67sLr/issues) in Apify Console to let us know.
315+
309316
# 🚀 Roadmap (January 2025)
310317

311318
- Document examples for [Superinference.ai](https://superinterface.ai/) and [LibreChat](https://www.librechat.ai/).
312319
- Provide tools to search for Actors and load them as needed.
313320
- Add Apify's dataset and key-value store as resources.
314321
- Add tools such as Actor logs and Actor runs for debugging.
315-
- Prune Actors input schema to reduce context size.

src/actorDefinition.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Ajv } from 'ajv';
22
import { ApifyClient } from 'apify-client';
33

4+
import { MAX_DESCRIPTION_LENGTH, MAX_ENUM_LENGTH } from './const.js';
45
import { log } from './logger.js';
5-
import type { ActorDefinitionWithDesc, Tool } from './types';
6+
import type { ActorDefinitionWithDesc, Tool, SchemaProperties } from './types.js';
67

78
/**
89
* Get actor input schema by actor name.
@@ -52,6 +53,38 @@ async function fetchActorDefinition(actorFullName: string): Promise<ActorDefinit
5253
}
5354
}
5455

56+
/**
57+
* Shortens the description and enum values of schema properties.
58+
* @param properties
59+
*/
60+
function shortenProperties(properties: { [key: string]: SchemaProperties}): { [key: string]: SchemaProperties } {
61+
for (const property of Object.values(properties)) {
62+
if (property.description.length > MAX_DESCRIPTION_LENGTH) {
63+
property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`;
64+
}
65+
if (property.enum) {
66+
property.enum = property.enum.slice(0, MAX_ENUM_LENGTH);
67+
}
68+
if (property.enumTitles) {
69+
property.enumTitles = property.enumTitles.slice(0, MAX_ENUM_LENGTH);
70+
}
71+
}
72+
return properties;
73+
}
74+
75+
/**
76+
* Filters schema properties to include only the necessary fields.
77+
* @param properties
78+
*/
79+
function filterSchemaProperties(properties: { [key: string]: SchemaProperties }): { [key: string]: SchemaProperties } {
80+
const filteredProperties: { [key: string]: SchemaProperties } = {};
81+
for (const [key, property] of Object.entries(properties)) {
82+
const { title, description, enum: enumValues, enumTitles, type, default: defaultValue, prefill } = property;
83+
filteredProperties[key] = { title, description, enum: enumValues, enumTitles, type, default: defaultValue, prefill };
84+
}
85+
return filteredProperties;
86+
}
87+
5588
/**
5689
* Fetches actor input schemas by actor full names and creates MCP tools.
5790
*
@@ -70,6 +103,10 @@ export async function getActorsAsTools(actors: string[]): Promise<Tool[]> {
70103
const tools = [];
71104
for (const result of results) {
72105
if (result) {
106+
if (result.input && 'properties' in result.input && result.input) {
107+
const properties = filterSchemaProperties(result.input.properties as { [key: string]: SchemaProperties });
108+
result.input.properties = shortenProperties(properties);
109+
}
73110
try {
74111
tools.push({
75112
name: result.name.replace('/', '_'),

src/const.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ export const SERVER_VERSION = '0.1.0';
33

44
export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe';
55

6+
export const MAX_ENUM_LENGTH = 50;
7+
export const MAX_DESCRIPTION_LENGTH = 200;
8+
9+
export const USER_AGENT_ORIGIN = 'Origin/mcp-server';
10+
611
export const defaults = {
712
actors: [
813
'apify/instagram-scraper',

src/examples/client_sse.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ async def run() -> None:
3434

3535
tools = await session.list_tools()
3636
print("Available Tools:", tools, end="\n\n")
37+
for tool in tools.tools:
38+
print(f"\n### Tool name ###: {tool.name}")
39+
print(f"\tdescription: {tool.description}")
40+
print(f"\tinputSchema: {tool.inputSchema}")
3741

3842
if hasattr(tools, "tools") and not tools.tools:
3943
print("No tools available!")

src/server.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
66
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
77
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8+
import type { ApifyClientOptions } from 'apify';
89
import { Actor } from 'apify';
910
import { ApifyClient } from 'apify-client';
11+
import type { AxiosRequestConfig } from 'axios';
1012

1113
import { getActorsAsTools } from './actorDefinition.js';
1214
import {
@@ -15,6 +17,7 @@ import {
1517
defaults,
1618
SERVER_NAME,
1719
SERVER_VERSION,
20+
USER_AGENT_ORIGIN,
1821
} from './const.js';
1922
import { log } from './logger.js';
2023
import type { Tool } from './types';
@@ -43,6 +46,18 @@ export class ApifyMcpServer {
4346
this.setupToolHandlers();
4447
}
4548

49+
/**
50+
* Adds a User-Agent header to the request config.
51+
* @param config
52+
* @private
53+
*/
54+
private addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig {
55+
const updatedConfig = { ...config };
56+
updatedConfig.headers = updatedConfig.headers ?? {};
57+
updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`;
58+
return updatedConfig;
59+
}
60+
4661
/**
4762
* Calls an Apify actor and retrieves the dataset items.
4863
*
@@ -60,7 +75,9 @@ export class ApifyMcpServer {
6075
}
6176
try {
6277
log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`);
63-
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
78+
79+
const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] };
80+
const client = new ApifyClient({ ...options, token: process.env.APIFY_TOKEN });
6481
const actorClient = client.actor(actorName);
6582

6683
const results = await actorClient.call(input);

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ export interface Tool {
1818
inputSchema: object;
1919
ajvValidate: ValidateFunction;
2020
}
21+
22+
export interface SchemaProperties {
23+
title: string;
24+
description: string;
25+
enum: string[]; // Array of string options for the enum
26+
enumTitles: string[]; // Array of string titles for the enum
27+
type: string; // Data type (e.g., "string")
28+
default: string;
29+
prefill: string;
30+
}

0 commit comments

Comments
 (0)