Skip to content

Commit 93e1323

Browse files
Merge pull request #4 from yepcode/feature/YEP-2942
YEP-2942 Add mcp tools for yepcode storage
2 parents a79f25c + ea7af91 commit 93e1323

File tree

5 files changed

+247
-87
lines changed

5 files changed

+247
-87
lines changed

package-lock.json

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@
4646
"dev": "node --loader ts-node/esm src/index.ts",
4747
"build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
4848
"type-check": "tsc --noEmit",
49-
"inspector": "npx @modelcontextprotocol/inspector node --env-file=.env dist/index.js"
49+
"inspector": "npx @modelcontextprotocol/inspector@0.13.0 node --env-file=.env dist/index.js"
5050
},
5151
"dependencies": {
52-
"@modelcontextprotocol/sdk": "^1.12.1",
53-
"@yepcode/run": "^1.7.0",
52+
"@modelcontextprotocol/sdk": "^1.17.0",
53+
"@yepcode/run": "^1.8.0",
5454
"dotenv": "^16.4.7",
5555
"zod": "^3.24.2"
5656
},

src/server.ts

Lines changed: 149 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ import {
3131
import { z } from "zod";
3232
import { getVersion, isEmpty } from "./utils.js";
3333
import Logger from "./logger.js";
34+
import {
35+
DeleteObjectSchema,
36+
DownloadObjectSchema,
37+
ListObjectsSchema,
38+
storageToolDefinitions,
39+
storageToolNames,
40+
UploadObjectSchema,
41+
} from "./tools/storage-tool-definitions.js";
3442

3543
const RUN_PROCESS_TOOL_NAME_PREFIX = "run_ycp_";
3644
const RUN_PROCESS_TOOL_TAG = "mcp-tool";
@@ -197,47 +205,81 @@ class YepCodeMcpServer extends Server {
197205
};
198206
}
199207

208+
private getCodingRules = async (): Promise<string> => {
209+
try {
210+
let rulesMdFile = await fetch(
211+
"https://yepcode.io/docs/yepcode-coding-rules.md"
212+
).then((res) => res.text());
213+
rulesMdFile = rulesMdFile.substring(
214+
rulesMdFile.indexOf("## General Rules")
215+
);
216+
rulesMdFile = rulesMdFile.replace(
217+
/(\[Section titled .*\]\(#.*\)\n)/g,
218+
""
219+
);
220+
221+
return `Here you can find the general rules for YepCode coding:
222+
223+
${rulesMdFile}`;
224+
} catch (error) {
225+
return "";
226+
}
227+
};
228+
200229
private setupToolHandlers(): void {
201230
this.setRequestHandler(ListToolsRequestSchema, async () => {
202231
this.logger.info(`Handling ListTools request`);
203-
const envVars = await this.yepCodeEnv.getEnvVars();
204-
const tools = this.disableRunCodeTool
205-
? []
206-
: [
207-
{
208-
name: "run_code",
209-
description: `Execute LLM-generated code safely in YepCode’s secure, production-grade sandboxes.
210-
This tool is ideal when your AI agent needs to handle tasks that don’t have a predefined tool available — but could be solved by writing and running a custom script.
211-
212-
It supports external dependencies (NPM or PyPI), so it’s perfect for:
213-
• Complex data transformations
214-
• API calls to services not yet integrated
215-
• Custom logic implementations
216-
• One-off utility scripts
217-
218-
Tip: First try to find a tool that matches your task, but if not available, try generating the code and running it here!`,
219-
inputSchema: zodToJsonSchema(
220-
buildRunCodeSchema(envVars.map((envVar) => envVar.key))
221-
),
222-
},
223-
{
224-
name: "set_env_var",
225-
description:
226-
"Set a YepCode environment variable to be available for future code executions",
227-
inputSchema: zodToJsonSchema(SetEnvVarSchema),
228-
},
229-
{
230-
name: "remove_env_var",
231-
description: "Remove a YepCode environment variable",
232-
inputSchema: zodToJsonSchema(RemoveEnvVarSchema),
233-
},
234-
{
235-
name: "get_execution",
236-
description:
237-
"Get the status, result, logs, timeline, etc. of a YepCode execution",
238-
inputSchema: zodToJsonSchema(GetExecutionSchema),
239-
},
240-
];
232+
const tools = [
233+
{
234+
name: "set_env_var",
235+
title: "Set environment variable",
236+
description:
237+
"Set a YepCode environment variable to be available for future code executions",
238+
inputSchema: zodToJsonSchema(SetEnvVarSchema),
239+
},
240+
{
241+
name: "remove_env_var",
242+
title: "Remove environment variable",
243+
description: "Remove a YepCode environment variable",
244+
inputSchema: zodToJsonSchema(RemoveEnvVarSchema),
245+
},
246+
{
247+
name: "get_execution",
248+
title: "Get process execution",
249+
description:
250+
"Get the status, result, logs, timeline, etc. of a YepCode execution",
251+
inputSchema: zodToJsonSchema(GetExecutionSchema),
252+
},
253+
...storageToolDefinitions,
254+
];
255+
256+
if (!this.disableRunCodeTool) {
257+
const envVars = await this.yepCodeEnv.getEnvVars();
258+
const codingRules = await this.getCodingRules();
259+
tools.push({
260+
name: "run_code",
261+
title:
262+
"Execute LLM-generated code in YepCode’s remote and secure sandboxes",
263+
description: `This tool is ideal when your AI agent needs to handle tasks that don’t have a predefined tool available — but could be solved by writing and running a custom script.
264+
265+
It supports JavaScript and Python, both with external dependencies (NPM or PyPI), so it’s perfect for:
266+
* Complex data transformations
267+
* API calls to services not yet integrated
268+
* Custom logic implementations
269+
* One-off utility scripts
270+
* To use files as input, first upload them to YepCode Storage using the upload storage MCP tools. Then, access them in your code using the \`yepcode.storage\` helper methods to download the files.
271+
* To generate and output files, create them in the local execution storage, then upload them to YepCode Storage using the \`yepcode.storage\` helpers. Once uploaded, you can download them using the download storage MCP tool.
272+
273+
Tip: First try to find a tool that matches your task, but if not available, try generating the code and running it here.`,
274+
inputSchema: zodToJsonSchema(
275+
buildRunCodeSchema(
276+
envVars.map((envVar) => envVar.key),
277+
codingRules
278+
)
279+
),
280+
});
281+
}
282+
241283
let page = 0;
242284
let limit = 100;
243285
while (true) {
@@ -262,6 +304,7 @@ Tip: First try to find a tool that matches your task, but if not available, try
262304
}
263305
return {
264306
name: toolName,
307+
title: process.name,
265308
description: `${process.name}${
266309
process.description ? ` - ${process.description}` : ""
267310
}`,
@@ -405,6 +448,74 @@ Tip: First try to find a tool that matches your task, but if not available, try
405448
return await this.executionResult(data.executionId);
406449
}
407450
);
451+
452+
case storageToolNames.list:
453+
return this.handleToolRequest(
454+
ListObjectsSchema,
455+
request,
456+
async (data) => {
457+
const objects = await this.yepCodeApi.getObjects({
458+
prefix: data?.prefix || undefined,
459+
});
460+
return objects;
461+
}
462+
);
463+
case storageToolNames.upload:
464+
return this.handleToolRequest(
465+
UploadObjectSchema,
466+
request,
467+
async (data) => {
468+
const { filename, content } = data;
469+
470+
let fileContent: string | Buffer;
471+
472+
if (typeof content === "string") {
473+
fileContent = content;
474+
} else if (content.encoding === "base64") {
475+
fileContent = Buffer.from(content.data, "base64");
476+
} else {
477+
throw new Error("Invalid content format");
478+
}
479+
480+
await this.yepCodeApi.createObject({
481+
name: filename,
482+
file: new Blob([fileContent]),
483+
});
484+
return { result: `Object ${filename} uploaded successfully` };
485+
}
486+
);
487+
case storageToolNames.download:
488+
return this.handleToolRequest(
489+
DownloadObjectSchema,
490+
request,
491+
async (data) => {
492+
const { filename } = data;
493+
const stream = await this.yepCodeApi.getObject(filename);
494+
495+
const chunks: Buffer[] = [];
496+
for await (const chunk of stream) {
497+
chunks.push(Buffer.from(chunk));
498+
}
499+
const buffer = Buffer.concat(chunks);
500+
501+
return {
502+
content: buffer.toString("base64"),
503+
encoding: "base64",
504+
filename,
505+
size: buffer.length,
506+
};
507+
}
508+
);
509+
case storageToolNames.delete:
510+
return this.handleToolRequest(
511+
DeleteObjectSchema,
512+
request,
513+
async (data) => {
514+
const { filename } = data;
515+
await this.yepCodeApi.deleteObject(filename);
516+
return { result: `Object ${filename} deleted successfully` };
517+
}
518+
);
408519
default:
409520
this.logger.error(`Unknown tool requested: ${request.params.name}`);
410521
throw new McpError(

src/tools/storage-tool-definitions.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { z } from "zod";
2+
import { zodToJsonSchema } from "zod-to-json-schema";
3+
4+
export const storageToolNames = {
5+
list: "list_files",
6+
upload: "upload_file",
7+
download: "download_file",
8+
delete: "delete_file",
9+
};
10+
11+
export const ListObjectsSchema = z.object({
12+
prefix: z
13+
.string()
14+
.optional()
15+
.describe(
16+
"Prefix to filter files by. Filter results to include only files whose names begin with this prefix"
17+
),
18+
});
19+
20+
export const UploadObjectSchema = z.object({
21+
filename: z
22+
.string()
23+
.describe(
24+
"Filename or path where to upload the file (e.g., 'file.txt' or 'folder/file.txt')"
25+
),
26+
content: z
27+
.union([
28+
z.string().describe("File content as plain text (for text files)"),
29+
z
30+
.object({
31+
data: z.string().describe("File content encoded in base64"),
32+
encoding: z.literal("base64").describe("Encoding type"),
33+
})
34+
.describe("Base64 encoded file content (for binary files)"),
35+
])
36+
.describe(
37+
"File content. Use plain text for text files, or base64 object for binary files"
38+
),
39+
});
40+
41+
export const DownloadObjectSchema = z.object({
42+
filename: z
43+
.string()
44+
.describe(
45+
"Filename or path where to download the file (e.g., 'file.txt' or 'folder/file.txt')"
46+
),
47+
});
48+
49+
export const DeleteObjectSchema = z.object({
50+
filename: z
51+
.string()
52+
.describe(
53+
"Filename or path where to delete the file (e.g., 'file.txt' or 'folder/file.txt')"
54+
),
55+
});
56+
57+
export const storageToolDefinitions = [
58+
{
59+
name: storageToolNames.list,
60+
description: "List all files in storage",
61+
inputSchema: zodToJsonSchema(ListObjectsSchema),
62+
},
63+
{
64+
name: storageToolNames.upload,
65+
description: "Upload a file to storage",
66+
inputSchema: zodToJsonSchema(UploadObjectSchema),
67+
},
68+
{
69+
name: storageToolNames.download,
70+
description: "Download a file from storage",
71+
inputSchema: zodToJsonSchema(DownloadObjectSchema),
72+
},
73+
{
74+
name: storageToolNames.delete,
75+
description: "Delete a file from storage",
76+
inputSchema: zodToJsonSchema(DeleteObjectSchema),
77+
},
78+
];

0 commit comments

Comments
 (0)