@saber2pr/ai-agent 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/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @saber2pr/ai-agent
2
+
3
+ A lightweight local AI assistant based on the **MCP (Model Context Protocol)**. It automatically loads your local tools (from Cursor or VSCode MCP configurations) and provides an intelligent orchestration layer via OpenAI-compatible APIs.
4
+
5
+ ## ✨ Features
6
+
7
+ * **Native MCP Support**: Seamlessly connects to local MCP servers using Stdio transport.
8
+ * **Auto-Discovery**: Automatically reads tool definitions from `~/.cursor/mcp.json` and `~/.vscode/mcp.json`.
9
+ * **Persistent Configuration**: On the first run, it guides you through setting up your API endpoint, key, and model name, saving them to `~/.saber2pr-agent.json`.
10
+ * **Namespace Management**: Prevents tool name conflicts by automatically prefixing functions (e.g., `serverName__toolName`).
11
+ * **Interactive CLI**: Built-in REPL for multi-turn conversations and complex tool-chaining.
12
+
13
+ ## šŸ“¦ Installation
14
+
15
+ Install globally via npm:
16
+
17
+ ```bash
18
+ npm install -g @saber2pr/ai-agent
19
+ ```
20
+
21
+ Or run directly using `npx`:
22
+
23
+ ```bash
24
+ npx @saber2pr/ai-agent
25
+ ```
26
+
27
+ ## šŸš€ Quick Start
28
+
29
+ ### 1. Launch the Agent
30
+
31
+ Start the assistant by running the binary command:
32
+
33
+ ```bash
34
+ sagent
35
+ ```
36
+
37
+ ### 2. Initialize Configuration
38
+
39
+ If it's your first time running the agent, you will be prompted to provide:
40
+
41
+ * **API Base URL**: e.g., `https://api.openai.com/v1` or your custom proxy.
42
+ * **API Key**: Your model provider's API key.
43
+ * **Model Name**: e.g., `gpt-4o`, `claude-3-5-sonnet`, or `deepseek-v3`.
44
+
45
+ Your settings will be stored in `~/.saber2pr-agent.json` for future use.
46
+
47
+ ### 3. Connect Local Tools
48
+
49
+ The agent automatically scans the following paths for MCP configurations:
50
+
51
+ * `~/.cursor/mcp.json`
52
+ * `~/.vscode/mcp.json`
53
+
54
+ Ensure your MCP servers are configured in these files, and `sagent` will gain the ability to call them immediately.
55
+
56
+ ## šŸ› ļø Usage
57
+
58
+ | Command | Description |
59
+ | ------------------------ | ----------------------------------------------- |
60
+ | `~/.saber2pr-agent.json` | Manually edit this file to update API settings. |
61
+ | `exit` | Type during a chat to quit the program. |
62
+ | `sagent` | Enter interactive chat mode. |
63
+
64
+ ## šŸ—ļø Tech Stack
65
+
66
+ Built with:
67
+
68
+ * [@modelcontextprotocol/sdk](https://www.google.com/search?q=https://github.com/modelcontextprotocol/typescript-sdk) - Official MCP SDK.
69
+ * [openai](https://www.google.com/search?q=https://github.com/openai/openai-node) - Client for API interactions.
70
+ * [TypeScript](https://www.google.com/search?q=https://www.typescriptlang.org/) - Ensuring type safety and robustness.
71
+
72
+ ## šŸ“„ License
73
+
74
+ [ISC](https://www.google.com/search?q=./LICENSE) Ā© saber2pr
package/lib/agent.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ export default class McpAgent {
2
+ private openai;
3
+ private modelName;
4
+ private clients;
5
+ private allTools;
6
+ private messages;
7
+ constructor();
8
+ /**
9
+ * 1. API Configuration Management
10
+ * Checks for existing config or prompts user for input.
11
+ */
12
+ private ensureApiConfig;
13
+ /**
14
+ * 2. Load MCP server configs from common IDE paths
15
+ */
16
+ private loadMcpConfigs;
17
+ /**
18
+ * 3. Initialization
19
+ * Validates API credentials and connects to MCP servers.
20
+ */
21
+ init(): Promise<void>;
22
+ /**
23
+ * 4. Core Chat Logic
24
+ * Handles user input and recursive tool calls.
25
+ */
26
+ private processChat;
27
+ /**
28
+ * 5. Start the Interactive Shell
29
+ */
30
+ start(): Promise<void>;
31
+ }
package/lib/agent.js ADDED
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
40
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
41
+ const openai_1 = __importDefault(require("openai"));
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const path_1 = __importDefault(require("path"));
44
+ const os_1 = __importDefault(require("os"));
45
+ const readline = __importStar(require("readline"));
46
+ const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
47
+ // --- Core Class ---
48
+ class McpAgent {
49
+ constructor() {
50
+ this.modelName = "";
51
+ this.clients = [];
52
+ this.allTools = [];
53
+ this.messages = [];
54
+ this.messages.push({
55
+ role: "system",
56
+ content: "You are a powerful local assistant. You can access local tools provided by the user via the MCP protocol. Please answer questions by combining tool outputs and context.",
57
+ });
58
+ }
59
+ /**
60
+ * 1. API Configuration Management
61
+ * Checks for existing config or prompts user for input.
62
+ */
63
+ async ensureApiConfig() {
64
+ if (fs_1.default.existsSync(CONFIG_FILE)) {
65
+ try {
66
+ const config = JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
67
+ if (config.baseURL && config.apiKey && config.model) {
68
+ return config;
69
+ }
70
+ }
71
+ catch (e) {
72
+ console.error(`[Error] Failed to read ${CONFIG_FILE}, re-initializing...`);
73
+ }
74
+ }
75
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
76
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
77
+ console.log("\nšŸ”‘ API Configuration not found. Please provide the following details:");
78
+ const baseURL = await question("? API Base URL: ");
79
+ const apiKey = await question("? API Key: ");
80
+ const model = await question("? Model Name: ");
81
+ if (!baseURL || !apiKey || !model) {
82
+ console.error("āŒ Error: All fields (Base URL, API Key, Model Name) are required!");
83
+ process.exit(1);
84
+ }
85
+ const config = { baseURL, apiKey, model };
86
+ fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
87
+ console.log(`āœ… Configuration saved to ${CONFIG_FILE}\n`);
88
+ rl.close();
89
+ return config;
90
+ }
91
+ /**
92
+ * 2. Load MCP server configs from common IDE paths
93
+ */
94
+ loadMcpConfigs() {
95
+ const combinedConfig = { mcpServers: {} };
96
+ const configPaths = [
97
+ path_1.default.join(os_1.default.homedir(), ".cursor", "mcp.json"),
98
+ path_1.default.join(os_1.default.homedir(), ".vscode", "mcp.json"),
99
+ ];
100
+ for (const p of configPaths) {
101
+ if (fs_1.default.existsSync(p)) {
102
+ try {
103
+ const content = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
104
+ if (content.mcpServers) {
105
+ combinedConfig.mcpServers = { ...combinedConfig.mcpServers, ...content.mcpServers };
106
+ console.log(`[MCP] Config loaded from: ${p}`);
107
+ }
108
+ }
109
+ catch (e) {
110
+ console.error(`[Error] Failed to parse MCP config ${p}:`, e);
111
+ }
112
+ }
113
+ }
114
+ return combinedConfig;
115
+ }
116
+ /**
117
+ * 3. Initialization
118
+ * Validates API credentials and connects to MCP servers.
119
+ */
120
+ async init() {
121
+ // A. Setup & Validate OpenAI
122
+ const apiConfig = await this.ensureApiConfig();
123
+ this.openai = new openai_1.default({
124
+ baseURL: apiConfig.baseURL,
125
+ apiKey: apiConfig.apiKey,
126
+ });
127
+ this.modelName = apiConfig.model;
128
+ console.log("šŸ” Validating API configuration...");
129
+ try {
130
+ // Perform a lightweight check to verify URL and Key
131
+ await this.openai.models.list();
132
+ console.log("āœ… API validation successful.");
133
+ }
134
+ catch (e) {
135
+ console.error("\nāŒ API Connection Failed!");
136
+ console.error(`Reason: ${e.message}`);
137
+ console.log(`\nSuggestion: If you made a mistake, please delete or edit: ${CONFIG_FILE}`);
138
+ process.exit(1);
139
+ }
140
+ // B. Setup MCP Clients
141
+ const mcpConfig = this.loadMcpConfigs();
142
+ const serverEntries = Object.entries(mcpConfig.mcpServers);
143
+ if (serverEntries.length === 0) {
144
+ console.warn("āš ļø No MCP server configurations found.");
145
+ }
146
+ for (const [name, server] of serverEntries) {
147
+ try {
148
+ const transport = new stdio_js_1.StdioClientTransport({
149
+ command: server.command,
150
+ args: server.args || [],
151
+ env: { ...process.env, ...(server.env || {}) },
152
+ });
153
+ const client = new index_js_1.Client({ name, version: "1.0.0" }, { capabilities: {} });
154
+ await client.connect(transport);
155
+ const { tools } = await client.listTools();
156
+ const formatted = tools.map((t) => ({
157
+ type: "function",
158
+ function: {
159
+ name: `${name}__${t.name}`,
160
+ description: t.description,
161
+ parameters: t.inputSchema,
162
+ },
163
+ _originalName: t.name,
164
+ _client: client,
165
+ }));
166
+ this.allTools.push(...formatted);
167
+ this.clients.push(client);
168
+ console.log(`āœ… [${name}] Connected, loaded ${tools.length} tools`);
169
+ }
170
+ catch (e) {
171
+ console.error(`āŒ [${name}] Failed to start:`, e);
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * 4. Core Chat Logic
177
+ * Handles user input and recursive tool calls.
178
+ */
179
+ async processChat(userInput) {
180
+ this.messages.push({ role: "user", content: userInput });
181
+ let isThinking = true;
182
+ while (isThinking) {
183
+ const apiTools = this.allTools.map(({ _originalName, _client, ...rest }) => rest);
184
+ const response = await this.openai.chat.completions.create({
185
+ model: this.modelName,
186
+ messages: this.messages,
187
+ tools: apiTools.length > 0 ? apiTools : undefined,
188
+ tool_choice: "auto",
189
+ });
190
+ const message = response.choices[0].message;
191
+ // If no more tool calls, exit loop and show final response
192
+ if (!message.tool_calls || message.tool_calls.length === 0) {
193
+ this.messages.push(message);
194
+ console.log(`\nšŸ¤– Agent: ${message.content}`);
195
+ isThinking = false;
196
+ break;
197
+ }
198
+ // Handle tool calls requested by the model
199
+ this.messages.push(message);
200
+ console.log(`\nāš™ļø Model requested ${message.tool_calls.length} tool calls...`);
201
+ for (const toolCall of message.tool_calls) {
202
+ const toolInfo = this.allTools.find((t) => t.function.name === toolCall.function.name);
203
+ if (toolInfo) {
204
+ const args = JSON.parse(toolCall.function.arguments);
205
+ console.log(` - Executing: ${toolInfo.function.name}`);
206
+ try {
207
+ const result = await toolInfo._client.callTool({
208
+ name: toolInfo._originalName,
209
+ arguments: args,
210
+ });
211
+ this.messages.push({
212
+ role: "tool",
213
+ tool_call_id: toolCall.id,
214
+ content: JSON.stringify(result.content),
215
+ });
216
+ }
217
+ catch (error) {
218
+ console.error(` - Execution failed: ${error.message}`);
219
+ this.messages.push({
220
+ role: "tool",
221
+ tool_call_id: toolCall.id,
222
+ content: `Error: ${error.message}`,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * 5. Start the Interactive Shell
231
+ */
232
+ async start() {
233
+ await this.init();
234
+ const rl = readline.createInterface({
235
+ input: process.stdin,
236
+ output: process.stdout,
237
+ });
238
+ console.log(`\nšŸš€ Agent Started (Model: ${this.modelName})! Type 'exit' to quit.`);
239
+ const chatLoop = () => {
240
+ rl.question("\nšŸ‘¤ You: ", async (input) => {
241
+ if (input.toLowerCase() === "exit") {
242
+ console.log("Goodbye!");
243
+ rl.close();
244
+ process.exit(0);
245
+ }
246
+ try {
247
+ await this.processChat(input);
248
+ }
249
+ catch (err) {
250
+ console.error("\nāŒ System Error during chat:", err.message);
251
+ console.log("Try checking your API configuration or network connection.");
252
+ }
253
+ chatLoop();
254
+ });
255
+ };
256
+ chatLoop();
257
+ }
258
+ }
259
+ exports.default = McpAgent;
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const agent_1 = __importDefault(require("./agent"));
8
+ const manager = new agent_1.default();
9
+ manager.start();
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@saber2pr/ai-agent",
3
+ "version": "0.0.3",
4
+ "description": "AI Assistant CLI.",
5
+ "author": "saber2pr",
6
+ "license": "ISC",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "bin": {
11
+ "sagent": "./lib/index.js"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "registry": "https://registry.npmjs.org/"
16
+ },
17
+ "main": "./lib/index.js",
18
+ "scripts": {
19
+ "test": "node lib/index",
20
+ "start": "tsc --watch",
21
+ "prepublishOnly": "tsc"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.25.3",
25
+ "openai": "^6.16.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^16.3.3",
29
+ "prettier": "^2.4.1",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }