Skip to content

Commit e97851b

Browse files
committed
feat: improve tool output json markdown format
1 parent 663f640 commit e97851b

File tree

10 files changed

+30
-20
lines changed

10 files changed

+30
-20
lines changed

src/tools/actor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ EXAMPLES:
407407
}
408408
const toolsResponse = await client.listTools();
409409

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

413413
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}"`]);
@@ -422,7 +422,7 @@ EXAMPLES:
422422
}
423423
const content = [
424424
`Actor name: ${actorName}`,
425-
`Input schema: \n${JSON.stringify(details.inputSchema, null, 0)}`,
425+
`Input schema:\n\`\`\`json\n${JSON.stringify(details.inputSchema, null, 0)}\n\`\`\``,
426426
`To run Actor, use step="call" with Actor name format: "${actorName}"`,
427427
];
428428
// Add Skyfire instructions also in the info performStep since clients are most likely truncating
@@ -504,7 +504,7 @@ EXAMPLES:
504504
const { errors } = actor.tool.ajvValidate;
505505
const content = [
506506
`Input validation failed for Actor '${actorName}'. Please ensure your input matches the Actor's input schema.`,
507-
`Input schema:\n${JSON.stringify(actor.tool.inputSchema)}`,
507+
`Input schema:\n\`\`\`json\n${JSON.stringify(actor.tool.inputSchema)}\n\`\`\``,
508508
];
509509
if (errors && errors.length > 0) {
510510
content.push(`Validation errors: ${errors.map((e) => e.message).join(', ')}`);

src/tools/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export const actorDefinitionTool: ToolEntry = {
131131
const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties });
132132
v.input.properties = shortenProperties(properties);
133133
}
134-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
134+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v, null, 2)}\n\`\`\`` }] };
135135
},
136136
} as InternalTool,
137137
};

src/tools/dataset.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ USAGE EXAMPLES:
6767
if (!v) {
6868
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
6969
}
70-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
70+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
7171
},
7272
} as InternalTool,
7373
};
@@ -116,7 +116,7 @@ USAGE EXAMPLES:
116116
if (!v) {
117117
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
118118
}
119-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
119+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
120120
},
121121
} as InternalTool,
122122
};
@@ -191,7 +191,7 @@ USAGE EXAMPLES:
191191
return {
192192
content: [{
193193
type: 'text',
194-
text: JSON.stringify(schema),
194+
text: `\`\`\`json\n${JSON.stringify(schema)}\n\`\`\``,
195195
}],
196196
};
197197
},

src/tools/dataset_collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ USAGE EXAMPLES:
5454
desc: parsed.desc,
5555
unnamed: parsed.unnamed,
5656
});
57-
return { content: [{ type: 'text', text: JSON.stringify(datasets) }] };
57+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(datasets, null, 2)}\n\`\`\`` }] };
5858
},
5959
} as InternalTool,
6060
};

src/tools/get-actor-output.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Note: This tool is automatically included if the Apify MCP Server is configured
147147
.map((item) => cleanEmptyProperties(item))
148148
.filter((item) => item !== undefined);
149149

150-
let outputText = JSON.stringify(cleanedItems);
150+
let outputText = `\`\`\`json\n${JSON.stringify(cleanedItems)}\n\`\`\``;
151151
let truncated = false;
152152
if (outputText.length > TOOL_MAX_OUTPUT_CHARS) {
153153
outputText = outputText.slice(0, TOOL_MAX_OUTPUT_CHARS);

src/tools/key_value_store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ USAGE EXAMPLES:
3636
const parsed = getKeyValueStoreArgs.parse(args);
3737
const client = new ApifyClient({ token: apifyToken });
3838
const store = await client.keyValueStore(parsed.storeId).get();
39-
return { content: [{ type: 'text', text: JSON.stringify(store) }] };
39+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(store)}\n\`\`\`` }] };
4040
},
4141
} as InternalTool,
4242
};
@@ -82,7 +82,7 @@ USAGE EXAMPLES:
8282
exclusiveStartKey: parsed.exclusiveStartKey,
8383
limit: parsed.limit,
8484
});
85-
return { content: [{ type: 'text', text: JSON.stringify(keys) }] };
85+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(keys)}\n\`\`\`` }] };
8686
},
8787
} as InternalTool,
8888
};
@@ -120,7 +120,7 @@ USAGE EXAMPLES:
120120
const parsed = getKeyValueStoreRecordArgs.parse(args);
121121
const client = new ApifyClient({ token: apifyToken });
122122
const record = await client.keyValueStore(parsed.storeId).getRecord(parsed.recordKey);
123-
return { content: [{ type: 'text', text: JSON.stringify(record) }] };
123+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(record)}\n\`\`\`` }] };
124124
},
125125
} as InternalTool,
126126
};

src/tools/key_value_store_collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ USAGE EXAMPLES:
5454
desc: parsed.desc,
5555
unnamed: parsed.unnamed,
5656
});
57-
return { content: [{ type: 'text', text: JSON.stringify(stores) }] };
57+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(stores, null, 2)}\n\`\`\`` }] };
5858
},
5959
} as InternalTool,
6060
};

src/tools/run.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ USAGE EXAMPLES:
4646
if (!v) {
4747
return { content: [{ type: 'text', text: `Run with ID '${parsed.runId}' not found.` }] };
4848
}
49-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
49+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
5050
},
5151
} as InternalTool,
5252
};
@@ -116,7 +116,7 @@ USAGE EXAMPLES:
116116
const parsed = abortRunArgs.parse(args);
117117
const client = new ApifyClient({ token: apifyToken });
118118
const v = await client.run(parsed.runId).abort({ gracefully: parsed.gracefully });
119-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
119+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
120120
},
121121
} as InternalTool,
122122
};

src/tools/run_collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ USAGE EXAMPLES:
4747
const parsed = getUserRunsListArgs.parse(args);
4848
const client = new ApifyClient({ token: apifyToken });
4949
const runs = await client.runs().list({ limit: parsed.limit, offset: parsed.offset, desc: parsed.desc, status: parsed.status });
50-
return { content: [{ type: 'text', text: JSON.stringify(runs) }] };
50+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(runs, null, 2)}\n\`\`\`` }] };
5151
},
5252
} as InternalTool,
5353
};

tests/integration/suite.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ function expectToolNamesToContain(names: string[], toolNames: string[] = []) {
3131
toolNames.forEach((name) => expect(names).toContain(name));
3232
}
3333

34+
function extractJsonFromMarkdown(text: string): any {
35+
// Handle markdown code blocks like ```json
36+
const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/);
37+
if (jsonMatch) {
38+
return JSON.parse(jsonMatch[1]);
39+
}
40+
// If no markdown formatting, assume it's raw JSON
41+
return JSON.parse(text);
42+
}
43+
3444
async function callPythonExampleActor(client: Client, selectedToolName: string) {
3545
const result = await client.callTool({
3646
name: selectedToolName,
@@ -53,7 +63,7 @@ async function callPythonExampleActor(client: Client, selectedToolName: string)
5363
};
5464
// Parse the JSON to compare objects regardless of property order
5565
const actual = content[0];
56-
expect(JSON.parse(actual.text)).toEqual(JSON.parse(expected.text));
66+
expect(extractJsonFromMarkdown(actual.text)).toEqual(JSON.parse(expected.text));
5767
expect(actual.type).toBe(expected.type);
5868
}
5969

@@ -836,7 +846,7 @@ export function createIntegrationTestsSuite(
836846

837847
expect(outputResult.content).toBeDefined();
838848
const outputContent = outputResult.content as { text: string; type: string }[];
839-
const output = JSON.parse(outputContent[0].text);
849+
const output = extractJsonFromMarkdown(outputContent[0].text);
840850
expect(Array.isArray(output)).toBe(true);
841851
expect(output.length).toBeGreaterThan(0);
842852
expect(output[0]).toHaveProperty('metadata.title');
@@ -894,7 +904,7 @@ export function createIntegrationTestsSuite(
894904
// Validate the output contains the expected structure with metadata.title
895905
expect(outputResult.content).toBeDefined();
896906
const outputContent = outputResult.content as { text: string; type: string }[];
897-
const output = JSON.parse(outputContent[0].text);
907+
const output = extractJsonFromMarkdown(outputContent[0].text);
898908
expect(Array.isArray(output)).toBe(true);
899909
expect(output.length).toBeGreaterThan(0);
900910
expect(output[0]).toHaveProperty('metadata.title');
@@ -935,7 +945,7 @@ export function createIntegrationTestsSuite(
935945

936946
expect(outputResult.content).toBeDefined();
937947
const outputContent = outputResult.content as { text: string; type: string }[];
938-
const output = JSON.parse(outputContent[0].text);
948+
const output = extractJsonFromMarkdown(outputContent[0].text);
939949
expect(Array.isArray(output)).toBe(true);
940950
expect(output.length).toBe(1);
941951
expect(output[0]).toHaveProperty('first_number', input.first_number);

0 commit comments

Comments
 (0)