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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'yaml';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
// Skill definition schema
|
|
6
|
+
const SkillSchema = z.object({
|
|
7
|
+
skill: z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
name: z.string(),
|
|
10
|
+
description: z.string(),
|
|
11
|
+
category: z.string(),
|
|
12
|
+
pricing: z.union([z.literal('free'), z.number()]),
|
|
13
|
+
author: z.string().optional(),
|
|
14
|
+
version: z.string(),
|
|
15
|
+
inputs: z.record(z.object({
|
|
16
|
+
type: z.enum(['string', 'number', 'boolean', 'select', 'multiselect']),
|
|
17
|
+
required: z.boolean(),
|
|
18
|
+
default: z.any().optional(),
|
|
19
|
+
description: z.string(),
|
|
20
|
+
options: z.array(z.string()).optional(),
|
|
21
|
+
})).optional(),
|
|
22
|
+
context_needed: z.array(z.object({
|
|
23
|
+
description: z.string(),
|
|
24
|
+
pattern: z.string(),
|
|
25
|
+
max_files: z.number().optional(),
|
|
26
|
+
})).optional(),
|
|
27
|
+
execution: z.array(z.object({
|
|
28
|
+
step: z.string(),
|
|
29
|
+
}).passthrough()),
|
|
30
|
+
validation: z.array(z.object({
|
|
31
|
+
check: z.string(),
|
|
32
|
+
}).passthrough()).optional(),
|
|
33
|
+
success_message: z.string(),
|
|
34
|
+
estimated_time: z.number(),
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
export class SkillEngine {
|
|
38
|
+
constructor(workspacePath) {
|
|
39
|
+
this.workspacePath = workspacePath;
|
|
40
|
+
this.backupPath = path.join(workspacePath, '.skill-backups');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load and parse skill definition from YAML
|
|
44
|
+
*/
|
|
45
|
+
async loadSkill(skillPath) {
|
|
46
|
+
const content = await fs.readFile(skillPath, 'utf-8');
|
|
47
|
+
const parsed = yaml.parse(content);
|
|
48
|
+
return SkillSchema.parse(parsed);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute a skill with given inputs
|
|
52
|
+
*/
|
|
53
|
+
async executeSkill(skillDefinition, inputs, context) {
|
|
54
|
+
const { skill } = skillDefinition;
|
|
55
|
+
try {
|
|
56
|
+
// Step 1: Validate inputs
|
|
57
|
+
this.validateInputs(skill.inputs || {}, inputs);
|
|
58
|
+
// Step 2: Create backup of files we might modify
|
|
59
|
+
await this.createBackup(context.files || []);
|
|
60
|
+
// Step 3: Initialize execution state
|
|
61
|
+
const state = {
|
|
62
|
+
inputs,
|
|
63
|
+
context,
|
|
64
|
+
changes: [],
|
|
65
|
+
};
|
|
66
|
+
// Step 4: Run validation checks
|
|
67
|
+
if (skill.validation) {
|
|
68
|
+
for (const validation of skill.validation) {
|
|
69
|
+
await this.runValidation(validation, state);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Step 5: Execute steps
|
|
73
|
+
for (const step of skill.execution) {
|
|
74
|
+
await this.executeStep(step, state);
|
|
75
|
+
}
|
|
76
|
+
// Step 6: Return success
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
message: skill.success_message,
|
|
80
|
+
changes: state.changes,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
// Rollback on error
|
|
85
|
+
await this.rollback();
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
message: 'Skill execution failed',
|
|
89
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Validate inputs against skill definition
|
|
95
|
+
*/
|
|
96
|
+
validateInputs(inputDef, inputs) {
|
|
97
|
+
for (const [key, def] of Object.entries(inputDef)) {
|
|
98
|
+
const value = inputs[key];
|
|
99
|
+
// Check required
|
|
100
|
+
if (def.required && value === undefined) {
|
|
101
|
+
throw new Error(`Missing required input: ${key}`);
|
|
102
|
+
}
|
|
103
|
+
// Type validation
|
|
104
|
+
if (value !== undefined) {
|
|
105
|
+
switch (def.type) {
|
|
106
|
+
case 'string':
|
|
107
|
+
if (typeof value !== 'string') {
|
|
108
|
+
throw new Error(`Input ${key} must be a string`);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case 'number':
|
|
112
|
+
if (typeof value !== 'number') {
|
|
113
|
+
throw new Error(`Input ${key} must be a number`);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case 'boolean':
|
|
117
|
+
if (typeof value !== 'boolean') {
|
|
118
|
+
throw new Error(`Input ${key} must be a boolean`);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'select':
|
|
122
|
+
if (!def.options?.includes(value)) {
|
|
123
|
+
throw new Error(`Input ${key} must be one of: ${def.options?.join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case 'multiselect':
|
|
127
|
+
if (!Array.isArray(value)) {
|
|
128
|
+
throw new Error(`Input ${key} must be an array`);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Run validation check
|
|
137
|
+
*/
|
|
138
|
+
async runValidation(validation, state) {
|
|
139
|
+
const { check } = validation;
|
|
140
|
+
switch (check) {
|
|
141
|
+
case 'valid_javascript':
|
|
142
|
+
// Would need actual JS parser - simplified for MVP
|
|
143
|
+
break;
|
|
144
|
+
case 'no_syntax_errors':
|
|
145
|
+
// Would need linting - simplified for MVP
|
|
146
|
+
break;
|
|
147
|
+
case 'file_exists':
|
|
148
|
+
const filePath = this.resolvePath(validation.path, state);
|
|
149
|
+
try {
|
|
150
|
+
await fs.access(filePath);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
throw new Error(`File not found: ${filePath}`);
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Execute a single step
|
|
160
|
+
*/
|
|
161
|
+
async executeStep(step, state) {
|
|
162
|
+
switch (step.step) {
|
|
163
|
+
case 'read_file':
|
|
164
|
+
await this.stepReadFile(step, state);
|
|
165
|
+
break;
|
|
166
|
+
case 'write_file':
|
|
167
|
+
await this.stepWriteFile(step, state);
|
|
168
|
+
break;
|
|
169
|
+
case 'update_file':
|
|
170
|
+
await this.stepUpdateFile(step, state);
|
|
171
|
+
break;
|
|
172
|
+
case 'delete_file':
|
|
173
|
+
await this.stepDeleteFile(step, state);
|
|
174
|
+
break;
|
|
175
|
+
case 'rename_file':
|
|
176
|
+
await this.stepRenameFile(step, state);
|
|
177
|
+
break;
|
|
178
|
+
case 'for_each':
|
|
179
|
+
await this.stepForEach(step, state);
|
|
180
|
+
break;
|
|
181
|
+
case 'analyze_code':
|
|
182
|
+
case 'generate_types':
|
|
183
|
+
case 'transform':
|
|
184
|
+
// These would require AI/LLM calls - simplified for MVP
|
|
185
|
+
// In production, these would call Claude API
|
|
186
|
+
state[step.save_as] = `[Generated content for ${step.step}]`;
|
|
187
|
+
break;
|
|
188
|
+
default:
|
|
189
|
+
console.warn(`Unknown step type: ${step.step}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Read file step
|
|
194
|
+
*/
|
|
195
|
+
async stepReadFile(step, state) {
|
|
196
|
+
const filePath = this.resolvePath(step.path, state);
|
|
197
|
+
this.validateFilePath(filePath);
|
|
198
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
199
|
+
if (step.save_as) {
|
|
200
|
+
state[step.save_as] = content;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Write file step
|
|
205
|
+
*/
|
|
206
|
+
async stepWriteFile(step, state) {
|
|
207
|
+
const filePath = this.resolvePath(step.path, state);
|
|
208
|
+
this.validateFilePath(filePath);
|
|
209
|
+
const content = this.resolveValue(step.content, state);
|
|
210
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
211
|
+
state.changes.push({
|
|
212
|
+
type: 'create',
|
|
213
|
+
path: filePath,
|
|
214
|
+
content,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Update file step
|
|
219
|
+
*/
|
|
220
|
+
async stepUpdateFile(step, state) {
|
|
221
|
+
const filePath = this.resolvePath(step.path, state);
|
|
222
|
+
this.validateFilePath(filePath);
|
|
223
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
224
|
+
const newContent = this.resolveValue(step.content, state);
|
|
225
|
+
switch (step.operation) {
|
|
226
|
+
case 'insert_after':
|
|
227
|
+
const target = step.target;
|
|
228
|
+
content = content.replace(target, `${target}\n${newContent}`);
|
|
229
|
+
break;
|
|
230
|
+
case 'replace':
|
|
231
|
+
content = newContent;
|
|
232
|
+
break;
|
|
233
|
+
case 'append':
|
|
234
|
+
content += `\n${newContent}`;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
238
|
+
state.changes.push({
|
|
239
|
+
type: 'update',
|
|
240
|
+
path: filePath,
|
|
241
|
+
content,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Delete file step
|
|
246
|
+
*/
|
|
247
|
+
async stepDeleteFile(step, state) {
|
|
248
|
+
const filePath = this.resolvePath(step.path, state);
|
|
249
|
+
this.validateFilePath(filePath);
|
|
250
|
+
await fs.unlink(filePath);
|
|
251
|
+
state.changes.push({
|
|
252
|
+
type: 'delete',
|
|
253
|
+
path: filePath,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Rename file step
|
|
258
|
+
*/
|
|
259
|
+
async stepRenameFile(step, state) {
|
|
260
|
+
const fromPath = this.resolvePath(step.from, state);
|
|
261
|
+
const toPath = this.resolvePath(step.to, state);
|
|
262
|
+
this.validateFilePath(fromPath);
|
|
263
|
+
this.validateFilePath(toPath);
|
|
264
|
+
await fs.rename(fromPath, toPath);
|
|
265
|
+
state.changes.push({
|
|
266
|
+
type: 'delete',
|
|
267
|
+
path: fromPath,
|
|
268
|
+
});
|
|
269
|
+
state.changes.push({
|
|
270
|
+
type: 'create',
|
|
271
|
+
path: toPath,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* For each loop step
|
|
276
|
+
*/
|
|
277
|
+
async stepForEach(step, state) {
|
|
278
|
+
const items = this.resolveValue(step.items, state);
|
|
279
|
+
if (!Array.isArray(items)) {
|
|
280
|
+
throw new Error('for_each items must be an array');
|
|
281
|
+
}
|
|
282
|
+
for (const item of items) {
|
|
283
|
+
// Create sub-state with loop variable
|
|
284
|
+
const loopState = {
|
|
285
|
+
...state,
|
|
286
|
+
[step.as]: item,
|
|
287
|
+
};
|
|
288
|
+
// Execute sub-steps
|
|
289
|
+
for (const subStep of step.do) {
|
|
290
|
+
await this.executeStep(subStep, loopState);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Resolve path with variable substitution
|
|
296
|
+
*/
|
|
297
|
+
resolvePath(pathTemplate, state) {
|
|
298
|
+
const resolved = this.resolveValue(pathTemplate, state);
|
|
299
|
+
return path.resolve(this.workspacePath, resolved);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Resolve value with variable substitution
|
|
303
|
+
*/
|
|
304
|
+
resolveValue(template, state) {
|
|
305
|
+
if (typeof template !== 'string') {
|
|
306
|
+
return template;
|
|
307
|
+
}
|
|
308
|
+
// Replace ${variable} patterns
|
|
309
|
+
return template.replace(/\$\{([^}]+)\}/g, (match, varPath) => {
|
|
310
|
+
const value = this.getNestedValue(state, varPath);
|
|
311
|
+
return value !== undefined ? value : match;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get nested value from object (e.g., "context.files[0]")
|
|
316
|
+
*/
|
|
317
|
+
getNestedValue(obj, path) {
|
|
318
|
+
const parts = path.split('.');
|
|
319
|
+
let current = obj;
|
|
320
|
+
for (const part of parts) {
|
|
321
|
+
if (current === undefined)
|
|
322
|
+
return undefined;
|
|
323
|
+
current = current[part];
|
|
324
|
+
}
|
|
325
|
+
return current;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Validate file path is within workspace
|
|
329
|
+
*/
|
|
330
|
+
validateFilePath(filePath) {
|
|
331
|
+
const resolved = path.resolve(filePath);
|
|
332
|
+
const workspace = path.resolve(this.workspacePath);
|
|
333
|
+
if (!resolved.startsWith(workspace)) {
|
|
334
|
+
throw new Error('File path outside workspace not allowed');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Create backup of files
|
|
339
|
+
*/
|
|
340
|
+
async createBackup(files) {
|
|
341
|
+
await fs.mkdir(this.backupPath, { recursive: true });
|
|
342
|
+
const timestamp = Date.now();
|
|
343
|
+
for (const file of files) {
|
|
344
|
+
try {
|
|
345
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
346
|
+
const backupFile = path.join(this.backupPath, `${timestamp}-${path.basename(file)}`);
|
|
347
|
+
await fs.writeFile(backupFile, content, 'utf-8');
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
// File might not exist yet, skip backup
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Rollback changes on error
|
|
356
|
+
*/
|
|
357
|
+
async rollback() {
|
|
358
|
+
// In production, this would restore from backup
|
|
359
|
+
// For MVP, just log the error
|
|
360
|
+
console.error('Rollback triggered - restore from backup directory');
|
|
361
|
+
}
|
|
362
|
+
}
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'yaml';
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
const LIMITS = {
|
|
6
|
+
maxFiles: 10,
|
|
7
|
+
maxFileSize: 50000,
|
|
8
|
+
maxTotalChars: 400000,
|
|
9
|
+
};
|
|
10
|
+
export class SkillLibrary {
|
|
11
|
+
constructor(skillsPath, workspacePath) {
|
|
12
|
+
this.skillsPath = skillsPath;
|
|
13
|
+
this.workspacePath = workspacePath;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Load skill from YAML
|
|
17
|
+
*/
|
|
18
|
+
async loadSkill(skillId) {
|
|
19
|
+
const skillPath = path.join(this.skillsPath, `${skillId}.yaml`);
|
|
20
|
+
const content = await fs.readFile(skillPath, 'utf-8');
|
|
21
|
+
const parsed = yaml.parse(content);
|
|
22
|
+
return {
|
|
23
|
+
id: parsed.skill.id,
|
|
24
|
+
name: parsed.skill.name,
|
|
25
|
+
description: parsed.skill.description,
|
|
26
|
+
instructions: parsed.skill.instructions || this.generateInstructions(parsed.skill),
|
|
27
|
+
context_patterns: parsed.skill.context_patterns,
|
|
28
|
+
examples: parsed.skill.examples,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Gather file context based on patterns or explicit files
|
|
33
|
+
*/
|
|
34
|
+
async gatherContext(patterns = [], explicitFiles = []) {
|
|
35
|
+
const filePaths = new Set();
|
|
36
|
+
// Add explicit files
|
|
37
|
+
explicitFiles.forEach((f) => filePaths.add(path.resolve(this.workspacePath, f)));
|
|
38
|
+
// Add files matching patterns
|
|
39
|
+
for (const pattern of patterns) {
|
|
40
|
+
const matches = await glob(pattern, {
|
|
41
|
+
cwd: this.workspacePath,
|
|
42
|
+
absolute: true,
|
|
43
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
|
|
44
|
+
});
|
|
45
|
+
matches.slice(0, LIMITS.maxFiles).forEach((f) => filePaths.add(f));
|
|
46
|
+
}
|
|
47
|
+
// Rank and limit files
|
|
48
|
+
const rankedFiles = await this.rankFiles(Array.from(filePaths));
|
|
49
|
+
const limitedFiles = rankedFiles.slice(0, LIMITS.maxFiles);
|
|
50
|
+
// Read file contents
|
|
51
|
+
const files = [];
|
|
52
|
+
let totalChars = 0;
|
|
53
|
+
let truncated = false;
|
|
54
|
+
for (const filePath of limitedFiles) {
|
|
55
|
+
try {
|
|
56
|
+
const stats = await fs.stat(filePath);
|
|
57
|
+
if (stats.size > LIMITS.maxFileSize) {
|
|
58
|
+
truncated = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (totalChars + stats.size > LIMITS.maxTotalChars) {
|
|
62
|
+
truncated = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
66
|
+
const lines = content.split('\n').length;
|
|
67
|
+
files.push({
|
|
68
|
+
path: path.relative(this.workspacePath, filePath),
|
|
69
|
+
content,
|
|
70
|
+
lines,
|
|
71
|
+
});
|
|
72
|
+
totalChars += content.length;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error(`Failed to read ${filePath}:`, error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { files, totalChars, truncated };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rank files by relevance
|
|
82
|
+
*/
|
|
83
|
+
async rankFiles(files) {
|
|
84
|
+
const scored = await Promise.all(files.map(async (f) => {
|
|
85
|
+
const stats = await fs.stat(f).catch(() => null);
|
|
86
|
+
const relativePath = path.relative(this.workspacePath, f);
|
|
87
|
+
let score = 0;
|
|
88
|
+
// Deprioritize test files
|
|
89
|
+
if (relativePath.includes('test') || relativePath.includes('spec')) {
|
|
90
|
+
score -= 100;
|
|
91
|
+
}
|
|
92
|
+
// Prefer root-level files
|
|
93
|
+
const depth = relativePath.split(path.sep).length;
|
|
94
|
+
score -= depth * 10;
|
|
95
|
+
// Slight preference for smaller files
|
|
96
|
+
if (stats) {
|
|
97
|
+
score -= stats.size / 10000;
|
|
98
|
+
}
|
|
99
|
+
return { path: f, score };
|
|
100
|
+
}));
|
|
101
|
+
return scored.sort((a, b) => b.score - a.score).map((s) => s.path);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Format skill + context for Claude to read
|
|
105
|
+
*/
|
|
106
|
+
formatForClaude(skill, context) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
parts.push(`# ${skill.name}`);
|
|
109
|
+
parts.push(`${skill.description}\n`);
|
|
110
|
+
parts.push(`## Instructions`);
|
|
111
|
+
parts.push(skill.instructions);
|
|
112
|
+
parts.push('');
|
|
113
|
+
if (skill.examples) {
|
|
114
|
+
parts.push(`## Examples`);
|
|
115
|
+
parts.push(skill.examples);
|
|
116
|
+
parts.push('');
|
|
117
|
+
}
|
|
118
|
+
if (context.files.length > 0) {
|
|
119
|
+
parts.push(`## Context (${context.files.length} file${context.files.length > 1 ? 's' : ''})`);
|
|
120
|
+
for (const file of context.files) {
|
|
121
|
+
parts.push(`\n### ${file.path} (${file.lines} lines)`);
|
|
122
|
+
parts.push('```');
|
|
123
|
+
parts.push(file.content);
|
|
124
|
+
parts.push('```');
|
|
125
|
+
}
|
|
126
|
+
if (context.truncated) {
|
|
127
|
+
parts.push(`\n⚠️ Some files were excluded due to size limits.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
parts.push(`\n---`);
|
|
131
|
+
parts.push(`Use your Edit tool to make changes.`);
|
|
132
|
+
parts.push(`Use Read tool if you need additional files.`);
|
|
133
|
+
return parts.join('\n');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Backward compatibility for old YAML format
|
|
137
|
+
*/
|
|
138
|
+
generateInstructions(skillDef) {
|
|
139
|
+
return skillDef.description || 'Complete the task as described.';
|
|
140
|
+
}
|
|
141
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mentat-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for terminal AI tools (Claude Code, Cursor CLI, Codex CLI) - execute skills and hire AI workers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mentat": "./dist/setup.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"setup": "node dist/setup.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"cursor",
|
|
20
|
+
"codex",
|
|
21
|
+
"ai",
|
|
22
|
+
"agents",
|
|
23
|
+
"marketplace",
|
|
24
|
+
"mentat",
|
|
25
|
+
"dune"
|
|
26
|
+
],
|
|
27
|
+
"author": "Victor Gardrinier",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/vgardrinier/a2a_marketplace.git",
|
|
32
|
+
"directory": "mcp-server"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/vgardrinier/a2a_marketplace#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/vgardrinier/a2a_marketplace/issues"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
40
|
+
"glob": "^10.3.10",
|
|
41
|
+
"yaml": "^2.3.4",
|
|
42
|
+
"zod": "^3.22.4"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"typescript": "^5.3.0"
|
|
47
|
+
}
|
|
48
|
+
}
|