@mixio-pro/kalaasetu-mcp 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +10 -9
- package/src/test-context.ts +52 -0
- package/src/test-error-handling.ts +31 -0
- package/src/tools/fal/generate.ts +14 -9
- package/src/tools/fal/storage.ts +61 -58
- package/src/tools/gemini.ts +258 -237
- package/src/tools/image-to-video.ts +199 -185
- package/src/tools/perplexity.ts +192 -154
- package/src/tools/youtube.ts +51 -33
- package/src/utils/tool-wrapper.ts +86 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { FastMCP } from "fastmcp";
|
|
3
|
+
import pkg from "../package.json";
|
|
3
4
|
import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
|
|
4
5
|
import { imageToVideo } from "./tools/image-to-video";
|
|
5
6
|
import {
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
|
|
16
17
|
const server = new FastMCP({
|
|
17
18
|
name: "Kalaasetu MCP Server",
|
|
18
|
-
version:
|
|
19
|
+
version: pkg.version as any,
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// Gemini Image Tools
|
|
@@ -40,14 +41,14 @@ server.addTool(imageToVideo);
|
|
|
40
41
|
// server.addTool(perplexityVideos);
|
|
41
42
|
|
|
42
43
|
// Fal AI Tools
|
|
43
|
-
server.addTool(falListModels);
|
|
44
|
-
server.addTool(falSearchModels);
|
|
45
|
-
server.addTool(falGetSchema);
|
|
46
|
-
server.addTool(falGenerateContent);
|
|
47
|
-
server.addTool(falGetResult);
|
|
48
|
-
server.addTool(falGetStatus);
|
|
49
|
-
server.addTool(falCancelRequest);
|
|
50
|
-
server.addTool(falUploadFile);
|
|
44
|
+
// server.addTool(falListModels);
|
|
45
|
+
// server.addTool(falSearchModels);
|
|
46
|
+
// server.addTool(falGetSchema);
|
|
47
|
+
// server.addTool(falGenerateContent);
|
|
48
|
+
// server.addTool(falGetResult);
|
|
49
|
+
// server.addTool(falGetStatus);
|
|
50
|
+
// server.addTool(falCancelRequest);
|
|
51
|
+
// server.addTool(falUploadFile);
|
|
51
52
|
|
|
52
53
|
server.start({
|
|
53
54
|
transportType: "stdio",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { safeToolExecute } from "./utils/tool-wrapper";
|
|
3
|
+
|
|
4
|
+
async function runTest() {
|
|
5
|
+
console.log("Running Error Context Verification Test...");
|
|
6
|
+
|
|
7
|
+
// 1. Mock Schema Validation Error
|
|
8
|
+
const schema = z.object({
|
|
9
|
+
prompt: z.string(),
|
|
10
|
+
count: z.number().min(1).max(5),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const mockToolWithValidation = async (args: any) => {
|
|
14
|
+
// Simulate what happens in a real tool when Zod parsing fails inside the execute block
|
|
15
|
+
// OR if we manually parse and it fails
|
|
16
|
+
// Most tools currently don't re-parse inside execute because MCP handles it?
|
|
17
|
+
// Wait, the tools define `parameters` schema, but the `execute` function receives `args`.
|
|
18
|
+
// The MCP server framework usually does the validation BEFORE calling execute.
|
|
19
|
+
// BUT if we look at the code, some tools do manual checks or parsing.
|
|
20
|
+
|
|
21
|
+
// Let's simulate a manual validation failure inside execute, or a deep validation failure
|
|
22
|
+
try {
|
|
23
|
+
schema.parse(args);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
return "success";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const zodResult = await safeToolExecute(
|
|
31
|
+
async () => mockToolWithValidation({ prompt: 123, count: 10 }),
|
|
32
|
+
"ZodTool"
|
|
33
|
+
);
|
|
34
|
+
console.log("\n--- Zod Error Result ---");
|
|
35
|
+
console.log(JSON.stringify(zodResult, null, 2));
|
|
36
|
+
|
|
37
|
+
// 2. Mock API Error with Status
|
|
38
|
+
const mockToolWithApiError = async () => {
|
|
39
|
+
// Simulate a fetch error structure
|
|
40
|
+
const err: any = new Error("API request failed");
|
|
41
|
+
err.status = 429;
|
|
42
|
+
err.statusText = "Too Many Requests";
|
|
43
|
+
err.responseBody = "Rate limit exceeded. Retry in 60s.";
|
|
44
|
+
throw err;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const apiResult = await safeToolExecute(mockToolWithApiError, "ApiTool");
|
|
48
|
+
console.log("\n--- API Error Result ---");
|
|
49
|
+
console.log(JSON.stringify(apiResult, null, 2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
runTest();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { safeToolExecute } from "./utils/tool-wrapper";
|
|
2
|
+
|
|
3
|
+
async function runTest() {
|
|
4
|
+
console.log("Running Error Handling Verification Test...");
|
|
5
|
+
|
|
6
|
+
// Mock tool that throws an error
|
|
7
|
+
const mockTool = async () => {
|
|
8
|
+
throw new Error("Simulated tool failure");
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const result = await safeToolExecute(mockTool, "MockTool");
|
|
12
|
+
|
|
13
|
+
console.log("Result:", JSON.stringify(result, null, 2));
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
result.isError === true &&
|
|
17
|
+
result.content &&
|
|
18
|
+
result.content[0]?.text.includes(
|
|
19
|
+
"Tool execution failed in MockTool: Simulated tool failure"
|
|
20
|
+
)
|
|
21
|
+
) {
|
|
22
|
+
console.log(
|
|
23
|
+
"✅ Verification PASSED: Error was correctly caught and formatted."
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
console.error("❌ Verification FAILED: Unexpected result format.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
runTest();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { safeToolExecute } from "../../utils/tool-wrapper";
|
|
7
8
|
import {
|
|
8
9
|
FAL_QUEUE_URL,
|
|
9
10
|
FAL_DIRECT_URL,
|
|
@@ -80,18 +81,20 @@ export const falGenerateContent = {
|
|
|
80
81
|
parameters: Record<string, any>;
|
|
81
82
|
queue?: boolean;
|
|
82
83
|
}) => {
|
|
83
|
-
|
|
84
|
+
return safeToolExecute(async () => {
|
|
85
|
+
const sanitizedParams = sanitizeParameters(args.parameters);
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const baseUrl = args.queue ? FAL_QUEUE_URL : FAL_DIRECT_URL;
|
|
88
|
+
const url = `${baseUrl}/${args.model}`;
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
console.error(`[fal_generate] Submitting request to ${url}...`);
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
const result = await authenticatedRequest(url, "POST", sanitizedParams);
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
console.error(`[fal_generate] Request completed successfully`);
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
return JSON.stringify(result);
|
|
97
|
+
}, "fal_generate_content");
|
|
95
98
|
},
|
|
96
99
|
};
|
|
97
100
|
|
|
@@ -135,7 +138,9 @@ export const falCancelRequest = {
|
|
|
135
138
|
url: z.string().describe("The cancel_url from a queued request"),
|
|
136
139
|
}),
|
|
137
140
|
execute: async (args: { url: string }) => {
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
return safeToolExecute(async () => {
|
|
142
|
+
const result = await authenticatedRequest(args.url, "PUT");
|
|
143
|
+
return JSON.stringify(result);
|
|
144
|
+
}, "fal_cancel_request");
|
|
140
145
|
},
|
|
141
146
|
};
|
package/src/tools/fal/storage.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import * as fs from "fs";
|
|
8
|
+
import { safeToolExecute } from "../../utils/tool-wrapper";
|
|
8
9
|
import * as path from "path";
|
|
9
10
|
import { FAL_REST_URL, AUTHENTICATED_TIMEOUT, getApiKey } from "./config";
|
|
10
11
|
|
|
@@ -42,75 +43,77 @@ export const falUploadFile = {
|
|
|
42
43
|
path: z.string().describe("The absolute path to the file to upload"),
|
|
43
44
|
}),
|
|
44
45
|
execute: async (args: { path: string }) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
return safeToolExecute(async () => {
|
|
47
|
+
// Validate file exists
|
|
48
|
+
if (!fs.existsSync(args.path)) {
|
|
49
|
+
throw new Error(`File not found: ${args.path}`);
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const filename = path.basename(args.path);
|
|
53
|
+
const fileSize = fs.statSync(args.path).size;
|
|
54
|
+
const contentType = getMimeType(args.path);
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
// Step 1: Initiate upload
|
|
57
|
+
const initiateUrl = `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`;
|
|
58
|
+
const initiatePayload = {
|
|
59
|
+
content_type: contentType,
|
|
60
|
+
file_name: filename,
|
|
61
|
+
};
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
console.error(`[fal_upload] Initiating upload for ${filename}...`);
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
const initiateResponse = await fetch(initiateUrl, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Key ${getApiKey()}`,
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(initiatePayload),
|
|
72
|
+
signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
|
|
73
|
+
});
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
if (!initiateResponse.ok) {
|
|
76
|
+
const errorText = await initiateResponse.text();
|
|
77
|
+
throw new Error(
|
|
78
|
+
`[${initiateResponse.status}] Failed to initiate upload: ${errorText}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const initiateData = (await initiateResponse.json()) as {
|
|
83
|
+
file_url: string;
|
|
84
|
+
upload_url: string;
|
|
85
|
+
};
|
|
86
|
+
const { file_url, upload_url } = initiateData;
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// Step 2: Upload file content
|
|
89
|
+
console.error(`[fal_upload] Uploading file content...`);
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
const fileContent = fs.readFileSync(args.path);
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
const uploadResponse = await fetch(upload_url, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": contentType,
|
|
97
|
+
},
|
|
98
|
+
body: fileContent,
|
|
99
|
+
signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
|
|
100
|
+
});
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (!uploadResponse.ok) {
|
|
103
|
+
const errorText = await uploadResponse.text();
|
|
104
|
+
throw new Error(
|
|
105
|
+
`[${uploadResponse.status}] Failed to upload file: ${errorText}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
console.error(`[fal_upload] Upload completed successfully`);
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
return JSON.stringify({
|
|
112
|
+
file_url,
|
|
113
|
+
file_name: filename,
|
|
114
|
+
file_size: fileSize,
|
|
115
|
+
content_type: contentType,
|
|
116
|
+
});
|
|
117
|
+
}, "fal_upload_file");
|
|
115
118
|
},
|
|
116
119
|
};
|