@flomenco/claude-plugin-mcp 0.1.1 → 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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/package.json +1 -1
  3. package/src/index.js +176 -5
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.1",
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",
package/src/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
4
  import {
@@ -284,6 +283,127 @@ function openBrowser(url) {
284
283
  }
285
284
  }
286
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
+
287
407
  async function waitForOAuthCode(redirectUri, expectedState) {
288
408
  const parsed = new URL(redirectUri);
289
409
  if (parsed.protocol !== "http:") {
@@ -314,21 +434,43 @@ async function waitForOAuthCode(redirectUri, expectedState) {
314
434
  if (oauthError) {
315
435
  clearTimeout(timer);
316
436
  res.statusCode = 400;
317
- 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
+ );
318
446
  reject(new Error(`OAuth provider returned error=${oauthError}`));
319
447
  return;
320
448
  }
321
449
  if (!returnedCode) {
322
450
  clearTimeout(timer);
323
451
  res.statusCode = 400;
324
- 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
+ );
325
460
  reject(new Error("OAuth callback missing code"));
326
461
  return;
327
462
  }
328
463
  if (returnedState !== expectedState) {
329
464
  clearTimeout(timer);
330
465
  res.statusCode = 400;
331
- 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
+ );
332
474
  reject(new Error("OAuth state mismatch"));
333
475
  return;
334
476
  }
@@ -337,7 +479,12 @@ async function waitForOAuthCode(redirectUri, expectedState) {
337
479
  res.statusCode = 200;
338
480
  res.setHeader("content-type", "text/html; charset=utf-8");
339
481
  res.end(
340
- "<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
+ })
341
488
  );
342
489
  resolve(returnedCode);
343
490
  } catch (err) {
@@ -647,6 +794,23 @@ const tools = [
647
794
  additionalProperties: false,
648
795
  },
649
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
+ },
650
814
  {
651
815
  name: "flo_skill_routing",
652
816
  description:
@@ -839,6 +1003,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
839
1003
  return asTextResult(parseMaybeJson(bodyText) || bodyText);
840
1004
  }
841
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
+
842
1013
  if (name === "flo_skill_routing") {
843
1014
  const assetId = requireString(args.assetId, "assetId");
844
1015
  const prompt = `/flo:skill-routing ${assetId}`;