groove-dev 0.17.0 → 0.17.1

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.
@@ -11,10 +11,20 @@
11
11
  "transport": "stdio",
12
12
  "command": "npx",
13
13
  "args": ["-y", "@modelcontextprotocol/server-slack"],
14
+ "authType": "api-key",
14
15
  "envKeys": [
15
16
  { "key": "SLACK_BOT_TOKEN", "label": "Bot Token", "placeholder": "xoxb-...", "required": true },
16
17
  { "key": "SLACK_TEAM_ID", "label": "Team ID", "placeholder": "T01234567", "required": true }
17
18
  ],
19
+ "setupUrl": "https://api.slack.com/apps",
20
+ "setupSteps": [
21
+ "Go to api.slack.com/apps and click 'Create New App'",
22
+ "Choose 'From scratch', name it 'Groove', pick your workspace",
23
+ "Go to 'OAuth & Permissions', add scopes: channels:read, channels:history, chat:write, users:read",
24
+ "Click 'Install to Workspace' and authorize",
25
+ "Copy the 'Bot User OAuth Token' (starts with xoxb-)",
26
+ "Your Team ID is in the workspace URL: app.slack.com/client/TXXXXXXX"
27
+ ],
18
28
  "featured": true,
19
29
  "downloads": 0,
20
30
  "rating": 0,
@@ -33,9 +43,16 @@
33
43
  "transport": "stdio",
34
44
  "command": "npx",
35
45
  "args": ["-y", "@modelcontextprotocol/server-github"],
46
+ "authType": "api-key",
36
47
  "envKeys": [
37
48
  { "key": "GITHUB_PERSONAL_ACCESS_TOKEN", "label": "Personal Access Token", "placeholder": "ghp_...", "required": true }
38
49
  ],
50
+ "setupUrl": "https://github.com/settings/tokens/new",
51
+ "setupSteps": [
52
+ "Click the link below to create a new token on GitHub",
53
+ "Select scopes: repo, read:org, read:user",
54
+ "Click 'Generate token' and copy it"
55
+ ],
39
56
  "featured": true,
40
57
  "downloads": 0,
41
58
  "rating": 0,
@@ -54,9 +71,16 @@
54
71
  "transport": "stdio",
55
72
  "command": "npx",
56
73
  "args": ["-y", "@stripe/agent-toolkit", "mcp"],
74
+ "authType": "api-key",
57
75
  "envKeys": [
58
76
  { "key": "STRIPE_SECRET_KEY", "label": "Secret Key", "placeholder": "sk_...", "required": true }
59
77
  ],
78
+ "setupUrl": "https://dashboard.stripe.com/apikeys",
79
+ "setupSteps": [
80
+ "Click the link below to open your Stripe API keys",
81
+ "Copy the 'Secret key' (starts with sk_live_ or sk_test_)",
82
+ "Use a test key (sk_test_) for safe experimentation"
83
+ ],
60
84
  "featured": true,
61
85
  "downloads": 0,
62
86
  "rating": 0,
@@ -75,10 +99,17 @@
75
99
  "transport": "stdio",
76
100
  "command": "npx",
77
101
  "args": ["-y", "@anthropic-ai/mcp-server-google-calendar"],
102
+ "authType": "oauth-google",
103
+ "oauthScopes": ["https://www.googleapis.com/auth/calendar"],
78
104
  "envKeys": [
79
- { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
80
- { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
81
- { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
105
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true, "hidden": true },
106
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true, "hidden": true },
107
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true, "hidden": true }
108
+ ],
109
+ "setupSteps": [
110
+ "Click 'Connect with Google' below",
111
+ "Sign in and authorize Groove to access your calendar",
112
+ "That's it — your credentials are stored securely"
82
113
  ],
83
114
  "featured": false,
84
115
  "downloads": 0,
@@ -98,10 +129,17 @@
98
129
  "transport": "stdio",
99
130
  "command": "npx",
100
131
  "args": ["-y", "@anthropic-ai/mcp-server-gmail"],
132
+ "authType": "oauth-google",
133
+ "oauthScopes": ["https://www.googleapis.com/auth/gmail.modify"],
101
134
  "envKeys": [
102
- { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
103
- { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
104
- { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
135
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true, "hidden": true },
136
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true, "hidden": true },
137
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true, "hidden": true }
138
+ ],
139
+ "setupSteps": [
140
+ "Click 'Connect with Google' below",
141
+ "Sign in and authorize Groove to access your email",
142
+ "That's it — your credentials are stored securely"
105
143
  ],
106
144
  "featured": false,
107
145
  "downloads": 0,
@@ -121,9 +159,14 @@
121
159
  "transport": "stdio",
122
160
  "command": "npx",
123
161
  "args": ["-y", "@modelcontextprotocol/server-postgres"],
162
+ "authType": "api-key",
124
163
  "envKeys": [
125
164
  { "key": "POSTGRES_CONNECTION_STRING", "label": "Connection String", "placeholder": "postgresql://user:pass@host:5432/db", "required": true }
126
165
  ],
166
+ "setupSteps": [
167
+ "Enter your PostgreSQL connection string",
168
+ "Format: postgresql://username:password@host:port/database"
169
+ ],
127
170
  "featured": false,
128
171
  "downloads": 0,
129
172
  "rating": 0,
@@ -142,9 +185,16 @@
142
185
  "transport": "stdio",
143
186
  "command": "npx",
144
187
  "args": ["-y", "@modelcontextprotocol/server-brave-search"],
188
+ "authType": "api-key",
145
189
  "envKeys": [
146
190
  { "key": "BRAVE_API_KEY", "label": "API Key", "placeholder": "BSA...", "required": true }
147
191
  ],
192
+ "setupUrl": "https://brave.com/search/api/",
193
+ "setupSteps": [
194
+ "Click the link below to get a Brave Search API key",
195
+ "Sign up for a free plan (2,000 queries/month)",
196
+ "Copy your API key"
197
+ ],
148
198
  "featured": false,
149
199
  "downloads": 0,
150
200
  "rating": 0,
@@ -163,10 +213,17 @@
163
213
  "transport": "stdio",
164
214
  "command": "npx",
165
215
  "args": ["-y", "@modelcontextprotocol/server-gdrive"],
216
+ "authType": "oauth-google",
217
+ "oauthScopes": ["https://www.googleapis.com/auth/drive.readonly"],
166
218
  "envKeys": [
167
- { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
168
- { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
169
- { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
219
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true, "hidden": true },
220
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true, "hidden": true },
221
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true, "hidden": true }
222
+ ],
223
+ "setupSteps": [
224
+ "Click 'Connect with Google' below",
225
+ "Sign in and authorize Groove to access your Drive",
226
+ "That's it — your credentials are stored securely"
170
227
  ],
171
228
  "featured": false,
172
229
  "downloads": 0,
@@ -186,9 +243,16 @@
186
243
  "transport": "stdio",
187
244
  "command": "npx",
188
245
  "args": ["-y", "@ibraheem4/linear-mcp-server"],
246
+ "authType": "api-key",
189
247
  "envKeys": [
190
248
  { "key": "LINEAR_API_KEY", "label": "API Key", "required": true }
191
249
  ],
250
+ "setupUrl": "https://linear.app/settings/api",
251
+ "setupSteps": [
252
+ "Click the link below to open Linear API settings",
253
+ "Click 'Create key', give it a label like 'Groove'",
254
+ "Copy the API key"
255
+ ],
192
256
  "featured": false,
193
257
  "downloads": 0,
194
258
  "rating": 0,
@@ -207,9 +271,17 @@
207
271
  "transport": "stdio",
208
272
  "command": "npx",
209
273
  "args": ["-y", "@notionhq/notion-mcp-server"],
274
+ "authType": "api-key",
210
275
  "envKeys": [
211
276
  { "key": "NOTION_API_KEY", "label": "Integration Token", "placeholder": "ntn_...", "required": true }
212
277
  ],
278
+ "setupUrl": "https://www.notion.so/my-integrations",
279
+ "setupSteps": [
280
+ "Click the link below to open Notion integrations",
281
+ "Click 'New integration', name it 'Groove'",
282
+ "Copy the 'Internal Integration Secret'",
283
+ "Share the pages/databases you want accessible with the integration"
284
+ ],
213
285
  "featured": false,
214
286
  "downloads": 0,
215
287
  "rating": 0,
@@ -228,9 +300,18 @@
228
300
  "transport": "stdio",
229
301
  "command": "npx",
230
302
  "args": ["-y", "mcp-discord"],
303
+ "authType": "api-key",
231
304
  "envKeys": [
232
305
  { "key": "DISCORD_BOT_TOKEN", "label": "Bot Token", "required": true }
233
306
  ],
307
+ "setupUrl": "https://discord.com/developers/applications",
308
+ "setupSteps": [
309
+ "Click the link below to open Discord Developer Portal",
310
+ "Click 'New Application', name it 'Groove'",
311
+ "Go to 'Bot' tab, click 'Reset Token', copy it",
312
+ "Enable 'Message Content Intent' under Privileged Gateway Intents",
313
+ "Go to OAuth2 > URL Generator, select 'bot' scope, invite to your server"
314
+ ],
234
315
  "featured": false,
235
316
  "downloads": 0,
236
317
  "rating": 0,
@@ -249,10 +330,16 @@
249
330
  "transport": "stdio",
250
331
  "command": "npx",
251
332
  "args": ["-y", "mcp-home-assistant"],
333
+ "authType": "api-key",
252
334
  "envKeys": [
253
335
  { "key": "HOME_ASSISTANT_URL", "label": "HA URL", "placeholder": "http://homeassistant.local:8123", "required": true },
254
336
  { "key": "HOME_ASSISTANT_TOKEN", "label": "Long-Lived Access Token", "required": true }
255
337
  ],
338
+ "setupSteps": [
339
+ "Enter your Home Assistant URL (usually http://homeassistant.local:8123)",
340
+ "In HA: Profile (bottom left) > Security > Long-Lived Access Tokens",
341
+ "Click 'Create Token', name it 'Groove', copy the token"
342
+ ],
256
343
  "featured": false,
257
344
  "downloads": 0,
258
345
  "rating": 0,
@@ -271,6 +358,7 @@
271
358
  "transport": "stdio",
272
359
  "command": "npx",
273
360
  "args": ["-y", "@modelcontextprotocol/server-filesystem"],
361
+ "authType": "none",
274
362
  "envKeys": [],
275
363
  "featured": false,
276
364
  "downloads": 0,
@@ -290,9 +378,16 @@
290
378
  "transport": "stdio",
291
379
  "command": "npx",
292
380
  "args": ["-y", "@modelcontextprotocol/server-google-maps"],
381
+ "authType": "api-key",
293
382
  "envKeys": [
294
383
  { "key": "GOOGLE_MAPS_API_KEY", "label": "API Key", "required": true }
295
384
  ],
385
+ "setupUrl": "https://console.cloud.google.com/apis/credentials",
386
+ "setupSteps": [
387
+ "Click the link below to open Google Cloud Console",
388
+ "Create or select a project, then 'Create Credentials' > 'API Key'",
389
+ "Enable the Maps JavaScript API, Geocoding API, and Places API"
390
+ ],
296
391
  "featured": false,
297
392
  "downloads": 0,
298
393
  "rating": 0,
@@ -311,6 +406,7 @@
311
406
  "transport": "stdio",
312
407
  "command": "npx",
313
408
  "args": ["-y", "@modelcontextprotocol/server-sqlite"],
409
+ "authType": "none",
314
410
  "envKeys": [],
315
411
  "featured": false,
316
412
  "downloads": 0,
@@ -505,6 +505,58 @@ export function createApi(app, daemon) {
505
505
  }
506
506
  });
507
507
 
508
+ // --- Google OAuth flow ---
509
+
510
+ app.get('/api/integrations/google-oauth/status', (req, res) => {
511
+ res.json({ configured: daemon.integrations.isGoogleOAuthConfigured() });
512
+ });
513
+
514
+ app.post('/api/integrations/google-oauth/setup', (req, res) => {
515
+ try {
516
+ const { clientId, clientSecret } = req.body || {};
517
+ if (!clientId || !clientSecret) return res.status(400).json({ error: 'clientId and clientSecret are required' });
518
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_ID', clientId);
519
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_SECRET', clientSecret);
520
+ res.json({ ok: true });
521
+ } catch (err) {
522
+ res.status(400).json({ error: err.message });
523
+ }
524
+ });
525
+
526
+ app.post('/api/integrations/:id/oauth/start', (req, res) => {
527
+ try {
528
+ const url = daemon.integrations.getOAuthUrl(req.params.id);
529
+ res.json({ url });
530
+ } catch (err) {
531
+ res.status(400).json({ error: err.message });
532
+ }
533
+ });
534
+
535
+ app.get('/api/integrations/oauth/callback', async (req, res) => {
536
+ try {
537
+ const { code, state } = req.query;
538
+ if (!code || !state) return res.status(400).send('Missing code or state parameter');
539
+ await daemon.integrations.handleOAuthCallback(code, state);
540
+ // Return a nice HTML page that auto-closes
541
+ res.send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e6e6e6">
542
+ <div style="text-align:center">
543
+ <div style="font-size:48px;margin-bottom:16px">&#10003;</div>
544
+ <h2>Connected!</h2>
545
+ <p style="color:#7a8394">You can close this tab and return to Groove.</p>
546
+ <script>setTimeout(()=>window.close(),2000)</script>
547
+ </div>
548
+ </body></html>`);
549
+ } catch (err) {
550
+ res.status(400).send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e06c75">
551
+ <div style="text-align:center">
552
+ <h2>Connection Failed</h2>
553
+ <p>${err.message}</p>
554
+ <p style="color:#7a8394">Close this tab and try again in Groove.</p>
555
+ </div>
556
+ </body></html>`);
557
+ }
558
+ });
559
+
508
560
  // --- Agent Integrations (attach/detach) ---
509
561
 
510
562
  app.post('/api/agents/:agentId/integrations/:integrationId', (req, res) => {
@@ -359,6 +359,92 @@ export class IntegrationStore {
359
359
  }
360
360
  }
361
361
 
362
+ /**
363
+ * Start an OAuth flow for a Google integration.
364
+ * Returns the authorization URL to open in a browser.
365
+ */
366
+ getOAuthUrl(integrationId) {
367
+ const entry = this.registry.find((s) => s.id === integrationId);
368
+ if (!entry) throw new Error(`Integration not found: ${integrationId}`);
369
+ if (entry.authType !== 'oauth-google') throw new Error('Integration does not use OAuth');
370
+
371
+ // Check if user has provided their own Google OAuth client (stored globally)
372
+ const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
373
+ const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
374
+ if (!clientId || !clientSecret) {
375
+ throw new Error('Google OAuth not configured. Set up your Google Cloud project first.');
376
+ }
377
+
378
+ const port = this.daemon.port || 31415;
379
+ const redirectUri = `http://localhost:${port}/api/integrations/oauth/callback`;
380
+ const scopes = entry.oauthScopes || [];
381
+
382
+ const params = new URLSearchParams({
383
+ client_id: clientId,
384
+ redirect_uri: redirectUri,
385
+ response_type: 'code',
386
+ scope: scopes.join(' '),
387
+ access_type: 'offline',
388
+ prompt: 'consent',
389
+ state: integrationId,
390
+ });
391
+
392
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
393
+ }
394
+
395
+ /**
396
+ * Handle OAuth callback — exchange code for tokens.
397
+ */
398
+ async handleOAuthCallback(code, integrationId) {
399
+ const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
400
+ const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
401
+ if (!clientId || !clientSecret) {
402
+ throw new Error('Google OAuth credentials not found');
403
+ }
404
+
405
+ const port = this.daemon.port || 31415;
406
+ const redirectUri = `http://localhost:${port}/api/integrations/oauth/callback`;
407
+
408
+ const res = await fetch('https://oauth2.googleapis.com/token', {
409
+ method: 'POST',
410
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
411
+ body: new URLSearchParams({
412
+ code,
413
+ client_id: clientId,
414
+ client_secret: clientSecret,
415
+ redirect_uri: redirectUri,
416
+ grant_type: 'authorization_code',
417
+ }),
418
+ });
419
+
420
+ if (!res.ok) {
421
+ const err = await res.json().catch(() => ({}));
422
+ throw new Error(`OAuth token exchange failed: ${err.error_description || err.error || 'unknown'}`);
423
+ }
424
+
425
+ const tokens = await res.json();
426
+
427
+ // Store the tokens for this integration
428
+ this.setCredential(integrationId, 'GOOGLE_CLIENT_ID', clientId);
429
+ this.setCredential(integrationId, 'GOOGLE_CLIENT_SECRET', clientSecret);
430
+ if (tokens.refresh_token) {
431
+ this.setCredential(integrationId, 'GOOGLE_REFRESH_TOKEN', tokens.refresh_token);
432
+ }
433
+
434
+ this.daemon.audit.log('integration.oauth.complete', { id: integrationId });
435
+
436
+ return { ok: true, integrationId };
437
+ }
438
+
439
+ /**
440
+ * Check if Google OAuth is configured (user has set up their Cloud project).
441
+ */
442
+ isGoogleOAuthConfigured() {
443
+ const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
444
+ const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
445
+ return !!(clientId && clientSecret);
446
+ }
447
+
362
448
  // --- Internal ---
363
449
 
364
450
  _isInstalled(integrationId) {