tuna-agent 0.1.111 → 0.1.112
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/dist/mcp/setup.js +20 -0
- package/dist/mcp/tool-server.d.ts +11 -0
- package/dist/mcp/tool-server.js +459 -0
- package/package.json +1 -1
package/dist/mcp/setup.js
CHANGED
|
@@ -379,6 +379,7 @@ function buildMem0McpConfig(agentName) {
|
|
|
379
379
|
export function setupMcpConfig(config) {
|
|
380
380
|
const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
|
|
381
381
|
const ideaServerPath = path.join(__dirname, 'idea-server.js');
|
|
382
|
+
const toolServerPath = path.join(__dirname, 'tool-server.js');
|
|
382
383
|
const browserServerPath = path.join(__dirname, 'browser-server.js');
|
|
383
384
|
const mcpServers = {
|
|
384
385
|
'tuna-knowledge': {
|
|
@@ -399,6 +400,15 @@ export function setupMcpConfig(config) {
|
|
|
399
400
|
],
|
|
400
401
|
env: { TUNA_AGENT_TOKEN: config.agentToken },
|
|
401
402
|
},
|
|
403
|
+
'tuna-tools': {
|
|
404
|
+
command: process.execPath,
|
|
405
|
+
args: [
|
|
406
|
+
toolServerPath,
|
|
407
|
+
'--api-url', config.apiUrl,
|
|
408
|
+
'--agent-id', config.agentId,
|
|
409
|
+
],
|
|
410
|
+
env: { TUNA_AGENT_TOKEN: config.agentToken },
|
|
411
|
+
},
|
|
402
412
|
'tuna-browser': {
|
|
403
413
|
command: process.execPath,
|
|
404
414
|
args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
|
|
@@ -431,6 +441,7 @@ export function getMcpConfigPath() {
|
|
|
431
441
|
export function writeAgentFolderMcpConfig(agentFolderPath, config, agentId) {
|
|
432
442
|
const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
|
|
433
443
|
const ideaServerPath = path.join(__dirname, 'idea-server.js');
|
|
444
|
+
const toolServerPath = path.join(__dirname, 'tool-server.js');
|
|
434
445
|
const browserServerPath = path.join(__dirname, 'browser-server.js');
|
|
435
446
|
// Derive agent name from folder path (e.g. ~/agents/co-founder → "co-founder")
|
|
436
447
|
// config.name is the machine name, NOT the agent name
|
|
@@ -465,6 +476,15 @@ export function writeAgentFolderMcpConfig(agentFolderPath, config, agentId) {
|
|
|
465
476
|
],
|
|
466
477
|
env: { TUNA_AGENT_TOKEN: config.agentToken },
|
|
467
478
|
},
|
|
479
|
+
'tuna-tools': {
|
|
480
|
+
command: process.execPath,
|
|
481
|
+
args: [
|
|
482
|
+
toolServerPath,
|
|
483
|
+
'--api-url', config.apiUrl,
|
|
484
|
+
'--agent-id', mcpAgentId,
|
|
485
|
+
],
|
|
486
|
+
env: { TUNA_AGENT_TOKEN: config.agentToken },
|
|
487
|
+
},
|
|
468
488
|
'tuna-browser': {
|
|
469
489
|
command: process.execPath,
|
|
470
490
|
args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tool Library MCP Server for Tuna Agent
|
|
4
|
+
*
|
|
5
|
+
* Stdio-based MCP server that exposes tool research/discovery tools to Claude Code.
|
|
6
|
+
* Communicates with the Tuna API using agent token auth.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node tool-server.js --api-url https://api.tuna.ai --token xxx --agent-id abc123
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tool Library MCP Server for Tuna Agent
|
|
4
|
+
*
|
|
5
|
+
* Stdio-based MCP server that exposes tool research/discovery tools to Claude Code.
|
|
6
|
+
* Communicates with the Tuna API using agent token auth.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node tool-server.js --api-url https://api.tuna.ai --token xxx --agent-id abc123
|
|
10
|
+
*/
|
|
11
|
+
function parseArgs() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
let apiUrl = '';
|
|
14
|
+
let token = process.env.TUNA_AGENT_TOKEN || '';
|
|
15
|
+
let agentId = '';
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
if (args[i] === '--api-url' && args[i + 1])
|
|
18
|
+
apiUrl = args[++i];
|
|
19
|
+
else if (args[i] === '--token' && args[i + 1])
|
|
20
|
+
token = args[++i];
|
|
21
|
+
else if (args[i] === '--agent-id' && args[i + 1])
|
|
22
|
+
agentId = args[++i];
|
|
23
|
+
}
|
|
24
|
+
if (!apiUrl || !token || !agentId) {
|
|
25
|
+
process.stderr.write('Usage: tool-server --api-url URL [--token TOKEN] --agent-id ID\n');
|
|
26
|
+
process.stderr.write('Token can also be set via TUNA_AGENT_TOKEN env var\n');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return { apiUrl, token, agentId };
|
|
30
|
+
}
|
|
31
|
+
// ===== API Client =====
|
|
32
|
+
async function apiCall(config, method, path, body) {
|
|
33
|
+
const url = `${config.apiUrl}${path}`;
|
|
34
|
+
const headers = {
|
|
35
|
+
'Authorization': `Bearer ${config.token}`,
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
};
|
|
38
|
+
const options = { method, headers };
|
|
39
|
+
if (body)
|
|
40
|
+
options.body = JSON.stringify(body);
|
|
41
|
+
const res = await fetch(url, options);
|
|
42
|
+
const json = await res.json();
|
|
43
|
+
if (!res.ok || (json.code && json.code >= 400)) {
|
|
44
|
+
throw new Error(json.message || `API error: ${res.status}`);
|
|
45
|
+
}
|
|
46
|
+
return json.data;
|
|
47
|
+
}
|
|
48
|
+
function sendResponse(res) {
|
|
49
|
+
process.stdout.write(JSON.stringify(res) + '\n');
|
|
50
|
+
}
|
|
51
|
+
function sendResult(id, result) {
|
|
52
|
+
sendResponse({ jsonrpc: '2.0', id: id ?? null, result });
|
|
53
|
+
}
|
|
54
|
+
function sendError(id, code, message) {
|
|
55
|
+
sendResponse({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
|
56
|
+
}
|
|
57
|
+
// ===== Tool Definitions =====
|
|
58
|
+
const TOOLS = [
|
|
59
|
+
{
|
|
60
|
+
name: 'create_tool',
|
|
61
|
+
description: 'Create a new tool entry in the tool library. Captures open-source tools/technologies from research or discovery.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
name: { type: 'string', description: 'Name of the tool/technology' },
|
|
66
|
+
category: { type: 'string', description: 'Category (e.g. "ai/ml", "media", "productivity", "dev-tools", "health", "finance")' },
|
|
67
|
+
source: { type: 'string', enum: ['manual', 'discover', 'trending'], description: 'How this tool was found (default: manual)' },
|
|
68
|
+
description: { type: 'string', description: 'Overview of the tool' },
|
|
69
|
+
github_url: { type: 'string', description: 'GitHub repository URL' },
|
|
70
|
+
website_url: { type: 'string', description: 'Official website URL' },
|
|
71
|
+
stars: { type: 'number', description: 'GitHub stars count' },
|
|
72
|
+
license: { type: 'string', description: 'License type (e.g. MIT, Apache-2.0)' },
|
|
73
|
+
key_features: { type: 'string', description: 'Comma-separated key features' },
|
|
74
|
+
use_cases: { type: 'string', description: 'Comma-separated use cases' },
|
|
75
|
+
tech_stack: { type: 'string', description: 'Tech stack / languages used' },
|
|
76
|
+
pros: { type: 'string', description: 'Comma-separated pros' },
|
|
77
|
+
cons: { type: 'string', description: 'Comma-separated cons' },
|
|
78
|
+
alternatives: { type: 'string', description: 'Comma-separated alternative tools' },
|
|
79
|
+
combo_ideas: { type: 'string', description: 'Comma-separated ideas for combining with other tools' },
|
|
80
|
+
mobile_potential: { type: 'number', description: 'Mobile app potential score 1-5 (default: 3)' },
|
|
81
|
+
relevance: { type: 'string', description: 'Relevance notes for Tuna/app ideas' },
|
|
82
|
+
notes: { type: 'string', description: 'Additional notes' },
|
|
83
|
+
tags: { type: 'string', description: 'Comma-separated tags' },
|
|
84
|
+
},
|
|
85
|
+
required: ['name', 'category'],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'update_tool',
|
|
90
|
+
description: 'Update an existing tool entry. Can update any field including status, score, notes, etc.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
tool_id: { type: 'string', description: 'The ID of the tool to update' },
|
|
95
|
+
name: { type: 'string', description: 'New name' },
|
|
96
|
+
category: { type: 'string', description: 'New category' },
|
|
97
|
+
status: { type: 'string', enum: ['new', 'researched', 'archived'], description: 'New status' },
|
|
98
|
+
source: { type: 'string', enum: ['manual', 'discover', 'trending'], description: 'New source' },
|
|
99
|
+
description: { type: 'string', description: 'New description' },
|
|
100
|
+
github_url: { type: 'string', description: 'New GitHub URL' },
|
|
101
|
+
website_url: { type: 'string', description: 'New website URL' },
|
|
102
|
+
stars: { type: 'number', description: 'New stars count' },
|
|
103
|
+
license: { type: 'string', description: 'New license' },
|
|
104
|
+
key_features: { type: 'string', description: 'Comma-separated key features' },
|
|
105
|
+
use_cases: { type: 'string', description: 'Comma-separated use cases' },
|
|
106
|
+
tech_stack: { type: 'string', description: 'New tech stack' },
|
|
107
|
+
pros: { type: 'string', description: 'Comma-separated pros' },
|
|
108
|
+
cons: { type: 'string', description: 'Comma-separated cons' },
|
|
109
|
+
alternatives: { type: 'string', description: 'Comma-separated alternatives' },
|
|
110
|
+
combo_ideas: { type: 'string', description: 'Comma-separated combo ideas' },
|
|
111
|
+
mobile_potential: { type: 'number', description: 'New mobile potential score 1-5' },
|
|
112
|
+
relevance: { type: 'string', description: 'New relevance notes' },
|
|
113
|
+
notes: { type: 'string', description: 'New notes' },
|
|
114
|
+
tags: { type: 'string', description: 'Comma-separated tags' },
|
|
115
|
+
},
|
|
116
|
+
required: ['tool_id'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'list_tools',
|
|
121
|
+
description: 'List tools in the library with optional filters. Returns tools with status indicators.',
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
status: { type: 'string', enum: ['new', 'researched', 'archived'], description: 'Filter by status' },
|
|
126
|
+
category: { type: 'string', description: 'Filter by category' },
|
|
127
|
+
source: { type: 'string', enum: ['manual', 'discover', 'trending'], description: 'Filter by source' },
|
|
128
|
+
agent_id: { type: 'string', description: 'Filter by agent ID (defaults to current agent)' },
|
|
129
|
+
page: { type: 'number', description: 'Page number (default 1)' },
|
|
130
|
+
limit: { type: 'number', description: 'Items per page (default 20)' },
|
|
131
|
+
sort_by: { type: 'string', description: 'Field to sort by (e.g. "mobile_potential", "stars", "created_at")' },
|
|
132
|
+
sort_order: { type: 'string', enum: ['asc', 'desc'], description: 'Sort order' },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'search_tools',
|
|
138
|
+
description: 'Search tools by keyword query. Searches across name, description, notes, and tags.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
query: { type: 'string', description: 'Search query string' },
|
|
143
|
+
status: { type: 'string', enum: ['new', 'researched', 'archived'], description: 'Filter by status' },
|
|
144
|
+
category: { type: 'string', description: 'Filter by category' },
|
|
145
|
+
page: { type: 'number', description: 'Page number' },
|
|
146
|
+
limit: { type: 'number', description: 'Items per page' },
|
|
147
|
+
},
|
|
148
|
+
required: ['query'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'archive_tool',
|
|
153
|
+
description: 'Archive a tool by setting its status to archived.',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
tool_id: { type: 'string', description: 'The ID of the tool to archive' },
|
|
158
|
+
},
|
|
159
|
+
required: ['tool_id'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'bulk_update_tools',
|
|
164
|
+
description: 'Update multiple tools at once. Useful for batch status changes or categorization.',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
tool_ids: { type: 'string', description: 'Comma-separated list of tool IDs to update' },
|
|
169
|
+
status: { type: 'string', enum: ['new', 'researched', 'archived'], description: 'New status for all tools' },
|
|
170
|
+
category: { type: 'string', description: 'New category for all tools' },
|
|
171
|
+
mobile_potential: { type: 'number', description: 'New mobile potential score for all tools' },
|
|
172
|
+
},
|
|
173
|
+
required: ['tool_ids'],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
// ===== Status Emoji Helper =====
|
|
178
|
+
function statusEmoji(status) {
|
|
179
|
+
switch (status) {
|
|
180
|
+
case 'new': return '\u2B50'; // ⭐
|
|
181
|
+
case 'researched': return '\uD83D\uDD2C'; // 🔬
|
|
182
|
+
case 'archived': return '\uD83D\uDCE6'; // 📦
|
|
183
|
+
default: return '\u2753'; // ❓
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// ===== Helper: parse comma-separated strings to arrays =====
|
|
187
|
+
function parseCSV(value) {
|
|
188
|
+
if (!value)
|
|
189
|
+
return undefined;
|
|
190
|
+
return value.split(',').map((s) => s.trim()).filter(Boolean);
|
|
191
|
+
}
|
|
192
|
+
// ===== Request Handler =====
|
|
193
|
+
async function handleRequest(config, req) {
|
|
194
|
+
try {
|
|
195
|
+
switch (req.method) {
|
|
196
|
+
case 'initialize':
|
|
197
|
+
sendResult(req.id, {
|
|
198
|
+
protocolVersion: '2024-11-05',
|
|
199
|
+
capabilities: { tools: {} },
|
|
200
|
+
serverInfo: { name: 'tuna-tool-library', version: '1.0.0' },
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
case 'notifications/initialized':
|
|
204
|
+
break;
|
|
205
|
+
case 'tools/list':
|
|
206
|
+
sendResult(req.id, { tools: TOOLS });
|
|
207
|
+
break;
|
|
208
|
+
case 'tools/call': {
|
|
209
|
+
const toolName = req.params?.name ?? '';
|
|
210
|
+
const args = req.params?.arguments ?? {};
|
|
211
|
+
const result = await handleToolCall(config, toolName, args);
|
|
212
|
+
sendResult(req.id, result);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'ping':
|
|
216
|
+
sendResult(req.id, {});
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
if (req.id !== undefined) {
|
|
220
|
+
sendError(req.id, -32601, `Method not found: ${req.method}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
226
|
+
if (req.id !== undefined) {
|
|
227
|
+
sendError(req.id, -32603, message);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function handleToolCall(config, toolName, args) {
|
|
232
|
+
try {
|
|
233
|
+
switch (toolName) {
|
|
234
|
+
case 'create_tool': {
|
|
235
|
+
if (!args.name || !args.category) {
|
|
236
|
+
return { content: [{ type: 'text', text: 'Error: name and category are required' }], isError: true };
|
|
237
|
+
}
|
|
238
|
+
const body = {
|
|
239
|
+
name: args.name,
|
|
240
|
+
category: args.category,
|
|
241
|
+
agent_id: config.agentId,
|
|
242
|
+
};
|
|
243
|
+
if (args.source)
|
|
244
|
+
body.source = args.source;
|
|
245
|
+
if (args.description)
|
|
246
|
+
body.description = args.description;
|
|
247
|
+
if (args.github_url)
|
|
248
|
+
body.github_url = args.github_url;
|
|
249
|
+
if (args.website_url)
|
|
250
|
+
body.website_url = args.website_url;
|
|
251
|
+
if (args.stars)
|
|
252
|
+
body.stars = Number(args.stars);
|
|
253
|
+
if (args.license)
|
|
254
|
+
body.license = args.license;
|
|
255
|
+
if (args.key_features)
|
|
256
|
+
body.key_features = parseCSV(args.key_features);
|
|
257
|
+
if (args.use_cases)
|
|
258
|
+
body.use_cases = parseCSV(args.use_cases);
|
|
259
|
+
if (args.tech_stack)
|
|
260
|
+
body.tech_stack = args.tech_stack;
|
|
261
|
+
if (args.pros)
|
|
262
|
+
body.pros = parseCSV(args.pros);
|
|
263
|
+
if (args.cons)
|
|
264
|
+
body.cons = parseCSV(args.cons);
|
|
265
|
+
if (args.alternatives)
|
|
266
|
+
body.alternatives = parseCSV(args.alternatives);
|
|
267
|
+
if (args.combo_ideas)
|
|
268
|
+
body.combo_ideas = parseCSV(args.combo_ideas);
|
|
269
|
+
if (args.mobile_potential)
|
|
270
|
+
body.mobile_potential = Number(args.mobile_potential);
|
|
271
|
+
if (args.relevance)
|
|
272
|
+
body.relevance = args.relevance;
|
|
273
|
+
if (args.notes)
|
|
274
|
+
body.notes = args.notes;
|
|
275
|
+
if (args.tags)
|
|
276
|
+
body.tags = parseCSV(args.tags);
|
|
277
|
+
const data = await apiCall(config, 'POST', '/agent-tool', body);
|
|
278
|
+
return { content: [{ type: 'text', text: `Tool "${data.name}" created (ID: ${data._id})` }] };
|
|
279
|
+
}
|
|
280
|
+
case 'update_tool': {
|
|
281
|
+
if (!args.tool_id) {
|
|
282
|
+
return { content: [{ type: 'text', text: 'Error: tool_id is required' }], isError: true };
|
|
283
|
+
}
|
|
284
|
+
const body = {};
|
|
285
|
+
if (args.name)
|
|
286
|
+
body.name = args.name;
|
|
287
|
+
if (args.category)
|
|
288
|
+
body.category = args.category;
|
|
289
|
+
if (args.status)
|
|
290
|
+
body.status = args.status;
|
|
291
|
+
if (args.source)
|
|
292
|
+
body.source = args.source;
|
|
293
|
+
if (args.description)
|
|
294
|
+
body.description = args.description;
|
|
295
|
+
if (args.github_url)
|
|
296
|
+
body.github_url = args.github_url;
|
|
297
|
+
if (args.website_url)
|
|
298
|
+
body.website_url = args.website_url;
|
|
299
|
+
if (args.stars)
|
|
300
|
+
body.stars = Number(args.stars);
|
|
301
|
+
if (args.license)
|
|
302
|
+
body.license = args.license;
|
|
303
|
+
if (args.key_features)
|
|
304
|
+
body.key_features = parseCSV(args.key_features);
|
|
305
|
+
if (args.use_cases)
|
|
306
|
+
body.use_cases = parseCSV(args.use_cases);
|
|
307
|
+
if (args.tech_stack)
|
|
308
|
+
body.tech_stack = args.tech_stack;
|
|
309
|
+
if (args.pros)
|
|
310
|
+
body.pros = parseCSV(args.pros);
|
|
311
|
+
if (args.cons)
|
|
312
|
+
body.cons = parseCSV(args.cons);
|
|
313
|
+
if (args.alternatives)
|
|
314
|
+
body.alternatives = parseCSV(args.alternatives);
|
|
315
|
+
if (args.combo_ideas)
|
|
316
|
+
body.combo_ideas = parseCSV(args.combo_ideas);
|
|
317
|
+
if (args.mobile_potential)
|
|
318
|
+
body.mobile_potential = Number(args.mobile_potential);
|
|
319
|
+
if (args.relevance)
|
|
320
|
+
body.relevance = args.relevance;
|
|
321
|
+
if (args.notes)
|
|
322
|
+
body.notes = args.notes;
|
|
323
|
+
if (args.tags)
|
|
324
|
+
body.tags = parseCSV(args.tags);
|
|
325
|
+
const data = await apiCall(config, 'PUT', `/agent-tool/${args.tool_id}`, body);
|
|
326
|
+
return { content: [{ type: 'text', text: `Tool "${data.name}" updated (ID: ${data._id})` }] };
|
|
327
|
+
}
|
|
328
|
+
case 'list_tools': {
|
|
329
|
+
const params = new URLSearchParams();
|
|
330
|
+
if (args.agent_id)
|
|
331
|
+
params.set('agent_id', args.agent_id);
|
|
332
|
+
if (args.status)
|
|
333
|
+
params.set('status', args.status);
|
|
334
|
+
if (args.category)
|
|
335
|
+
params.set('category', args.category);
|
|
336
|
+
if (args.source)
|
|
337
|
+
params.set('source', args.source);
|
|
338
|
+
if (args.page)
|
|
339
|
+
params.set('page', args.page);
|
|
340
|
+
if (args.limit)
|
|
341
|
+
params.set('limit', args.limit || '100');
|
|
342
|
+
if (args.sort_by)
|
|
343
|
+
params.set('sort_by', args.sort_by);
|
|
344
|
+
if (args.sort_order)
|
|
345
|
+
params.set('sort_order', args.sort_order);
|
|
346
|
+
const data = await apiCall(config, 'GET', `/agent-tool?${params.toString()}`);
|
|
347
|
+
const tools = data.items || [];
|
|
348
|
+
if (tools.length === 0) {
|
|
349
|
+
return { content: [{ type: 'text', text: 'No tools found.' }] };
|
|
350
|
+
}
|
|
351
|
+
const listing = tools.map((tool) => {
|
|
352
|
+
const emoji = statusEmoji(tool.status);
|
|
353
|
+
const mp = tool.mobile_potential != null ? ` | Mobile: ${tool.mobile_potential}/5` : '';
|
|
354
|
+
const stars = tool.stars ? ` | Stars: ${tool.stars}` : '';
|
|
355
|
+
const tags = tool.tags && tool.tags.length > 0 ? ` | Tags: ${tool.tags.join(', ')}` : '';
|
|
356
|
+
return `- ${emoji} **${tool.name}** (ID: ${tool._id})\n Category: ${tool.category} | Source: ${tool.source}${mp}${stars}${tags}\n Created: ${tool.created_at}`;
|
|
357
|
+
}).join('\n\n');
|
|
358
|
+
const total = data.total != null ? ` (total: ${data.total})` : '';
|
|
359
|
+
return { content: [{ type: 'text', text: `Found ${tools.length} tool(s)${total}:\n\n${listing}` }] };
|
|
360
|
+
}
|
|
361
|
+
case 'search_tools': {
|
|
362
|
+
if (!args.query) {
|
|
363
|
+
return { content: [{ type: 'text', text: 'Error: query is required' }], isError: true };
|
|
364
|
+
}
|
|
365
|
+
const params = new URLSearchParams();
|
|
366
|
+
params.set('q', args.query);
|
|
367
|
+
if (args.status)
|
|
368
|
+
params.set('status', args.status);
|
|
369
|
+
if (args.category)
|
|
370
|
+
params.set('category', args.category);
|
|
371
|
+
if (args.page)
|
|
372
|
+
params.set('page', args.page);
|
|
373
|
+
if (args.limit)
|
|
374
|
+
params.set('limit', args.limit);
|
|
375
|
+
const data = await apiCall(config, 'GET', `/agent-tool/search?${params.toString()}`);
|
|
376
|
+
const tools = data.items || [];
|
|
377
|
+
if (tools.length === 0) {
|
|
378
|
+
return { content: [{ type: 'text', text: `No tools found matching "${args.query}".` }] };
|
|
379
|
+
}
|
|
380
|
+
const listing = tools.map((tool) => {
|
|
381
|
+
const emoji = statusEmoji(tool.status);
|
|
382
|
+
const mp = tool.mobile_potential != null ? ` | Mobile: ${tool.mobile_potential}/5` : '';
|
|
383
|
+
const stars = tool.stars ? ` | Stars: ${tool.stars}` : '';
|
|
384
|
+
const tags = tool.tags && tool.tags.length > 0 ? ` | Tags: ${tool.tags.join(', ')}` : '';
|
|
385
|
+
return `- ${emoji} **${tool.name}** (ID: ${tool._id})\n Category: ${tool.category} | Source: ${tool.source}${mp}${stars}${tags}`;
|
|
386
|
+
}).join('\n\n');
|
|
387
|
+
const total = data.total != null ? ` (total: ${data.total})` : '';
|
|
388
|
+
return { content: [{ type: 'text', text: `Search results for "${args.query}" — ${tools.length} tool(s)${total}:\n\n${listing}` }] };
|
|
389
|
+
}
|
|
390
|
+
case 'archive_tool': {
|
|
391
|
+
if (!args.tool_id) {
|
|
392
|
+
return { content: [{ type: 'text', text: 'Error: tool_id is required' }], isError: true };
|
|
393
|
+
}
|
|
394
|
+
const data = await apiCall(config, 'PUT', `/agent-tool/${args.tool_id}/archive`, {});
|
|
395
|
+
return { content: [{ type: 'text', text: `Tool "${data.name}" archived (ID: ${data._id})` }] };
|
|
396
|
+
}
|
|
397
|
+
case 'bulk_update_tools': {
|
|
398
|
+
if (!args.tool_ids) {
|
|
399
|
+
return { content: [{ type: 'text', text: 'Error: tool_ids is required' }], isError: true };
|
|
400
|
+
}
|
|
401
|
+
const body = {
|
|
402
|
+
tool_ids: args.tool_ids.split(',').map((id) => id.trim()),
|
|
403
|
+
data: {},
|
|
404
|
+
};
|
|
405
|
+
const updateData = body.data;
|
|
406
|
+
if (args.status)
|
|
407
|
+
updateData.status = args.status;
|
|
408
|
+
if (args.category)
|
|
409
|
+
updateData.category = args.category;
|
|
410
|
+
if (args.mobile_potential)
|
|
411
|
+
updateData.mobile_potential = Number(args.mobile_potential);
|
|
412
|
+
if (Object.keys(updateData).length === 0) {
|
|
413
|
+
return { content: [{ type: 'text', text: 'Error: at least one of status, category, or mobile_potential is required' }], isError: true };
|
|
414
|
+
}
|
|
415
|
+
const data = await apiCall(config, 'PUT', '/agent-tool/bulk', body);
|
|
416
|
+
return { content: [{ type: 'text', text: `Bulk update complete: ${data.updated_count} tool(s) updated.` }] };
|
|
417
|
+
}
|
|
418
|
+
default:
|
|
419
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
424
|
+
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// ===== Stdio Transport =====
|
|
428
|
+
function startServer(config) {
|
|
429
|
+
process.stderr.write(`[tool-mcp] Starting with agent ${config.agentId}\n`);
|
|
430
|
+
let buffer = '';
|
|
431
|
+
process.stdin.setEncoding('utf-8');
|
|
432
|
+
process.stdin.on('data', (chunk) => {
|
|
433
|
+
buffer += chunk;
|
|
434
|
+
const lines = buffer.split('\n');
|
|
435
|
+
buffer = lines.pop() ?? '';
|
|
436
|
+
for (const line of lines) {
|
|
437
|
+
const trimmed = line.trim();
|
|
438
|
+
if (!trimmed)
|
|
439
|
+
continue;
|
|
440
|
+
try {
|
|
441
|
+
const req = JSON.parse(trimmed);
|
|
442
|
+
handleRequest(config, req).catch((err) => {
|
|
443
|
+
process.stderr.write(`[tool-mcp] Error handling ${req.method}: ${err}\n`);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
process.stderr.write(`[tool-mcp] Failed to parse JSON: ${trimmed.slice(0, 100)}\n`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
process.stdin.on('end', () => {
|
|
452
|
+
process.stderr.write('[tool-mcp] stdin closed, shutting down\n');
|
|
453
|
+
process.exit(0);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
// ===== Main =====
|
|
457
|
+
const config = parseArgs();
|
|
458
|
+
startServer(config);
|
|
459
|
+
export {};
|