Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/tools/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ EXAMPLES:
}
const toolsResponse = await client.listTools();

const toolsInfo = toolsResponse.tools.map((tool) => `**${tool.name}**\n${tool.description || 'No description'}\nInput schema: ${JSON.stringify(tool.inputSchema, null, 2)}`,
const toolsInfo = toolsResponse.tools.map((tool) => `**${tool.name}**\n${tool.description || 'No description'}\nInput schema:\n\`\`\`json\n${JSON.stringify(tool.inputSchema)}\n\`\`\``,
).join('\n\n');

return buildMCPResponse([`This is an MCP Server Actor with the following tools:\n\n${toolsInfo}\n\nTo call a tool, use step="call" with actor name format: "${baseActorName}:{toolName}"`]);
Expand All @@ -422,7 +422,7 @@ EXAMPLES:
}
const content = [
`Actor name: ${actorName}`,
`Input schema: \n${JSON.stringify(details.inputSchema, null, 0)}`,
`Input schema:\n\`\`\`json\n${JSON.stringify(details.inputSchema)}\n\`\`\``,
`To run Actor, use step="call" with Actor name format: "${actorName}"`,
];
// Add Skyfire instructions also in the info performStep since clients are most likely truncating
Expand Down Expand Up @@ -504,7 +504,7 @@ EXAMPLES:
const { errors } = actor.tool.ajvValidate;
const content = [
`Input validation failed for Actor '${actorName}'. Please ensure your input matches the Actor's input schema.`,
`Input schema:\n${JSON.stringify(actor.tool.inputSchema)}`,
`Input schema:\n\`\`\`json\n${JSON.stringify(actor.tool.inputSchema)}\n\`\`\``,
];
if (errors && errors.length > 0) {
content.push(`Validation errors: ${errors.map((e) => e.message).join(', ')}`);
Expand Down
2 changes: 1 addition & 1 deletion src/tools/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const actorDefinitionTool: ToolEntry = {
const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties });
v.input.properties = shortenProperties(properties);
}
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
},
} as InternalTool,
};
6 changes: 3 additions & 3 deletions src/tools/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ USAGE EXAMPLES:
if (!v) {
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
}
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -116,7 +116,7 @@ USAGE EXAMPLES:
if (!v) {
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
}
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -191,7 +191,7 @@ USAGE EXAMPLES:
return {
content: [{
type: 'text',
text: JSON.stringify(schema),
text: `\`\`\`json\n${JSON.stringify(schema)}\n\`\`\``,
}],
};
},
Expand Down
2 changes: 1 addition & 1 deletion src/tools/dataset_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ USAGE EXAMPLES:
desc: parsed.desc,
unnamed: parsed.unnamed,
});
return { content: [{ type: 'text', text: JSON.stringify(datasets) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(datasets)}\n\`\`\`` }] };
},
} as InternalTool,
};
2 changes: 1 addition & 1 deletion src/tools/fetch-actor-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ USAGE EXAMPLES:

// Include input schema if it has properties
if (details.inputSchema.properties || Object.keys(details.inputSchema.properties).length !== 0) {
content.push({ type: 'text', text: `# [Input schema](${actorUrl}/input)\n\`\`\`json\n${JSON.stringify(details.inputSchema, null, 0)}\n\`\`\`` });
content.push({ type: 'text', text: `# [Input schema](${actorUrl}/input)\n\`\`\`json\n${JSON.stringify(details.inputSchema)}\n\`\`\`` });
}
// Return the actor card, README, and input schema (if it has non-empty properties) as separate text blocks
// This allows better formatting in the final output
Expand Down
2 changes: 1 addition & 1 deletion src/tools/get-actor-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Note: This tool is automatically included if the Apify MCP Server is configured
.map((item) => cleanEmptyProperties(item))
.filter((item) => item !== undefined);

let outputText = JSON.stringify(cleanedItems);
let outputText = `\`\`\`json\n${JSON.stringify(cleanedItems)}\n\`\`\``;
let truncated = false;
if (outputText.length > TOOL_MAX_OUTPUT_CHARS) {
outputText = outputText.slice(0, TOOL_MAX_OUTPUT_CHARS);
Expand Down
6 changes: 3 additions & 3 deletions src/tools/key_value_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ USAGE EXAMPLES:
const parsed = getKeyValueStoreArgs.parse(args);
const client = new ApifyClient({ token: apifyToken });
const store = await client.keyValueStore(parsed.storeId).get();
return { content: [{ type: 'text', text: JSON.stringify(store) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(store)}\n\`\`\`` }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -82,7 +82,7 @@ USAGE EXAMPLES:
exclusiveStartKey: parsed.exclusiveStartKey,
limit: parsed.limit,
});
return { content: [{ type: 'text', text: JSON.stringify(keys) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(keys)}\n\`\`\`` }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -120,7 +120,7 @@ USAGE EXAMPLES:
const parsed = getKeyValueStoreRecordArgs.parse(args);
const client = new ApifyClient({ token: apifyToken });
const record = await client.keyValueStore(parsed.storeId).getRecord(parsed.recordKey);
return { content: [{ type: 'text', text: JSON.stringify(record) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(record)}\n\`\`\`` }] };
},
} as InternalTool,
};
2 changes: 1 addition & 1 deletion src/tools/key_value_store_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ USAGE EXAMPLES:
desc: parsed.desc,
unnamed: parsed.unnamed,
});
return { content: [{ type: 'text', text: JSON.stringify(stores) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(stores)}\n\`\`\`` }] };
},
} as InternalTool,
};
4 changes: 2 additions & 2 deletions src/tools/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ USAGE EXAMPLES:
if (!v) {
return { content: [{ type: 'text', text: `Run with ID '${parsed.runId}' not found.` }] };
}
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -116,7 +116,7 @@ USAGE EXAMPLES:
const parsed = abortRunArgs.parse(args);
const client = new ApifyClient({ token: apifyToken });
const v = await client.run(parsed.runId).abort({ gracefully: parsed.gracefully });
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
},
} as InternalTool,
};
2 changes: 1 addition & 1 deletion src/tools/run_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ USAGE EXAMPLES:
const parsed = getUserRunsListArgs.parse(args);
const client = new ApifyClient({ token: apifyToken });
const runs = await client.runs().list({ limit: parsed.limit, offset: parsed.offset, desc: parsed.desc, status: parsed.status });
return { content: [{ type: 'text', text: JSON.stringify(runs) }] };
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(runs)}\n\`\`\`` }] };
},
} as InternalTool,
};
2 changes: 1 addition & 1 deletion src/utils/actor-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Results summary:
Actor output data schema:
* You can use this schema to understand the structure of the output data and, for example, retrieve specific fields based on your current task.
\`\`\`json
${JSON.stringify(displaySchema, null, 2)}
${JSON.stringify(displaySchema)}
\`\`\`

Above this text block is a preview of the Actor output containing ${result.previewItems.length} item(s).${itemCount !== result.previewItems.length ? ` You have access only to a limited preview of the Actor output. Do not present this as the full output, as you have only ${result.previewItems.length} item(s) available instead of the full ${itemCount} item(s). Be aware of this and inform users about the currently loaded count and the total available output items count.` : ''}
Expand Down
19 changes: 15 additions & 4 deletions tests/integration/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ function expectToolNamesToContain(names: string[], toolNames: string[] = []) {
toolNames.forEach((name) => expect(names).toContain(name));
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extractJsonFromMarkdown(text: string): any {
// Handle markdown code blocks like ```json
const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/);
if (jsonMatch) {
return JSON.parse(jsonMatch[1]);
}
// If no markdown formatting, assume it's raw JSON
return JSON.parse(text);
}

async function callPythonExampleActor(client: Client, selectedToolName: string) {
const result = await client.callTool({
name: selectedToolName,
Expand All @@ -53,7 +64,7 @@ async function callPythonExampleActor(client: Client, selectedToolName: string)
};
// Parse the JSON to compare objects regardless of property order
const actual = content[0];
expect(JSON.parse(actual.text)).toEqual(JSON.parse(expected.text));
expect(extractJsonFromMarkdown(actual.text)).toEqual(JSON.parse(expected.text));
expect(actual.type).toBe(expected.type);
}

Expand Down Expand Up @@ -836,7 +847,7 @@ export function createIntegrationTestsSuite(

expect(outputResult.content).toBeDefined();
const outputContent = outputResult.content as { text: string; type: string }[];
const output = JSON.parse(outputContent[0].text);
const output = extractJsonFromMarkdown(outputContent[0].text);
expect(Array.isArray(output)).toBe(true);
expect(output.length).toBeGreaterThan(0);
expect(output[0]).toHaveProperty('metadata.title');
Expand Down Expand Up @@ -894,7 +905,7 @@ export function createIntegrationTestsSuite(
// Validate the output contains the expected structure with metadata.title
expect(outputResult.content).toBeDefined();
const outputContent = outputResult.content as { text: string; type: string }[];
const output = JSON.parse(outputContent[0].text);
const output = extractJsonFromMarkdown(outputContent[0].text);
expect(Array.isArray(output)).toBe(true);
expect(output.length).toBeGreaterThan(0);
expect(output[0]).toHaveProperty('metadata.title');
Expand Down Expand Up @@ -935,7 +946,7 @@ export function createIntegrationTestsSuite(

expect(outputResult.content).toBeDefined();
const outputContent = outputResult.content as { text: string; type: string }[];
const output = JSON.parse(outputContent[0].text);
const output = extractJsonFromMarkdown(outputContent[0].text);
expect(Array.isArray(output)).toBe(true);
expect(output.length).toBe(1);
expect(output[0]).toHaveProperty('first_number', input.first_number);
Expand Down