@pipeshub-ai/mcp 2.3.0 → 2.3.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 (54) hide show
  1. package/README.md +1 -1
  2. package/bin/mcp-server.js +56 -15
  3. package/bin/mcp-server.js.map +7 -7
  4. package/bin/pipeshub.js +185 -24
  5. package/bin/pipeshub.js.map +7 -7
  6. package/esm/cli/client.d.ts.map +1 -1
  7. package/esm/cli/client.js +6 -1
  8. package/esm/cli/client.js.map +1 -1
  9. package/esm/cli/commands.d.ts.map +1 -1
  10. package/esm/cli/commands.js +22 -16
  11. package/esm/cli/commands.js.map +1 -1
  12. package/esm/cli/config.d.ts +2 -2
  13. package/esm/cli/config.js +2 -2
  14. package/esm/cli/init-qm.d.ts +51 -1
  15. package/esm/cli/init-qm.d.ts.map +1 -1
  16. package/esm/cli/init-qm.js +209 -16
  17. package/esm/cli/init-qm.js.map +1 -1
  18. package/esm/cli/pipeshub.js +4 -3
  19. package/esm/cli/pipeshub.js.map +1 -1
  20. package/esm/mcp-server/tools/_helpers.d.ts +20 -3
  21. package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
  22. package/esm/mcp-server/tools/_helpers.js +41 -4
  23. package/esm/mcp-server/tools/_helpers.js.map +1 -1
  24. package/esm/mcp-server/tools/pipeshubDirectory.d.ts.map +1 -1
  25. package/esm/mcp-server/tools/pipeshubDirectory.js +62 -8
  26. package/esm/mcp-server/tools/pipeshubDirectory.js.map +1 -1
  27. package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts +1 -1
  28. package/esm/mcp-server/tools/pipeshubSearch.js +1 -1
  29. package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
  30. package/esm/mcp-server/tools/pipeshubSources.js +2 -2
  31. package/esm/mcp-server/tools/pipeshubSources.js.map +1 -1
  32. package/esm/models/availablemodelsresponse.d.ts +1 -1
  33. package/esm/models/conversation.d.ts +1 -1
  34. package/esm/models/userteamsresponse.d.ts +1 -1
  35. package/esm/tool-names.js +1 -1
  36. package/esm/tool-names.js.map +1 -1
  37. package/package.json +1 -1
  38. package/qm/README.md +84 -71
  39. package/qm/SECURITY.md +7 -5
  40. package/qm/TROUBLESHOOTING.md +34 -29
  41. package/qm/qm.config.fragment.jsonc +12 -33
  42. package/qm/sandbox/Dockerfile +1 -1
  43. package/qm/sandbox/skills/pipeshub/SKILL.md +27 -7
  44. package/qm/sandbox/tools/pipeshub/tool.json +1 -1
  45. package/src/cli/client.ts +8 -1
  46. package/src/cli/commands.ts +22 -16
  47. package/src/cli/config.ts +2 -2
  48. package/src/cli/init-qm.ts +206 -17
  49. package/src/cli/pipeshub.ts +4 -3
  50. package/src/mcp-server/tools/_helpers.ts +42 -2
  51. package/src/mcp-server/tools/pipeshubDirectory.ts +65 -7
  52. package/src/mcp-server/tools/pipeshubSearch.ts +1 -1
  53. package/src/mcp-server/tools/pipeshubSources.ts +2 -2
  54. package/src/tool-names.ts +1 -1
package/bin/pipeshub.js CHANGED
@@ -166,7 +166,7 @@ function toolErrorToExit(message) {
166
166
  const m = message.match(/\(HTTP\s+(\d{3})/i);
167
167
  if (m && m[1] !== undefined)
168
168
  return statusToExit(Number(m[1]));
169
- if (/\b401\b|unautheni?ticated|no token provided/i.test(message)) {
169
+ if (/\b401\b|unautheni?ticated|no token provided|token expired|been revoked/i.test(message)) {
170
170
  return EXIT.UNAUTHENTICATED;
171
171
  }
172
172
  if (/\b403\b|not have permission|forbidden/i.test(message)) {
@@ -366,6 +366,116 @@ RUN npm install -g "@pipeshub-ai/mcp@${version}" \\
366
366
  && pipeshub --help >/dev/null
367
367
  # ----------------------------------------------------------------------------
368
368
  `;
369
+ function stripJsonComments(src) {
370
+ let out = "";
371
+ let inString = false;
372
+ let inLine = false;
373
+ let inBlock = false;
374
+ for (let i = 0;i < src.length; i++) {
375
+ const c = src[i];
376
+ const next = src[i + 1];
377
+ if (inLine) {
378
+ if (c === `
379
+ `) {
380
+ inLine = false;
381
+ out += c;
382
+ }
383
+ continue;
384
+ }
385
+ if (inBlock) {
386
+ if (c === "*" && next === "/") {
387
+ inBlock = false;
388
+ i++;
389
+ }
390
+ continue;
391
+ }
392
+ if (inString) {
393
+ out += c;
394
+ if (c === "\\") {
395
+ out += next ?? "";
396
+ i++;
397
+ continue;
398
+ }
399
+ if (c === '"')
400
+ inString = false;
401
+ continue;
402
+ }
403
+ if (c === '"') {
404
+ inString = true;
405
+ out += c;
406
+ continue;
407
+ }
408
+ if (c === "/" && next === "/") {
409
+ inLine = true;
410
+ i++;
411
+ continue;
412
+ }
413
+ if (c === "/" && next === "*") {
414
+ inBlock = true;
415
+ i++;
416
+ continue;
417
+ }
418
+ out += c;
419
+ }
420
+ return out;
421
+ }
422
+ function dropTrailingCommas(src) {
423
+ let out = "";
424
+ let inString = false;
425
+ for (let i = 0;i < src.length; i++) {
426
+ const c = src[i];
427
+ if (inString) {
428
+ out += c;
429
+ if (c === "\\") {
430
+ out += src[i + 1] ?? "";
431
+ i++;
432
+ continue;
433
+ }
434
+ if (c === '"')
435
+ inString = false;
436
+ continue;
437
+ }
438
+ if (c === '"') {
439
+ inString = true;
440
+ out += c;
441
+ continue;
442
+ }
443
+ if (c === ",") {
444
+ let j = i + 1;
445
+ while (j < src.length && /\s/.test(src[j]))
446
+ j++;
447
+ if (src[j] === "}" || src[j] === "]")
448
+ continue;
449
+ }
450
+ out += c;
451
+ }
452
+ return out;
453
+ }
454
+ async function readDeploymentShape(dest) {
455
+ try {
456
+ const raw = await readFile(join(dest, "qm.config.jsonc"), "utf8");
457
+ const cfg = JSON.parse(dropTrailingCommas(stripJsonComments(raw)));
458
+ const target = typeof cfg.target === "string" ? cfg.target : undefined;
459
+ const backend = typeof cfg.sandbox?.backend === "string" ? cfg.sandbox.backend : undefined;
460
+ if (!target && !backend)
461
+ return null;
462
+ return { target, backend };
463
+ } catch {
464
+ return null;
465
+ }
466
+ }
467
+ function imageSkipReason(shape) {
468
+ if (!shape)
469
+ return null;
470
+ if (shape.backend === "aws")
471
+ return "aws-microvm";
472
+ if (shape.backend === "sprites" || shape.target === "fly") {
473
+ return "sprites-ignores-image";
474
+ }
475
+ if (shape.target === "aws")
476
+ return "sprites-ignores-image";
477
+ return null;
478
+ }
369
479
  async function initQm(targetDir, force) {
370
480
  const root = await packageRoot();
371
481
  const bundle = join(root, "qm");
@@ -374,6 +484,7 @@ async function initQm(targetDir, force) {
374
484
  }
375
485
  const version = await packageVersion(root);
376
486
  const dest = resolve(targetDir);
487
+ const shape = await readDeploymentShape(dest);
377
488
  const written = [];
378
489
  const skipped = [];
379
490
  for (const sub of ["tools/pipeshub", "skills/pipeshub"]) {
@@ -381,7 +492,12 @@ async function initQm(targetDir, force) {
381
492
  }
382
493
  const dockerfile = join(dest, "sandbox", "Dockerfile");
383
494
  let dockerfileAction;
384
- if (!await exists(dockerfile)) {
495
+ const skipReason = imageSkipReason(shape);
496
+ let staleDockerfile = false;
497
+ if (skipReason !== null) {
498
+ staleDockerfile = await exists(dockerfile);
499
+ dockerfileAction = "skipped-unusable";
500
+ } else if (!await exists(dockerfile)) {
385
501
  await copyFile(join(bundle, "sandbox", "Dockerfile"), dockerfile);
386
502
  const body = await readFile(dockerfile, "utf8");
387
503
  await writeFile(dockerfile, body.replace(/ARG PIPESHUB_CLI_VERSION=.*/, `ARG PIPESHUB_CLI_VERSION=${version}`), "utf8");
@@ -404,7 +520,16 @@ async function initQm(targetDir, force) {
404
520
  } catch (e) {
405
521
  throw new CliError(`the QM bundle is incomplete — could not read ${fragmentPath} ` + `(${e.message}). Reinstall @pipeshub-ai/mcp.`);
406
522
  }
407
- return { written, skipped, dockerfileAction, version, configFragment };
523
+ return {
524
+ written,
525
+ skipped,
526
+ dockerfileAction,
527
+ version,
528
+ configFragment,
529
+ shape,
530
+ skipReason,
531
+ staleDockerfile
532
+ };
408
533
  }
409
534
  function renderInitReport(dest, r) {
410
535
  const lines = [];
@@ -416,6 +541,34 @@ function renderInitReport(dest, r) {
416
541
  for (const f of r.skipped)
417
542
  lines.push(` kept ${f} (already existed — use --force to replace)`);
418
543
  lines.push("");
544
+ if (r.dockerfileAction === "skipped-unusable") {
545
+ if (r.skipReason === "aws-microvm") {
546
+ lines.push("No sandbox/Dockerfile was written: AWS Lambda MicroVM sandboxes");
547
+ lines.push("have no way to install a binary, so the file could never run.");
548
+ } else {
549
+ lines.push("No sandbox/Dockerfile was written: Fly Sprites boot the stock");
550
+ lines.push("image and ignore a published one, so the file would look like the");
551
+ lines.push("install path while never running.");
552
+ }
553
+ lines.push("The skill installs the CLI on first use instead — that is the line");
554
+ lines.push("that actually executes, and it needs nothing from you.");
555
+ lines.push("");
556
+ }
557
+ if (r.staleDockerfile) {
558
+ lines.push("ACTION NEEDED: sandbox/Dockerfile already exists here, written by an");
559
+ lines.push("earlier version. This deployment cannot use it, and upcoming QM");
560
+ lines.push("validation rejects it rather than ignoring it — `qm check` will fail");
561
+ lines.push("with an error naming that file. Delete it, or remove the PipesHub");
562
+ lines.push("install block if the rest of it is yours.");
563
+ lines.push("");
564
+ }
565
+ if (r.skipReason === "aws-microvm") {
566
+ lines.push("Heads up on AWS: with Lambda MicroVM sandboxes the CLI cannot be");
567
+ lines.push("installed at all (yc-software/qm#350). The tool's guidance, network");
568
+ lines.push("allowlist, and approval rules still apply, but the binary will be");
569
+ lines.push("missing. The sprites backend is what this bundle is tested against.");
570
+ lines.push("");
571
+ }
419
572
  if (r.dockerfileAction === "appended") {
420
573
  lines.push("Appended the install block to your existing sandbox/Dockerfile.");
421
574
  } else if (r.dockerfileAction === "manual") {
@@ -424,20 +577,22 @@ function renderInitReport(dest, r) {
424
577
  lines.push("");
425
578
  lines.push("Two things left to do:");
426
579
  lines.push("");
427
- lines.push("1. Set your PipesHub origin in qm.config.jsonc. It must be a PUBLIC");
428
- lines.push(" HTTPS address QM sandboxes do not run on your machine, so");
429
- lines.push(" localhost and LAN addresses are unreachable from them:");
580
+ lines.push("1. Set `egress` in sandbox/tools/pipeshub/tool.json to your PipesHub");
581
+ lines.push(" hostname (no scheme, no path). It must be reachable over public HTTPS");
582
+ lines.push(" QM sandboxes do not run on your machine, so localhost is unreachable.");
430
583
  lines.push("");
431
- lines.push(' "sandbox": {');
432
- lines.push(' "env": { "PIPESHUB_BASE_URL": "https://pipeshub.your-company.com" }');
433
- lines.push(" }");
584
+ lines.push("2. Each person adds two personal keychain entries (service: pipeshub):");
585
+ lines.push(" PIPESHUB_TOKEN → their PAT (never paste it into chat)");
586
+ lines.push(" PIPESHUB_BASE_URL → public HTTPS origin, no /mcp path");
587
+ lines.push(" Do NOT put a token in sandbox.secretEnv (org-wide). Do NOT rely on");
588
+ lines.push(" sandbox.env for the URL — it does not reach the sandbox.");
434
589
  lines.push("");
435
- lines.push(" Do NOT put anyone's token in sandbox.secretEnv — that is org-wide and");
436
- lines.push(" would hand one person's credential to everybody. Each person adds");
437
- lines.push(" their own to their own keychain (service: pipeshub, kind: env).");
590
+ lines.push("Then: qm check && qm up");
438
591
  lines.push("");
439
- lines.push("2. Set `egress` in sandbox/tools/pipeshub/tool.json to your hostname,");
440
- lines.push(" then run: qm check && qm sandbox publish && qm up");
592
+ if (r.dockerfileAction !== "skipped-unusable") {
593
+ lines.push("On Sprites, `qm sandbox publish` does not put pipeshub on PATH.");
594
+ lines.push("The skill installs the CLI on first use.");
595
+ }
441
596
  return lines.join(`
442
597
  `);
443
598
  }
@@ -508,19 +663,25 @@ function connectHelp(ctx) {
508
663
  "Add your Personal Access Token to your own QM keychain:",
509
664
  "",
510
665
  " 1. In PipesHub, open Developer Settings → Personal Access Tokens.",
511
- " 2. Create a token. Deselect every scope, then select only:",
512
- " conversation:chat semantic:write kb:read user:read connector:read",
513
- " 3. In QM, add it to YOUR keychain (not a shared room) as:",
666
+ " 2. Create a token. The panel defaults are fine — do not deselect scopes,",
667
+ " and do not add semantic:read if asked.",
668
+ " 3. In QM, add two personal keychain credentials (not a shared room):",
669
+ " service: pipeshub",
670
+ " environment variable: PIPESHUB_TOKEN",
671
+ " value: the token only — no URL, no KEY= prefix",
672
+ " and",
514
673
  " service: pipeshub",
515
- " kind: env",
516
- " value: <the token value only — no URL, no KEY= prefix>",
674
+ " environment variable: PIPESHUB_BASE_URL",
675
+ " value: public HTTPS origin, no /mcp path",
517
676
  "",
518
- "It arrives in your sandbox as $PIPESHUB_TOKEN on the next turn.",
677
+ "They arrive in your sandbox on the next turn.",
519
678
  "",
520
679
  "Never paste the token into a chat message: chat transcripts are durable",
521
680
  "and pass through the model provider. The keychain exists to avoid that.",
522
681
  "",
523
- `The base URL is set by your admin and is currently: ${ctx.origin || "(unset)"}`
682
+ `The base URL currently visible here is: ${ctx.origin || "(unset)"}`,
683
+ "If it is unset, sandbox.env did not reach this sandbox — use the",
684
+ "PIPESHUB_BASE_URL keychain entry (or an org service credential)."
524
685
  ].join(`
525
686
  `);
526
687
  return { exit: EXIT.OK, payload: { requestId: ctx.requestId, help: text }, text };
@@ -613,7 +774,7 @@ async function ask(ctx, query, conversationId, chatMode) {
613
774
  citationCount: citations.length,
614
775
  citations,
615
776
  confidence: obj["confidence"] ?? null,
616
- warning: citations.length === 0 ? "No citations. Treat this answer as ungrounded it is not supported " + "by any retrieved document." : null
777
+ warning: citations.length === 0 ? "No citations — unsourced, so nothing in it can be verified. If " + "this is a refusal, relay it. If it asserts facts, do not repeat " + "them. Ignore confidence." : null
617
778
  }
618
779
  };
619
780
  }
@@ -876,7 +1037,7 @@ async function run(argv) {
876
1037
  }
877
1038
  if (origin === null) {
878
1039
  const alsoNoToken = token === null ? " Your PipesHub credential is also missing ($PIPESHUB_TOKEN is unset)." : "";
879
- throw new CliError("PIPESHUB_BASE_URL is not set — an admin sets it once for the deployment." + alsoNoToken + " Run 'pipeshub auth connect-help' for the steps.", EXIT.USAGE);
1040
+ throw new CliError("PIPESHUB_BASE_URL is not set — it must reach the sandbox as an env var " + "(keychain or org service credential, not sandbox.env)." + alsoNoToken + " Run 'pipeshub auth connect-help' for the steps.", EXIT.USAGE);
880
1041
  }
881
1042
  if (token === null) {
882
1043
  throw new CliError("No PipesHub credential found ($PIPESHUB_TOKEN is unset). " + "Run 'pipeshub auth connect-help' for setup steps.", EXIT.UNAUTHENTICATED);
@@ -942,5 +1103,5 @@ var code = await run(process.argv.slice(2)).catch(async (e) => {
942
1103
  });
943
1104
  process.exit(code);
944
1105
 
945
- //# debugId=D78484EFB8F0261E64756E2164756E21
1106
+ //# debugId=593C31ACCEC7553364756E2164756E21
946
1107
  //# sourceMappingURL=pipeshub.js.map