purmemo-mcp 2.0.0 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purmemo-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Official Model Context Protocol (MCP) server for Purmemo - Seamless OAuth authentication for your AI-powered second brain",
5
5
  "main": "src/server-oauth.js",
6
6
  "type": "module",
@@ -6,8 +6,13 @@
6
6
  import crypto from 'crypto';
7
7
  import express from 'express';
8
8
  import open from 'open';
9
+ import { spawn, exec } from 'child_process';
10
+ import { promisify } from 'util';
11
+ import os from 'os';
9
12
  import TokenStore from './token-store.js';
10
13
 
14
+ const execAsync = promisify(exec);
15
+
11
16
  class OAuthManager {
12
17
  constructor(config = {}) {
13
18
  this.apiUrl = config.apiUrl || process.env.PUO_MEMO_API_URL || 'https://api.purmemo.ai';
@@ -16,6 +21,142 @@ class OAuthManager {
16
21
  this.tokenStore = new TokenStore();
17
22
  this.server = null;
18
23
  this.pendingAuth = null;
24
+ this.platform = os.platform();
25
+ }
26
+
27
+ /**
28
+ * Robust browser opening with multiple fallback strategies
29
+ * Handles macOS security restrictions and provides user-friendly alternatives
30
+ */
31
+ async openBrowserRobustly(url) {
32
+ console.log(`🌐 Opening OAuth URL...`);
33
+
34
+ const strategies = [
35
+ () => this.tryOpenPackage(url),
36
+ () => this.tryDirectCommand(url),
37
+ () => this.tryAlternativeCommands(url),
38
+ () => this.tryAppleScript(url),
39
+ () => this.provideFallbackInstructions(url)
40
+ ];
41
+
42
+ for (let i = 0; i < strategies.length; i++) {
43
+ try {
44
+ const result = await strategies[i]();
45
+
46
+ if (result.success) {
47
+ if (result.requiresManualAction) {
48
+ // User needs to manually open the URL
49
+ return { opened: false, manualRequired: true, url };
50
+ }
51
+ console.log(`✅ Browser opened successfully`);
52
+ return { opened: true, manualRequired: false };
53
+ }
54
+ } catch (error) {
55
+ console.log(`Strategy ${i + 1} failed, trying next...`);
56
+ }
57
+ }
58
+
59
+ return { opened: false, manualRequired: true, url };
60
+ }
61
+
62
+ async tryOpenPackage(url) {
63
+ try {
64
+ await open(url, { wait: false });
65
+ await new Promise(resolve => setTimeout(resolve, 1000));
66
+ return { success: true };
67
+ } catch (error) {
68
+ throw new Error(`open package failed: ${error.message}`);
69
+ }
70
+ }
71
+
72
+ async tryDirectCommand(url) {
73
+ try {
74
+ let command;
75
+
76
+ switch (this.platform) {
77
+ case 'darwin':
78
+ command = `open "${url}"`;
79
+ break;
80
+ case 'win32':
81
+ command = `start "" "${url}"`;
82
+ break;
83
+ default:
84
+ command = `xdg-open "${url}"`;
85
+ }
86
+
87
+ await execAsync(command);
88
+ await new Promise(resolve => setTimeout(resolve, 1000));
89
+ return { success: true };
90
+ } catch (error) {
91
+ throw new Error(`direct command failed: ${error.message}`);
92
+ }
93
+ }
94
+
95
+ async tryAlternativeCommands(url) {
96
+ if (this.platform !== 'darwin') {
97
+ throw new Error('Not macOS');
98
+ }
99
+
100
+ const commands = [
101
+ `open -a Safari "${url}"`,
102
+ `open -a "Google Chrome" "${url}"`,
103
+ `open -a Firefox "${url}"`,
104
+ `/usr/bin/open "${url}"`
105
+ ];
106
+
107
+ for (const command of commands) {
108
+ try {
109
+ await execAsync(command);
110
+ await new Promise(resolve => setTimeout(resolve, 500));
111
+ return { success: true };
112
+ } catch (error) {
113
+ continue;
114
+ }
115
+ }
116
+
117
+ throw new Error('All alternative commands failed');
118
+ }
119
+
120
+ async tryAppleScript(url) {
121
+ if (this.platform !== 'darwin') {
122
+ throw new Error('Not macOS');
123
+ }
124
+
125
+ try {
126
+ const script = `tell application "Safari" to open location "${url}"`;
127
+ await execAsync(`osascript -e '${script}'`);
128
+ await new Promise(resolve => setTimeout(resolve, 1000));
129
+ return { success: true };
130
+ } catch (error) {
131
+ throw new Error(`AppleScript failed: ${error.message}`);
132
+ }
133
+ }
134
+
135
+ async provideFallbackInstructions(url) {
136
+ console.log('\n' + '━'.repeat(60));
137
+ console.log('🚨 BROWSER OPENING BLOCKED BY SECURITY');
138
+ console.log('━'.repeat(60));
139
+ console.log('');
140
+ console.log('Please manually copy and paste this URL into your browser:');
141
+ console.log('');
142
+ console.log('📋 COPY THIS URL:');
143
+ console.log('⬇'.repeat(40));
144
+ console.log(url);
145
+ console.log('⬆'.repeat(40));
146
+ console.log('');
147
+ console.log('📱 QUICK STEPS:');
148
+ console.log('1. Copy the URL above');
149
+ console.log('2. Open your browser');
150
+ console.log('3. Paste and press Enter');
151
+ console.log('4. Sign in with Google/GitHub');
152
+ console.log('');
153
+ console.log('⏰ Waiting for OAuth callback...');
154
+ console.log('━'.repeat(60));
155
+
156
+ return {
157
+ success: true,
158
+ requiresManualAction: true
159
+ };
19
160
  }
20
161
 
21
162
  /**
@@ -106,12 +247,13 @@ class OAuthManager {
106
247
  // Start local server for callback
107
248
  const authCode = await this.startCallbackServer(state);
108
249
 
109
- // Open browser for authentication
110
- console.log('📱 Opening browser for authentication...');
111
- console.log(' If browser doesn\'t open, visit:');
112
- console.log(` ${authUrl.toString()}\n`);
250
+ // Open browser for authentication with robust fallback
251
+ const browserResult = await this.openBrowserRobustly(authUrl.toString());
113
252
 
114
- await open(authUrl.toString());
253
+ if (!browserResult.opened && browserResult.manualRequired) {
254
+ // The robust method already displayed instructions
255
+ console.log(''); // Just add some spacing
256
+ }
115
257
 
116
258
  // Wait for callback with auth code
117
259
  const code = await authCode;
@@ -364,11 +364,41 @@ async function initialize() {
364
364
  console.log('Ready to serve MCP requests\n');
365
365
  }
366
366
 
367
- // Start server
368
- initialize().then(() => {
369
- const transport = new StdioServerTransport();
370
- server.connect(transport);
367
+ // Handle CLI commands if provided
368
+ async function handleCliCommands() {
369
+ const args = process.argv.slice(2);
370
+ if (args.length > 0) {
371
+ const command = args[0];
372
+
373
+ if (command === 'setup' || command === 'status' || command === 'logout' || command === 'upgrade') {
374
+ // Delegate to setup script for CLI commands
375
+ const { execSync } = await import('child_process');
376
+ const setupPath = new URL('./setup.js', import.meta.url).pathname;
377
+
378
+ try {
379
+ execSync(`node "${setupPath}" ${args.join(' ')}`, {
380
+ stdio: 'inherit',
381
+ cwd: process.cwd()
382
+ });
383
+ process.exit(0);
384
+ } catch (error) {
385
+ process.exit(error.status || 1);
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ // Start server or handle CLI commands
392
+ handleCliCommands().then(() => {
393
+ // If we get here, it's not a CLI command, so start the MCP server
394
+ initialize().then(() => {
395
+ const transport = new StdioServerTransport();
396
+ server.connect(transport);
397
+ }).catch(error => {
398
+ console.error('Failed to initialize:', error);
399
+ process.exit(1);
400
+ });
371
401
  }).catch(error => {
372
- console.error('Failed to initialize:', error);
402
+ console.error('Failed to handle CLI commands:', error);
373
403
  process.exit(1);
374
404
  });
package/src/setup.js CHANGED
File without changes