@vasperacapital/vaspera-mcp-server 0.1.1 → 0.2.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 +182 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3102 -0
- package/dist/cli.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/{index.js → server.js} +16 -12
- package/dist/server.js.map +1 -0
- package/package.json +6 -5
- package/src/cli.ts +315 -0
- package/src/{index.ts → server.ts} +14 -9
- package/tsup.config.ts +5 -4
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
package/src/cli.ts
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
|
|
7
|
+
// ASCII Art Banner
|
|
8
|
+
const BANNER = `
|
|
9
|
+
\x1b[36m╔═══════════════════════════════════════════════════════════════╗
|
|
10
|
+
║ ║
|
|
11
|
+
║ ██╗ ██╗ █████╗ ███████╗██████╗ ███████╗██████╗ █████╗ ║
|
|
12
|
+
║ ██║ ██║██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗██╔══██╗ ║
|
|
13
|
+
║ ██║ ██║███████║███████╗██████╔╝█████╗ ██████╔╝███████║ ║
|
|
14
|
+
║ ╚██╗ ██╔╝██╔══██║╚════██║██╔═══╝ ██╔══╝ ██╔══██╗██╔══██║ ║
|
|
15
|
+
║ ╚████╔╝ ██║ ██║███████║██║ ███████╗██║ ██║██║ ██║ ║
|
|
16
|
+
║ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ║
|
|
17
|
+
║ ║
|
|
18
|
+
║ \x1b[33mAI-Powered Product Management\x1b[36m ║
|
|
19
|
+
║ ║
|
|
20
|
+
╚═══════════════════════════════════════════════════════════════╝\x1b[0m
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const VERSION = '0.1.1';
|
|
24
|
+
|
|
25
|
+
const HELP = `
|
|
26
|
+
\x1b[1mUsage:\x1b[0m vasperapm <command> [options]
|
|
27
|
+
|
|
28
|
+
\x1b[1mCommands:\x1b[0m
|
|
29
|
+
\x1b[32minstall\x1b[0m Configure VasperaPM with Claude Code
|
|
30
|
+
\x1b[32mconnect\x1b[0m Set up your API key and integrations
|
|
31
|
+
\x1b[32mserve\x1b[0m Start the MCP server (used by Claude Code)
|
|
32
|
+
\x1b[32mstatus\x1b[0m Check your current configuration
|
|
33
|
+
\x1b[32mhelp\x1b[0m Show this help message
|
|
34
|
+
|
|
35
|
+
\x1b[1mExamples:\x1b[0m
|
|
36
|
+
$ vasperapm install # Set up VasperaPM with Claude Code
|
|
37
|
+
$ vasperapm connect # Configure API key interactively
|
|
38
|
+
$ vasperapm status # Check configuration status
|
|
39
|
+
|
|
40
|
+
\x1b[1mDocumentation:\x1b[0m
|
|
41
|
+
https://github.com/rcolkitt/VasperaPM
|
|
42
|
+
|
|
43
|
+
\x1b[1mGet an API Key:\x1b[0m
|
|
44
|
+
https://vasperapm.com/dashboard
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
// Get Claude config path
|
|
48
|
+
function getClaudeConfigPath(): string {
|
|
49
|
+
return join(homedir(), '.claude', 'claude_desktop_config.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get VasperaPM config path
|
|
53
|
+
function getVasperaConfigPath(): string {
|
|
54
|
+
return join(homedir(), '.vasperapm', 'config.json');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read JSON file safely
|
|
58
|
+
function readJsonFile(path: string): Record<string, unknown> | null {
|
|
59
|
+
try {
|
|
60
|
+
if (!existsSync(path)) return null;
|
|
61
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Write JSON file safely
|
|
68
|
+
function writeJsonFile(path: string, data: Record<string, unknown>): void {
|
|
69
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
70
|
+
if (!existsSync(dir)) {
|
|
71
|
+
mkdirSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Prompt for input
|
|
77
|
+
function prompt(question: string): Promise<string> {
|
|
78
|
+
const rl = createInterface({
|
|
79
|
+
input: process.stdin,
|
|
80
|
+
output: process.stdout,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
rl.question(question, (answer) => {
|
|
85
|
+
rl.close();
|
|
86
|
+
resolve(answer.trim());
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Print success message
|
|
92
|
+
function success(message: string): void {
|
|
93
|
+
console.log(`\x1b[32m✓\x1b[0m ${message}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Print error message
|
|
97
|
+
function error(message: string): void {
|
|
98
|
+
console.log(`\x1b[31m✗\x1b[0m ${message}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Print info message
|
|
102
|
+
function info(message: string): void {
|
|
103
|
+
console.log(`\x1b[36mℹ\x1b[0m ${message}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Print warning message
|
|
107
|
+
function warn(message: string): void {
|
|
108
|
+
console.log(`\x1b[33m⚠\x1b[0m ${message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Install command - Configure Claude Code
|
|
112
|
+
async function install(): Promise<void> {
|
|
113
|
+
console.log(BANNER);
|
|
114
|
+
console.log('\x1b[1mInstalling VasperaPM for Claude Code...\x1b[0m\n');
|
|
115
|
+
|
|
116
|
+
const configPath = getClaudeConfigPath();
|
|
117
|
+
let config = readJsonFile(configPath) as { mcpServers?: Record<string, unknown> } | null;
|
|
118
|
+
|
|
119
|
+
if (!config) {
|
|
120
|
+
config = { mcpServers: {} };
|
|
121
|
+
info('Creating Claude Code configuration...');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!config.mcpServers) {
|
|
125
|
+
config.mcpServers = {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check if already installed
|
|
129
|
+
if (config.mcpServers['vaspera-pm']) {
|
|
130
|
+
warn('VasperaPM is already installed in Claude Code.');
|
|
131
|
+
const answer = await prompt('\nDo you want to reinstall? (y/N): ');
|
|
132
|
+
if (answer.toLowerCase() !== 'y') {
|
|
133
|
+
info('Installation cancelled.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get API key
|
|
139
|
+
const vasperaConfig = readJsonFile(getVasperaConfigPath()) as { apiKey?: string } | null;
|
|
140
|
+
let apiKey = vasperaConfig?.apiKey || process.env.VASPERA_API_KEY || '';
|
|
141
|
+
|
|
142
|
+
if (!apiKey) {
|
|
143
|
+
console.log('\n\x1b[1mAPI Key Setup\x1b[0m');
|
|
144
|
+
console.log('Get your API key at: \x1b[36mhttps://vasperapm.com/dashboard\x1b[0m\n');
|
|
145
|
+
apiKey = await prompt('Enter your VasperaPM API key (or press Enter to use test mode): ');
|
|
146
|
+
|
|
147
|
+
if (!apiKey) {
|
|
148
|
+
apiKey = 'vpm_test_local_development_key_for_testing';
|
|
149
|
+
info('Using test mode API key (limited features)');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add VasperaPM to Claude config
|
|
154
|
+
config.mcpServers['vaspera-pm'] = {
|
|
155
|
+
command: 'vasperapm',
|
|
156
|
+
args: ['serve'],
|
|
157
|
+
env: {
|
|
158
|
+
VASPERA_API_KEY: apiKey,
|
|
159
|
+
NODE_ENV: apiKey.includes('test') ? 'development' : 'production',
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
writeJsonFile(configPath, config);
|
|
164
|
+
|
|
165
|
+
// Save API key to VasperaPM config
|
|
166
|
+
const vasperaDir = join(homedir(), '.vasperapm');
|
|
167
|
+
if (!existsSync(vasperaDir)) {
|
|
168
|
+
mkdirSync(vasperaDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
writeJsonFile(getVasperaConfigPath(), { apiKey });
|
|
171
|
+
|
|
172
|
+
console.log('\n');
|
|
173
|
+
success('VasperaPM installed successfully!');
|
|
174
|
+
console.log('\n\x1b[1mNext steps:\x1b[0m');
|
|
175
|
+
console.log(' 1. Restart Claude Code (or VSCode)');
|
|
176
|
+
console.log(' 2. Look for VasperaPM tools in the MCP panel');
|
|
177
|
+
console.log(' 3. Try: "Generate a PRD for a todo app"\n');
|
|
178
|
+
|
|
179
|
+
console.log('\x1b[1mAvailable Tools:\x1b[0m');
|
|
180
|
+
console.log(' \x1b[33m•\x1b[0m generate_prd - Create PRDs from context');
|
|
181
|
+
console.log(' \x1b[33m•\x1b[0m infer_prd_from_code - Analyze code to generate PRD');
|
|
182
|
+
console.log(' \x1b[33m•\x1b[0m generate_code - Generate code from PRD');
|
|
183
|
+
console.log(' \x1b[33m•\x1b[0m sync_jira/linear/github - Sync with PM tools');
|
|
184
|
+
console.log(' \x1b[33m•\x1b[0m generate_api_spec - Create OpenAPI specs');
|
|
185
|
+
console.log(' \x1b[33m•\x1b[0m generate_test_cases - Create test plans\n');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Connect command - Set up API key and integrations
|
|
189
|
+
async function connect(): Promise<void> {
|
|
190
|
+
console.log(BANNER);
|
|
191
|
+
console.log('\x1b[1mConnect VasperaPM to your services...\x1b[0m\n');
|
|
192
|
+
|
|
193
|
+
// API Key setup
|
|
194
|
+
console.log('\x1b[1m1. API Key\x1b[0m');
|
|
195
|
+
const vasperaConfig = readJsonFile(getVasperaConfigPath()) as { apiKey?: string } | null;
|
|
196
|
+
|
|
197
|
+
if (vasperaConfig?.apiKey) {
|
|
198
|
+
const masked = vasperaConfig.apiKey.substring(0, 12) + '...' + vasperaConfig.apiKey.substring(vasperaConfig.apiKey.length - 4);
|
|
199
|
+
info(`Current API key: ${masked}`);
|
|
200
|
+
const answer = await prompt('Update API key? (y/N): ');
|
|
201
|
+
if (answer.toLowerCase() !== 'y') {
|
|
202
|
+
success('Keeping existing API key');
|
|
203
|
+
} else {
|
|
204
|
+
const newKey = await prompt('Enter new API key: ');
|
|
205
|
+
if (newKey) {
|
|
206
|
+
writeJsonFile(getVasperaConfigPath(), { ...vasperaConfig, apiKey: newKey });
|
|
207
|
+
success('API key updated');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
console.log('Get your API key at: \x1b[36mhttps://vasperapm.com/dashboard\x1b[0m\n');
|
|
212
|
+
const apiKey = await prompt('Enter your VasperaPM API key: ');
|
|
213
|
+
if (apiKey) {
|
|
214
|
+
writeJsonFile(getVasperaConfigPath(), { apiKey });
|
|
215
|
+
success('API key saved');
|
|
216
|
+
} else {
|
|
217
|
+
warn('No API key provided. Some features will be limited.');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log('\n\x1b[1m2. Integrations\x1b[0m');
|
|
222
|
+
console.log('Configure integrations at: \x1b[36mhttps://vasperapm.com/dashboard/integrations\x1b[0m\n');
|
|
223
|
+
console.log(' \x1b[33m•\x1b[0m Jira - Sync PRDs to Jira epics/stories');
|
|
224
|
+
console.log(' \x1b[33m•\x1b[0m Linear - Export tasks to Linear projects');
|
|
225
|
+
console.log(' \x1b[33m•\x1b[0m GitHub - Create issues and track progress\n');
|
|
226
|
+
|
|
227
|
+
success('Connection setup complete!');
|
|
228
|
+
console.log('\nRun \x1b[36mvasperapm status\x1b[0m to check your configuration.\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Status command - Check configuration
|
|
232
|
+
async function status(): Promise<void> {
|
|
233
|
+
console.log(BANNER);
|
|
234
|
+
console.log('\x1b[1mVasperaPM Status\x1b[0m\n');
|
|
235
|
+
|
|
236
|
+
// Check VasperaPM config
|
|
237
|
+
const vasperaConfig = readJsonFile(getVasperaConfigPath()) as { apiKey?: string } | null;
|
|
238
|
+
if (vasperaConfig?.apiKey) {
|
|
239
|
+
const masked = vasperaConfig.apiKey.substring(0, 12) + '...' + vasperaConfig.apiKey.substring(vasperaConfig.apiKey.length - 4);
|
|
240
|
+
success(`API Key: ${masked}`);
|
|
241
|
+
|
|
242
|
+
if (vasperaConfig.apiKey.includes('_live_')) {
|
|
243
|
+
info('Mode: Production');
|
|
244
|
+
} else if (vasperaConfig.apiKey.includes('_test_')) {
|
|
245
|
+
info('Mode: Test/Development');
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
warn('API Key: Not configured');
|
|
249
|
+
console.log(' Run \x1b[36mvasperapm connect\x1b[0m to set up your API key\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check Claude Code config
|
|
253
|
+
const claudeConfig = readJsonFile(getClaudeConfigPath()) as { mcpServers?: Record<string, unknown> } | null;
|
|
254
|
+
if (claudeConfig?.mcpServers?.['vaspera-pm']) {
|
|
255
|
+
success('Claude Code: Installed');
|
|
256
|
+
} else {
|
|
257
|
+
warn('Claude Code: Not installed');
|
|
258
|
+
console.log(' Run \x1b[36mvasperapm install\x1b[0m to set up Claude Code integration\n');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Version info
|
|
262
|
+
console.log(`\n\x1b[1mVersion:\x1b[0m ${VERSION}`);
|
|
263
|
+
console.log('\x1b[1mDocs:\x1b[0m https://github.com/rcolkitt/VasperaPM\n');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Serve command - Start MCP server
|
|
267
|
+
async function serve(): Promise<void> {
|
|
268
|
+
// Import and run the MCP server
|
|
269
|
+
const { startServer } = await import('./server.js');
|
|
270
|
+
await startServer();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Main CLI
|
|
274
|
+
async function main(): Promise<void> {
|
|
275
|
+
const args = process.argv.slice(2);
|
|
276
|
+
const command = args[0]?.toLowerCase();
|
|
277
|
+
|
|
278
|
+
switch (command) {
|
|
279
|
+
case 'install':
|
|
280
|
+
await install();
|
|
281
|
+
break;
|
|
282
|
+
case 'connect':
|
|
283
|
+
await connect();
|
|
284
|
+
break;
|
|
285
|
+
case 'serve':
|
|
286
|
+
await serve();
|
|
287
|
+
break;
|
|
288
|
+
case 'status':
|
|
289
|
+
await status();
|
|
290
|
+
break;
|
|
291
|
+
case 'help':
|
|
292
|
+
case '--help':
|
|
293
|
+
case '-h':
|
|
294
|
+
console.log(BANNER);
|
|
295
|
+
console.log(HELP);
|
|
296
|
+
break;
|
|
297
|
+
case '--version':
|
|
298
|
+
case '-v':
|
|
299
|
+
console.log(`vasperapm v${VERSION}`);
|
|
300
|
+
break;
|
|
301
|
+
case undefined:
|
|
302
|
+
console.log(BANNER);
|
|
303
|
+
console.log(HELP);
|
|
304
|
+
break;
|
|
305
|
+
default:
|
|
306
|
+
error(`Unknown command: ${command}`);
|
|
307
|
+
console.log(HELP);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
main().catch((err) => {
|
|
313
|
+
error(err.message);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
});
|
|
@@ -6,15 +6,17 @@ import {
|
|
|
6
6
|
ErrorCode,
|
|
7
7
|
McpError,
|
|
8
8
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import { validateApiKey
|
|
9
|
+
import { validateApiKey } from './middleware/auth.js';
|
|
10
10
|
import { trackUsage } from './middleware/usage.js';
|
|
11
11
|
import { checkRateLimit } from './middleware/rate-limit.js';
|
|
12
12
|
import { tools, toolHandlers } from './tools/index.js';
|
|
13
13
|
|
|
14
|
+
const VERSION = '0.1.1';
|
|
15
|
+
|
|
14
16
|
const server = new Server(
|
|
15
17
|
{
|
|
16
18
|
name: 'vaspera-pm',
|
|
17
|
-
version:
|
|
19
|
+
version: VERSION,
|
|
18
20
|
},
|
|
19
21
|
{
|
|
20
22
|
capabilities: {
|
|
@@ -127,14 +129,17 @@ function sanitizeArgs(args: Record<string, unknown> | undefined): Record<string,
|
|
|
127
129
|
return sanitized;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
// Start server
|
|
131
|
-
async function
|
|
132
|
+
// Start server (exported for CLI)
|
|
133
|
+
export async function startServer(): Promise<void> {
|
|
132
134
|
const transport = new StdioServerTransport();
|
|
133
135
|
await server.connect(transport);
|
|
134
|
-
console.error(
|
|
136
|
+
console.error(`VasperaPM MCP Server v${VERSION} running`);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// Allow direct execution
|
|
140
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
141
|
+
startServer().catch((error) => {
|
|
142
|
+
console.error('Failed to start MCP server:', error);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
|
145
|
+
}
|
package/tsup.config.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { defineConfig } from 'tsup';
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
entry:
|
|
4
|
+
entry: {
|
|
5
|
+
cli: 'src/cli.ts',
|
|
6
|
+
server: 'src/server.ts',
|
|
7
|
+
},
|
|
5
8
|
format: ['esm'],
|
|
6
9
|
dts: true,
|
|
7
10
|
clean: true,
|
|
8
11
|
sourcemap: true,
|
|
9
12
|
target: 'node20',
|
|
10
|
-
|
|
11
|
-
js: '#!/usr/bin/env node',
|
|
12
|
-
},
|
|
13
|
+
splitting: false,
|
|
13
14
|
});
|
package/dist/index.d.ts
DELETED