@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 +74 -0
- package/lib/agent.d.ts +31 -0
- package/lib/agent.js +259 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/package.json +32 -0
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
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
|
+
}
|