mentat-mcp 1.0.0
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 +122 -0
- package/dist/index.js +353 -0
- package/dist/setup.js +209 -0
- package/dist/skills/engine.js +362 -0
- package/dist/skills.js +141 -0
- package/package.json +48 -0
- package/src/index.ts +426 -0
- package/src/setup.ts +249 -0
- package/src/skills.ts +199 -0
- package/tsconfig.json +16 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { SkillLibrary } from './skills.js';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
const API_BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
|
12
|
+
const WORKSPACE_PATH = process.cwd();
|
|
13
|
+
const SKILLS_PATH = path.join(WORKSPACE_PATH, 'skills');
|
|
14
|
+
|
|
15
|
+
class AgentMarketplaceServer {
|
|
16
|
+
private server: Server;
|
|
17
|
+
private skillLibrary: SkillLibrary;
|
|
18
|
+
private apiKey: string | null = null;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.server = new Server(
|
|
22
|
+
{
|
|
23
|
+
name: 'agent-marketplace',
|
|
24
|
+
version: '2.0.0',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
this.skillLibrary = new SkillLibrary(SKILLS_PATH, WORKSPACE_PATH);
|
|
34
|
+
|
|
35
|
+
this.setupHandlers();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private setupHandlers() {
|
|
39
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
40
|
+
tools: [
|
|
41
|
+
{
|
|
42
|
+
name: 'execute_skill',
|
|
43
|
+
description: 'Execute a skill locally (instant, free)',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
skillId: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Skill ID to execute (e.g., seo-meta-tags)',
|
|
50
|
+
},
|
|
51
|
+
inputs: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
description: 'Input parameters for the skill',
|
|
54
|
+
},
|
|
55
|
+
targetFiles: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { type: 'string' },
|
|
58
|
+
description: 'Files to operate on',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ['skillId'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'hire_worker',
|
|
66
|
+
description: 'Hire a specialist worker for custom work (paid)',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
task: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Description of the task',
|
|
73
|
+
},
|
|
74
|
+
specialty: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Worker specialty (optional)',
|
|
77
|
+
},
|
|
78
|
+
budget: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
description: 'Maximum budget in USD',
|
|
81
|
+
},
|
|
82
|
+
context: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
description: 'Context files and metadata',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['task'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'check_job',
|
|
92
|
+
description: 'Check status of a job',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
jobId: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Job ID to check',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
required: ['jobId'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'approve_job',
|
|
106
|
+
description: 'Approve job and release payment',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
jobId: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Job ID to approve',
|
|
113
|
+
},
|
|
114
|
+
rating: {
|
|
115
|
+
type: 'number',
|
|
116
|
+
description: 'Rating from 1-5',
|
|
117
|
+
minimum: 1,
|
|
118
|
+
maximum: 5,
|
|
119
|
+
},
|
|
120
|
+
feedback: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Optional feedback',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['jobId', 'rating'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'reject_job',
|
|
130
|
+
description: 'Reject job and request refund',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
jobId: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Job ID to reject',
|
|
137
|
+
},
|
|
138
|
+
reason: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
description: 'Reason for rejection',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ['jobId', 'reason'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'check_wallet',
|
|
148
|
+
description: 'Check wallet balance',
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
158
|
+
const { name, arguments: args } = request.params;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
switch (name) {
|
|
162
|
+
case 'execute_skill':
|
|
163
|
+
return await this.executeSkill(args as any);
|
|
164
|
+
case 'hire_worker':
|
|
165
|
+
return await this.hireWorker(args as any);
|
|
166
|
+
case 'check_job':
|
|
167
|
+
return await this.checkJob(args as any);
|
|
168
|
+
case 'approve_job':
|
|
169
|
+
return await this.approveJob(args as any);
|
|
170
|
+
case 'reject_job':
|
|
171
|
+
return await this.rejectJob(args as any);
|
|
172
|
+
case 'check_wallet':
|
|
173
|
+
return await this.checkWallet();
|
|
174
|
+
default:
|
|
175
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: 'text',
|
|
182
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
isError: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async executeSkill(args: {
|
|
192
|
+
skillId: string;
|
|
193
|
+
inputs?: Record<string, any>;
|
|
194
|
+
targetFiles?: string[];
|
|
195
|
+
}) {
|
|
196
|
+
// Load skill definition
|
|
197
|
+
const skill = await this.skillLibrary.loadSkill(args.skillId);
|
|
198
|
+
|
|
199
|
+
// Gather context from files
|
|
200
|
+
const context = await this.skillLibrary.gatherContext(
|
|
201
|
+
skill.context_patterns || [],
|
|
202
|
+
args.targetFiles || []
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Format for Claude to read
|
|
206
|
+
const formattedPrompt = this.skillLibrary.formatForClaude(skill, context);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: formattedPrompt,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async hireWorker(args: {
|
|
219
|
+
task: string;
|
|
220
|
+
specialty?: string;
|
|
221
|
+
budget?: number;
|
|
222
|
+
context?: Record<string, any>;
|
|
223
|
+
}) {
|
|
224
|
+
const response = await fetch(`${API_BASE_URL}/api/match`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
},
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
task: args.task,
|
|
231
|
+
specialty: args.specialty,
|
|
232
|
+
budget: args.budget,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
throw new Error(`Match API failed: ${response.statusText}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { match } = await response.json();
|
|
241
|
+
|
|
242
|
+
if (match.type === 'skill') {
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: 'text',
|
|
247
|
+
text: `Found matching skill: ${match.skill.name}\n\nExecute with: execute_skill(skillId: "${match.skill.id}")`,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (match.type === 'worker') {
|
|
254
|
+
const { matches, recommendation } = match;
|
|
255
|
+
|
|
256
|
+
// Build transparent worker list with reasoning
|
|
257
|
+
let text = '💡 Recommended: Hire a Worker\n\n';
|
|
258
|
+
text += `Why: ${recommendation}\n\n`;
|
|
259
|
+
text += '─────────────────────────────────────\n';
|
|
260
|
+
text += `Top ${Math.min(matches.length, 5)} Matching Workers:\n\n`;
|
|
261
|
+
|
|
262
|
+
matches.slice(0, 5).forEach((m: any, index: number) => {
|
|
263
|
+
const confidenceIcon =
|
|
264
|
+
m.confidence === 'high' ? '🟢' : m.confidence === 'medium' ? '🟡' : '🔴';
|
|
265
|
+
|
|
266
|
+
text += `${index + 1}. ${m.worker.name} ${confidenceIcon}\n`;
|
|
267
|
+
text += ` Specialty: ${m.worker.specialty}\n`;
|
|
268
|
+
text += ` Rating: ${m.worker.reputationScore}/5 (${m.worker.completionCount} jobs)\n`;
|
|
269
|
+
text += ` Avg Time: ~${m.worker.avgCompletionTime} min\n`;
|
|
270
|
+
text += ` Est Cost: $${m.worker.pricing}\n`;
|
|
271
|
+
text += ` Match: ${Math.round(m.score)}% - ${m.reasons.join(', ')}\n`;
|
|
272
|
+
text += ` Confidence: ${m.confidence.toUpperCase()}\n`;
|
|
273
|
+
text += '\n';
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
text += '─────────────────────────────────────\n\n';
|
|
277
|
+
text += '📌 To hire a worker, note their number and:\n';
|
|
278
|
+
text += ' 1. Check your wallet: check_wallet()\n';
|
|
279
|
+
text += ' 2. Create job with preferred worker\n\n';
|
|
280
|
+
|
|
281
|
+
if (matches.length > 5) {
|
|
282
|
+
text += `💡 Showing top 5 of ${matches.length} matching workers\n\n`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
text += 'Or type: execute_skill(...) to try a pre-built skill instead';
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: 'text', text }],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
content: [
|
|
294
|
+
{
|
|
295
|
+
type: 'text',
|
|
296
|
+
text: match.message || 'No match found',
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async checkJob(args: { jobId: string }) {
|
|
303
|
+
const response = await fetch(`${API_BASE_URL}/api/jobs/${args.jobId}`, {
|
|
304
|
+
headers: this.getAuthHeaders(),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
throw new Error(`Job fetch failed: ${response.statusText}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { job } = await response.json();
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
content: [
|
|
315
|
+
{
|
|
316
|
+
type: 'text',
|
|
317
|
+
text: `Job ${job.id}\n\nStatus: ${job.status}\nTask: ${job.task}\nBudget: $${job.budget}\nCreated: ${new Date(job.createdAt).toLocaleString()}\n${job.deliveredAt ? `\nDelivered: ${new Date(job.deliveredAt).toLocaleString()}` : ''}`,
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private async approveJob(args: {
|
|
324
|
+
jobId: string;
|
|
325
|
+
rating: number;
|
|
326
|
+
feedback?: string;
|
|
327
|
+
}) {
|
|
328
|
+
const response = await fetch(
|
|
329
|
+
`${API_BASE_URL}/api/jobs/${args.jobId}/approve`,
|
|
330
|
+
{
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: {
|
|
333
|
+
...this.getAuthHeaders(),
|
|
334
|
+
'Content-Type': 'application/json',
|
|
335
|
+
},
|
|
336
|
+
body: JSON.stringify({
|
|
337
|
+
rating: args.rating,
|
|
338
|
+
feedback: args.feedback,
|
|
339
|
+
}),
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
throw new Error(`Approval failed: ${response.statusText}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const { job } = await response.json();
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
content: [
|
|
351
|
+
{
|
|
352
|
+
type: 'text',
|
|
353
|
+
text: `✓ Job approved!\n\nPayment released to worker.\nRating: ${args.rating}/5`,
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private async rejectJob(args: { jobId: string; reason: string }) {
|
|
360
|
+
const response = await fetch(
|
|
361
|
+
`${API_BASE_URL}/api/jobs/${args.jobId}/reject`,
|
|
362
|
+
{
|
|
363
|
+
method: 'POST',
|
|
364
|
+
headers: {
|
|
365
|
+
...this.getAuthHeaders(),
|
|
366
|
+
'Content-Type': 'application/json',
|
|
367
|
+
},
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
reason: args.reason,
|
|
370
|
+
}),
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
throw new Error(`Rejection failed: ${response.statusText}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
content: [
|
|
380
|
+
{
|
|
381
|
+
type: 'text',
|
|
382
|
+
text: `✓ Job rejected.\n\nFunds refunded to your wallet.`,
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private async checkWallet() {
|
|
389
|
+
const response = await fetch(`${API_BASE_URL}/api/wallet`, {
|
|
390
|
+
headers: this.getAuthHeaders(),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (!response.ok) {
|
|
394
|
+
throw new Error(`Wallet fetch failed: ${response.statusText}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const { balance, needsTopUp } = await response.json();
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: 'text',
|
|
403
|
+
text: `Wallet Balance: $${balance.toFixed(2)}\n${needsTopUp ? '\n⚠️ Low balance - consider topping up' : ''}`,
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private getAuthHeaders(): Record<string, string> {
|
|
410
|
+
if (this.apiKey) {
|
|
411
|
+
return {
|
|
412
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async run() {
|
|
419
|
+
const transport = new StdioServerTransport();
|
|
420
|
+
await this.server.connect(transport);
|
|
421
|
+
console.error('Agent Marketplace MCP server running on stdio');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const server = new AgentMarketplaceServer();
|
|
426
|
+
server.run().catch(console.error);
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
10
|
+
const API_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
|
11
|
+
const SETUP_PORT = 3456;
|
|
12
|
+
|
|
13
|
+
interface ClaudeConfig {
|
|
14
|
+
mcpServers: {
|
|
15
|
+
[key: string]: {
|
|
16
|
+
command: string;
|
|
17
|
+
args: string[];
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Terminal-first setup for Agent Marketplace MCP
|
|
25
|
+
*
|
|
26
|
+
* Flow:
|
|
27
|
+
* 1. Start local server to receive auth token
|
|
28
|
+
* 2. Open browser to authenticate
|
|
29
|
+
* 3. Receive token from callback
|
|
30
|
+
* 4. Save token and configure Claude Code
|
|
31
|
+
* 5. Done!
|
|
32
|
+
*/
|
|
33
|
+
async function setup() {
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('╔════════════════════════════════════════════════╗');
|
|
36
|
+
console.log('║ 🚀 Agent Marketplace Setup ║');
|
|
37
|
+
console.log('╚════════════════════════════════════════════════╝');
|
|
38
|
+
console.log('');
|
|
39
|
+
|
|
40
|
+
// Step 1: Start local callback server
|
|
41
|
+
console.log('📡 Starting local server to receive auth token...');
|
|
42
|
+
const token = await startCallbackServer();
|
|
43
|
+
|
|
44
|
+
// Step 2: Save token
|
|
45
|
+
console.log('💾 Saving authentication token...');
|
|
46
|
+
await saveToken(token);
|
|
47
|
+
|
|
48
|
+
// Step 3: Configure Claude Code
|
|
49
|
+
console.log('⚙️ Configuring Claude Code...');
|
|
50
|
+
await configureClaudeCode(token);
|
|
51
|
+
|
|
52
|
+
// Step 4: Success!
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('╔════════════════════════════════════════════════╗');
|
|
55
|
+
console.log('║ ✅ Setup Complete! ║');
|
|
56
|
+
console.log('╚════════════════════════════════════════════════╝');
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log('Next steps:');
|
|
59
|
+
console.log('1. Restart Claude Code');
|
|
60
|
+
console.log('2. Open any project');
|
|
61
|
+
console.log('3. Try: @agentmarketplace execute_skill --skillId seo-meta-tags');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Available skills:');
|
|
64
|
+
console.log(' • seo-meta-tags - Add SEO meta tags');
|
|
65
|
+
console.log(' • typescript-convert - Convert JS to TypeScript');
|
|
66
|
+
console.log(' • add-loading-states - Add loading states');
|
|
67
|
+
console.log(' • add-error-boundaries - Add error boundaries');
|
|
68
|
+
console.log(' • fix-eslint - Fix ESLint errors');
|
|
69
|
+
console.log(' • optimize-images - Optimize images');
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('Need help? Visit: https://agentmarketplace.com/docs');
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start local server to receive auth token from web callback
|
|
77
|
+
*/
|
|
78
|
+
function startCallbackServer(): Promise<string> {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const server = http.createServer((req, res) => {
|
|
81
|
+
// Parse URL
|
|
82
|
+
const url = new URL(req.url || '', `http://localhost:${SETUP_PORT}`);
|
|
83
|
+
|
|
84
|
+
if (url.pathname === '/callback') {
|
|
85
|
+
const token = url.searchParams.get('token');
|
|
86
|
+
|
|
87
|
+
if (!token) {
|
|
88
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
89
|
+
res.end('Missing token');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Send success response
|
|
94
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
95
|
+
res.end(`
|
|
96
|
+
<!DOCTYPE html>
|
|
97
|
+
<html>
|
|
98
|
+
<head>
|
|
99
|
+
<title>Setup Complete</title>
|
|
100
|
+
<style>
|
|
101
|
+
body {
|
|
102
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
height: 100vh;
|
|
107
|
+
margin: 0;
|
|
108
|
+
background: #f5f5f5;
|
|
109
|
+
}
|
|
110
|
+
.container {
|
|
111
|
+
text-align: center;
|
|
112
|
+
background: white;
|
|
113
|
+
padding: 48px;
|
|
114
|
+
border-radius: 12px;
|
|
115
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
116
|
+
}
|
|
117
|
+
h1 { color: #10b981; margin: 0 0 16px 0; }
|
|
118
|
+
p { color: #6b7280; margin: 0; }
|
|
119
|
+
</style>
|
|
120
|
+
</head>
|
|
121
|
+
<body>
|
|
122
|
+
<div class="container">
|
|
123
|
+
<h1>✅ Authentication Complete!</h1>
|
|
124
|
+
<p>You can close this window and return to your terminal.</p>
|
|
125
|
+
</div>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
128
|
+
`);
|
|
129
|
+
|
|
130
|
+
// Close server and resolve with token
|
|
131
|
+
server.close();
|
|
132
|
+
resolve(token);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
server.listen(SETUP_PORT, () => {
|
|
137
|
+
console.log('✓ Local server started');
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log('🌐 Opening browser to authenticate...');
|
|
140
|
+
|
|
141
|
+
// Open browser
|
|
142
|
+
const authUrl = `${API_URL}/setup/auth?callback=http://localhost:${SETUP_PORT}/callback`;
|
|
143
|
+
openBrowser(authUrl);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Timeout after 5 minutes
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
server.close();
|
|
149
|
+
reject(new Error('Setup timed out after 5 minutes'));
|
|
150
|
+
}, 5 * 60 * 1000);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Save auth token to config file
|
|
156
|
+
*/
|
|
157
|
+
async function saveToken(token: string): Promise<void> {
|
|
158
|
+
const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.agentmarketplace');
|
|
159
|
+
const configPath = path.join(configDir, 'config.json');
|
|
160
|
+
|
|
161
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
162
|
+
await fs.writeFile(
|
|
163
|
+
configPath,
|
|
164
|
+
JSON.stringify({ authToken: token, apiUrl: API_URL }, null, 2)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
console.log(`✓ Saved to: ${configPath}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Configure Claude Code MCP settings
|
|
172
|
+
*/
|
|
173
|
+
async function configureClaudeCode(token: string): Promise<void> {
|
|
174
|
+
const configPath = getClaudeConfigPath();
|
|
175
|
+
|
|
176
|
+
if (!configPath) {
|
|
177
|
+
console.log('⚠️ Could not find Claude Code config');
|
|
178
|
+
console.log(' You may need to manually add the MCP server');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Read existing config or create new
|
|
183
|
+
let config: ClaudeConfig;
|
|
184
|
+
try {
|
|
185
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
186
|
+
config = JSON.parse(content);
|
|
187
|
+
} catch {
|
|
188
|
+
config = { mcpServers: {} };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Add agentmarketplace MCP server
|
|
192
|
+
const mcpServerPath = path.join(__dirname, 'index.js');
|
|
193
|
+
config.mcpServers.agentmarketplace = {
|
|
194
|
+
command: 'node',
|
|
195
|
+
args: [mcpServerPath],
|
|
196
|
+
env: {
|
|
197
|
+
AUTH_TOKEN: token,
|
|
198
|
+
API_URL: API_URL,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Write config
|
|
203
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
204
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
205
|
+
|
|
206
|
+
console.log(`✓ Configured Claude Code at: ${configPath}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get Claude Code config path based on OS
|
|
211
|
+
*/
|
|
212
|
+
function getClaudeConfigPath(): string | null {
|
|
213
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
214
|
+
|
|
215
|
+
if (process.platform === 'darwin') {
|
|
216
|
+
// macOS
|
|
217
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
218
|
+
} else if (process.platform === 'win32') {
|
|
219
|
+
// Windows
|
|
220
|
+
return path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
221
|
+
} else if (process.platform === 'linux') {
|
|
222
|
+
// Linux
|
|
223
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Open URL in default browser
|
|
231
|
+
*/
|
|
232
|
+
function openBrowser(url: string) {
|
|
233
|
+
const command = process.platform === 'darwin'
|
|
234
|
+
? 'open'
|
|
235
|
+
: process.platform === 'win32'
|
|
236
|
+
? 'start'
|
|
237
|
+
: 'xdg-open';
|
|
238
|
+
|
|
239
|
+
exec(`${command} "${url}"`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Run setup
|
|
243
|
+
setup().catch((error) => {
|
|
244
|
+
console.error('');
|
|
245
|
+
console.error('❌ Setup failed:', error.message);
|
|
246
|
+
console.error('');
|
|
247
|
+
console.error('Need help? Visit: https://agentmarketplace.com/docs');
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|