Skip to content

Commit 0f1fbda

Browse files
committed
Merge branch 'master' into segment-telemetry
2 parents 6cd4af3 + 02343ed commit 0f1fbda

34 files changed

+471
-201
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.5.6](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.6) (2025-11-19)
6+
7+
### 🚀 Features
8+
9+
- Add tool annotations ([#333](https://github.com/apify/apify-mcp-server/pull/333)) ([149cbbc](https://github.com/apify/apify-mcp-server/commit/149cbbc7f5ee1db6c4bed68b2beead4197e990b3)) by [@jirispilka](https://github.com/jirispilka), closes [#327](https://github.com/apify/apify-mcp-server/issues/327)
10+
- Improve error handling (required for claude connector) ([#331](https://github.com/apify/apify-mcp-server/pull/331)) ([005db2a](https://github.com/apify/apify-mcp-server/commit/005db2a3af24595e67d9548597ca09302be0f9fa)) by [@jirispilka](https://github.com/jirispilka)
11+
12+
13+
## [0.5.5](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.5) (2025-11-14)
14+
15+
### 🚀 Features
16+
17+
- Refactor(logging): centralize HTTP error logging ([#336](https://github.com/apify/apify-mcp-server/pull/336)) ([7b0a52d](https://github.com/apify/apify-mcp-server/commit/7b0a52d1e3baeb732747b1cbf58cb70ad1a135a7)) by [@jirispilka](https://github.com/jirispilka)
18+
19+
20+
## [0.5.2](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.2) (2025-11-12)
21+
22+
### 🚀 Features
23+
24+
- Update search-actors tool ([#321](https://github.com/apify/apify-mcp-server/pull/321)) ([602abc5](https://github.com/apify/apify-mcp-server/commit/602abc55bff1f59e937d8e24acc8d7aad64852c2)) by [@jirispilka](https://github.com/jirispilka)
25+
26+
### 🐛 Bug Fixes
27+
28+
- Update readme, add image with clients ([#326](https://github.com/apify/apify-mcp-server/pull/326)) ([da560ab](https://github.com/apify/apify-mcp-server/commit/da560abcbd8b7cb2553032899b8cc963357495e5)) by [@jirispilka](https://github.com/jirispilka)
29+
- Deduplicate error logs, use info for 404/400 errors, fix ajv validate when it contains $ref ([#335](https://github.com/apify/apify-mcp-server/pull/335)) ([0ebbf50](https://github.com/apify/apify-mcp-server/commit/0ebbf5069a38e4168c5b031764136c66db4f2e18)) by [@jirispilka](https://github.com/jirispilka)
30+
31+
532
## [0.5.1](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.1) (2025-10-27)
633

734
### 🚀 Features

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ Here is an overview list of all the tools provided by the Apify MCP Server.
179179
180180
> 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.
181181
182+
### Tool annotations
183+
184+
All tools include metadata annotations to help MCP clients and LLMs understand tool behavior:
185+
186+
- **`title`**: Short display name for the tool (e.g., "Search Actors", "Call Actor", "apify/rag-web-browser")
187+
- **`readOnlyHint`**: `true` for tools that only read data without modifying state (e.g., `get-dataset`, `fetch-actor-details`)
188+
- **`openWorldHint`**: `true` for tools that access external resources outside the Apify platform (e.g., `call-actor` executes external Actors, `get-html-skeleton` scrapes external websites). Tools that interact only with the Apify platform (like `search-actors` or `fetch-apify-docs`) do not have this hint.
189+
182190
### Tools configuration
183191

184192
The `tools` configuration parameter is used to specify loaded tools - either categories or specific tools directly, and Apify Actors. For example, `tools=storage,runs` loads two categories; `tools=add-actor` loads just one tool.

evals/evaluation-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function filterById(testCases: TestCase[], idPattern: string): TestCase[]
7373
export async function loadTools(): Promise<ToolBase[]> {
7474
const apifyClient = new ApifyClient({ token: process.env.APIFY_API_TOKEN || '' });
7575
const urlTools = await processParamsGetTools('', apifyClient);
76-
return urlTools.map((t: ToolEntry) => getToolPublicFieldOnly(t.tool)) as ToolBase[];
76+
return urlTools.map((t: ToolEntry) => getToolPublicFieldOnly(t)) as ToolBase[];
7777
}
7878

7979
export function transformToolsToOpenAIFormat(tools: ToolBase[]): OpenAI.Chat.Completions.ChatCompletionTool[] {

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": "0.2",
33
"name": "apify-mcp-server",
44
"display_name": "Apify MCP server",
5-
"version": "0.5.1",
5+
"version": "0.5.6",
66
"description": "Extract data from any website using thousands of tools from the Apify Store.",
77
"long_description": "Apify is the world's largest marketplace of tools for web scraping, data extraction, and web automation. You can extract structured data from social media, e-commerce, search engines, maps, travel sites, or any other website.",
88
"keywords": [

package-lock.json

Lines changed: 2 additions & 2 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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apify/actors-mcp-server",
3-
"version": "0.5.1",
3+
"version": "0.5.6",
44
"type": "module",
55
"description": "Apify MCP Server",
66
"mcpName": "com.apify/apify-mcp-server",

server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/apify/apify-mcp-server",
88
"source": "github"
99
},
10-
"version": "0.5.1",
10+
"version": "0.5.6",
1111
"remotes": [
1212
{
1313
"type": "streamable-http",

src/actor/server.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ export function createExpressApp(
2626
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
2727

2828
function respondWithError(res: Response, error: unknown, logMessage: string, statusCode = 500) {
29-
log.error('Error in request', { logMessage, error });
29+
if (statusCode >= 500) {
30+
// Server errors (>= 500) - log as exception
31+
log.exception(error instanceof Error ? error : new Error(String(error)), 'Error in request', { logMessage, statusCode });
32+
} else {
33+
// Client errors (< 500) - log as softFail without stack trace
34+
const errorMessage = error instanceof Error ? error.message : String(error);
35+
log.softFail('Error in request', { logMessage, error: errorMessage, statusCode });
36+
}
3037
if (!res.headersSent) {
3138
res.status(statusCode).json({
3239
jsonrpc: '2.0',
@@ -110,7 +117,7 @@ export function createExpressApp(
110117
});
111118
const sessionId = new URL(req.url, `http://${req.headers.host}`).searchParams.get('sessionId');
112119
if (!sessionId) {
113-
log.error('No session ID provided in POST request');
120+
log.softFail('No session ID provided in POST request', { statusCode: 400 });
114121
res.status(400).json({
115122
jsonrpc: '2.0',
116123
error: {
@@ -125,7 +132,7 @@ export function createExpressApp(
125132
if (transport) {
126133
await transport.handlePostMessage(req, res);
127134
} else {
128-
log.error('Server is not connected to the client.');
135+
log.softFail('Server is not connected to the client.', { statusCode: 400 });
129136
res.status(400).json({
130137
jsonrpc: '2.0',
131138
error: {
@@ -232,7 +239,7 @@ export function createExpressApp(
232239
return;
233240
}
234241

235-
log.error('Session not found', { sessionId });
242+
log.softFail('Session not found', { sessionId, statusCode: 400 });
236243
res.status(400).send('Bad Request: Session not found').end();
237244
});
238245

src/mcp/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
55
import log from '@apify/log';
66

77
import { TimeoutError } from '../errors.js';
8+
import { logHttpError } from '../utils/logging.js';
89
import { ACTORIZED_MCP_CONNECTION_TIMEOUT_MSEC } from './const.js';
910
import { getMCPServerID } from './utils.js';
1011

@@ -40,8 +41,7 @@ export async function connectMCPClient(
4041
log.warning('Connection to MCP server using SSE transport timed out', { url });
4142
return null;
4243
}
43-
44-
log.error('Failed to connect to MCP server using SSE transport', { cause: error });
44+
logHttpError(error, 'Failed to connect to MCP server using SSE transport', { url, cause: error });
4545
throw error;
4646
}
4747
}

src/mcp/server.ts

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { callActorGetDataset, defaultTools, getActorsAsTools, toolCategories } f
4040
import { decodeDotPropertyNames } from '../tools/utils.js';
4141
import type { ToolEntry } from '../types.js';
4242
import { buildActorResponseContent } from '../utils/actor-response.js';
43+
import { logHttpError } from '../utils/logging.js';
4344
import { buildMCPResponse } from '../utils/mcp.js';
4445
import { createProgressTracker } from '../utils/progress.js';
4546
import { cloneToolEntry, getToolPublicFieldOnly } from '../utils/tools.js';
@@ -210,7 +211,7 @@ export class ActorsMcpServer {
210211
* Loads missing toolNames from a provided list of tool names.
211212
* Skips toolNames that are already loaded and loads only the missing ones.
212213
* @param toolNames - Array of tool names to ensure are loaded
213-
* @param apifyToken - Apify API token for authentication
214+
* @param apifyClient
214215
*/
215216
public async loadToolsByName(toolNames: string[], apifyClient: ApifyClient) {
216217
const loadedTools = this.listAllToolNames();
@@ -245,7 +246,7 @@ export class ActorsMcpServer {
245246
* Load actors as tools, upsert them to the server, and return the tool entries.
246247
* This is a public method that wraps getActorsAsTools and handles the upsert operation.
247248
* @param actorIdsOrNames - Array of actor IDs or names to load as tools
248-
* @param apifyToken - Apify API token for authentication
249+
* @param apifyClient
249250
* @returns Promise<ToolEntry[]> - Array of loaded tool entries
250251
*/
251252
public async loadActorsAsTools(actorIdsOrNames: string[], apifyClient: ApifyClient): Promise<ToolEntry[]> {
@@ -343,8 +344,8 @@ export class ActorsMcpServer {
343344
}
344345
} else {
345346
// No skyfire mode and telemetry disabled - store tools as-is
346-
for (const wrap of tools) {
347-
this.tools.set(wrap.name, wrap);
347+
for (const tool of tools) {
348+
this.tools.set(tool.name, tool);
348349
}
349350
}
350351
if (shouldNotifyToolsChangedHandler) this.notifyToolsChangedHandler();
@@ -532,8 +533,10 @@ export class ActorsMcpServer {
532533

533534
// Validate token
534535
if (!apifyToken && !this.options.skyfireMode) {
535-
const msg = 'APIFY_TOKEN is required. It must be set in the environment variables or passed as a parameter in the body.';
536-
log.error(msg);
536+
const msg = `APIFY_TOKEN is required but was not provided.
537+
Please set the APIFY_TOKEN environment variable or pass it as a parameter in the request body.
538+
You can obtain your Apify token from https://console.apify.com/account/integrations.`;
539+
log.softFail(msg, { statusCode: 400 });
537540
await this.server.sendLoggingMessage({ level: 'error', data: msg });
538541
throw new McpError(
539542
ErrorCode.InvalidParams,
@@ -556,17 +559,21 @@ export class ActorsMcpServer {
556559
const tool = Array.from(this.tools.values())
557560
.find((t) => t.name === name || (t.type === 'actor' && t.actorFullName === name));
558561
if (!tool) {
559-
const msg = `Tool ${name} not found. Available tools: ${this.listToolNames().join(', ')}`;
560-
log.error(msg);
562+
const availableTools = this.listToolNames();
563+
const msg = `Tool "${name}" was not found.
564+
Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
565+
Please verify the tool name is correct. You can list all available tools using the tools/list request.`;
566+
log.softFail(msg, { statusCode: 404 });
561567
await this.server.sendLoggingMessage({ level: 'error', data: msg });
562568
throw new McpError(
563569
ErrorCode.InvalidParams,
564570
msg,
565571
);
566572
}
567573
if (!args) {
568-
const msg = `Missing arguments for tool ${name}`;
569-
log.error(msg);
574+
const msg = `Missing arguments for tool "${name}".
575+
Please provide the required arguments for this tool. Check the tool's input schema using ${HelperTools.ACTOR_GET_DETAILS} tool to see what parameters are required.`;
576+
log.softFail(msg, { statusCode: 400 });
570577
await this.server.sendLoggingMessage({ level: 'error', data: msg });
571578
throw new McpError(
572579
ErrorCode.InvalidParams,
@@ -578,8 +585,12 @@ export class ActorsMcpServer {
578585
args = decodeDotPropertyNames(args);
579586
log.debug('Validate arguments for tool', { toolName: tool.name, input: args });
580587
if (!tool.ajvValidate(args)) {
581-
const msg = `Invalid arguments for tool ${tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool.ajvValidate.errors)}`;
582-
log.error(msg);
588+
const errors = tool?.ajvValidate.errors || [];
589+
const errorMessages = errors.map((e: { message?: string; instancePath?: string }) => `${e.instancePath || 'root'}: ${e.message || 'validation error'}`).join('; ');
590+
const msg = `Invalid arguments for tool "${tool.name}".
591+
Validation errors: ${errorMessages}.
592+
Please check the tool's input schema using ${HelperTools.ACTOR_GET_DETAILS} tool and ensure all required parameters are provided with correct types and values.`;
593+
log.softFail(msg, { statusCode: 400 });
583594
await this.server.sendLoggingMessage({ level: 'error', data: msg });
584595
throw new McpError(
585596
ErrorCode.InvalidParams,
@@ -653,14 +664,11 @@ export class ActorsMcpServer {
653664
try {
654665
client = await connectMCPClient(tool.serverUrl, apifyToken);
655666
if (!client) {
656-
const msg = `Failed to connect to MCP server ${tool.serverUrl}`;
657-
log.error(msg);
667+
const msg = `Failed to connect to MCP server at "${tool.serverUrl}".
668+
Please verify the server URL is correct and accessible, and ensure you have a valid Apify token with appropriate permissions.`;
669+
log.softFail(msg, { statusCode: 408 }); // 408 Request Timeout
658670
await this.server.sendLoggingMessage({ level: 'error', data: msg });
659-
return {
660-
content: [
661-
{ type: 'text', text: msg },
662-
],
663-
};
671+
return buildMCPResponse([msg], true);
664672
}
665673

666674
// Only set up notification handlers if progressToken is provided by the client
@@ -701,12 +709,7 @@ export class ActorsMcpServer {
701709
if (this.options.skyfireMode
702710
&& args['skyfire-pay-id'] === undefined
703711
) {
704-
return {
705-
content: [{
706-
type: 'text',
707-
text: SKYFIRE_TOOL_INSTRUCTIONS,
708-
}],
709-
};
712+
return buildMCPResponse([SKYFIRE_TOOL_INSTRUCTIONS]);
710713
}
711714

712715
// Create progress tracker if progressToken is available
@@ -748,15 +751,19 @@ export class ActorsMcpServer {
748751
}
749752
}
750753
} catch (error) {
751-
log.error('Error occurred while calling tool', { toolName: name, error });
754+
logHttpError(error, 'Error occurred while calling tool', { toolName: name });
752755
const errorMessage = (error instanceof Error) ? error.message : 'Unknown error';
753756
return buildMCPResponse([
754-
`Error calling tool ${name}: ${errorMessage}`,
755-
]);
757+
`Error calling tool "${name}": ${errorMessage}.
758+
Please verify the tool name, input parameters, and ensure all required resources are available.`,
759+
], true);
756760
}
757761

758-
const msg = `Unknown tool: ${name}`;
759-
log.error(msg);
762+
const availableTools = this.listToolNames();
763+
const msg = `Unknown tool type for "${name}".
764+
Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
765+
Please verify the tool name and ensure the tool is properly registered.`;
766+
log.softFail(msg, { statusCode: 404 });
760767
await this.server.sendLoggingMessage({
761768
level: 'error',
762769
data: msg,

0 commit comments

Comments
 (0)