orbitx 0.0.1 → 0.0.3
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/.env.example +1 -0
- package/Readme.md +81 -0
- package/orbitx.png +0 -0
- package/package.json +17 -2
- package/public/index.html +397 -0
- package/src/core/ai-provider.ts +25 -0
- package/src/core/base-agent.ts +297 -0
- package/src/core/deepseek-provider.ts +71 -0
- package/src/core/mcp-client.ts +113 -0
- package/src/core/mcp-computer.ts +5 -0
- package/src/core/mcp-connection.ts +12 -0
- package/src/core/mcp-ipc-connection.ts +153 -0
- package/src/core/mcp-server.ts +49 -0
- package/src/core/mcp.ts +23 -0
- package/src/core/ollama-provider.ts +70 -0
- package/src/core/skill.ts +16 -0
- package/src/index.ts +17 -0
- package/src/templates/researcher.ts +73 -0
- package/src/templates/simple.ts +64 -0
- package/src/tests/mcp.test.ts +73 -0
- package/src/tools/fs/index.ts +2 -0
- package/src/tools/fs/read-file.ts +54 -0
- package/src/tools/fs/write-file.ts +73 -0
- package/src/tools/utils/get-current-time.ts +45 -0
- package/src/tools/utils/index.ts +1 -0
- package/src/tools/www/fetch.ts +161 -0
- package/src/tools/www/index.ts +3 -0
- package/src/tools/www/read-web-page.ts +350 -0
- package/src/tools/www/web-search.ts +102 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type MCPTool from "./mcp";
|
|
2
|
+
import type MCPConnection from "./mcp-connection";
|
|
3
|
+
|
|
4
|
+
export class MCPServer {
|
|
5
|
+
#connection;
|
|
6
|
+
#tools: MCPTool[] = [];
|
|
7
|
+
constructor(connection: MCPConnection) {
|
|
8
|
+
this.#connection = connection;
|
|
9
|
+
this.#connection.on("read", (data: any) => {
|
|
10
|
+
const topic = data.topic;
|
|
11
|
+
const pid = data.pid;
|
|
12
|
+
if (topic == "getTools") {
|
|
13
|
+
this.#connection.emit("write", {
|
|
14
|
+
pid,
|
|
15
|
+
topic: "getToolsCallback",
|
|
16
|
+
tools: this.#tools.map(tool => ({
|
|
17
|
+
name: tool.getOptions().name,
|
|
18
|
+
description: tool.getOptions().description,
|
|
19
|
+
inputs: tool.getOptions().inputs
|
|
20
|
+
}))
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (topic == "toolCall") {
|
|
24
|
+
const tool = this.#tools.find(t => t.getOptions().name == data.tool);
|
|
25
|
+
if (tool) {
|
|
26
|
+
tool.getOptions().execute(data.envID, data.inputs).then((response) => {
|
|
27
|
+
this.#connection.emit("write", {
|
|
28
|
+
pid,
|
|
29
|
+
topic: "toolCallCallback",
|
|
30
|
+
output: response
|
|
31
|
+
});
|
|
32
|
+
}).catch(error => {
|
|
33
|
+
this.#connection.emit("write", {
|
|
34
|
+
pid,
|
|
35
|
+
topic: "toolCallCallback",
|
|
36
|
+
output: { error: error.message }
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
registerTool(tool: MCPTool) {
|
|
45
|
+
this.#tools.push(tool);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default MCPServer;
|
package/src/core/mcp.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class MCPTool {
|
|
2
|
+
#options;
|
|
3
|
+
constructor(options: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputs: {
|
|
7
|
+
name: string;
|
|
8
|
+
type: "number" | "string" | "boolean" | "object" | "array";
|
|
9
|
+
description: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
default?: any;
|
|
12
|
+
}[];
|
|
13
|
+
execute: (envID: string, inputs: Record<string, any>) => Promise<any>;
|
|
14
|
+
}) {
|
|
15
|
+
this.#options = options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getOptions() {
|
|
19
|
+
return this.#options;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default MCPTool;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Ollama } from "ollama";
|
|
2
|
+
import AIProvider, { type Message, type ChatResponse, type StreamCallback } from "./ai-provider";
|
|
3
|
+
|
|
4
|
+
export class OllamaProvider extends AIProvider {
|
|
5
|
+
private client: Ollama;
|
|
6
|
+
private model: string;
|
|
7
|
+
|
|
8
|
+
constructor(model: string, host: string = "http://localhost:11434") {
|
|
9
|
+
super();
|
|
10
|
+
this.client = new Ollama({ host });
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async chat(
|
|
15
|
+
messages: Message[],
|
|
16
|
+
streamCallback?: StreamCallback
|
|
17
|
+
): Promise<ChatResponse> {
|
|
18
|
+
const formattedMessages = messages.map(msg => ({
|
|
19
|
+
role: msg.role,
|
|
20
|
+
content: msg.content || ""
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
if (streamCallback) {
|
|
24
|
+
const stream = await this.client.chat({
|
|
25
|
+
model: this.model,
|
|
26
|
+
messages: formattedMessages as any,
|
|
27
|
+
stream: true
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let fullContent = "";
|
|
31
|
+
let promptEvalCount = 0;
|
|
32
|
+
let evalCount = 0;
|
|
33
|
+
|
|
34
|
+
for await (const chunk of stream) {
|
|
35
|
+
const content = chunk.message?.content || "";
|
|
36
|
+
if (content) {
|
|
37
|
+
fullContent += content;
|
|
38
|
+
streamCallback({ role: "assistant", content, done: false });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (chunk.prompt_eval_count !== undefined) {
|
|
42
|
+
promptEvalCount = chunk.prompt_eval_count;
|
|
43
|
+
}
|
|
44
|
+
if (chunk.eval_count !== undefined) {
|
|
45
|
+
evalCount = chunk.eval_count;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
streamCallback({ role: "assistant", content: "", done: true });
|
|
50
|
+
return {
|
|
51
|
+
content: fullContent,
|
|
52
|
+
inputTokens: promptEvalCount,
|
|
53
|
+
outputTokens: evalCount
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
const response = await this.client.chat({
|
|
57
|
+
model: this.model,
|
|
58
|
+
messages: formattedMessages as any
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
content: response.message?.content || "",
|
|
63
|
+
inputTokens: response.prompt_eval_count || 0,
|
|
64
|
+
outputTokens: response.eval_count || 0
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default OllamaProvider;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { AIProvider } from "./core/ai-provider";
|
|
2
|
+
export { BaseAgent } from "./core/base-agent";
|
|
3
|
+
export { DeepSeekProvider } from "./core/deepseek-provider";
|
|
4
|
+
export { MCPClient } from "./core/mcp-client";
|
|
5
|
+
export { MCPConnection } from "./core/mcp-connection";
|
|
6
|
+
export { MCPIPCConnection } from "./core/mcp-ipc-connection";
|
|
7
|
+
export { MCPServer } from "./core/mcp-server";
|
|
8
|
+
export { MCPTool } from "./core/mcp";
|
|
9
|
+
export { OllamaProvider } from "./core/ollama-provider";
|
|
10
|
+
export { Skill } from "./core/skill";
|
|
11
|
+
|
|
12
|
+
export { ResearcherAgent } from "./templates/researcher";
|
|
13
|
+
export { SimpleAgent } from "./templates/simple";
|
|
14
|
+
|
|
15
|
+
export { ReadFileTool, WriteFileTool } from "./tools/fs";
|
|
16
|
+
export { GetCurrentTimeTool } from "./tools/utils";
|
|
17
|
+
export { FetchTool, ReadWebPageTool, WebSearchTool } from "./tools/www";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Message } from "../core/ai-provider";
|
|
2
|
+
import type AIProvider from "../core/ai-provider";
|
|
3
|
+
import BaseAgent from "../core/base-agent";
|
|
4
|
+
import type MCPTool from "../core/mcp";
|
|
5
|
+
import MCPClient from "../core/mcp-client";
|
|
6
|
+
import MCPConnection from "../core/mcp-connection";
|
|
7
|
+
import MCPServer from "../core/mcp-server";
|
|
8
|
+
import type Skill from "../core/skill";
|
|
9
|
+
import { ReadWebPageTool, WebSearchTool } from "../tools/www";
|
|
10
|
+
|
|
11
|
+
export class ResearcherAgent extends BaseAgent {
|
|
12
|
+
constructor({
|
|
13
|
+
aiProvider,
|
|
14
|
+
instruction = `you are research agent you have some rules you don't know about any true otherwise first search about it
|
|
15
|
+
your each topic search iteration contain two step
|
|
16
|
+
1. use web-search tool to get some urls from search engine
|
|
17
|
+
2. read 4 url with best topic near to out search query in this step you execute 4 read-web-page tool call at once
|
|
18
|
+
if result is resolve needs we stop otherwise do above operation more
|
|
19
|
+
YOU MUST DO two step before decide to response don't do web-search alone!! at less do search and read page operation 3 times to to get as much information as possible`,
|
|
20
|
+
tools = [
|
|
21
|
+
WebSearchTool,
|
|
22
|
+
ReadWebPageTool
|
|
23
|
+
],
|
|
24
|
+
skills = [],
|
|
25
|
+
maxMemorizeToken = 30000,
|
|
26
|
+
initData = {
|
|
27
|
+
memory: "",
|
|
28
|
+
messagesFull: [],
|
|
29
|
+
fullInputMissTokens: 0,
|
|
30
|
+
fullInputHitTokens: 0,
|
|
31
|
+
fullOutputTokens: 0,
|
|
32
|
+
messagesCompact: [],
|
|
33
|
+
currentInputMissTokens: 0,
|
|
34
|
+
currentInputHitTokens: 0,
|
|
35
|
+
currentOutputTokens: 0,
|
|
36
|
+
}
|
|
37
|
+
}: {
|
|
38
|
+
instruction?: string;
|
|
39
|
+
tools?: MCPTool[];
|
|
40
|
+
aiProvider: AIProvider;
|
|
41
|
+
skills?: Skill[];
|
|
42
|
+
maxMemorizeToken?: number;
|
|
43
|
+
initData?: {
|
|
44
|
+
memory: string;
|
|
45
|
+
messagesFull: Message[];
|
|
46
|
+
fullInputMissTokens: number;
|
|
47
|
+
fullInputHitTokens: number;
|
|
48
|
+
fullOutputTokens: number;
|
|
49
|
+
messagesCompact: Message[];
|
|
50
|
+
currentInputMissTokens: number;
|
|
51
|
+
currentInputHitTokens: number;
|
|
52
|
+
currentOutputTokens: number;
|
|
53
|
+
}
|
|
54
|
+
}) {
|
|
55
|
+
const conn = new MCPConnection();
|
|
56
|
+
const mcpServer = new MCPServer(conn);
|
|
57
|
+
tools.forEach(tool => {
|
|
58
|
+
mcpServer.registerTool(tool);
|
|
59
|
+
});
|
|
60
|
+
const mcpClient = new MCPClient("DEFAULT_ENV", conn);
|
|
61
|
+
super({
|
|
62
|
+
aiProvider,
|
|
63
|
+
instruction,
|
|
64
|
+
mcpClient,
|
|
65
|
+
allowedTools: tools,
|
|
66
|
+
skills,
|
|
67
|
+
maxMemorizeToken,
|
|
68
|
+
initData,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default ResearcherAgent;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Message } from "../core/ai-provider";
|
|
2
|
+
import type AIProvider from "../core/ai-provider";
|
|
3
|
+
import BaseAgent from "../core/base-agent";
|
|
4
|
+
import type MCPTool from "../core/mcp";
|
|
5
|
+
import MCPClient from "../core/mcp-client";
|
|
6
|
+
import MCPConnection from "../core/mcp-connection";
|
|
7
|
+
import MCPServer from "../core/mcp-server";
|
|
8
|
+
import type Skill from "../core/skill";
|
|
9
|
+
|
|
10
|
+
export class SimpleAgent extends BaseAgent {
|
|
11
|
+
constructor({
|
|
12
|
+
aiProvider,
|
|
13
|
+
instruction = "",
|
|
14
|
+
tools = [],
|
|
15
|
+
skills = [],
|
|
16
|
+
maxMemorizeToken = 16000,
|
|
17
|
+
initData = {
|
|
18
|
+
memory: "",
|
|
19
|
+
messagesFull: [],
|
|
20
|
+
fullInputMissTokens: 0,
|
|
21
|
+
fullInputHitTokens: 0,
|
|
22
|
+
fullOutputTokens: 0,
|
|
23
|
+
messagesCompact: [],
|
|
24
|
+
currentInputMissTokens: 0,
|
|
25
|
+
currentInputHitTokens: 0,
|
|
26
|
+
currentOutputTokens: 0,
|
|
27
|
+
}
|
|
28
|
+
}: {
|
|
29
|
+
instruction: string;
|
|
30
|
+
tools: MCPTool[];
|
|
31
|
+
aiProvider: AIProvider;
|
|
32
|
+
skills?: Skill[];
|
|
33
|
+
maxMemorizeToken?: number;
|
|
34
|
+
initData?: {
|
|
35
|
+
memory: string;
|
|
36
|
+
messagesFull: Message[];
|
|
37
|
+
fullInputMissTokens: number;
|
|
38
|
+
fullInputHitTokens: number;
|
|
39
|
+
fullOutputTokens: number;
|
|
40
|
+
messagesCompact: Message[];
|
|
41
|
+
currentInputMissTokens: number;
|
|
42
|
+
currentInputHitTokens: number;
|
|
43
|
+
currentOutputTokens: number;
|
|
44
|
+
}
|
|
45
|
+
}) {
|
|
46
|
+
const conn = new MCPConnection();
|
|
47
|
+
const mcpServer = new MCPServer(conn);
|
|
48
|
+
tools.forEach(tool => {
|
|
49
|
+
mcpServer.registerTool(tool);
|
|
50
|
+
});
|
|
51
|
+
const mcpClient = new MCPClient("DEFAULT_ENV", conn);
|
|
52
|
+
super({
|
|
53
|
+
aiProvider,
|
|
54
|
+
instruction,
|
|
55
|
+
mcpClient,
|
|
56
|
+
allowedTools: tools,
|
|
57
|
+
skills,
|
|
58
|
+
maxMemorizeToken,
|
|
59
|
+
initData,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default SimpleAgent;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import MCPIPCConnection from "../core/mcp-ipc-connection";
|
|
5
|
+
import MCPServer from "../core/mcp-server";
|
|
6
|
+
import MCPClient from "../core/mcp-client";
|
|
7
|
+
import { GetCurrentTimeTool } from "../tools/utils";
|
|
8
|
+
import { FetchTool, WebSearchTool } from "../tools/www";
|
|
9
|
+
|
|
10
|
+
describe("MCP Client and Server", async () => {
|
|
11
|
+
const path1 = os.platform() == "win32" ? `\\\\.\\pipe\\test${Math.random() * 1000}` : `/tmp/mcp_test${Math.random() * 1000}.sock`;
|
|
12
|
+
const path2 = os.platform() == "win32" ? `\\\\.\\pipe\\test${Math.random() * 1000}` : `/tmp/mcp_test${Math.random() * 1000}.sock`;
|
|
13
|
+
|
|
14
|
+
const ipcConnection11 = new MCPIPCConnection(path1);
|
|
15
|
+
const ipcConnection21 = new MCPIPCConnection(path2);
|
|
16
|
+
|
|
17
|
+
const ipcConnection12 = new MCPIPCConnection(path1);
|
|
18
|
+
const ipcConnection22 = new MCPIPCConnection(path2);
|
|
19
|
+
|
|
20
|
+
let mcpServer1: MCPServer;
|
|
21
|
+
let mcpServer2: MCPServer;
|
|
22
|
+
let mcpClient: MCPClient;
|
|
23
|
+
|
|
24
|
+
await test("should connect MCP Client to Servers and get empty tools", async () => {
|
|
25
|
+
mcpServer1 = new MCPServer(ipcConnection11);
|
|
26
|
+
mcpServer2 = new MCPServer(ipcConnection21);
|
|
27
|
+
mcpClient = new MCPClient("DEFAULT_ENV", [ipcConnection12, ipcConnection22]);
|
|
28
|
+
const result = await mcpClient.getTools();
|
|
29
|
+
assert.equal(result.length, 0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await test("should register tool on client side", async () => {
|
|
33
|
+
mcpClient.registerTool(GetCurrentTimeTool);
|
|
34
|
+
const result = await mcpClient.getTools();
|
|
35
|
+
|
|
36
|
+
assert.equal(result.length, 1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await test("should register tool on server1 side", async () => {
|
|
40
|
+
mcpServer1.registerTool(FetchTool);
|
|
41
|
+
const result = await mcpClient.getTools();
|
|
42
|
+
|
|
43
|
+
assert.equal(result.length, 2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await test("should register tool on server2 side", async () => {
|
|
47
|
+
mcpServer2.registerTool(WebSearchTool);
|
|
48
|
+
const result = await mcpClient.getTools();
|
|
49
|
+
|
|
50
|
+
assert.equal(result.length, 3);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await test("should call tool on client side", async () => {
|
|
54
|
+
const result = await mcpClient.callTool(GetCurrentTimeTool.getOptions().name, {});
|
|
55
|
+
|
|
56
|
+
assert.ok(typeof result == "string");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await test("should call tool on server1 side", async () => {
|
|
60
|
+
const result = await mcpClient.callTool(FetchTool.getOptions().name, { url: "https://google.com" });
|
|
61
|
+
|
|
62
|
+
assert.ok(typeof result == "string");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await test("should call tool on server2 side", async () => {
|
|
66
|
+
const result = await mcpClient.callTool(WebSearchTool.getOptions().name, { query: "test" });
|
|
67
|
+
|
|
68
|
+
assert.ok(typeof result == "string");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// Need Close Connections
|
|
73
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import MCPTool from "../../core/mcp";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export const ReadFileTool = new MCPTool({
|
|
6
|
+
name: "read-file",
|
|
7
|
+
description: "Reads text content from a file at the specified path.",
|
|
8
|
+
inputs: [{
|
|
9
|
+
name: "filePath",
|
|
10
|
+
type: "string",
|
|
11
|
+
description: `Absolute or relative path to the file (e.g., "/home/user/doc.txt" or "./file.txt")`,
|
|
12
|
+
required: true,
|
|
13
|
+
}],
|
|
14
|
+
execute: async (_: string, inputs: Record<string, any>): Promise<any> => {
|
|
15
|
+
try {
|
|
16
|
+
const { filePath } = inputs;
|
|
17
|
+
|
|
18
|
+
if (!filePath || typeof filePath !== "string") {
|
|
19
|
+
throw new Error("filePath must be a non-empty string");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalizedPath = path.normalize(filePath);
|
|
23
|
+
|
|
24
|
+
const data = await fs.readFile(normalizedPath, "utf-8");
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
message: `File read successfully: ${normalizedPath}`,
|
|
28
|
+
path: normalizedPath,
|
|
29
|
+
content: data,
|
|
30
|
+
size: data.length
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
} catch (error) {
|
|
34
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
35
|
+
|
|
36
|
+
if (error instanceof Error && "code" in error) {
|
|
37
|
+
const err = error as NodeJS.ErrnoException;
|
|
38
|
+
if (err.code === "ENOENT") {
|
|
39
|
+
errorMessage = `File not found: ${inputs.filePath}`;
|
|
40
|
+
} else if (err.code === "EACCES") {
|
|
41
|
+
errorMessage = `Permission denied: ${inputs.filePath}`;
|
|
42
|
+
} else if (err.code === "EISDIR") {
|
|
43
|
+
errorMessage = `Path is a directory, not a file: ${inputs.filePath}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
error: errorMessage
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default ReadFileTool;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import MCPTool from "../../core/mcp";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export const WriteFileTool = new MCPTool({
|
|
6
|
+
name: "write-file",
|
|
7
|
+
description: "Writes text content to a file at the specified path. Creates new file or overwrites existing.",
|
|
8
|
+
inputs: [{
|
|
9
|
+
name: "filePath",
|
|
10
|
+
type: "string",
|
|
11
|
+
description: `Absolute or relative path to the file (e.g., "/home/user/doc.txt" or "./file.txt")`,
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "data",
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Text content to write (supports multi-line)",
|
|
18
|
+
required: true,
|
|
19
|
+
}],
|
|
20
|
+
execute: async (_: string, inputs: Record<string, any>): Promise<any> => {
|
|
21
|
+
try {
|
|
22
|
+
const { filePath, data } = inputs;
|
|
23
|
+
|
|
24
|
+
if (!filePath || typeof filePath !== "string") {
|
|
25
|
+
throw new Error("filePath must be a non-empty string");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof data !== "string") {
|
|
29
|
+
throw new Error("data must be a string");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const normalizedPath = path.normalize(filePath);
|
|
33
|
+
|
|
34
|
+
const dir = path.dirname(normalizedPath);
|
|
35
|
+
await fs.mkdir(dir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
await fs.writeFile(normalizedPath, data, "utf-8");
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
message: `File written: ${normalizedPath}`,
|
|
41
|
+
path: normalizedPath,
|
|
42
|
+
size: data.length
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
47
|
+
|
|
48
|
+
if (error instanceof Error && "code" in error) {
|
|
49
|
+
const err = error as NodeJS.ErrnoException;
|
|
50
|
+
|
|
51
|
+
if (err.code === "EACCES") {
|
|
52
|
+
errorMessage = `Permission denied: Cannot write to ${inputs.filePath}`;
|
|
53
|
+
} else if (err.code === "ENOENT") {
|
|
54
|
+
errorMessage = `Directory does not exist or cannot be created: ${path.dirname(inputs.filePath)}`;
|
|
55
|
+
} else if (err.code === "EISDIR") {
|
|
56
|
+
errorMessage = `Path is a directory, cannot write file: ${inputs.filePath}`;
|
|
57
|
+
} else if (err.code === "ENOSPC") {
|
|
58
|
+
errorMessage = `No space left on device: Cannot write to ${inputs.filePath}`;
|
|
59
|
+
} else if (err.code === "EROFS") {
|
|
60
|
+
errorMessage = `Read-only file system: Cannot write to ${inputs.filePath}`;
|
|
61
|
+
} else if (err.code === "ENAMETOOLONG") {
|
|
62
|
+
errorMessage = `File path is too long: ${inputs.filePath}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
error: errorMessage
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export default WriteFileTool;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import MCPTool from "../../core/mcp";
|
|
2
|
+
|
|
3
|
+
export const GetCurrentTimeTool = new MCPTool({
|
|
4
|
+
name: "get-current-time",
|
|
5
|
+
description: "Returns current time (24h) and today's date in Gregorian, Persian, and Islamic calendars.",
|
|
6
|
+
inputs: [],
|
|
7
|
+
execute: async (_: string, __: Record<string, any>): Promise<any> => {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
const time = now.toLocaleTimeString("en-US", {
|
|
10
|
+
hour: "2-digit",
|
|
11
|
+
minute: "2-digit",
|
|
12
|
+
hour12: false
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const gregorian = now.toLocaleDateString("en-US", {
|
|
16
|
+
weekday: "long",
|
|
17
|
+
year: "numeric",
|
|
18
|
+
month: "long",
|
|
19
|
+
day: "numeric"
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const persian = now.toLocaleDateString("fa-IR-u-ca-persian", {
|
|
23
|
+
weekday: "long",
|
|
24
|
+
year: "numeric",
|
|
25
|
+
month: "long",
|
|
26
|
+
day: "numeric"
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const hijri = now.toLocaleDateString("ar-SA-u-ca-islamic", {
|
|
30
|
+
weekday: "long",
|
|
31
|
+
year: "numeric",
|
|
32
|
+
month: "long",
|
|
33
|
+
day: "numeric"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
time,
|
|
38
|
+
gregorian,
|
|
39
|
+
persian,
|
|
40
|
+
hijri
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default GetCurrentTimeTool;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GetCurrentTimeTool } from "./get-current-time"
|