@pipeshub-ai/mcp 2.2.0 → 2.3.0

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 (107) hide show
  1. package/README.md +2 -0
  2. package/bin/mcp-server.js +648 -175
  3. package/bin/mcp-server.js.map +17 -11
  4. package/bin/pipeshub.js +946 -0
  5. package/bin/pipeshub.js.map +14 -0
  6. package/esm/cli/client.d.ts +54 -0
  7. package/esm/cli/client.d.ts.map +1 -0
  8. package/esm/cli/client.js +230 -0
  9. package/esm/cli/client.js.map +1 -0
  10. package/esm/cli/commands.d.ts +30 -0
  11. package/esm/cli/commands.d.ts.map +1 -0
  12. package/esm/cli/commands.js +321 -0
  13. package/esm/cli/commands.js.map +1 -0
  14. package/esm/cli/config.d.ts +73 -0
  15. package/esm/cli/config.d.ts.map +1 -0
  16. package/esm/cli/config.js +218 -0
  17. package/esm/cli/config.js.map +1 -0
  18. package/esm/cli/init-qm.d.ts +11 -0
  19. package/esm/cli/init-qm.d.ts.map +1 -0
  20. package/esm/cli/init-qm.js +170 -0
  21. package/esm/cli/init-qm.js.map +1 -0
  22. package/esm/cli/pipeshub.d.ts +2 -0
  23. package/esm/cli/pipeshub.d.ts.map +1 -0
  24. package/esm/cli/pipeshub.js +296 -0
  25. package/esm/cli/pipeshub.js.map +1 -0
  26. package/esm/funcs/connectorLookupRecordByIdentifier.d.ts +68 -0
  27. package/esm/funcs/connectorLookupRecordByIdentifier.d.ts.map +1 -0
  28. package/esm/funcs/connectorLookupRecordByIdentifier.js +138 -0
  29. package/esm/funcs/connectorLookupRecordByIdentifier.js.map +1 -0
  30. package/esm/funcs/connectorNavigateKnowledgeGraph.d.ts +75 -0
  31. package/esm/funcs/connectorNavigateKnowledgeGraph.d.ts.map +1 -0
  32. package/esm/funcs/connectorNavigateKnowledgeGraph.js +152 -0
  33. package/esm/funcs/connectorNavigateKnowledgeGraph.js.map +1 -0
  34. package/esm/lib/base64.d.ts +1 -1
  35. package/esm/lib/base64.d.ts.map +1 -1
  36. package/esm/lib/base64.js +6 -3
  37. package/esm/lib/base64.js.map +1 -1
  38. package/esm/lib/bytes-to-base64.d.ts +16 -0
  39. package/esm/lib/bytes-to-base64.d.ts.map +1 -0
  40. package/esm/lib/bytes-to-base64.js +23 -0
  41. package/esm/lib/bytes-to-base64.js.map +1 -0
  42. package/esm/mcp-server/instructions.d.ts +1 -1
  43. package/esm/mcp-server/instructions.d.ts.map +1 -1
  44. package/esm/mcp-server/instructions.js +30 -0
  45. package/esm/mcp-server/instructions.js.map +1 -1
  46. package/esm/mcp-server/shared.d.ts +2 -2
  47. package/esm/mcp-server/shared.d.ts.map +1 -1
  48. package/esm/mcp-server/shared.js +7 -3
  49. package/esm/mcp-server/shared.js.map +1 -1
  50. package/esm/mcp-server/tools/_agui.d.ts +34 -0
  51. package/esm/mcp-server/tools/_agui.d.ts.map +1 -0
  52. package/esm/mcp-server/tools/_agui.js +91 -0
  53. package/esm/mcp-server/tools/_agui.js.map +1 -0
  54. package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
  55. package/esm/mcp-server/tools/_helpers.js +13 -6
  56. package/esm/mcp-server/tools/_helpers.js.map +1 -1
  57. package/esm/mcp-server/tools/pipeshubChat.d.ts +0 -3
  58. package/esm/mcp-server/tools/pipeshubChat.d.ts.map +1 -1
  59. package/esm/mcp-server/tools/pipeshubChat.js +75 -98
  60. package/esm/mcp-server/tools/pipeshubChat.js.map +1 -1
  61. package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts +118 -1
  62. package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts.map +1 -1
  63. package/esm/mcp-server/tools/pipeshubGetRecordContent.js +180 -29
  64. package/esm/mcp-server/tools/pipeshubGetRecordContent.js.map +1 -1
  65. package/esm/mcp-server/tools/pipeshubSearch.d.ts.map +1 -1
  66. package/esm/mcp-server/tools/pipeshubSearch.js +14 -1
  67. package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
  68. package/esm/models/availablemodelsresponse.d.ts +1 -1
  69. package/esm/models/conversation.d.ts +1 -1
  70. package/esm/models/lookuprecordbyidentifierop.d.ts +7 -0
  71. package/esm/models/lookuprecordbyidentifierop.d.ts.map +1 -0
  72. package/esm/models/lookuprecordbyidentifierop.js +14 -0
  73. package/esm/models/lookuprecordbyidentifierop.js.map +1 -0
  74. package/esm/models/navigateknowledgegraphop.d.ts +14 -0
  75. package/esm/models/navigateknowledgegraphop.d.ts.map +1 -0
  76. package/esm/models/navigateknowledgegraphop.js +25 -0
  77. package/esm/models/navigateknowledgegraphop.js.map +1 -0
  78. package/esm/models/userteamsresponse.d.ts +1 -1
  79. package/esm/tool-names.js +3 -3
  80. package/esm/tool-names.js.map +1 -1
  81. package/package.json +3 -2
  82. package/qm/README.md +247 -0
  83. package/qm/SECURITY.md +134 -0
  84. package/qm/TROUBLESHOOTING.md +218 -0
  85. package/qm/qm.config.fragment.jsonc +42 -0
  86. package/qm/sandbox/Dockerfile +22 -0
  87. package/qm/sandbox/skills/pipeshub/SKILL.md +69 -0
  88. package/qm/sandbox/tools/pipeshub/tool.json +36 -0
  89. package/src/cli/client.ts +272 -0
  90. package/src/cli/commands.ts +403 -0
  91. package/src/cli/config.ts +229 -0
  92. package/src/cli/init-qm.ts +215 -0
  93. package/src/cli/pipeshub.ts +333 -0
  94. package/src/funcs/connectorLookupRecordByIdentifier.ts +200 -0
  95. package/src/funcs/connectorNavigateKnowledgeGraph.ts +215 -0
  96. package/src/lib/base64.ts +6 -3
  97. package/src/lib/bytes-to-base64.ts +24 -0
  98. package/src/mcp-server/instructions.ts +30 -0
  99. package/src/mcp-server/shared.ts +7 -3
  100. package/src/mcp-server/tools/_agui.ts +138 -0
  101. package/src/mcp-server/tools/_helpers.ts +13 -6
  102. package/src/mcp-server/tools/pipeshubChat.ts +78 -97
  103. package/src/mcp-server/tools/pipeshubGetRecordContent.ts +213 -29
  104. package/src/mcp-server/tools/pipeshubSearch.ts +14 -1
  105. package/src/models/lookuprecordbyidentifierop.ts +26 -0
  106. package/src/models/navigateknowledgegraphop.ts +56 -0
  107. package/src/tool-names.ts +3 -3
package/bin/mcp-server.js CHANGED
@@ -50456,6 +50456,36 @@ the user can verify.
50456
50456
  - \`pipeshub_agents\` — list the org's configured **agents** (specialized
50457
50457
  assistants with their own prompt, tools, and knowledge scope).
50458
50458
 
50459
+ ## Knowledge Sources
50460
+
50461
+ A source (an app connector or a KB) holds record groups (a project, space,
50462
+ drive, folder), which hold records (one ticket, page, file). Records nest —
50463
+ an epic over its stories, a page over its child pages.
50464
+
50465
+ **Every tool returns metadata or content.**
50466
+
50467
+ - \`pipeshub_search\` / \`pipeshub_chat\` — content, but only a ranked sample of
50468
+ blocks: never all blocks of a record, never every record that matches, never
50469
+ a complete list of what exists.
50470
+ - \`mode:"lookup"\` and \`mode:"navigate"\` — metadata: recordIds, fields, and
50471
+ structure. No document text. \`mode:"content"\` — one whole record.
50472
+
50473
+ **Combining them.** Search finds a starting point by wording; navigate on a
50474
+ \`recordId\` reveals what search structurally cannot — siblings, children,
50475
+ linked records, and the group's total count. For a question that must be
50476
+ exhaustive ("all", "how many", "every"), do NOT count search hits or trust a
50477
+ chat answer: navigate the record group, which reports the real total. For a
50478
+ record's siblings, navigate its parent — take the id from the \`Path\` line.
50479
+
50480
+ **A named record** — an issue key, a Confluence / Drive / Slack link, an
50481
+ external ID → \`mode:"lookup"\` FIRST. It returns that record's metadata (for a
50482
+ ticket: status, assignee, priority, dates) plus its \`recordId\`, which often
50483
+ answers the question outright. Never guess a \`recordId\`.
50484
+
50485
+ Pass a source's id from \`pipeshub_sources\` as \`apps: ["<id>"]\` to search or
50486
+ chat, and as \`nodeId\` to navigate. Navigate and lookup end in a \`Next:\` line
50487
+ naming the exact follow-up call — follow it.
50488
+
50459
50489
  ## Agents
50460
50490
 
50461
50491
  Some orgs configure **agents** for specific jobs (e.g. a Slack messenger, a
@@ -50595,10 +50625,17 @@ var init_registration = __esm(() => {
50595
50625
  init_requestid();
50596
50626
  });
50597
50627
 
50598
- // src/lib/base64.ts
50628
+ // src/lib/bytes-to-base64.ts
50599
50629
  function bytesToBase64(u8arr) {
50600
- return btoa(String.fromCodePoint(...u8arr));
50630
+ let binary = "";
50631
+ for (let i = 0;i < u8arr.length; i += BASE64_CHUNK) {
50632
+ binary += String.fromCharCode(...u8arr.subarray(i, i + BASE64_CHUNK));
50633
+ }
50634
+ return btoa(binary);
50601
50635
  }
50636
+ var BASE64_CHUNK = 32768;
50637
+
50638
+ // src/lib/base64.ts
50602
50639
  function bytesFromBase64(encoded) {
50603
50640
  return Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
50604
50641
  }
@@ -51919,9 +51956,6 @@ function createRegisterPrompt(logger, server, getSDK, allowedScopes) {
51919
51956
  }
51920
51957
 
51921
51958
  // src/mcp-server/shared.ts
51922
- function bytesToBase642(u8arr) {
51923
- return btoa(String.fromCodePoint(...u8arr));
51924
- }
51925
51959
  async function consumeStream(stream) {
51926
51960
  const reader = stream.getReader();
51927
51961
  const chunks = [];
@@ -51942,13 +51976,13 @@ async function valueToBase64(value) {
51942
51976
  if (value == null) {
51943
51977
  return null;
51944
51978
  } else if (value instanceof Uint8Array) {
51945
- return bytesToBase642(value);
51979
+ return bytesToBase64(value);
51946
51980
  } else if (value instanceof ArrayBuffer) {
51947
- return bytesToBase642(new Uint8Array(value));
51981
+ return bytesToBase64(new Uint8Array(value));
51948
51982
  } else if (value instanceof Response || value instanceof Blob) {
51949
- return bytesToBase642(new Uint8Array(await value.arrayBuffer()));
51983
+ return bytesToBase64(new Uint8Array(await value.arrayBuffer()));
51950
51984
  } else if (value instanceof ReadableStream) {
51951
- return bytesToBase642(await consumeStream(value));
51985
+ return bytesToBase64(await consumeStream(value));
51952
51986
  } else if (typeof value === "string") {
51953
51987
  return base64Schema.parse(value);
51954
51988
  } else {
@@ -52912,6 +52946,77 @@ var init_agentsStreamMessage = __esm(() => {
52912
52946
  init_async();
52913
52947
  });
52914
52948
 
52949
+ // src/mcp-server/tools/_agui.ts
52950
+ function newChatStreamState() {
52951
+ return {
52952
+ conversation: null,
52953
+ recordsUsed: undefined,
52954
+ error: null,
52955
+ conversationId: null,
52956
+ title: null,
52957
+ text: new Map,
52958
+ rootMessageIds: new Set
52959
+ };
52960
+ }
52961
+ function applyAGUIFrame(state, frame) {
52962
+ const data = frame.data;
52963
+ const d = data && typeof data === "object" ? data : {};
52964
+ switch (frame.event) {
52965
+ case AGUIEvent.RUN_FINISHED: {
52966
+ const result = d.result;
52967
+ if (!result || typeof result !== "object")
52968
+ return false;
52969
+ state.conversation = result.conversation ?? null;
52970
+ state.recordsUsed = result.recordsUsed ?? result.meta?.recordsUsed;
52971
+ return true;
52972
+ }
52973
+ case AGUIEvent.RUN_ERROR: {
52974
+ state.error = (typeof data === "string" ? data : null) ?? d.message ?? d.error ?? frame.raw ?? "Stream error";
52975
+ return true;
52976
+ }
52977
+ case AGUIEvent.CUSTOM: {
52978
+ if (d.name === "conversation_created") {
52979
+ const value = d.value ?? {};
52980
+ state.conversationId = value.conversationId ?? state.conversationId;
52981
+ state.title = value.title ?? state.title;
52982
+ }
52983
+ return false;
52984
+ }
52985
+ case AGUIEvent.TEXT_MESSAGE_CONTENT: {
52986
+ if (typeof d.delta !== "string" || d.delta.length === 0)
52987
+ return false;
52988
+ const id = typeof d.messageId === "string" ? d.messageId : "";
52989
+ state.text.set(id, (state.text.get(id) ?? "") + d.delta);
52990
+ if (d.parentRunId == null)
52991
+ state.rootMessageIds.add(id);
52992
+ return false;
52993
+ }
52994
+ default:
52995
+ return false;
52996
+ }
52997
+ }
52998
+ function salvagedText(state) {
52999
+ const pick2 = (ids) => {
53000
+ let best = null;
53001
+ for (const id of ids) {
53002
+ const text = state.text.get(id);
53003
+ if (text && (best === null || text.length > best.length))
53004
+ best = text;
53005
+ }
53006
+ return best;
53007
+ };
53008
+ return pick2(state.rootMessageIds) ?? pick2(state.text.keys());
53009
+ }
53010
+ var AGUIEvent;
53011
+ var init__agui = __esm(() => {
53012
+ AGUIEvent = {
53013
+ RUN_FINISHED: "RUN_FINISHED",
53014
+ RUN_ERROR: "RUN_ERROR",
53015
+ CUSTOM: "CUSTOM",
53016
+ TEXT_MESSAGE_CONTENT: "TEXT_MESSAGE_CONTENT"
53017
+ };
53018
+ });
53019
+
52915
53020
  // src/funcs/agentsListAgents.ts
52916
53021
  function agentsListAgents(client$, request, options) {
52917
53022
  return new APIPromise($do5(client$, request, options));
@@ -53121,15 +53226,17 @@ function trimCitations(citations) {
53121
53226
  if (!Array.isArray(citations))
53122
53227
  return [];
53123
53228
  return citations.map((c) => {
53124
- const md = c?.metadata ?? {};
53229
+ const cd = c?.citationData ?? c;
53230
+ const md = cd?.metadata ?? c?.metadata ?? {};
53231
+ const content = typeof cd?.content === "string" ? cd.content : c?.content;
53125
53232
  return {
53126
- recordId: md.recordId ?? c?.recordId,
53127
- recordName: md.recordName ?? c?.recordName,
53128
- snippet: typeof c?.content === "string" ? c.content.slice(0, 280) : md.blockText ?? null,
53233
+ recordId: md.recordId ?? cd?.recordId ?? c?.recordId,
53234
+ recordName: md.recordName ?? cd?.recordName ?? c?.recordName,
53235
+ snippet: typeof content === "string" ? content.slice(0, 280) : md.blockText ?? null,
53129
53236
  mimeType: md.mimeType,
53130
53237
  webUrl: md.webUrl,
53131
53238
  pageNum: md.pageNum,
53132
- chunkIndex: c?.chunkIndex ?? md.chunkIndex
53239
+ chunkIndex: cd?.chunkIndex ?? c?.chunkIndex ?? md.chunkIndex
53133
53240
  };
53134
53241
  });
53135
53242
  }
@@ -53302,9 +53409,10 @@ var init_pipeshubChat = __esm(() => {
53302
53409
  init_conversationsStreamMessage();
53303
53410
  init_agentsStreamConversation();
53304
53411
  init_agentsStreamMessage();
53412
+ init__agui();
53305
53413
  init__helpers();
53306
53414
  FiltersShape = object({
53307
- apps: array(string2()).optional().describe("Source-scoping ids. Mix connector instance UUIDs with the synthetic " + "`knowledgeBase_<orgId>` id (use pipeshub_sources to discover them). " + "Empty / omitted means no app-side restriction."),
53415
+ apps: array(string2()).optional().describe("Source-scoping ids from `pipeshub_sources` — connector instance and / " + "or knowledge base ids, mixed freely. The legacy org-wide " + "`knowledgeBase_<orgId>` id is still accepted on deployments that " + "predate per-KB sources. Empty / omitted means no app-side " + "restriction."),
53308
53416
  kb: array(string2()).optional().describe("Legacy / unused. Leave empty.")
53309
53417
  }).optional();
53310
53418
  args = {
@@ -53318,40 +53426,45 @@ var init_pipeshubChat = __esm(() => {
53318
53426
  chatMode: _enum([
53319
53427
  "internal_search",
53320
53428
  "web_search",
53321
- "auto",
53322
- "quick",
53323
- "verification",
53324
- "deep"
53429
+ "quick"
53325
53430
  ]).optional().describe("Response strategy. The valid values depend on whether `agentId` is set:\n" + "- WITHOUT `agentId` (plain chat): `internal_search` — answer from the " + "org's indexed knowledge (default) — or `web_search` — answer from the " + `live web.
53326
- ` + "- WITH `agentId` (agent chat): `auto` (let the agent decide; default), " + "`quick`, `verification`, or `deep`.")
53431
+ ` + "- WITH `agentId` (agent chat): `quick` is the only supported mode and " + "is sent automatically, so this argument can be omitted.")
53327
53432
  };
53328
53433
  tool$pipeshubChat = {
53329
53434
  name: "pipeshub_chat",
53330
- description: `**Primary chat tool handles both internal knowledge queries and web search.**
53331
-
53332
- **Internal search** (default, \`chatMode: "internal_search"\`): Use whenever
53333
- the user asks about their documents, files, knowledge base, company policies,
53334
- or anything that could plausibly be answered by content in their PipesHub-indexed
53335
- sources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).
53336
- Grounds the answer in the user's actual data and returns citations.
53337
- Answers come from a few retrieved passages, not whole documents — for
53338
- any task needing a document's full content, use
53339
- \`pipeshub_get_record_content\` instead.
53340
-
53341
- **Web search** (\`chatMode: "web_search"\`): Use when the user asks about
53342
- current events, public information, or anything unlikely to be in the org's
53343
- internal knowledge base. Pass \`chatMode: "web_search"\` and this tool will
53344
- search the public web instead.
53345
-
53346
- **When to pick this over other tools:**
53347
- - "Summarize <doc>" / "key points of <doc>" / "what does <document> say
53348
- about X?" → NOT this tool. Use \`pipeshub_search\` →
53349
- \`pipeshub_get_record_content\`: answering for a specific document
53350
- requires its full content, and chat only sees a few retrieved
53351
- passages, never the whole document.
53435
+ description: `Ask a question, get an answer grounded in the org's indexed data with
53436
+ citations. It reads a few retrieved passages — never a whole document,
53437
+ never a complete list.
53438
+
53439
+ **Three questions this tool gets WRONG. Check them first:**
53440
+ - **Structure** "what's under this epic?", "which pages are in this
53441
+ space?", "what links to this ticket?", "what's in this folder?"
53442
+ \`pipeshub_get_record_content\` \`mode:"navigate"\`. Ranking cannot see how
53443
+ records relate.
53444
+ - **Exhaustive** — "how many X?", "list ALL the Y", "every Z" →
53445
+ \`mode:"navigate"\`, which reports the group's real total. This tool
53446
+ undercounts and will not say so.
53447
+ - **One named document** — summarize it, extract from it, what does it say
53448
+ about X \`pipeshub_search\` for the \`recordId\`, then \`mode:"content"\`.
53449
+
53450
+ Everything else about the org's knowledge belongs here: policies,
53451
+ processes, decisions, history, "what do we know about X", and any question
53452
+ spanning several documents.
53453
+
53454
+ **Internal search** (default, \`chatMode: "internal_search"\`): the user's
53455
+ documents, files, knowledge base, company policies anything in their
53456
+ PipesHub-indexed sources (Drive, Box, Confluence, Slack, Gmail, Jira, the
53457
+ org's KB, ...).
53458
+
53459
+ **Web search** (\`chatMode: "web_search"\`): current events or public
53460
+ information unlikely to be in the org's knowledge base.
53461
+
53462
+ Both are plain-chat modes. **Agent chat** — pass an \`agentId\` from
53463
+ \`pipeshub_agents\` — runs against that agent's own prompt, tools and knowledge;
53464
+ \`quick\` is its only mode, requires the \`agentId\`, and is sent automatically.
53465
+
53352
53466
  - "What's our policy on Y?" → \`pipeshub_chat\` (internal_search)
53353
53467
  - "What's in the news about Z?" → \`pipeshub_chat\` (web_search)
53354
- - "What is the latest version of <library>?" → \`pipeshub_chat\` (web_search)
53355
53468
  - "Find / locate the file named X" → \`pipeshub_search\` (then
53356
53469
  \`pipeshub_download_record\` if the user wants the bytes).
53357
53470
 
@@ -53381,9 +53494,10 @@ cited document, take \`citations[*].recordId\` and call
53381
53494
  args,
53382
53495
  tool: async (client, args2, ctx) => {
53383
53496
  const fetchOptions = { signal: ctx.signal };
53497
+ const plainChatMode = args2.chatMode === "web_search" ? "web_search" : "internal_search";
53384
53498
  let response;
53385
53499
  if (args2.agentId) {
53386
- const agentChatMode = args2.chatMode ?? "auto";
53500
+ const agentChatMode = "quick";
53387
53501
  if (args2.conversationId) {
53388
53502
  const [result] = await agentsStreamMessage(client, {
53389
53503
  agentKey: args2.agentId,
@@ -53423,7 +53537,7 @@ cited document, take \`citations[*].recordId\` and call
53423
53537
  modelKey: args2.modelKey,
53424
53538
  modelName: args2.modelName,
53425
53539
  modelFriendlyName: args2.modelFriendlyName,
53426
- chatMode: args2.chatMode ?? "internal_search"
53540
+ chatMode: plainChatMode
53427
53541
  }
53428
53542
  }, { fetchOptions }).$inspect();
53429
53543
  if (!result.ok)
@@ -53436,7 +53550,7 @@ cited document, take \`citations[*].recordId\` and call
53436
53550
  modelKey: args2.modelKey,
53437
53551
  modelName: args2.modelName,
53438
53552
  modelFriendlyName: args2.modelFriendlyName,
53439
- chatMode: args2.chatMode ?? "internal_search"
53553
+ chatMode: plainChatMode
53440
53554
  }, { fetchOptions }).$inspect();
53441
53555
  if (!result.ok)
53442
53556
  return errorResult(result.error.message);
@@ -53445,62 +53559,36 @@ cited document, take \`citations[*].recordId\` and call
53445
53559
  const httpErr = await httpErrorResult(response, "PipesHub chat request");
53446
53560
  if (httpErr)
53447
53561
  return httpErr;
53448
- let finalConversation = null;
53449
- let recordsUsed;
53450
- let lastAccumulated = null;
53451
- let errorMessage = null;
53562
+ const state = newChatStreamState();
53452
53563
  try {
53453
53564
  for await (const frame of iterateSSE(response)) {
53454
- switch (frame.event) {
53455
- case "complete": {
53456
- const d = frame.data ?? {};
53457
- finalConversation = d.conversation ?? null;
53458
- recordsUsed = d.recordsUsed ?? d.meta?.recordsUsed;
53459
- break;
53460
- }
53461
- case "error": {
53462
- const d = frame.data ?? {};
53463
- errorMessage = typeof d === "string" ? d : d.error ?? d.message ?? frame.raw ?? "Stream error";
53464
- break;
53465
- }
53466
- case "answer_chunk": {
53467
- const d = frame.data ?? {};
53468
- if (typeof d.accumulated === "string") {
53469
- lastAccumulated = d.accumulated;
53470
- } else if (typeof d.content === "string") {
53471
- lastAccumulated = (lastAccumulated ?? "") + d.content;
53472
- }
53473
- break;
53474
- }
53475
- default:
53476
- break;
53477
- }
53478
- if (finalConversation || errorMessage)
53565
+ if (applyAGUIFrame(state, frame))
53479
53566
  break;
53480
53567
  }
53481
53568
  } catch (e) {
53482
53569
  return errorResult(`SSE stream failed: ${e.message}`);
53483
53570
  }
53484
- if (errorMessage)
53485
- return errorResult(errorMessage);
53486
- if (finalConversation) {
53571
+ if (state.error)
53572
+ return errorResult(state.error);
53573
+ if (state.conversation) {
53487
53574
  return jsonResult({
53488
- ...trimConversation(finalConversation),
53489
- recordsUsed
53575
+ ...trimConversation(state.conversation),
53576
+ recordsUsed: state.recordsUsed
53490
53577
  });
53491
53578
  }
53492
- if (lastAccumulated) {
53579
+ const salvaged = salvagedText(state);
53580
+ if (salvaged) {
53493
53581
  return jsonResult({
53494
- conversationId: null,
53495
- title: null,
53582
+ conversationId: state.conversationId,
53583
+ title: state.title,
53496
53584
  status: "Inprogress",
53497
- answer: lastAccumulated,
53585
+ answer: salvaged,
53498
53586
  confidence: null,
53499
53587
  citations: [],
53500
53588
  followUpQuestions: [],
53501
53589
  messageCount: 0,
53502
- recordsUsed,
53503
- warning: "Stream ended without a `complete` frame; answer is " + "the last accumulated chunk and citations are unavailable."
53590
+ recordsUsed: state.recordsUsed,
53591
+ warning: "Stream ended without a terminal RUN_FINISHED; answer is the " + "accumulated TEXT_MESSAGE_CONTENT and citations are unavailable."
53504
53592
  });
53505
53593
  }
53506
53594
  return errorResult("Stream ended without any usable frames.");
@@ -53669,10 +53757,23 @@ Typical uses:
53669
53757
  - Show the user a ranked list of matching files when they ask "find /
53670
53758
  search for X".
53671
53759
 
53760
+ Not for structural questions — what is under this epic, which pages are
53761
+ in this space, what links to this ticket. Ranking by content cannot show
53762
+ how records relate; use \`pipeshub_get_record_content\` \`mode:"navigate"\`.
53763
+
53764
+ **A ranked sample, never a complete list.** Hits are the top-scoring
53765
+ blocks from the best-matching records — not all blocks of any record, and
53766
+ not every record that matches. Never count them to answer "how many" /
53767
+ "all" / "every"; navigate the record group instead, which reports its
53768
+ real total.
53769
+
53672
53770
  The response is trimmed to one row per hit:
53673
53771
  \`{ recordId, recordName, score, snippet, mimeType, webUrl, ... }\`.
53674
53772
  Highest \`score\` first; multiple hits may share the same \`recordId\`
53675
- (different chunks of the same record).`,
53773
+ (different blocks of the same record).
53774
+
53775
+ When presenting results to the user, link each record using its
53776
+ \`webUrl\` (when present).`,
53676
53777
  scopes: ["read"],
53677
53778
  annotations: {
53678
53779
  title: "Semantic search",
@@ -53966,44 +54067,306 @@ var init_connectorGetRecordContent = __esm(() => {
53966
54067
  init_async();
53967
54068
  });
53968
54069
 
54070
+ // src/models/lookuprecordbyidentifierop.ts
54071
+ var LookupRecordByIdentifierRequest$zodSchema;
54072
+ var init_lookuprecordbyidentifierop = __esm(() => {
54073
+ init_zod();
54074
+ LookupRecordByIdentifierRequest$zodSchema = object({
54075
+ connectorName: string2().describe("Connector hint to prioritise resolution order, e.g. `JIRA`, `CONFLUENCE`, `GOOGLE_DRIVE`, `SLACK`. Cannot widen beyond the connectors the caller can access.").optional(),
54076
+ identifiers: array(string2().min(1).max(2048)).min(1).max(10).describe("The references to resolve: URLs, issue keys such as `PA-1787`, or bare external system IDs. Paste each exactly as you found it — tracking parameters and fragments are handled. Up to 10 per call.")
54077
+ });
54078
+ });
54079
+
54080
+ // src/funcs/connectorLookupRecordByIdentifier.ts
54081
+ function connectorLookupRecordByIdentifier(client$, request, options) {
54082
+ return new APIPromise($do9(client$, request, options));
54083
+ }
54084
+ async function $do9(client$, request, options) {
54085
+ const parsed$ = safeParse4(request, (value$) => LookupRecordByIdentifierRequest$zodSchema.parse(value$), "Input validation failed");
54086
+ if (!parsed$.ok) {
54087
+ return [parsed$, { status: "invalid" }];
54088
+ }
54089
+ const payload$ = parsed$.value;
54090
+ const body$ = null;
54091
+ const path$ = pathToFunc("/connectors/record/lookup")();
54092
+ const query$ = encodeFormQuery({
54093
+ connectorName: payload$.connectorName,
54094
+ identifiers: payload$.identifiers
54095
+ });
54096
+ const headers$ = new Headers(compactMap({
54097
+ Accept: "application/json"
54098
+ }));
54099
+ const securityInput = await extractSecurity(client$._options.security);
54100
+ const requestSecurity = resolveGlobalSecurity(securityInput);
54101
+ const context = {
54102
+ options: client$._options,
54103
+ baseURL: options?.serverURL ?? client$._baseURL ?? "",
54104
+ operationID: "lookupRecordByIdentifier",
54105
+ oAuth2Scopes: ["kb:read", "connector:read"],
54106
+ resolvedSecurity: requestSecurity,
54107
+ securitySource: client$._options.security,
54108
+ retryConfig: options?.retries || client$._options.retryConfig || { strategy: "none" },
54109
+ retryCodes: options?.retryCodes || [
54110
+ "429",
54111
+ "500",
54112
+ "502",
54113
+ "503",
54114
+ "504"
54115
+ ]
54116
+ };
54117
+ const requestRes = client$._createRequest(context, {
54118
+ security: requestSecurity,
54119
+ method: "GET",
54120
+ baseURL: options?.serverURL,
54121
+ path: path$,
54122
+ headers: headers$,
54123
+ query: query$,
54124
+ body: body$,
54125
+ userAgent: client$._options.userAgent,
54126
+ timeoutMs: options?.timeoutMs || client$._options.timeoutMs || -1
54127
+ }, options);
54128
+ if (!requestRes.ok) {
54129
+ return [requestRes, { status: "invalid" }];
54130
+ }
54131
+ const req$ = requestRes.value;
54132
+ const doResult = await client$._do(req$, {
54133
+ context,
54134
+ errorCodes: [],
54135
+ retryConfig: context.retryConfig,
54136
+ retryCodes: context.retryCodes
54137
+ });
54138
+ if (!doResult.ok) {
54139
+ return [doResult, { status: "request-error", request: req$ }];
54140
+ }
54141
+ return [doResult, {
54142
+ status: "complete",
54143
+ request: req$,
54144
+ response: doResult.value
54145
+ }];
54146
+ }
54147
+ var init_connectorLookupRecordByIdentifier = __esm(() => {
54148
+ init_encodings();
54149
+ init_primitives();
54150
+ init_schemas4();
54151
+ init_security2();
54152
+ init_url();
54153
+ init_lookuprecordbyidentifierop();
54154
+ init_async();
54155
+ });
54156
+
54157
+ // src/models/navigateknowledgegraphop.ts
54158
+ var NavigateKnowledgeGraphRequest$zodSchema;
54159
+ var init_navigateknowledgegraphop = __esm(() => {
54160
+ init_zod();
54161
+ NavigateKnowledgeGraphRequest$zodSchema = object({
54162
+ createdAfter: string2().describe("Filter children by source creation time. ISO 8601 `YYYY-MM-DD`, or a full datetime that MUST carry a timezone offset — a naive datetime is rejected rather than assumed to be UTC.").optional(),
54163
+ createdBefore: string2().describe("Filter children by source creation time. `YYYY-MM-DD` is inclusive of the whole day.").optional(),
54164
+ depth: int().min(1).max(3).describe("Levels of descendants to return in one call. Above 1, `rows` is a flat list of all descendants down to that level rather than only direct children, and each row carries its own `level`.").optional(),
54165
+ limit: int().min(50).max(200).describe("Children per page. The minimum is 50 — smaller values are rejected rather than silently raised.").optional(),
54166
+ modifiedAfter: string2().describe("Filter children by source modification time.").optional(),
54167
+ modifiedBefore: string2().describe("Filter children by source modification time.").optional(),
54168
+ nodeId: string2().min(1).max(2048).describe("The node to open. Take it from an `id` in a previous navigate or lookup response. Omit it entirely for the flat listing of everything reachable — the usual starting point. A URL or an issue key such as `PA-1787` also works: it is resolved to its record automatically.").optional(),
54169
+ nodeTypes: array(string2()).describe("Restrict children to these node types. Repeat the parameter for multiple types: `?nodeTypes=record&nodeTypes=folder`.").optional(),
54170
+ page: int().min(1).describe("Page number, 1-indexed.").optional()
54171
+ });
54172
+ });
54173
+
54174
+ // src/funcs/connectorNavigateKnowledgeGraph.ts
54175
+ function connectorNavigateKnowledgeGraph(client$, request, options) {
54176
+ return new APIPromise($do10(client$, request, options));
54177
+ }
54178
+ async function $do10(client$, request, options) {
54179
+ const parsed$ = safeParse4(request, (value$) => NavigateKnowledgeGraphRequest$zodSchema.optional().parse(value$), "Input validation failed");
54180
+ if (!parsed$.ok) {
54181
+ return [parsed$, { status: "invalid" }];
54182
+ }
54183
+ const payload$ = parsed$.value;
54184
+ const body$ = null;
54185
+ const path$ = pathToFunc("/connectors/navigate")();
54186
+ const query$ = encodeFormQuery({
54187
+ createdAfter: payload$?.createdAfter,
54188
+ createdBefore: payload$?.createdBefore,
54189
+ depth: payload$?.depth,
54190
+ limit: payload$?.limit,
54191
+ modifiedAfter: payload$?.modifiedAfter,
54192
+ modifiedBefore: payload$?.modifiedBefore,
54193
+ nodeId: payload$?.nodeId,
54194
+ nodeTypes: payload$?.nodeTypes,
54195
+ page: payload$?.page
54196
+ });
54197
+ const headers$ = new Headers(compactMap({
54198
+ Accept: "application/json"
54199
+ }));
54200
+ const securityInput = await extractSecurity(client$._options.security);
54201
+ const requestSecurity = resolveGlobalSecurity(securityInput);
54202
+ const context = {
54203
+ options: client$._options,
54204
+ baseURL: options?.serverURL ?? client$._baseURL ?? "",
54205
+ operationID: "navigateKnowledgeGraph",
54206
+ oAuth2Scopes: ["kb:read", "connector:read"],
54207
+ resolvedSecurity: requestSecurity,
54208
+ securitySource: client$._options.security,
54209
+ retryConfig: options?.retries || client$._options.retryConfig || { strategy: "none" },
54210
+ retryCodes: options?.retryCodes || [
54211
+ "429",
54212
+ "500",
54213
+ "502",
54214
+ "503",
54215
+ "504"
54216
+ ]
54217
+ };
54218
+ const requestRes = client$._createRequest(context, {
54219
+ security: requestSecurity,
54220
+ method: "GET",
54221
+ baseURL: options?.serverURL,
54222
+ path: path$,
54223
+ headers: headers$,
54224
+ query: query$,
54225
+ body: body$,
54226
+ userAgent: client$._options.userAgent,
54227
+ timeoutMs: options?.timeoutMs || client$._options.timeoutMs || -1
54228
+ }, options);
54229
+ if (!requestRes.ok) {
54230
+ return [requestRes, { status: "invalid" }];
54231
+ }
54232
+ const req$ = requestRes.value;
54233
+ const doResult = await client$._do(req$, {
54234
+ context,
54235
+ errorCodes: [],
54236
+ retryConfig: context.retryConfig,
54237
+ retryCodes: context.retryCodes
54238
+ });
54239
+ if (!doResult.ok) {
54240
+ return [doResult, { status: "request-error", request: req$ }];
54241
+ }
54242
+ return [doResult, {
54243
+ status: "complete",
54244
+ request: req$,
54245
+ response: doResult.value
54246
+ }];
54247
+ }
54248
+ var init_connectorNavigateKnowledgeGraph = __esm(() => {
54249
+ init_encodings();
54250
+ init_primitives();
54251
+ init_schemas4();
54252
+ init_security2();
54253
+ init_url();
54254
+ init_navigateknowledgegraphop();
54255
+ init_async();
54256
+ });
54257
+
53969
54258
  // src/mcp-server/tools/pipeshubGetRecordContent.ts
54259
+ async function renderedResult(response) {
54260
+ const parsed = await readJson(response);
54261
+ if (!parsed.ok)
54262
+ return parsed.result;
54263
+ const text = parsed.value?.text;
54264
+ return typeof text === "string" && text.trim().length > 0 ? { content: [{ type: "text", text }] } : jsonResult(parsed.value);
54265
+ }
53970
54266
  var args4, tool$pipeshubGetRecordContent;
53971
54267
  var init_pipeshubGetRecordContent = __esm(() => {
53972
54268
  init_zod();
53973
54269
  init_connectorGetRecordContent();
54270
+ init_connectorLookupRecordByIdentifier();
54271
+ init_connectorNavigateKnowledgeGraph();
53974
54272
  init__helpers();
53975
54273
  args4 = {
53976
- recordId: string2().min(1).describe("Record identifier usually a UUID for connector-sourced records or " + "a 24-character ObjectId for uploaded records. Get it from a chat " + "citation (`citations[*].recordId`) or from a `pipeshub_search` hit.")
54274
+ mode: _enum(["content", "navigate", "lookup"]).default("content").describe("`content` (default) reads a record's full text by `recordId`. " + "`lookup` resolves a URL / issue key / external ID to a recordId. " + "`navigate` browses the knowledge graph tree."),
54275
+ recordId: string2().min(1).optional().describe("Record identifier — usually a UUID for connector-sourced records or " + "a 24-character ObjectId for uploaded records. Get it from a chat " + "citation (`citations[*].recordId`) or from a `pipeshub_search` hit. " + "Required when `mode` is `content`."),
54276
+ nodeId: string2().min(1).max(2048).optional().describe("The node to open. Take it from a `record_id=` or `node_id=` shown in a " + "previous navigate or lookup response, from a search hit's `recordId`, " + "or from a `pipeshub_sources` id — a KB or connector id opens that " + "source directly. Omit it entirely for the flat listing of everything " + "reachable, newest first — the usual starting point. A URL or an issue " + "key such as `PA-1787` also works: it is resolved to its record " + "automatically, so no separate lookup is needed."),
54277
+ page: int().min(1).default(1).describe("Page number, 1-indexed."),
54278
+ limit: int().min(50).max(200).default(50).describe("Children per page. The minimum is 50 — smaller values are rejected " + "rather than silently raised."),
54279
+ depth: int().min(1).max(3).default(1).describe("Levels of descendants to return in one call. Above 1, the listing is a " + "flat list of all descendants down to that level rather than only " + "direct children, and each row carries its own `level`."),
54280
+ nodeTypes: array(string2()).optional().describe('Restrict children to these node types, e.g. `["record", "folder"]`.'),
54281
+ createdAfter: string2().optional().describe("Filter children by source creation time. ISO 8601 `YYYY-MM-DD`, or a " + "full datetime that MUST carry a timezone offset — a naive datetime is " + "rejected rather than assumed to be UTC."),
54282
+ createdBefore: string2().optional().describe("Filter children by source creation time. `YYYY-MM-DD` is inclusive of " + "the whole day."),
54283
+ modifiedAfter: string2().optional().describe("Filter children by source modification time. Same formats as " + "`createdAfter`."),
54284
+ modifiedBefore: string2().optional().describe("Filter children by source modification time. Same formats as " + "`createdBefore`."),
54285
+ identifiers: union([
54286
+ string2().min(1).max(2048),
54287
+ array(string2().min(1).max(2048)).min(1).max(10)
54288
+ ]).optional().describe("The reference(s) to resolve: a URL, an issue key such as `PA-1787`, or " + "a bare external system ID. Paste each exactly as you found it — " + "tracking parameters and fragments are handled. Pass a single string, " + "or an array of up to 10 to resolve them in one call. Required when " + "`mode` is `lookup`."),
54289
+ connectorName: string2().optional().describe("Optional hint that prioritises resolution order, e.g. `JIRA`, " + "`CONFLUENCE`, `GOOGLE_DRIVE`, `SLACK`. It cannot widen the search beyond the " + "connectors you can already access. Useful on a retry when a lookup " + "came back empty.")
53977
54290
  };
53978
54291
  tool$pipeshubGetRecordContent = {
53979
54292
  name: "pipeshub_get_record_content",
53980
- description: `Read a record's full parsed content by \`recordId\` the only way to
53981
- see a document's COMPLETE text.
53982
-
53983
- Use it whenever the answer depends on a document's full content — any
53984
- task where missing a part could make the answer wrong: summarize / key
53985
- points / action items; extract or list ALL of something; check whether
53986
- or where the doc mentions X; translate, rewrite, outline, or review the
53987
- doc; compare named docs (fetch each); any question scoped to one named
53988
- document. \`pipeshub_chat\` cannot do these it only sees a few
53989
- retrieved passages, never the whole document. Get the \`recordId\` from
53990
- a \`pipeshub_search\` top hit or a chat citation.
53991
-
53992
- Judge by the user's INTENT, not their keywords: they need not say
53993
- "summarize", "key points", or "extract". Reason about what a good
53994
- answer requires if it would need the whole document (e.g. "what's
53995
- this doc about?", "walk me through the report", "anything in here
53996
- about Y?"), that is a full-content task, so call this tool.
53997
-
53998
- Returns a single \`content\` string: a short metadata header (title,
53999
- source, key fields, and a pre-generated summary) followed by the
54000
- record's full parsed text paragraphs, tables, and lists in reading
54001
- order. For a record with no extractable content, \`content\` is the
54002
- literal \`No record found\`. Use \`pipeshub_download_record\` only when
54003
- you need the original file bytes.`,
54293
+ description: `Three operations on the org's records. Pick by what you hold:
54294
+
54295
+ \`mode:"lookup"\` — a URL, issue key (PA-1787), or external ID
54296
+ its recordId plus the record's metadata
54297
+ \`mode:"navigate"\` a question about structure: what is under X,
54298
+ what links to Y browses the hierarchy
54299
+ \`mode:"content"\` — a recordId, and you need the document's COMPLETE text
54300
+
54301
+ **\`mode:"content"\` (default)**the only way to see a document's complete
54302
+ text. Use it whenever missing part of the document could make the answer
54303
+ wrong: summarize, extract or list ALL of something, check whether or where
54304
+ a doc mentions X, review, or compare named docs. \`pipeshub_chat\` cannot do
54305
+ these it never sees a whole document.
54306
+
54307
+ Judge by the user's INTENT, not their keywords: "what's this doc about?",
54308
+ "walk me through the report", "anything in here about Y?" are all
54309
+ full-content tasks. Get the \`recordId\` from a \`pipeshub_search\` top hit, a
54310
+ chat citation, or \`mode:"lookup"\`.
54311
+
54312
+ Returns one \`content\` string: a metadata header (title, source, key fields,
54313
+ pre-generated summary) then the full parsed text. A record with no
54314
+ extractable content returns the literal \`No record found\`. Use
54315
+ \`pipeshub_download_record\` only for the original file bytes.
54316
+
54317
+ **\`mode:"navigate"\`** — browse the hierarchy: RecordGroup (project /
54318
+ space / drive / folder) → Record (epic / story / page / file) → children,
54319
+ with breadcrumbs, related links and record IDs.
54320
+
54321
+ Use it when the question depends on structure rather than wording: what is
54322
+ under this epic, which pages sit in this space, what is linked to this
54323
+ ticket, what is in this folder — and every "how many" / "all of" / "every"
54324
+ question. Search ranks by content; only this shows how records relate, and
54325
+ only this gives a count you can trust.
54326
+
54327
+ Omit \`nodeId\` for a flat listing of everything reachable, most recently
54328
+ updated first — the usual starting point. A URL, an issue key, or a
54329
+ \`pipeshub_sources\` id also works and resolves automatically.
54330
+
54331
+ Pass \`depth:2\` or \`depth:3\` to see several levels in ONE call — an epic's
54332
+ stories AND their subtasks, a space's pages AND their children — instead of
54333
+ one call per level. Use it whenever the question needs an overview of a
54334
+ hierarchy rather than a single node.
54335
+
54336
+ Opening a record also prints that record's own metadata — for a ticket,
54337
+ status, assignee, priority and dates — so a question about one record is
54338
+ often answered by this call alone. It returns no document text; for that,
54339
+ re-call with \`mode:"content"\`.
54340
+
54341
+ Returns \`Path\` breadcrumbs, the current node's metadata, a children listing
54342
+ carrying \`record_id=\` or \`node_id=\` per row plus the group's total
54343
+ (\`Children 1-50 of 61\`), \`Related\` cross-references, and a \`Next:\` line.
54344
+ One page is usually every child, so only pass \`page:2\` when that \`Next:\`
54345
+ line says more exist.
54346
+
54347
+ **\`mode:"lookup"\`** — turn an external reference into a recordId, the first
54348
+ step whenever the question names one. Returns that record's metadata (for a
54349
+ ticket: status, assignee, priority, dates) plus its recordId, which
54350
+ \`mode:"navigate"\` takes to list what is under it and \`mode:"content"\` takes
54351
+ to read it.
54352
+
54353
+ Handles Jira keys and URLs, Confluence, Drive, Slack permalinks, Linear,
54354
+ Notion, ServiceNow sys_id, SharePoint, Gmail/Outlook, and any connector
54355
+ whose records index a web URL. Resolution searches ALL connectors you can
54356
+ access, regardless of any source filter you used elsewhere.
54357
+
54358
+ A miss is a 200 with empty \`matches\` and the input echoed in
54359
+ \`not_found_identifiers\` — that may mean no-access, not non-existence. Use
54360
+ \`mode:"navigate"\` to confirm the record exists before telling the user it
54361
+ does not. If \`ambiguous\` is true, pick from \`matches\` rather than taking
54362
+ the first.
54363
+
54364
+ Navigate and lookup return a rendered text view whose closing \`Next:\` line
54365
+ names the exact follow-up call — follow it. When presenting a record, link
54366
+ it using the \`Web URL\` from its metadata header (when present).`,
54004
54367
  scopes: ["read"],
54005
54368
  annotations: {
54006
- title: "Get a record's full parsed content",
54369
+ title: "Read, browse, or resolve records",
54007
54370
  destructiveHint: false,
54008
54371
  idempotentHint: true,
54009
54372
  openWorldHint: false,
@@ -54011,9 +54374,48 @@ you need the original file bytes.`,
54011
54374
  },
54012
54375
  args: args4,
54013
54376
  tool: async (client, args5, ctx) => {
54377
+ const fetchOptions = { fetchOptions: { signal: ctx.signal } };
54378
+ if (args5.mode === "navigate") {
54379
+ const [r] = await connectorNavigateKnowledgeGraph(client, {
54380
+ nodeId: args5.nodeId,
54381
+ page: args5.page,
54382
+ limit: args5.limit,
54383
+ depth: args5.depth,
54384
+ nodeTypes: args5.nodeTypes,
54385
+ createdAfter: args5.createdAfter,
54386
+ createdBefore: args5.createdBefore,
54387
+ modifiedAfter: args5.modifiedAfter,
54388
+ modifiedBefore: args5.modifiedBefore
54389
+ }, fetchOptions).$inspect();
54390
+ if (!r.ok)
54391
+ return errorResult(r.error.message);
54392
+ const httpErr2 = await httpErrorResult(r.value, "Navigate knowledge graph");
54393
+ if (httpErr2)
54394
+ return httpErr2;
54395
+ return renderedResult(r.value);
54396
+ }
54397
+ if (args5.mode === "lookup") {
54398
+ const identifiers = typeof args5.identifiers === "string" ? [args5.identifiers] : args5.identifiers ?? [];
54399
+ if (identifiers.length === 0) {
54400
+ return errorResult('`identifiers` is required when mode is "lookup".');
54401
+ }
54402
+ const [r] = await connectorLookupRecordByIdentifier(client, {
54403
+ identifiers,
54404
+ connectorName: args5.connectorName
54405
+ }, fetchOptions).$inspect();
54406
+ if (!r.ok)
54407
+ return errorResult(r.error.message);
54408
+ const httpErr2 = await httpErrorResult(r.value, "Record lookup");
54409
+ if (httpErr2)
54410
+ return httpErr2;
54411
+ return renderedResult(r.value);
54412
+ }
54413
+ if (!args5.recordId) {
54414
+ return errorResult('`recordId` is required when mode is "content".');
54415
+ }
54014
54416
  const [result] = await connectorGetRecordContent(client, {
54015
54417
  recordId: args5.recordId
54016
- }, { fetchOptions: { signal: ctx.signal } }).$inspect();
54418
+ }, fetchOptions).$inspect();
54017
54419
  if (!result.ok)
54018
54420
  return errorResult(result.error.message);
54019
54421
  const httpErr = await httpErrorResult(result.value, "Get record content");
@@ -54040,9 +54442,9 @@ var init_getallusersop = __esm(() => {
54040
54442
 
54041
54443
  // src/funcs/usersGetAllUsers.ts
54042
54444
  function usersGetAllUsers(client$, request, options) {
54043
- return new APIPromise($do9(client$, request, options));
54445
+ return new APIPromise($do11(client$, request, options));
54044
54446
  }
54045
- async function $do9(client$, request, options) {
54447
+ async function $do11(client$, request, options) {
54046
54448
  const parsed$ = safeParse4(request, (value$) => GetAllUsersRequest$zodSchema.optional().parse(value$), "Input validation failed");
54047
54449
  if (!parsed$.ok) {
54048
54450
  return [parsed$, { status: "invalid" }];
@@ -54127,9 +54529,9 @@ var init_getuserbyidop = __esm(() => {
54127
54529
 
54128
54530
  // src/funcs/usersGetUserById.ts
54129
54531
  function usersGetUserById(client$, request, options) {
54130
- return new APIPromise($do10(client$, request, options));
54532
+ return new APIPromise($do12(client$, request, options));
54131
54533
  }
54132
- async function $do10(client$, request, options) {
54534
+ async function $do12(client$, request, options) {
54133
54535
  const parsed$ = safeParse4(request, (value$) => GetUserByIdRequest$zodSchema.parse(value$), "Input validation failed");
54134
54536
  if (!parsed$.ok) {
54135
54537
  return [parsed$, { status: "invalid" }];
@@ -54216,9 +54618,9 @@ var init_getallusergroupsop = __esm(() => {
54216
54618
 
54217
54619
  // src/funcs/userGroupsGetAllUserGroups.ts
54218
54620
  function userGroupsGetAllUserGroups(client$, request, options) {
54219
- return new APIPromise($do11(client$, request, options));
54621
+ return new APIPromise($do13(client$, request, options));
54220
54622
  }
54221
- async function $do11(client$, request, options) {
54623
+ async function $do13(client$, request, options) {
54222
54624
  const parsed$ = safeParse4(request, (value$) => GetAllUserGroupsRequest$zodSchema.optional().parse(value$), "Input validation failed");
54223
54625
  if (!parsed$.ok) {
54224
54626
  return [parsed$, { status: "invalid" }];
@@ -54308,9 +54710,9 @@ var init_getuserteamsop = __esm(() => {
54308
54710
 
54309
54711
  // src/funcs/teamsGetUserTeams.ts
54310
54712
  function teamsGetUserTeams(client$, request, options) {
54311
- return new APIPromise($do12(client$, request, options));
54713
+ return new APIPromise($do14(client$, request, options));
54312
54714
  }
54313
- async function $do12(client$, request, options) {
54715
+ async function $do14(client$, request, options) {
54314
54716
  const parsed$ = safeParse4(request, (value$) => GetUserTeamsRequest$zodSchema.optional().parse(value$), "Input validation failed");
54315
54717
  if (!parsed$.ok) {
54316
54718
  return [parsed$, { status: "invalid" }];
@@ -54540,9 +54942,9 @@ var init_getknowledgehubrootnodesop = __esm(() => {
54540
54942
 
54541
54943
  // src/funcs/knowledgeHubGetKnowledgeHubRootNodes.ts
54542
54944
  function knowledgeHubGetKnowledgeHubRootNodes(client$, request, options) {
54543
- return new APIPromise($do13(client$, request, options));
54945
+ return new APIPromise($do15(client$, request, options));
54544
54946
  }
54545
- async function $do13(client$, request, options) {
54947
+ async function $do15(client$, request, options) {
54546
54948
  const parsed$ = safeParse4(request, (value$) => GetKnowledgeHubRootNodesRequest$zodSchema.optional().parse(value$), "Input validation failed");
54547
54949
  if (!parsed$.ok) {
54548
54950
  return [parsed$, { status: "invalid" }];
@@ -54658,9 +55060,9 @@ var init_getavailablemodelsbytypeop = __esm(() => {
54658
55060
 
54659
55061
  // src/funcs/aiModelsProvidersGetAvailableModelsByType.ts
54660
55062
  function aiModelsProvidersGetAvailableModelsByType(client$, request, options) {
54661
- return new APIPromise($do14(client$, request, options));
55063
+ return new APIPromise($do16(client$, request, options));
54662
55064
  }
54663
- async function $do14(client$, request, options) {
55065
+ async function $do16(client$, request, options) {
54664
55066
  const parsed$ = safeParse4(request, (value$) => GetAvailableModelsByTypeRequest$zodSchema.parse(value$), "Input validation failed");
54665
55067
  if (!parsed$.ok) {
54666
55068
  return [parsed$, { status: "invalid" }];
@@ -54986,31 +55388,39 @@ var init_tool_names = __esm(() => {
54986
55388
  },
54987
55389
  {
54988
55390
  name: "pipeshub_chat",
54989
- description: `**Primary chat tool handles both internal knowledge queries and web search.**
54990
-
54991
- **Internal search** (default, \`chatMode: "internal_search"\`): Use whenever
54992
- the user asks about their documents, files, knowledge base, company policies,
54993
- or anything that could plausibly be answered by content in their PipesHub-indexed
54994
- sources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).
54995
- Grounds the answer in the user's actual data and returns citations.
54996
- Answers come from a few retrieved passages, not whole documents — for
54997
- any task needing a document's full content, use
54998
- \`pipeshub_get_record_content\` instead.
54999
-
55000
- **Web search** (\`chatMode: "web_search"\`): Use when the user asks about
55001
- current events, public information, or anything unlikely to be in the org's
55002
- internal knowledge base. Pass \`chatMode: "web_search"\` and this tool will
55003
- search the public web instead.
55004
-
55005
- **When to pick this over other tools:**
55006
- - "Summarize <doc>" / "key points of <doc>" / "what does <document> say
55007
- about X?" → NOT this tool. Use \`pipeshub_search\` →
55008
- \`pipeshub_get_record_content\`: answering for a specific document
55009
- requires its full content, and chat only sees a few retrieved
55010
- passages, never the whole document.
55391
+ description: `Ask a question, get an answer grounded in the org's indexed data with
55392
+ citations. It reads a few retrieved passages — never a whole document,
55393
+ never a complete list.
55394
+
55395
+ **Three questions this tool gets WRONG. Check them first:**
55396
+ - **Structure** "what's under this epic?", "which pages are in this
55397
+ space?", "what links to this ticket?", "what's in this folder?"
55398
+ \`pipeshub_get_record_content\` \`mode:"navigate"\`. Ranking cannot see how
55399
+ records relate.
55400
+ - **Exhaustive** — "how many X?", "list ALL the Y", "every Z" →
55401
+ \`mode:"navigate"\`, which reports the group's real total. This tool
55402
+ undercounts and will not say so.
55403
+ - **One named document** — summarize it, extract from it, what does it say
55404
+ about X \`pipeshub_search\` for the \`recordId\`, then \`mode:"content"\`.
55405
+
55406
+ Everything else about the org's knowledge belongs here: policies,
55407
+ processes, decisions, history, "what do we know about X", and any question
55408
+ spanning several documents.
55409
+
55410
+ **Internal search** (default, \`chatMode: "internal_search"\`): the user's
55411
+ documents, files, knowledge base, company policies anything in their
55412
+ PipesHub-indexed sources (Drive, Box, Confluence, Slack, Gmail, Jira, the
55413
+ org's KB, ...).
55414
+
55415
+ **Web search** (\`chatMode: "web_search"\`): current events or public
55416
+ information unlikely to be in the org's knowledge base.
55417
+
55418
+ Both are plain-chat modes. **Agent chat** — pass an \`agentId\` from
55419
+ \`pipeshub_agents\` — runs against that agent's own prompt, tools and knowledge;
55420
+ \`quick\` is its only mode, requires the \`agentId\`, and is sent automatically.
55421
+
55011
55422
  - "What's our policy on Y?" → \`pipeshub_chat\` (internal_search)
55012
55423
  - "What's in the news about Z?" → \`pipeshub_chat\` (web_search)
55013
- - "What is the latest version of <library>?" → \`pipeshub_chat\` (web_search)
55014
55424
  - "Find / locate the file named X" → \`pipeshub_search\` (then
55015
55425
  \`pipeshub_download_record\` if the user wants the bytes).
55016
55426
 
@@ -55049,10 +55459,23 @@ Typical uses:
55049
55459
  - Show the user a ranked list of matching files when they ask "find /
55050
55460
  search for X".
55051
55461
 
55462
+ Not for structural questions — what is under this epic, which pages are
55463
+ in this space, what links to this ticket. Ranking by content cannot show
55464
+ how records relate; use \`pipeshub_get_record_content\` \`mode:"navigate"\`.
55465
+
55466
+ **A ranked sample, never a complete list.** Hits are the top-scoring
55467
+ blocks from the best-matching records — not all blocks of any record, and
55468
+ not every record that matches. Never count them to answer "how many" /
55469
+ "all" / "every"; navigate the record group instead, which reports its
55470
+ real total.
55471
+
55052
55472
  The response is trimmed to one row per hit:
55053
55473
  \`{ recordId, recordName, score, snippet, mimeType, webUrl, ... }\`.
55054
55474
  Highest \`score\` first; multiple hits may share the same \`recordId\`
55055
- (different chunks of the same record).`
55475
+ (different blocks of the same record).
55476
+
55477
+ When presenting results to the user, link each record using its
55478
+ \`webUrl\` (when present).`
55056
55479
  },
55057
55480
  {
55058
55481
  name: "pipeshub_download_record",
@@ -55060,30 +55483,80 @@ Highest \`score\` first; multiple hits may share the same \`recordId\`
55060
55483
  },
55061
55484
  {
55062
55485
  name: "pipeshub_get_record_content",
55063
- description: `Read a record's full parsed content by \`recordId\` the only way to
55064
- see a document's COMPLETE text.
55065
-
55066
- Use it whenever the answer depends on a document's full content — any
55067
- task where missing a part could make the answer wrong: summarize / key
55068
- points / action items; extract or list ALL of something; check whether
55069
- or where the doc mentions X; translate, rewrite, outline, or review the
55070
- doc; compare named docs (fetch each); any question scoped to one named
55071
- document. \`pipeshub_chat\` cannot do these it only sees a few
55072
- retrieved passages, never the whole document. Get the \`recordId\` from
55073
- a \`pipeshub_search\` top hit or a chat citation.
55074
-
55075
- Judge by the user's INTENT, not their keywords: they need not say
55076
- "summarize", "key points", or "extract". Reason about what a good
55077
- answer requires if it would need the whole document (e.g. "what's
55078
- this doc about?", "walk me through the report", "anything in here
55079
- about Y?"), that is a full-content task, so call this tool.
55080
-
55081
- Returns a single \`content\` string: a short metadata header (title,
55082
- source, key fields, and a pre-generated summary) followed by the
55083
- record's full parsed text paragraphs, tables, and lists in reading
55084
- order. For a record with no extractable content, \`content\` is the
55085
- literal \`No record found\`. Use \`pipeshub_download_record\` only when
55086
- you need the original file bytes.`
55486
+ description: `Three operations on the org's records. Pick by what you hold:
55487
+
55488
+ \`mode:"lookup"\` — a URL, issue key (PA-1787), or external ID
55489
+ its recordId plus the record's metadata
55490
+ \`mode:"navigate"\` a question about structure: what is under X,
55491
+ what links to Y browses the hierarchy
55492
+ \`mode:"content"\` — a recordId, and you need the document's COMPLETE text
55493
+
55494
+ **\`mode:"content"\` (default)**the only way to see a document's complete
55495
+ text. Use it whenever missing part of the document could make the answer
55496
+ wrong: summarize, extract or list ALL of something, check whether or where
55497
+ a doc mentions X, review, or compare named docs. \`pipeshub_chat\` cannot do
55498
+ these it never sees a whole document.
55499
+
55500
+ Judge by the user's INTENT, not their keywords: "what's this doc about?",
55501
+ "walk me through the report", "anything in here about Y?" are all
55502
+ full-content tasks. Get the \`recordId\` from a \`pipeshub_search\` top hit, a
55503
+ chat citation, or \`mode:"lookup"\`.
55504
+
55505
+ Returns one \`content\` string: a metadata header (title, source, key fields,
55506
+ pre-generated summary) then the full parsed text. A record with no
55507
+ extractable content returns the literal \`No record found\`. Use
55508
+ \`pipeshub_download_record\` only for the original file bytes.
55509
+
55510
+ **\`mode:"navigate"\`** — browse the hierarchy: RecordGroup (project /
55511
+ space / drive / folder) → Record (epic / story / page / file) → children,
55512
+ with breadcrumbs, related links and record IDs.
55513
+
55514
+ Use it when the question depends on structure rather than wording: what is
55515
+ under this epic, which pages sit in this space, what is linked to this
55516
+ ticket, what is in this folder — and every "how many" / "all of" / "every"
55517
+ question. Search ranks by content; only this shows how records relate, and
55518
+ only this gives a count you can trust.
55519
+
55520
+ Omit \`nodeId\` for a flat listing of everything reachable, most recently
55521
+ updated first — the usual starting point. A URL, an issue key, or a
55522
+ \`pipeshub_sources\` id also works and resolves automatically.
55523
+
55524
+ Pass \`depth:2\` or \`depth:3\` to see several levels in ONE call — an epic's
55525
+ stories AND their subtasks, a space's pages AND their children — instead of
55526
+ one call per level. Use it whenever the question needs an overview of a
55527
+ hierarchy rather than a single node.
55528
+
55529
+ Opening a record also prints that record's own metadata — for a ticket,
55530
+ status, assignee, priority and dates — so a question about one record is
55531
+ often answered by this call alone. It returns no document text; for that,
55532
+ re-call with \`mode:"content"\`.
55533
+
55534
+ Returns \`Path\` breadcrumbs, the current node's metadata, a children listing
55535
+ carrying \`record_id=\` or \`node_id=\` per row plus the group's total
55536
+ (\`Children 1-50 of 61\`), \`Related\` cross-references, and a \`Next:\` line.
55537
+ One page is usually every child, so only pass \`page:2\` when that \`Next:\`
55538
+ line says more exist.
55539
+
55540
+ **\`mode:"lookup"\`** — turn an external reference into a recordId, the first
55541
+ step whenever the question names one. Returns that record's metadata (for a
55542
+ ticket: status, assignee, priority, dates) plus its recordId, which
55543
+ \`mode:"navigate"\` takes to list what is under it and \`mode:"content"\` takes
55544
+ to read it.
55545
+
55546
+ Handles Jira keys and URLs, Confluence, Drive, Slack permalinks, Linear,
55547
+ Notion, ServiceNow sys_id, SharePoint, Gmail/Outlook, and any connector
55548
+ whose records index a web URL. Resolution searches ALL connectors you can
55549
+ access, regardless of any source filter you used elsewhere.
55550
+
55551
+ A miss is a 200 with empty \`matches\` and the input echoed in
55552
+ \`not_found_identifiers\` — that may mean no-access, not non-existence. Use
55553
+ \`mode:"navigate"\` to confirm the record exists before telling the user it
55554
+ does not. If \`ambiguous\` is true, pick from \`matches\` rather than taking
55555
+ the first.
55556
+
55557
+ Navigate and lookup return a rendered text view whose closing \`Next:\` line
55558
+ names the exact follow-up call — follow it. When presenting a record, link
55559
+ it using the \`Web URL\` from its metadata header (when present).`
55087
55560
  },
55088
55561
  {
55089
55562
  name: "pipeshub_directory",
@@ -58720,5 +59193,5 @@ export {
58720
59193
  app
58721
59194
  };
58722
59195
 
58723
- //# debugId=59975E6F38134B6C64756E2164756E21
59196
+ //# debugId=90FD24D95D2C4D3664756E2164756E21
58724
59197
  //# sourceMappingURL=mcp-server.js.map