@vasperacapital/vaspera-mcp-server 0.1.0 → 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/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
+ });
@@ -1,4 +1,4 @@
1
- import { QUOTA_LIMITS, type SubscriptionTier } from '@vaspera/shared';
1
+ import { QUOTA_LIMITS, type SubscriptionTier } from '@vasperacapital/vaspera-shared';
2
2
 
3
3
  // Response type from validate-key API
4
4
  interface ValidateKeyResponse {
@@ -1,4 +1,4 @@
1
- import type { SubscriptionTier } from '@vaspera/shared';
1
+ import type { SubscriptionTier } from '@vasperacapital/vaspera-shared';
2
2
 
3
3
  export interface RateLimitResult {
4
4
  allowed: boolean;
@@ -6,15 +6,17 @@ import {
6
6
  ErrorCode,
7
7
  McpError,
8
8
  } from '@modelcontextprotocol/sdk/types.js';
9
- import { validateApiKey, type ValidationResult } from './middleware/auth.js';
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: '1.0.0',
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 main() {
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('VasperaPM MCP Server v1.0.0 running');
136
+ console.error(`VasperaPM MCP Server v${VERSION} running`);
135
137
  }
136
138
 
137
- main().catch((error) => {
138
- console.error('Failed to start MCP server:', error);
139
- process.exit(1);
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: ['src/index.ts'],
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
- banner: {
11
- js: '#!/usr/bin/env node',
12
- },
13
+ splitting: false,
13
14
  });
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }