@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.
- package/README.md +3 -0
- package/package.json +1 -1
- 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
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("<", "<")
|
|
290
|
+
.replaceAll(">", ">")
|
|
291
|
+
.replaceAll('"', """)
|
|
292
|
+
.replaceAll("'", "'");
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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}`;
|