Skip to content

Commit 1a03d08

Browse files
committed
fix error and input handling
1 parent 2361b71 commit 1a03d08

File tree

6 files changed

+118
-23
lines changed

6 files changed

+118
-23
lines changed

src/tools/actor.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,20 @@ export const getActor: ToolEntry = {
245245
ajvValidate: ajv.compile(zodToJsonSchema(getActorArgs)),
246246
call: async (toolArgs) => {
247247
const { args, apifyToken } = toolArgs;
248-
const parsed = getActorArgs.parse(args);
248+
const { actorId } = getActorArgs.parse(args);
249+
if (!actorId || typeof actorId !== 'string' || actorId.trim() === '') {
250+
return { content: [{ type: 'text', text: 'Actor ID is required.' }] };
251+
}
249252
const client = new ApifyClient({ token: apifyToken });
250-
// Get Actor - contains a lot of irrelevant information
251-
const actor = await client.actor(parsed.actorId).get();
252-
return { content: [{ type: 'text', text: JSON.stringify(actor) }] };
253+
try {
254+
const actor = await client.actor(actorId).get();
255+
if (!actor) {
256+
return { content: [{ type: 'text', text: `Actor '${actorId}' not found.` }] };
257+
}
258+
return { content: [{ type: 'text', text: JSON.stringify(actor) }] };
259+
} catch {
260+
return { content: [{ type: 'text', text: `Invalid actor ID or actor not found.` }] };
261+
}
253262
},
254263
} as InternalTool,
255264
};

src/tools/dataset.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,19 @@ export const getDataset: ToolEntry = {
5252
call: async (toolArgs) => {
5353
const { args, apifyToken } = toolArgs;
5454
const parsed = getDatasetArgs.parse(args);
55+
if (!parsed.datasetId || typeof parsed.datasetId !== 'string' || parsed.datasetId.trim() === '') {
56+
return { content: [{ type: 'text', text: 'Dataset ID is required.' }] };
57+
}
5558
const client = new ApifyClient({ token: apifyToken });
56-
const v = await client.dataset(parsed.datasetId).get();
57-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
59+
try {
60+
const v = await client.dataset(parsed.datasetId).get();
61+
if (!v) {
62+
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
63+
}
64+
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
65+
} catch {
66+
return { content: [{ type: 'text', text: `Invalid dataset ID or dataset not found.` }] };
67+
}
5868
},
5969
} as InternalTool,
6070
};
@@ -82,23 +92,32 @@ export const getDatasetItems: ToolEntry = {
8292
call: async (toolArgs) => {
8393
const { args, apifyToken } = toolArgs;
8494
const parsed = getDatasetItemsArgs.parse(args);
95+
if (!parsed.datasetId || typeof parsed.datasetId !== 'string' || parsed.datasetId.trim() === '') {
96+
return { content: [{ type: 'text', text: 'Dataset ID is required.' }] };
97+
}
8598
const client = new ApifyClient({ token: apifyToken });
99+
try {
100+
// Convert comma-separated strings to arrays
101+
const fields = parsed.fields?.split(',').map((f) => f.trim());
102+
const omit = parsed.omit?.split(',').map((f) => f.trim());
103+
const flatten = parsed.flatten?.split(',').map((f) => f.trim());
86104

87-
// Convert comma-separated strings to arrays
88-
const fields = parsed.fields?.split(',').map((f) => f.trim());
89-
const omit = parsed.omit?.split(',').map((f) => f.trim());
90-
const flatten = parsed.flatten?.split(',').map((f) => f.trim());
91-
92-
const v = await client.dataset(parsed.datasetId).listItems({
93-
clean: parsed.clean,
94-
offset: parsed.offset,
95-
limit: parsed.limit,
96-
fields,
97-
omit,
98-
desc: parsed.desc,
99-
flatten,
100-
});
101-
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
105+
const v = await client.dataset(parsed.datasetId).listItems({
106+
clean: parsed.clean,
107+
offset: parsed.offset,
108+
limit: parsed.limit,
109+
fields,
110+
omit,
111+
desc: parsed.desc,
112+
flatten,
113+
});
114+
if (!v) {
115+
return { content: [{ type: 'text', text: `Dataset '${parsed.datasetId}' not found.` }] };
116+
}
117+
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
118+
} catch {
119+
return { content: [{ type: 'text', text: `Invalid input or dataset not found.` }] };
120+
}
102121
},
103122
} as InternalTool,
104123
};

src/tools/helpers.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { HelperTools } from '../const.js';
66
import type { InternalTool, ToolEntry } from '../types';
77
import { getActorsAsTools } from './actor.js';
88
import { actorNameToToolName } from './utils.js';
9+
import { ApifyApiError } from 'apify-client';
10+
import log from '@apify/log';
911

1012
const ajv = new Ajv({ coerceTypes: 'array', strict: false });
1113

@@ -79,7 +81,32 @@ export const addTool: ToolEntry = {
7981
call: async (toolArgs) => {
8082
const { apifyMcpServer, mcpServer, apifyToken, args } = toolArgs;
8183
const parsed = addToolArgsSchema.parse(args);
82-
const tools = await getActorsAsTools([parsed.actorName], apifyToken);
84+
if (!parsed.actorName || typeof parsed.actorName !== 'string' || parsed.actorName.trim() === '') {
85+
return { content: [{ type: 'text', text: 'Actor name is required.' }] };
86+
}
87+
if (apifyMcpServer.listAllToolNames().includes(parsed.actorName)) {
88+
return {
89+
content: [{
90+
type: 'text',
91+
text: `Actor ${parsed.actorName} is already available. No new tools were added.`,
92+
}],
93+
};
94+
}
95+
let tools;
96+
try {
97+
tools = await getActorsAsTools([parsed.actorName], apifyToken);
98+
} catch (error) {
99+
if (error instanceof ApifyApiError) {
100+
log.error(`[addTool] Failed to add Actor ${parsed.actorName}: ${error.message}`);
101+
return {
102+
content: [{
103+
type: 'text',
104+
text: `Failed to add Actor ${parsed.actorName}. Error: ${error.message}`,
105+
}],
106+
};
107+
}
108+
throw error;
109+
}
83110
const toolsAdded = apifyMcpServer.upsertTools(tools, true);
84111
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
85112

@@ -112,8 +139,19 @@ export const removeTool: ToolEntry = {
112139
// TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool
113140
call: async (toolArgs) => {
114141
const { apifyMcpServer, mcpServer, args } = toolArgs;
115-
116142
const parsed = removeToolArgsSchema.parse(args);
143+
// Check if tool exists before attempting removal
144+
if (!apifyMcpServer.tools.has(parsed.toolName)) {
145+
// Send notification so client can update its tool list
146+
// just in case the client tool list is out of sync
147+
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
148+
return {
149+
content: [{
150+
type: 'text',
151+
text: `Tool '${args.toolName}' not found. No tools were removed.`,
152+
}],
153+
};
154+
}
117155
const removedTools = apifyMcpServer.removeToolsByName([parsed.toolName], true);
118156
await mcpServer.notification({ method: 'notifications/tools/list_changed' });
119157
return { content: [{ type: 'text', text: `Tools removed: ${removedTools.join(', ')}` }] };

src/tools/key_value_store.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export const getKeyValueStore: ToolEntry = {
2929
call: async (toolArgs) => {
3030
const { args, apifyToken } = toolArgs;
3131
const parsed = getKeyValueStoreArgs.parse(args);
32+
if (!parsed.storeId || typeof parsed.storeId !== 'string' || parsed.storeId.trim() === '') {
33+
return { content: [{ type: 'text', text: 'Store ID is required.' }] };
34+
}
3235
const client = new ApifyClient({ token: apifyToken });
3336
const store = await client.keyValueStore(parsed.storeId).get();
3437
return { content: [{ type: 'text', text: JSON.stringify(store) }] };
@@ -65,6 +68,9 @@ export const getKeyValueStoreKeys: ToolEntry = {
6568
call: async (toolArgs) => {
6669
const { args, apifyToken } = toolArgs;
6770
const parsed = getKeyValueStoreKeysArgs.parse(args);
71+
if (!parsed.storeId || typeof parsed.storeId !== 'string' || parsed.storeId.trim() === '') {
72+
return { content: [{ type: 'text', text: 'Store ID is required.' }] };
73+
}
6874
const client = new ApifyClient({ token: apifyToken });
6975
const keys = await client.keyValueStore(parsed.storeId).listKeys({
7076
exclusiveStartKey: parsed.exclusiveStartKey,
@@ -100,6 +106,12 @@ export const getKeyValueStoreRecord: ToolEntry = {
100106
call: async (toolArgs) => {
101107
const { args, apifyToken } = toolArgs;
102108
const parsed = getKeyValueStoreRecordArgs.parse(args);
109+
if (!parsed.storeId || typeof parsed.storeId !== 'string' || parsed.storeId.trim() === '') {
110+
return { content: [{ type: 'text', text: 'Store ID is required.' }] };
111+
}
112+
if (!parsed.recordKey || typeof parsed.recordKey !== 'string' || parsed.recordKey.trim() === '') {
113+
return { content: [{ type: 'text', text: 'Record key is required.' }] };
114+
}
103115
const client = new ApifyClient({ token: apifyToken });
104116
const record = await client.keyValueStore(parsed.storeId).getRecord(parsed.recordKey);
105117
return { content: [{ type: 'text', text: JSON.stringify(record) }] };

src/tools/run.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export const getActorRun: ToolEntry = {
3333
call: async (toolArgs) => {
3434
const { args, apifyToken } = toolArgs;
3535
const parsed = getActorRunArgs.parse(args);
36+
if (!parsed.runId || typeof parsed.runId !== 'string' || parsed.runId.trim() === '') {
37+
return { content: [{ type: 'text', text: 'Run ID is required.' }] };
38+
}
3639
const client = new ApifyClient({ token: apifyToken });
3740
const v = await client.run(parsed.runId).get();
3841
return { content: [{ type: 'text', text: JSON.stringify(v) }] };
@@ -64,6 +67,9 @@ export const getActorLog: ToolEntry = {
6467
call: async (toolArgs) => {
6568
const { args, apifyToken } = toolArgs;
6669
const parsed = GetRunLogArgs.parse(args);
70+
if (!parsed.runId || typeof parsed.runId !== 'string' || parsed.runId.trim() === '') {
71+
return { content: [{ type: 'text', text: 'Run ID is required.' }] };
72+
}
6773
const client = new ApifyClient({ token: apifyToken });
6874
const v = await client.run(parsed.runId).log().get() ?? '';
6975
const lines = v.split('\n');
@@ -89,6 +95,9 @@ export const abortActorRun: ToolEntry = {
8995
call: async (toolArgs) => {
9096
const { args, apifyToken } = toolArgs;
9197
const parsed = abortRunArgs.parse(args);
98+
if (!parsed.runId || typeof parsed.runId !== 'string' || parsed.runId.trim() === '') {
99+
return { content: [{ type: 'text', text: 'Run ID is required.' }] };
100+
}
92101
const client = new ApifyClient({ token: apifyToken });
93102
const v = await client.run(parsed.runId).abort({ gracefully: parsed.gracefully });
94103
return { content: [{ type: 'text', text: JSON.stringify(v) }] };

src/tools/store_collection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ export const searchActors: ToolEntry = {
8888
call: async (toolArgs) => {
8989
const { args, apifyToken } = toolArgs;
9090
const parsed = searchActorsArgsSchema.parse(args);
91+
if (!parsed.search || parsed.search.trim() === '') {
92+
return {
93+
content: [{
94+
type: 'text',
95+
text: 'Search string must not be empty. Please provide keywords to search for Actors.',
96+
}],
97+
};
98+
}
9199
const actors = await searchActorsByKeywords(
92100
parsed.search,
93101
apifyToken,

0 commit comments

Comments
 (0)