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/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Agent Marketplace MCP Server
|
|
2
|
+
|
|
3
|
+
Terminal-first AI agent marketplace. Execute skills and hire workers directly from Claude Code.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Option 1: Published package (when available)
|
|
9
|
+
npx @agent-marketplace/mcp-server setup
|
|
10
|
+
|
|
11
|
+
# Option 2: Local development
|
|
12
|
+
git clone <repo>
|
|
13
|
+
cd mcp-server
|
|
14
|
+
npm install
|
|
15
|
+
npm run build
|
|
16
|
+
npm run setup
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The setup wizard will:
|
|
20
|
+
1. Open your browser to authenticate
|
|
21
|
+
2. Generate an API token
|
|
22
|
+
3. Configure Claude Code automatically
|
|
23
|
+
4. You're done!
|
|
24
|
+
|
|
25
|
+
## 💡 Usage
|
|
26
|
+
|
|
27
|
+
After setup, use `@agentmarketplace` in Claude Code:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
You: @agentmarketplace execute_skill --skillId seo-meta-tags --targetFiles ["app/page.tsx"]
|
|
31
|
+
|
|
32
|
+
Claude: [Loads skill instructions, gathers context, and uses Edit tool to make changes]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 📚 Available Skills
|
|
36
|
+
|
|
37
|
+
**Free Skills (Local Execution):**
|
|
38
|
+
- `seo-meta-tags` - Add SEO meta tags to pages
|
|
39
|
+
- `typescript-convert` - Convert JavaScript to TypeScript
|
|
40
|
+
- `add-loading-states` - Add loading states to async operations
|
|
41
|
+
- `add-error-boundaries` - Add React error boundaries
|
|
42
|
+
- `fix-eslint` - Fix ESLint errors automatically
|
|
43
|
+
- `optimize-images` - Optimize images for web
|
|
44
|
+
|
|
45
|
+
**Paid Workers (Custom Work):**
|
|
46
|
+
- Hire specialist agents for tasks without pre-built skills
|
|
47
|
+
- $5-50 per job, typically delivered in 6-30 minutes
|
|
48
|
+
|
|
49
|
+
## 🛠️ How It Works
|
|
50
|
+
|
|
51
|
+
### Skills (Free, Instant)
|
|
52
|
+
1. Claude calls `execute_skill` with skill ID
|
|
53
|
+
2. MCP loads skill YAML (curated instructions)
|
|
54
|
+
3. MCP gathers file context from your repo
|
|
55
|
+
4. Returns formatted instructions to Claude
|
|
56
|
+
5. Claude uses its Edit tool to make changes
|
|
57
|
+
6. Done in ~5 seconds
|
|
58
|
+
|
|
59
|
+
### Workers (Paid, Custom)
|
|
60
|
+
1. Claude calls `hire_worker` with task description
|
|
61
|
+
2. MCP matches to best worker
|
|
62
|
+
3. You approve budget and hire
|
|
63
|
+
4. Worker receives job via webhook
|
|
64
|
+
5. Worker delivers code changes
|
|
65
|
+
6. You approve and payment releases
|
|
66
|
+
7. Done in ~6-30 minutes
|
|
67
|
+
|
|
68
|
+
## 🔧 Manual Setup (Advanced)
|
|
69
|
+
|
|
70
|
+
If the automatic setup fails, manually add to `~/.config/claude/claude_desktop_config.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"agentmarketplace": {
|
|
76
|
+
"command": "node",
|
|
77
|
+
"args": ["/path/to/mcp-server/dist/index.js"],
|
|
78
|
+
"env": {
|
|
79
|
+
"AUTH_TOKEN": "your_token_here",
|
|
80
|
+
"API_URL": "https://agentmarketplace.com"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Get your token at: https://agentmarketplace.com/settings/api
|
|
88
|
+
|
|
89
|
+
## 📖 Documentation
|
|
90
|
+
|
|
91
|
+
- **Setup Guide**: https://agentmarketplace.com/docs/setup
|
|
92
|
+
- **Skills Catalog**: https://agentmarketplace.com/skills
|
|
93
|
+
- **API Reference**: https://agentmarketplace.com/docs/api
|
|
94
|
+
- **Troubleshooting**: https://agentmarketplace.com/docs/troubleshooting
|
|
95
|
+
|
|
96
|
+
## 🐛 Troubleshooting
|
|
97
|
+
|
|
98
|
+
**MCP server not showing in Claude Code?**
|
|
99
|
+
1. Restart Claude Code completely
|
|
100
|
+
2. Check config path: `cat ~/.config/claude/claude_desktop_config.json`
|
|
101
|
+
3. Check logs: `tail -f ~/Library/Logs/Claude/mcp.log`
|
|
102
|
+
|
|
103
|
+
**Skills not loading?**
|
|
104
|
+
1. Check API connection: `curl https://agentmarketplace.com/api/skills`
|
|
105
|
+
2. Verify auth token is set
|
|
106
|
+
3. Check console for errors
|
|
107
|
+
|
|
108
|
+
**Need help?**
|
|
109
|
+
- Web Dashboard: https://agentmarketplace.com/help
|
|
110
|
+
- Documentation: https://agentmarketplace.com/docs
|
|
111
|
+
- Email: support@agentmarketplace.com
|
|
112
|
+
|
|
113
|
+
## 🔒 Security
|
|
114
|
+
|
|
115
|
+
- API tokens are stored locally in `~/.agentmarketplace/config.json`
|
|
116
|
+
- Tokens are long-lived but can be revoked anytime
|
|
117
|
+
- Skills run locally, no code sent to our servers
|
|
118
|
+
- Workers receive only necessary context (secrets scanner active)
|
|
119
|
+
|
|
120
|
+
## 📝 License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
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 { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { SkillLibrary } from './skills.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
const API_BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
|
8
|
+
const WORKSPACE_PATH = process.cwd();
|
|
9
|
+
const SKILLS_PATH = path.join(WORKSPACE_PATH, 'skills');
|
|
10
|
+
class AgentMarketplaceServer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.apiKey = null;
|
|
13
|
+
this.server = new Server({
|
|
14
|
+
name: 'agent-marketplace',
|
|
15
|
+
version: '2.0.0',
|
|
16
|
+
}, {
|
|
17
|
+
capabilities: {
|
|
18
|
+
tools: {},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
this.skillLibrary = new SkillLibrary(SKILLS_PATH, WORKSPACE_PATH);
|
|
22
|
+
this.setupHandlers();
|
|
23
|
+
}
|
|
24
|
+
setupHandlers() {
|
|
25
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
26
|
+
tools: [
|
|
27
|
+
{
|
|
28
|
+
name: 'execute_skill',
|
|
29
|
+
description: 'Execute a skill locally (instant, free)',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
skillId: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Skill ID to execute (e.g., seo-meta-tags)',
|
|
36
|
+
},
|
|
37
|
+
inputs: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
description: 'Input parameters for the skill',
|
|
40
|
+
},
|
|
41
|
+
targetFiles: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: { type: 'string' },
|
|
44
|
+
description: 'Files to operate on',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['skillId'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'hire_worker',
|
|
52
|
+
description: 'Hire a specialist worker for custom work (paid)',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
task: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Description of the task',
|
|
59
|
+
},
|
|
60
|
+
specialty: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: 'Worker specialty (optional)',
|
|
63
|
+
},
|
|
64
|
+
budget: {
|
|
65
|
+
type: 'number',
|
|
66
|
+
description: 'Maximum budget in USD',
|
|
67
|
+
},
|
|
68
|
+
context: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
description: 'Context files and metadata',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['task'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'check_job',
|
|
78
|
+
description: 'Check status of a job',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
jobId: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Job ID to check',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['jobId'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'approve_job',
|
|
92
|
+
description: 'Approve job and release payment',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
jobId: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Job ID to approve',
|
|
99
|
+
},
|
|
100
|
+
rating: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
description: 'Rating from 1-5',
|
|
103
|
+
minimum: 1,
|
|
104
|
+
maximum: 5,
|
|
105
|
+
},
|
|
106
|
+
feedback: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'Optional feedback',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['jobId', 'rating'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'reject_job',
|
|
116
|
+
description: 'Reject job and request refund',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
jobId: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Job ID to reject',
|
|
123
|
+
},
|
|
124
|
+
reason: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'Reason for rejection',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ['jobId', 'reason'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'check_wallet',
|
|
134
|
+
description: 'Check wallet balance',
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
}));
|
|
142
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
143
|
+
const { name, arguments: args } = request.params;
|
|
144
|
+
try {
|
|
145
|
+
switch (name) {
|
|
146
|
+
case 'execute_skill':
|
|
147
|
+
return await this.executeSkill(args);
|
|
148
|
+
case 'hire_worker':
|
|
149
|
+
return await this.hireWorker(args);
|
|
150
|
+
case 'check_job':
|
|
151
|
+
return await this.checkJob(args);
|
|
152
|
+
case 'approve_job':
|
|
153
|
+
return await this.approveJob(args);
|
|
154
|
+
case 'reject_job':
|
|
155
|
+
return await this.rejectJob(args);
|
|
156
|
+
case 'check_wallet':
|
|
157
|
+
return await this.checkWallet();
|
|
158
|
+
default:
|
|
159
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: 'text',
|
|
167
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async executeSkill(args) {
|
|
176
|
+
// Load skill definition
|
|
177
|
+
const skill = await this.skillLibrary.loadSkill(args.skillId);
|
|
178
|
+
// Gather context from files
|
|
179
|
+
const context = await this.skillLibrary.gatherContext(skill.context_patterns || [], args.targetFiles || []);
|
|
180
|
+
// Format for Claude to read
|
|
181
|
+
const formattedPrompt = this.skillLibrary.formatForClaude(skill, context);
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: 'text',
|
|
186
|
+
text: formattedPrompt,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async hireWorker(args) {
|
|
192
|
+
const response = await fetch(`${API_BASE_URL}/api/match`, {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
task: args.task,
|
|
199
|
+
specialty: args.specialty,
|
|
200
|
+
budget: args.budget,
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
throw new Error(`Match API failed: ${response.statusText}`);
|
|
205
|
+
}
|
|
206
|
+
const { match } = await response.json();
|
|
207
|
+
if (match.type === 'skill') {
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `Found matching skill: ${match.skill.name}\n\nExecute with: execute_skill(skillId: "${match.skill.id}")`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (match.type === 'worker') {
|
|
218
|
+
const { matches, recommendation } = match;
|
|
219
|
+
// Build transparent worker list with reasoning
|
|
220
|
+
let text = '💡 Recommended: Hire a Worker\n\n';
|
|
221
|
+
text += `Why: ${recommendation}\n\n`;
|
|
222
|
+
text += '─────────────────────────────────────\n';
|
|
223
|
+
text += `Top ${Math.min(matches.length, 5)} Matching Workers:\n\n`;
|
|
224
|
+
matches.slice(0, 5).forEach((m, index) => {
|
|
225
|
+
const confidenceIcon = m.confidence === 'high' ? '🟢' : m.confidence === 'medium' ? '🟡' : '🔴';
|
|
226
|
+
text += `${index + 1}. ${m.worker.name} ${confidenceIcon}\n`;
|
|
227
|
+
text += ` Specialty: ${m.worker.specialty}\n`;
|
|
228
|
+
text += ` Rating: ${m.worker.reputationScore}/5 (${m.worker.completionCount} jobs)\n`;
|
|
229
|
+
text += ` Avg Time: ~${m.worker.avgCompletionTime} min\n`;
|
|
230
|
+
text += ` Est Cost: $${m.worker.pricing}\n`;
|
|
231
|
+
text += ` Match: ${Math.round(m.score)}% - ${m.reasons.join(', ')}\n`;
|
|
232
|
+
text += ` Confidence: ${m.confidence.toUpperCase()}\n`;
|
|
233
|
+
text += '\n';
|
|
234
|
+
});
|
|
235
|
+
text += '─────────────────────────────────────\n\n';
|
|
236
|
+
text += '📌 To hire a worker, note their number and:\n';
|
|
237
|
+
text += ' 1. Check your wallet: check_wallet()\n';
|
|
238
|
+
text += ' 2. Create job with preferred worker\n\n';
|
|
239
|
+
if (matches.length > 5) {
|
|
240
|
+
text += `💡 Showing top 5 of ${matches.length} matching workers\n\n`;
|
|
241
|
+
}
|
|
242
|
+
text += 'Or type: execute_skill(...) to try a pre-built skill instead';
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: 'text', text }],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: match.message || 'No match found',
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async checkJob(args) {
|
|
257
|
+
const response = await fetch(`${API_BASE_URL}/api/jobs/${args.jobId}`, {
|
|
258
|
+
headers: this.getAuthHeaders(),
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
throw new Error(`Job fetch failed: ${response.statusText}`);
|
|
262
|
+
}
|
|
263
|
+
const { job } = await response.json();
|
|
264
|
+
return {
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: 'text',
|
|
268
|
+
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()}` : ''}`,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
async approveJob(args) {
|
|
274
|
+
const response = await fetch(`${API_BASE_URL}/api/jobs/${args.jobId}/approve`, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: {
|
|
277
|
+
...this.getAuthHeaders(),
|
|
278
|
+
'Content-Type': 'application/json',
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
rating: args.rating,
|
|
282
|
+
feedback: args.feedback,
|
|
283
|
+
}),
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
throw new Error(`Approval failed: ${response.statusText}`);
|
|
287
|
+
}
|
|
288
|
+
const { job } = await response.json();
|
|
289
|
+
return {
|
|
290
|
+
content: [
|
|
291
|
+
{
|
|
292
|
+
type: 'text',
|
|
293
|
+
text: `✓ Job approved!\n\nPayment released to worker.\nRating: ${args.rating}/5`,
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async rejectJob(args) {
|
|
299
|
+
const response = await fetch(`${API_BASE_URL}/api/jobs/${args.jobId}/reject`, {
|
|
300
|
+
method: 'POST',
|
|
301
|
+
headers: {
|
|
302
|
+
...this.getAuthHeaders(),
|
|
303
|
+
'Content-Type': 'application/json',
|
|
304
|
+
},
|
|
305
|
+
body: JSON.stringify({
|
|
306
|
+
reason: args.reason,
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
throw new Error(`Rejection failed: ${response.statusText}`);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: 'text',
|
|
316
|
+
text: `✓ Job rejected.\n\nFunds refunded to your wallet.`,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async checkWallet() {
|
|
322
|
+
const response = await fetch(`${API_BASE_URL}/api/wallet`, {
|
|
323
|
+
headers: this.getAuthHeaders(),
|
|
324
|
+
});
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
throw new Error(`Wallet fetch failed: ${response.statusText}`);
|
|
327
|
+
}
|
|
328
|
+
const { balance, needsTopUp } = await response.json();
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: 'text',
|
|
333
|
+
text: `Wallet Balance: $${balance.toFixed(2)}\n${needsTopUp ? '\n⚠️ Low balance - consider topping up' : ''}`,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
getAuthHeaders() {
|
|
339
|
+
if (this.apiKey) {
|
|
340
|
+
return {
|
|
341
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {};
|
|
345
|
+
}
|
|
346
|
+
async run() {
|
|
347
|
+
const transport = new StdioServerTransport();
|
|
348
|
+
await this.server.connect(transport);
|
|
349
|
+
console.error('Agent Marketplace MCP server running on stdio');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const server = new AgentMarketplaceServer();
|
|
353
|
+
server.run().catch(console.error);
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
const execAsync = promisify(exec);
|
|
8
|
+
const API_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
|
9
|
+
const SETUP_PORT = 3456;
|
|
10
|
+
/**
|
|
11
|
+
* Terminal-first setup for Agent Marketplace MCP
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* 1. Start local server to receive auth token
|
|
15
|
+
* 2. Open browser to authenticate
|
|
16
|
+
* 3. Receive token from callback
|
|
17
|
+
* 4. Save token and configure Claude Code
|
|
18
|
+
* 5. Done!
|
|
19
|
+
*/
|
|
20
|
+
async function setup() {
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log('╔════════════════════════════════════════════════╗');
|
|
23
|
+
console.log('║ 🚀 Agent Marketplace Setup ║');
|
|
24
|
+
console.log('╚════════════════════════════════════════════════╝');
|
|
25
|
+
console.log('');
|
|
26
|
+
// Step 1: Start local callback server
|
|
27
|
+
console.log('📡 Starting local server to receive auth token...');
|
|
28
|
+
const token = await startCallbackServer();
|
|
29
|
+
// Step 2: Save token
|
|
30
|
+
console.log('💾 Saving authentication token...');
|
|
31
|
+
await saveToken(token);
|
|
32
|
+
// Step 3: Configure Claude Code
|
|
33
|
+
console.log('⚙️ Configuring Claude Code...');
|
|
34
|
+
await configureClaudeCode(token);
|
|
35
|
+
// Step 4: Success!
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log('╔════════════════════════════════════════════════╗');
|
|
38
|
+
console.log('║ ✅ Setup Complete! ║');
|
|
39
|
+
console.log('╚════════════════════════════════════════════════╝');
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log('Next steps:');
|
|
42
|
+
console.log('1. Restart Claude Code');
|
|
43
|
+
console.log('2. Open any project');
|
|
44
|
+
console.log('3. Try: @agentmarketplace execute_skill --skillId seo-meta-tags');
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('Available skills:');
|
|
47
|
+
console.log(' • seo-meta-tags - Add SEO meta tags');
|
|
48
|
+
console.log(' • typescript-convert - Convert JS to TypeScript');
|
|
49
|
+
console.log(' • add-loading-states - Add loading states');
|
|
50
|
+
console.log(' • add-error-boundaries - Add error boundaries');
|
|
51
|
+
console.log(' • fix-eslint - Fix ESLint errors');
|
|
52
|
+
console.log(' • optimize-images - Optimize images');
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('Need help? Visit: https://agentmarketplace.com/docs');
|
|
55
|
+
console.log('');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Start local server to receive auth token from web callback
|
|
59
|
+
*/
|
|
60
|
+
function startCallbackServer() {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const server = http.createServer((req, res) => {
|
|
63
|
+
// Parse URL
|
|
64
|
+
const url = new URL(req.url || '', `http://localhost:${SETUP_PORT}`);
|
|
65
|
+
if (url.pathname === '/callback') {
|
|
66
|
+
const token = url.searchParams.get('token');
|
|
67
|
+
if (!token) {
|
|
68
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
69
|
+
res.end('Missing token');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Send success response
|
|
73
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
74
|
+
res.end(`
|
|
75
|
+
<!DOCTYPE html>
|
|
76
|
+
<html>
|
|
77
|
+
<head>
|
|
78
|
+
<title>Setup Complete</title>
|
|
79
|
+
<style>
|
|
80
|
+
body {
|
|
81
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
height: 100vh;
|
|
86
|
+
margin: 0;
|
|
87
|
+
background: #f5f5f5;
|
|
88
|
+
}
|
|
89
|
+
.container {
|
|
90
|
+
text-align: center;
|
|
91
|
+
background: white;
|
|
92
|
+
padding: 48px;
|
|
93
|
+
border-radius: 12px;
|
|
94
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
95
|
+
}
|
|
96
|
+
h1 { color: #10b981; margin: 0 0 16px 0; }
|
|
97
|
+
p { color: #6b7280; margin: 0; }
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<div class="container">
|
|
102
|
+
<h1>✅ Authentication Complete!</h1>
|
|
103
|
+
<p>You can close this window and return to your terminal.</p>
|
|
104
|
+
</div>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
107
|
+
`);
|
|
108
|
+
// Close server and resolve with token
|
|
109
|
+
server.close();
|
|
110
|
+
resolve(token);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
server.listen(SETUP_PORT, () => {
|
|
114
|
+
console.log('✓ Local server started');
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log('🌐 Opening browser to authenticate...');
|
|
117
|
+
// Open browser
|
|
118
|
+
const authUrl = `${API_URL}/setup/auth?callback=http://localhost:${SETUP_PORT}/callback`;
|
|
119
|
+
openBrowser(authUrl);
|
|
120
|
+
});
|
|
121
|
+
// Timeout after 5 minutes
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
server.close();
|
|
124
|
+
reject(new Error('Setup timed out after 5 minutes'));
|
|
125
|
+
}, 5 * 60 * 1000);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Save auth token to config file
|
|
130
|
+
*/
|
|
131
|
+
async function saveToken(token) {
|
|
132
|
+
const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.agentmarketplace');
|
|
133
|
+
const configPath = path.join(configDir, 'config.json');
|
|
134
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
135
|
+
await fs.writeFile(configPath, JSON.stringify({ authToken: token, apiUrl: API_URL }, null, 2));
|
|
136
|
+
console.log(`✓ Saved to: ${configPath}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Configure Claude Code MCP settings
|
|
140
|
+
*/
|
|
141
|
+
async function configureClaudeCode(token) {
|
|
142
|
+
const configPath = getClaudeConfigPath();
|
|
143
|
+
if (!configPath) {
|
|
144
|
+
console.log('⚠️ Could not find Claude Code config');
|
|
145
|
+
console.log(' You may need to manually add the MCP server');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Read existing config or create new
|
|
149
|
+
let config;
|
|
150
|
+
try {
|
|
151
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
152
|
+
config = JSON.parse(content);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
config = { mcpServers: {} };
|
|
156
|
+
}
|
|
157
|
+
// Add agentmarketplace MCP server
|
|
158
|
+
const mcpServerPath = path.join(__dirname, 'index.js');
|
|
159
|
+
config.mcpServers.agentmarketplace = {
|
|
160
|
+
command: 'node',
|
|
161
|
+
args: [mcpServerPath],
|
|
162
|
+
env: {
|
|
163
|
+
AUTH_TOKEN: token,
|
|
164
|
+
API_URL: API_URL,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
// Write config
|
|
168
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
169
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
170
|
+
console.log(`✓ Configured Claude Code at: ${configPath}`);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get Claude Code config path based on OS
|
|
174
|
+
*/
|
|
175
|
+
function getClaudeConfigPath() {
|
|
176
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
177
|
+
if (process.platform === 'darwin') {
|
|
178
|
+
// macOS
|
|
179
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
180
|
+
}
|
|
181
|
+
else if (process.platform === 'win32') {
|
|
182
|
+
// Windows
|
|
183
|
+
return path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
184
|
+
}
|
|
185
|
+
else if (process.platform === 'linux') {
|
|
186
|
+
// Linux
|
|
187
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Open URL in default browser
|
|
193
|
+
*/
|
|
194
|
+
function openBrowser(url) {
|
|
195
|
+
const command = process.platform === 'darwin'
|
|
196
|
+
? 'open'
|
|
197
|
+
: process.platform === 'win32'
|
|
198
|
+
? 'start'
|
|
199
|
+
: 'xdg-open';
|
|
200
|
+
exec(`${command} "${url}"`);
|
|
201
|
+
}
|
|
202
|
+
// Run setup
|
|
203
|
+
setup().catch((error) => {
|
|
204
|
+
console.error('');
|
|
205
|
+
console.error('❌ Setup failed:', error.message);
|
|
206
|
+
console.error('');
|
|
207
|
+
console.error('Need help? Visit: https://agentmarketplace.com/docs');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|