Skip to content
Draft
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
9 changes: 5 additions & 4 deletions src/tools/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import { getActorMCPServerPath, getActorMCPServerURL } from '../mcp/actors.js';
import { connectMCPClient } from '../mcp/client.js';
import { getMCPServerTools } from '../mcp/proxy.js';
import { actorDefinitionPrunedCache } from '../state.js';
import type { ActorDefinitionStorage, ActorInfo, ApifyToken, DatasetItem, ToolEntry } from '../types.js';
import type { ActorDefinitionStorage, ActorInfo, ApifyToken, DatasetItem, IActorInputSchema, ToolEntry } from '../types.js';
import { ensureOutputWithinCharLimit, getActorDefinitionStorageFieldNames, getActorMcpUrlCached } from '../utils/actor.js';
import { fetchActorDetails } from '../utils/actor-details.js';
import { buildActorResponseContent } from '../utils/actor-response.js';
import { ajv } from '../utils/ajv.js';
import { jsonSchemaToMarkdown } from '../utils/json-schema-to-markdown.js';
import { buildMCPResponse } from '../utils/mcp.js';
import type { ProgressTracker } from '../utils/progress.js';
import type { JsonSchemaProperty } from '../utils/schema-generation.js';
Expand Down Expand Up @@ -388,7 +389,7 @@ The step parameter enforces this workflow - you cannot call an Actor without fir
client = await connectMCPClient(mcpServerUrl, apifyToken);
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'}\n\n${jsonSchemaToMarkdown(tool.inputSchema as IActorInputSchema)}`,
).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 @@ -402,7 +403,7 @@ The step parameter enforces this workflow - you cannot call an Actor without fir
return buildMCPResponse([`Actor information for '${baseActorName}' was not found. Please check the Actor ID or name and ensure the Actor exists.`]);
}
const content = [
{ type: 'text', text: `**Input Schema:**\n${JSON.stringify(details.inputSchema, null, 0)}` },
{ type: 'text', text: jsonSchemaToMarkdown(details.inputSchema) },
];
/**
* Add Skyfire instructions also in the info step since clients are most likely truncating the long tool description of the call-actor.
Expand Down Expand Up @@ -478,7 +479,7 @@ The step parameter enforces this workflow - you cannot call an Actor without fir
if (errors && errors.length > 0) {
return buildMCPResponse([
`Input validation failed for Actor '${actorName}': ${errors.map((e) => e.message).join(', ')}`,
`Input Schema:\n${JSON.stringify(actor.tool.inputSchema)}`,
jsonSchemaToMarkdown(actor.tool.inputSchema as IActorInputSchema),
]);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/tools/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js';
import type {
ActorDefinitionPruned,
ActorDefinitionWithDesc,
IActorInputSchema,
InternalTool,
ISchemaProperties,
ToolEntry,
} from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonSchemaToMarkdown } from '../utils/json-schema-to-markdown.js';
import { filterSchemaProperties, shortenProperties } from './utils.js';

/**
Expand Down Expand Up @@ -131,7 +133,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: jsonSchemaToMarkdown((v.input || {}) as IActorInputSchema) }] };
},
} as InternalTool,
};
7 changes: 4 additions & 3 deletions src/tools/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { parseCommaSeparatedList } from '../utils/generic.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';
import { generateSchemaFromItems } from '../utils/schema-generation.js';

const getDatasetArgs = z.object({
Expand Down Expand Up @@ -61,7 +62,7 @@ export const getDataset: ToolEntry = {
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: jsonToMarkdown(v) }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -108,7 +109,7 @@ export const getDatasetItems: ToolEntry = {
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: jsonToMarkdown(v) }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -176,7 +177,7 @@ export const getDatasetSchema: ToolEntry = {
return {
content: [{
type: 'text',
text: JSON.stringify(schema),
text: JSON.stringify(schema), // TODO: jsonSchemaToMarkdown don't have implemented array support
}],
};
},
Expand Down
3 changes: 2 additions & 1 deletion src/tools/dataset_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

const getUserDatasetsListArgs = z.object({
offset: z.number()
Expand Down Expand Up @@ -48,7 +49,7 @@ export const getUserDatasetsList: ToolEntry = {
desc: parsed.desc,
unnamed: parsed.unnamed,
});
return { content: [{ type: 'text', text: JSON.stringify(datasets) }] };
return { content: [{ type: 'text', text: jsonToMarkdown(datasets) }] };
},
} as InternalTool,
};
3 changes: 2 additions & 1 deletion src/tools/fetch-actor-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { fetchActorDetails } from '../utils/actor-details.js';
import { ajv } from '../utils/ajv.js';
import { jsonSchemaToMarkdown } from '../utils/json-schema-to-markdown.js';

const fetchActorDetailsToolArgsSchema = z.object({
actor: z.string()
Expand Down Expand Up @@ -42,7 +43,7 @@ export const fetchActorDetailsTool: ToolEntry = {
content: [
{ type: 'text', text: `**Actor card**:\n${details.actorCard}` },
{ type: 'text', text: `**README:**\n${details.readme}` },
{ type: 'text', text: `**Input Schema:**\n${JSON.stringify(details.inputSchema, null, 0)}` },
{ type: 'text', text: jsonSchemaToMarkdown(details.inputSchema) },
],
};
},
Expand Down
3 changes: 2 additions & 1 deletion src/tools/get-actor-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HelperTools, SKYFIRE_TOOL_INSTRUCTIONS, TOOL_MAX_OUTPUT_CHARS } from '.
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { getValuesByDotKeys, parseCommaSeparatedList } from '../utils/generic.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

/**
* Zod schema for get-actor-output tool arguments
Expand Down Expand Up @@ -134,7 +135,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 = jsonToMarkdown(cleanedItems);
let truncated = false;
if (outputText.length > TOOL_MAX_OUTPUT_CHARS) {
outputText = outputText.slice(0, TOOL_MAX_OUTPUT_CHARS);
Expand Down
7 changes: 4 additions & 3 deletions src/tools/key_value_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

const getKeyValueStoreArgs = z.object({
storeId: z.string()
Expand All @@ -30,7 +31,7 @@ export const getKeyValueStore: ToolEntry = {
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: store ? jsonToMarkdown(store) : 'Value not found' }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -70,7 +71,7 @@ export const getKeyValueStoreKeys: ToolEntry = {
exclusiveStartKey: parsed.exclusiveStartKey,
limit: parsed.limit,
});
return { content: [{ type: 'text', text: JSON.stringify(keys) }] };
return { content: [{ type: 'text', text: jsonToMarkdown(keys) }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -104,7 +105,7 @@ export const getKeyValueStoreRecord: ToolEntry = {
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: record ? jsonToMarkdown(record) : 'Value not found' }] };
},
} as InternalTool,
};
3 changes: 2 additions & 1 deletion src/tools/key_value_store_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

const getUserKeyValueStoresListArgs = z.object({
offset: z.number()
Expand Down Expand Up @@ -48,7 +49,7 @@ export const getUserKeyValueStoresList: ToolEntry = {
desc: parsed.desc,
unnamed: parsed.unnamed,
});
return { content: [{ type: 'text', text: JSON.stringify(stores) }] };
return { content: [{ type: 'text', text: jsonToMarkdown(stores) }] };
},
} as InternalTool,
};
5 changes: 3 additions & 2 deletions src/tools/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

const getActorRunArgs = z.object({
runId: z.string()
Expand Down Expand Up @@ -40,7 +41,7 @@ export const getActorRun: ToolEntry = {
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: jsonToMarkdown(v) }] };
},
} as InternalTool,
};
Expand Down Expand Up @@ -96,7 +97,7 @@ export const abortActorRun: ToolEntry = {
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: jsonToMarkdown(v) }] };
},
} as InternalTool,
};
3 changes: 2 additions & 1 deletion src/tools/run_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { InternalTool, ToolEntry } from '../types.js';
import { ajv } from '../utils/ajv.js';
import { jsonToMarkdown } from '../utils/json-to-markdown.js';

const getUserRunsListArgs = z.object({
offset: z.number()
Expand Down Expand Up @@ -40,7 +41,7 @@ export const getUserRunsList: ToolEntry = {
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: jsonToMarkdown(runs) }] };
},
} as InternalTool,
};
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ISchemaProperties {
enumTitles?: string[]; // Array of string titles for the enum
default?: unknown;
prefill?: unknown;
format?: string;

items?: ISchemaProperties;
editor?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/utils/actor-response.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CallActorGetDatasetResult } from '../tools/actor.js';
import { jsonToMarkdown } from './json-to-markdown.js';

/**
* Builds the response content for Actor tool calls.
Expand Down Expand Up @@ -47,7 +48,7 @@ If you need to retrieve additional data, use the "get-actor-output" tool with: d
`;

const itemsPreviewText = result.previewItems.length > 0
? JSON.stringify(result.previewItems)
? jsonToMarkdown(result.previewItems)
: `No items available for preview—either the Actor did not return any items or they are too large for preview. In this case, use the "get-actor-output" tool.`;

// Build content array
Expand Down
75 changes: 75 additions & 0 deletions src/utils/json-schema-to-markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { IActorInputSchema, ISchemaProperties } from '../types';

function visibleEmpty(value: string) {
return value === '' ? '<empty>' : value;
}

function formatProperty(key: string, value: ISchemaProperties, requiredFields: Set<string>, level = 2): string {
const isRequired = requiredFields.has(key);
const requiredText = isRequired ? 'required' : 'optional';

let result = `${'#'.repeat(level)} \`${key}\` ${requiredText} ${value.type}`;

if (value.format) {
result += ` format:${value.format}`;
}

if (value.prefill !== undefined && !Array.isArray(value.prefill)) {
result += ' prefill:';
result += visibleEmpty(String(value.prefill));
} else if (value.default !== undefined) {
result += ' default:';
result += visibleEmpty(String(value.default));
}

// Handle nested properties for objects
if (value.type === 'object' && value.properties) {
result += '\n';
const nestedEntries = Object.entries(value.properties);
for (let i = 0; i < nestedEntries.length; i++) {
const [nestedKey, nestedValue] = nestedEntries[i];
result += formatProperty(nestedKey, nestedValue, requiredFields, level + 1);
if (i < nestedEntries.length - 1) {
result += '\n';
}
}
return result;
}

if (value.enum || value.description) {
result += '\n';
if (value.enum) {
let enumLine = 'options: ';
enumLine += value.enum.map(visibleEmpty).join(', ');
result += enumLine;
result += '\n';
}
if (value.description) {
result += value.description;
}
}

return result;
}

export function jsonSchemaToMarkdown(inputSchema: IActorInputSchema) {
const requiredFields = new Set(Array.isArray(inputSchema.required) ? inputSchema.required : []);

let markdown = '# JSON Schema';
if (inputSchema.description) {
markdown += '\n\n';
markdown += inputSchema.description;
}
markdown += '\n\n'; // Add blank line after title/description

const properties = inputSchema.properties ? Object.entries(inputSchema.properties) : [];
for (let i = 0; i < properties.length; i++) {
const [key, value] = properties[i];
markdown += formatProperty(key, value, requiredFields);
if (i < properties.length - 1) {
markdown += '\n\n'; // Add blank line between properties
}
}

return markdown;
}
Loading