@flomenco/claude-plugin-mcp 0.1.0 → 0.1.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/README.md CHANGED
@@ -11,6 +11,7 @@ It exposes authentication + plugin tools:
11
11
  - `flo_auth_logout`
12
12
 
13
13
  - `flo_search`
14
+ - `flo_query`
14
15
  - `flo_skill_routing`
15
16
  - `flo_qc_logo`
16
17
  - `flo_command` (raw passthrough for troubleshooting)
@@ -161,6 +162,7 @@ After Claude starts with MCP enabled:
161
162
  - Run `flo_auth_setup_help` if you need the Flo settings URL for locating `FLO_OAUTH_CLIENT_ID`
162
163
  - Run `flo_plugin_healthcheck` (expect `runtime.reachable: true`)
163
164
  - Run `flo_search` with `query="hail mary"`
165
+ - Or run `flo_query` with `query="hail mary"` to get filename-first results and next command templates
164
166
  - `flo_skill_routing` with an `assetId` from search
165
167
  - `flo_qc_logo` with `assetId` and `referenceAssetId`
166
168
 
@@ -171,5 +173,6 @@ One-shot E2E:
171
173
  Expected output shape:
172
174
 
173
175
  - search: `status`, `menuItems[]`
176
+ - query: `status`, `filenames[]`, `results[]`
174
177
  - skill-routing: `status`, `skills[]`, includes `/flo:qc-logo`
175
178
  - qc-logo: `status`, `result.overall|summary|assetId|assetUrl|findings`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flomenco/claude-plugin-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Flo Claude Co-Work plugin bridge for Claude Desktop/Code",
5
5
  "private": false,
6
6
  "license": "UNLICENSED",
@@ -22,6 +22,7 @@
22
22
  "type": "module",
23
23
  "files": [
24
24
  "src",
25
+ "tools",
25
26
  "README.md"
26
27
  ],
27
28
  "bin": {
package/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
4
  import {
@@ -282,6 +283,127 @@ function openBrowser(url) {
282
283
  }
283
284
  }
284
285
 
286
+ function escapeHtml(value) {
287
+ return String(value || "")
288
+ .replaceAll("&", "&")
289
+ .replaceAll("<", "&lt;")
290
+ .replaceAll(">", "&gt;")
291
+ .replaceAll('"', "&quot;")
292
+ .replaceAll("'", "&#39;");
293
+ }
294
+
295
+ function renderOAuthCallbackPage({ status, title, message, details }) {
296
+ const isSuccess = status === "success";
297
+ const badgeText = isSuccess ? "Flo Connected" : "Flo Connection Error";
298
+ const badgeColor = isSuccess ? "#22c55e" : "#ef4444";
299
+ const symbol = isSuccess ? "✓" : "!";
300
+ const detailsHtml = details
301
+ ? `<pre class="details">${escapeHtml(details)}</pre>`
302
+ : "";
303
+
304
+ return `<!doctype html>
305
+ <html lang="en">
306
+ <head>
307
+ <meta charset="utf-8" />
308
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
309
+ <title>${escapeHtml(title)}</title>
310
+ <style>
311
+ :root {
312
+ color-scheme: dark;
313
+ }
314
+ body {
315
+ margin: 0;
316
+ min-height: 100vh;
317
+ font-family:
318
+ Inter,
319
+ -apple-system,
320
+ BlinkMacSystemFont,
321
+ "Segoe UI",
322
+ Roboto,
323
+ "Helvetica Neue",
324
+ Arial,
325
+ sans-serif;
326
+ background: radial-gradient(circle at top, #1a2138 0%, #0b1020 58%);
327
+ color: #e5e7eb;
328
+ display: grid;
329
+ place-items: center;
330
+ padding: 24px;
331
+ }
332
+ .card {
333
+ width: min(560px, 100%);
334
+ border: 1px solid rgba(148, 163, 184, 0.28);
335
+ background: rgba(15, 23, 42, 0.76);
336
+ backdrop-filter: blur(8px);
337
+ border-radius: 16px;
338
+ padding: 26px;
339
+ box-shadow: 0 16px 42px rgba(2, 6, 23, 0.35);
340
+ }
341
+ .badge {
342
+ display: inline-flex;
343
+ align-items: center;
344
+ gap: 8px;
345
+ border-radius: 999px;
346
+ border: 1px solid ${badgeColor};
347
+ color: ${badgeColor};
348
+ padding: 6px 10px;
349
+ font-size: 12px;
350
+ font-weight: 700;
351
+ letter-spacing: 0.02em;
352
+ }
353
+ .badge span {
354
+ display: inline-grid;
355
+ place-items: center;
356
+ width: 16px;
357
+ height: 16px;
358
+ border-radius: 999px;
359
+ background: ${badgeColor};
360
+ color: #ffffff;
361
+ font-size: 12px;
362
+ line-height: 1;
363
+ }
364
+ h1 {
365
+ margin: 14px 0 10px;
366
+ font-size: 24px;
367
+ line-height: 1.2;
368
+ }
369
+ p {
370
+ margin: 0;
371
+ color: #cbd5e1;
372
+ line-height: 1.5;
373
+ }
374
+ .hint {
375
+ margin-top: 16px;
376
+ border-top: 1px solid rgba(148, 163, 184, 0.2);
377
+ padding-top: 14px;
378
+ font-size: 14px;
379
+ color: #94a3b8;
380
+ }
381
+ .details {
382
+ margin: 14px 0 0;
383
+ padding: 12px;
384
+ border-radius: 10px;
385
+ border: 1px solid rgba(148, 163, 184, 0.26);
386
+ background: rgba(2, 6, 23, 0.55);
387
+ color: #dbeafe;
388
+ white-space: pre-wrap;
389
+ word-break: break-word;
390
+ font-size: 12px;
391
+ line-height: 1.45;
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+ <main class="card">
397
+ <div class="badge"><span>${symbol}</span>${badgeText}</div>
398
+ <h1>${escapeHtml(title)}</h1>
399
+ <p>${escapeHtml(message)}</p>
400
+ ${detailsHtml}
401
+ <p class="hint">You can close this tab and return to Claude.</p>
402
+ </main>
403
+ </body>
404
+ </html>`;
405
+ }
406
+
285
407
  async function waitForOAuthCode(redirectUri, expectedState) {
286
408
  const parsed = new URL(redirectUri);
287
409
  if (parsed.protocol !== "http:") {
@@ -312,21 +434,43 @@ async function waitForOAuthCode(redirectUri, expectedState) {
312
434
  if (oauthError) {
313
435
  clearTimeout(timer);
314
436
  res.statusCode = 400;
315
- res.end("OAuth failed. You can close this window.");
437
+ res.setHeader("content-type", "text/html; charset=utf-8");
438
+ res.end(
439
+ renderOAuthCallbackPage({
440
+ status: "error",
441
+ title: "Flo OAuth failed",
442
+ message: "The identity provider returned an error.",
443
+ details: `error=${oauthError}`,
444
+ })
445
+ );
316
446
  reject(new Error(`OAuth provider returned error=${oauthError}`));
317
447
  return;
318
448
  }
319
449
  if (!returnedCode) {
320
450
  clearTimeout(timer);
321
451
  res.statusCode = 400;
322
- res.end("Missing code. You can close this window.");
452
+ res.setHeader("content-type", "text/html; charset=utf-8");
453
+ res.end(
454
+ renderOAuthCallbackPage({
455
+ status: "error",
456
+ title: "Flo OAuth failed",
457
+ message: "The callback did not include an authorization code.",
458
+ })
459
+ );
323
460
  reject(new Error("OAuth callback missing code"));
324
461
  return;
325
462
  }
326
463
  if (returnedState !== expectedState) {
327
464
  clearTimeout(timer);
328
465
  res.statusCode = 400;
329
- res.end("Invalid state. You can close this window.");
466
+ res.setHeader("content-type", "text/html; charset=utf-8");
467
+ res.end(
468
+ renderOAuthCallbackPage({
469
+ status: "error",
470
+ title: "Flo OAuth failed",
471
+ message: "State mismatch detected. Please retry login.",
472
+ })
473
+ );
330
474
  reject(new Error("OAuth state mismatch"));
331
475
  return;
332
476
  }
@@ -335,7 +479,12 @@ async function waitForOAuthCode(redirectUri, expectedState) {
335
479
  res.statusCode = 200;
336
480
  res.setHeader("content-type", "text/html; charset=utf-8");
337
481
  res.end(
338
- "<html><body><h3>Flo OAuth complete.</h3><p>You can close this tab and return to Claude.</p></body></html>"
482
+ renderOAuthCallbackPage({
483
+ status: "success",
484
+ title: "Flo OAuth complete",
485
+ message:
486
+ "Authentication succeeded and your plugin token is now linked.",
487
+ })
339
488
  );
340
489
  resolve(returnedCode);
341
490
  } catch (err) {
@@ -645,6 +794,23 @@ const tools = [
645
794
  additionalProperties: false,
646
795
  },
647
796
  },
797
+ {
798
+ name: "flo_query",
799
+ description:
800
+ "Run /flo:query against interface-agent and return filename-first candidates plus follow-up commands.",
801
+ inputSchema: {
802
+ type: "object",
803
+ properties: {
804
+ query: { type: "string", minLength: 1 },
805
+ authToken: {
806
+ type: "string",
807
+ description: "Optional bearer token override for this call only.",
808
+ },
809
+ },
810
+ required: ["query"],
811
+ additionalProperties: false,
812
+ },
813
+ },
648
814
  {
649
815
  name: "flo_skill_routing",
650
816
  description:
@@ -837,6 +1003,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
837
1003
  return asTextResult(parseMaybeJson(bodyText) || bodyText);
838
1004
  }
839
1005
 
1006
+ if (name === "flo_query") {
1007
+ const query = requireString(args.query, "query");
1008
+ const prompt = `/flo:query ${query}`;
1009
+ const bodyText = await invokeInterfaceAgent(prompt, args.authToken);
1010
+ return asTextResult(parseMaybeJson(bodyText) || bodyText);
1011
+ }
1012
+
840
1013
  if (name === "flo_skill_routing") {
841
1014
  const assetId = requireString(args.assetId, "assetId");
842
1015
  const prompt = `/flo:skill-routing ${assetId}`;
@@ -0,0 +1,238 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { execFileSync } from "node:child_process";
5
+
6
+ const SERVER_KEY = "flo-plugin";
7
+
8
+ function defaultDesktopConfigPath() {
9
+ if (process.platform === "darwin") {
10
+ return path.join(
11
+ os.homedir(),
12
+ "Library",
13
+ "Application Support",
14
+ "Claude",
15
+ "claude_desktop_config.json"
16
+ );
17
+ }
18
+ if (process.platform === "win32") {
19
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
20
+ return path.join(appData, "Claude", "claude_desktop_config.json");
21
+ }
22
+ return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
23
+ }
24
+
25
+ async function readJson(filePath) {
26
+ try {
27
+ const raw = await readFile(filePath, "utf8");
28
+ return JSON.parse(raw);
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+
34
+ function normalizedPluginEnv() {
35
+ const raw = (process.env.FLO_PLUGIN_ENV || "dev").trim().toLowerCase();
36
+ if (raw === "prod" || raw === "production") {
37
+ return "prod";
38
+ }
39
+ if (raw === "stg" || raw === "stage" || raw === "staging") {
40
+ return "stg";
41
+ }
42
+ return "dev";
43
+ }
44
+
45
+ function defaultInvocationUrlForEnv() {
46
+ const env = normalizedPluginEnv();
47
+ if (env === "prod") {
48
+ return "https://plugin.floapp.co/invocations";
49
+ }
50
+ if (env === "stg") {
51
+ return "https://plugin.stg.floapp.co/invocations";
52
+ }
53
+ return "https://plugin.dev.floapp.co/invocations";
54
+ }
55
+
56
+ function defaultUserPoolNameForEnv() {
57
+ const env = normalizedPluginEnv();
58
+ if (env === "prod") {
59
+ return "flo-prod";
60
+ }
61
+ if (env === "stg") {
62
+ return "flo-stg";
63
+ }
64
+ return "flo-dev";
65
+ }
66
+
67
+ function defaultPluginClientNameForEnv() {
68
+ const env = normalizedPluginEnv();
69
+ if (env === "prod") {
70
+ return "flo-prod-claude-plugin";
71
+ }
72
+ if (env === "stg") {
73
+ return "flo-stg-claude-plugin";
74
+ }
75
+ return "flo-dev-claude-plugin";
76
+ }
77
+
78
+ function defaultSettingsUrlForEnv() {
79
+ const env = normalizedPluginEnv();
80
+ if (env === "prod") {
81
+ return "https://floapp.co/settings/api";
82
+ }
83
+ if (env === "stg") {
84
+ return "https://stg.floapp.co/settings/api";
85
+ }
86
+ return "https://dev.floapp.co/settings/api";
87
+ }
88
+
89
+ function runAwsJson(args) {
90
+ const profile = (process.env.AWS_PROFILE || "").trim();
91
+ const cmdArgs = [...args];
92
+ if (profile) {
93
+ cmdArgs.push("--profile", profile);
94
+ }
95
+ const stdout = execFileSync("aws", cmdArgs, {
96
+ encoding: "utf8",
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+ return JSON.parse(stdout);
100
+ }
101
+
102
+ function discoverOauthClient() {
103
+ const explicit = (process.env.FLO_OAUTH_CLIENT_ID || "").trim();
104
+ const region = (process.env.AWS_REGION || "us-east-1").trim();
105
+ const userPoolName = (
106
+ process.env.FLO_COGNITO_USER_POOL_NAME || defaultUserPoolNameForEnv()
107
+ ).trim();
108
+ const preferredClientName = (
109
+ process.env.FLO_COGNITO_PLUGIN_CLIENT_NAME || defaultPluginClientNameForEnv()
110
+ ).trim();
111
+ const fallbackClientName = (
112
+ process.env.FLO_COGNITO_FALLBACK_CLIENT_NAME || `${userPoolName}-spa`
113
+ ).trim();
114
+
115
+ if (explicit) {
116
+ return {
117
+ clientId: explicit,
118
+ clientName: "(manual)",
119
+ userPoolId: (process.env.FLO_OAUTH_USER_POOL_ID || "").trim(),
120
+ userPoolName,
121
+ region,
122
+ settingsUrl:
123
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
124
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
125
+ defaultSettingsUrlForEnv(),
126
+ };
127
+ }
128
+
129
+ const pools = runAwsJson([
130
+ "cognito-idp",
131
+ "list-user-pools",
132
+ "--region",
133
+ region,
134
+ "--max-results",
135
+ "60",
136
+ "--output",
137
+ "json",
138
+ ]);
139
+ const userPool = (pools.UserPools || []).find((p) => p?.Name === userPoolName);
140
+ if (!userPool?.Id) {
141
+ throw new Error(
142
+ `Could not find Cognito user pool "${userPoolName}". Set FLO_OAUTH_CLIENT_ID manually.`
143
+ );
144
+ }
145
+
146
+ const clients = runAwsJson([
147
+ "cognito-idp",
148
+ "list-user-pool-clients",
149
+ "--region",
150
+ region,
151
+ "--user-pool-id",
152
+ userPool.Id,
153
+ "--max-results",
154
+ "60",
155
+ "--output",
156
+ "json",
157
+ ]);
158
+ const list = clients.UserPoolClients || [];
159
+ const preferred = list.find((c) => c?.ClientName === preferredClientName);
160
+ const fallback = list.find((c) => c?.ClientName === fallbackClientName);
161
+ const picked = preferred || fallback;
162
+ if (!picked?.ClientId) {
163
+ throw new Error(
164
+ `Could not find Cognito app client "${preferredClientName}" (or "${fallbackClientName}") in pool "${userPoolName}". Set FLO_OAUTH_CLIENT_ID manually.`
165
+ );
166
+ }
167
+ return {
168
+ clientId: picked.ClientId,
169
+ clientName: picked.ClientName || preferredClientName,
170
+ userPoolId: userPool.Id,
171
+ userPoolName,
172
+ region,
173
+ settingsUrl:
174
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
175
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
176
+ defaultSettingsUrlForEnv(),
177
+ };
178
+ }
179
+
180
+ function resolveInvocationUrl() {
181
+ if ((process.env.FLO_INTERFACE_AGENT_INVOCATION_URL || "").trim()) {
182
+ return process.env.FLO_INTERFACE_AGENT_INVOCATION_URL.trim();
183
+ }
184
+ return defaultInvocationUrlForEnv();
185
+ }
186
+
187
+ async function resolveServerConfig() {
188
+ const rootDir = path.resolve(process.cwd());
189
+ const serverPath = path.join(rootDir, "packages", "flo-claude-plugin-mcp", "src", "index.js");
190
+
191
+ const invocationUrl = resolveInvocationUrl();
192
+ const authorizeUrl =
193
+ process.env.FLO_OAUTH_AUTHORIZE_URL ||
194
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/authorize";
195
+ const tokenUrl =
196
+ process.env.FLO_OAUTH_TOKEN_URL ||
197
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/token";
198
+ const oauthClient = discoverOauthClient();
199
+
200
+ return {
201
+ command: "node",
202
+ args: [serverPath],
203
+ env: {
204
+ FLO_INTERFACE_AGENT_INVOCATION_URL: invocationUrl,
205
+ FLO_PLUGIN_ENV: normalizedPluginEnv(),
206
+ FLO_OAUTH_AUTHORIZE_URL: authorizeUrl,
207
+ FLO_OAUTH_TOKEN_URL: tokenUrl,
208
+ FLO_OAUTH_CLIENT_ID: oauthClient.clientId,
209
+ FLO_OAUTH_USER_POOL_ID: oauthClient.userPoolId,
210
+ FLO_OAUTH_USER_POOL_NAME: oauthClient.userPoolName,
211
+ FLO_OAUTH_EXPECTED_CLIENT_NAME: oauthClient.clientName,
212
+ FLO_OAUTH_SETTINGS_URL: oauthClient.settingsUrl,
213
+ FLO_OAUTH_REDIRECT_URI:
214
+ process.env.FLO_OAUTH_REDIRECT_URI || "http://127.0.0.1:8787/callback",
215
+ FLO_OAUTH_SCOPES: process.env.FLO_OAUTH_SCOPES || "openid email profile",
216
+ },
217
+ };
218
+ }
219
+
220
+ async function main() {
221
+ const configPath = process.env.CLAUDE_DESKTOP_CONFIG_PATH || defaultDesktopConfigPath();
222
+ const config = await readJson(configPath);
223
+ config.mcpServers = config.mcpServers || {};
224
+ config.mcpServers[SERVER_KEY] = await resolveServerConfig();
225
+
226
+ await mkdir(path.dirname(configPath), { recursive: true });
227
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
228
+
229
+ console.log(`Installed "${SERVER_KEY}" into ${configPath}`);
230
+ const env = config.mcpServers[SERVER_KEY].env;
231
+ console.log(`OAuth client ID: ${env.FLO_OAUTH_CLIENT_ID}`);
232
+ console.log(`OAuth settings URL: ${env.FLO_OAUTH_SETTINGS_URL}`);
233
+ }
234
+
235
+ main().catch((err) => {
236
+ console.error(err instanceof Error ? err.message : String(err));
237
+ process.exitCode = 1;
238
+ });
@@ -0,0 +1,186 @@
1
+ import { execFileSync } from "node:child_process";
2
+
3
+ function normalizedPluginEnv() {
4
+ const raw = (process.env.FLO_PLUGIN_ENV || "dev").trim().toLowerCase();
5
+ if (raw === "prod" || raw === "production") {
6
+ return "prod";
7
+ }
8
+ if (raw === "stg" || raw === "stage" || raw === "staging") {
9
+ return "stg";
10
+ }
11
+ return "dev";
12
+ }
13
+
14
+ function defaultInvocationUrlForEnv() {
15
+ const env = normalizedPluginEnv();
16
+ if (env === "prod") {
17
+ return "https://plugin.floapp.co/invocations";
18
+ }
19
+ if (env === "stg") {
20
+ return "https://plugin.stg.floapp.co/invocations";
21
+ }
22
+ return "https://plugin.dev.floapp.co/invocations";
23
+ }
24
+
25
+ function defaultUserPoolNameForEnv() {
26
+ const env = normalizedPluginEnv();
27
+ if (env === "prod") {
28
+ return "flo-prod";
29
+ }
30
+ if (env === "stg") {
31
+ return "flo-stg";
32
+ }
33
+ return "flo-dev";
34
+ }
35
+
36
+ function defaultPluginClientNameForEnv() {
37
+ const env = normalizedPluginEnv();
38
+ if (env === "prod") {
39
+ return "flo-prod-claude-plugin";
40
+ }
41
+ if (env === "stg") {
42
+ return "flo-stg-claude-plugin";
43
+ }
44
+ return "flo-dev-claude-plugin";
45
+ }
46
+
47
+ function defaultSettingsUrlForEnv() {
48
+ const env = normalizedPluginEnv();
49
+ if (env === "prod") {
50
+ return "https://floapp.co/settings/api";
51
+ }
52
+ if (env === "stg") {
53
+ return "https://stg.floapp.co/settings/api";
54
+ }
55
+ return "https://dev.floapp.co/settings/api";
56
+ }
57
+
58
+ function runAwsJson(args) {
59
+ const profile = (process.env.AWS_PROFILE || "").trim();
60
+ const cmdArgs = [...args];
61
+ if (profile) {
62
+ cmdArgs.push("--profile", profile);
63
+ }
64
+ const stdout = execFileSync("aws", cmdArgs, {
65
+ encoding: "utf8",
66
+ stdio: ["ignore", "pipe", "pipe"],
67
+ });
68
+ return JSON.parse(stdout);
69
+ }
70
+
71
+ function discoverOauthClient() {
72
+ const explicit = (process.env.FLO_OAUTH_CLIENT_ID || "").trim();
73
+ const region = (process.env.AWS_REGION || "us-east-1").trim();
74
+ const userPoolName = (
75
+ process.env.FLO_COGNITO_USER_POOL_NAME || defaultUserPoolNameForEnv()
76
+ ).trim();
77
+ const preferredClientName = (
78
+ process.env.FLO_COGNITO_PLUGIN_CLIENT_NAME || defaultPluginClientNameForEnv()
79
+ ).trim();
80
+ const fallbackClientName = (
81
+ process.env.FLO_COGNITO_FALLBACK_CLIENT_NAME || `${userPoolName}-spa`
82
+ ).trim();
83
+ if (explicit) {
84
+ return {
85
+ clientId: explicit,
86
+ clientName: "(manual)",
87
+ userPoolId: (process.env.FLO_OAUTH_USER_POOL_ID || "").trim(),
88
+ userPoolName,
89
+ region,
90
+ settingsUrl:
91
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
92
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
93
+ defaultSettingsUrlForEnv(),
94
+ };
95
+ }
96
+
97
+ const pools = runAwsJson([
98
+ "cognito-idp",
99
+ "list-user-pools",
100
+ "--region",
101
+ region,
102
+ "--max-results",
103
+ "60",
104
+ "--output",
105
+ "json",
106
+ ]);
107
+ const userPool = (pools.UserPools || []).find((p) => p?.Name === userPoolName);
108
+ if (!userPool?.Id) {
109
+ throw new Error(`Unable to find user pool "${userPoolName}"`);
110
+ }
111
+
112
+ const clients = runAwsJson([
113
+ "cognito-idp",
114
+ "list-user-pool-clients",
115
+ "--region",
116
+ region,
117
+ "--user-pool-id",
118
+ userPool.Id,
119
+ "--max-results",
120
+ "60",
121
+ "--output",
122
+ "json",
123
+ ]);
124
+ const list = clients.UserPoolClients || [];
125
+ const preferred = list.find((c) => c?.ClientName === preferredClientName);
126
+ const fallback = list.find((c) => c?.ClientName === fallbackClientName);
127
+ const picked = preferred || fallback;
128
+ if (!picked?.ClientId) {
129
+ throw new Error(
130
+ `Unable to find app client "${preferredClientName}" or "${fallbackClientName}"`
131
+ );
132
+ }
133
+ return {
134
+ clientId: picked.ClientId,
135
+ clientName: picked.ClientName || preferredClientName,
136
+ userPoolId: userPool.Id,
137
+ userPoolName,
138
+ region,
139
+ settingsUrl:
140
+ (process.env.FLO_OAUTH_SETTINGS_URL || "").trim() ||
141
+ (process.env.FLO_OAUTH_CLIENT_ID_HELP_URL || "").trim() ||
142
+ defaultSettingsUrlForEnv(),
143
+ };
144
+ }
145
+
146
+ function resolveInvocationUrl() {
147
+ if ((process.env.FLO_INTERFACE_AGENT_INVOCATION_URL || "").trim()) {
148
+ return process.env.FLO_INTERFACE_AGENT_INVOCATION_URL.trim();
149
+ }
150
+ return defaultInvocationUrlForEnv();
151
+ }
152
+
153
+ async function main() {
154
+ const invocationUrl = resolveInvocationUrl();
155
+ const authorizeUrl =
156
+ process.env.FLO_OAUTH_AUTHORIZE_URL ||
157
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/authorize";
158
+ const tokenUrl =
159
+ process.env.FLO_OAUTH_TOKEN_URL ||
160
+ "https://flomenco-dev.auth.us-east-1.amazoncognito.com/oauth2/token";
161
+ const oauthClient = discoverOauthClient();
162
+ const redirectUri =
163
+ process.env.FLO_OAUTH_REDIRECT_URI || "http://127.0.0.1:8787/callback";
164
+ const scopes = process.env.FLO_OAUTH_SCOPES || "openid email profile";
165
+
166
+ const json = {
167
+ invocationUrl,
168
+ oauthAuthorizeUrl: authorizeUrl,
169
+ oauthTokenUrl: tokenUrl,
170
+ oauthClientId: oauthClient.clientId,
171
+ oauthClientName: oauthClient.clientName,
172
+ oauthUserPoolId: oauthClient.userPoolId,
173
+ oauthUserPoolName: oauthClient.userPoolName,
174
+ oauthSettingsUrl: oauthClient.settingsUrl,
175
+ oauthRedirectUri: redirectUri,
176
+ oauthScopes: scopes,
177
+ };
178
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
179
+ }
180
+
181
+ try {
182
+ await main();
183
+ } catch (err) {
184
+ console.error(err instanceof Error ? err.message : String(err));
185
+ process.exitCode = 1;
186
+ }
@@ -0,0 +1,48 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+
5
+ const SERVER_KEY = "flo-plugin";
6
+
7
+ function defaultDesktopConfigPath() {
8
+ if (process.platform === "darwin") {
9
+ return path.join(
10
+ os.homedir(),
11
+ "Library",
12
+ "Application Support",
13
+ "Claude",
14
+ "claude_desktop_config.json"
15
+ );
16
+ }
17
+ if (process.platform === "win32") {
18
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
19
+ return path.join(appData, "Claude", "claude_desktop_config.json");
20
+ }
21
+ return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
22
+ }
23
+
24
+ async function main() {
25
+ const configPath = process.env.CLAUDE_DESKTOP_CONFIG_PATH || defaultDesktopConfigPath();
26
+ let raw;
27
+ try {
28
+ raw = await readFile(configPath, "utf8");
29
+ } catch {
30
+ console.log(`No Claude config found at ${configPath}; nothing to uninstall.`);
31
+ return;
32
+ }
33
+
34
+ const config = JSON.parse(raw);
35
+ if (!config.mcpServers || !config.mcpServers[SERVER_KEY]) {
36
+ console.log(`"${SERVER_KEY}" not present in ${configPath}; nothing to uninstall.`);
37
+ return;
38
+ }
39
+
40
+ delete config.mcpServers[SERVER_KEY];
41
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
42
+ console.log(`Removed "${SERVER_KEY}" from ${configPath}`);
43
+ }
44
+
45
+ main().catch((err) => {
46
+ console.error(err instanceof Error ? err.message : String(err));
47
+ process.exitCode = 1;
48
+ });