@pipeshub-ai/mcp 2.0.1 → 2.2.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.
- package/README.md +90 -2
- package/bin/mcp-server.js +1156 -213
- package/bin/mcp-server.js.map +30 -16
- package/esm/funcs/agentsListAgents.d.ts +10 -0
- package/esm/funcs/agentsListAgents.d.ts.map +1 -0
- package/esm/funcs/agentsListAgents.js +83 -0
- package/esm/funcs/agentsListAgents.js.map +1 -0
- package/esm/funcs/agentsStreamConversation.d.ts +10 -0
- package/esm/funcs/agentsStreamConversation.d.ts.map +1 -0
- package/esm/funcs/agentsStreamConversation.js +83 -0
- package/esm/funcs/agentsStreamConversation.js.map +1 -0
- package/esm/funcs/agentsStreamMessage.d.ts +10 -0
- package/esm/funcs/agentsStreamMessage.d.ts.map +1 -0
- package/esm/funcs/agentsStreamMessage.js +86 -0
- package/esm/funcs/agentsStreamMessage.js.map +1 -0
- package/esm/funcs/connectorGetRecordContent.d.ts +43 -0
- package/esm/funcs/connectorGetRecordContent.d.ts.map +1 -0
- package/esm/funcs/connectorGetRecordContent.js +114 -0
- package/esm/funcs/connectorGetRecordContent.js.map +1 -0
- package/esm/funcs/conversationsStreamConversation.d.ts.map +1 -1
- package/esm/funcs/conversationsStreamConversation.js +2 -4
- package/esm/funcs/conversationsStreamConversation.js.map +1 -1
- package/esm/funcs/conversationsStreamMessage.d.ts.map +1 -1
- package/esm/funcs/conversationsStreamMessage.js +0 -7
- package/esm/funcs/conversationsStreamMessage.js.map +1 -1
- package/esm/funcs/userGroupsGetAllUserGroups.js +1 -1
- package/esm/funcs/userGroupsGetAllUserGroups.js.map +1 -1
- package/esm/hooks/registration.d.ts.map +1 -1
- package/esm/hooks/registration.js +2 -1
- package/esm/hooks/registration.js.map +1 -1
- package/esm/hooks/request-context.d.ts +10 -0
- package/esm/hooks/request-context.d.ts.map +1 -0
- package/esm/hooks/request-context.js +38 -0
- package/esm/hooks/request-context.js.map +1 -0
- package/esm/hooks/requestid.d.ts +5 -0
- package/esm/hooks/requestid.d.ts.map +1 -0
- package/esm/hooks/requestid.js +58 -0
- package/esm/hooks/requestid.js.map +1 -0
- package/esm/mcp-server/instructions.d.ts +2 -0
- package/esm/mcp-server/instructions.d.ts.map +1 -0
- package/esm/mcp-server/instructions.js +106 -0
- package/esm/mcp-server/instructions.js.map +1 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.d.ts +8 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.d.ts.map +1 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.js +31 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.js.map +1 -0
- package/esm/mcp-server/server.d.ts.map +1 -1
- package/esm/mcp-server/server.js +16 -62
- package/esm/mcp-server/server.js.map +1 -1
- package/esm/mcp-server/tools/_helpers.d.ts +78 -0
- package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
- package/esm/mcp-server/tools/_helpers.js +155 -1
- package/esm/mcp-server/tools/_helpers.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubAgents.d.ts +8 -0
- package/esm/mcp-server/tools/pipeshubAgents.d.ts.map +1 -0
- package/esm/mcp-server/tools/pipeshubAgents.js +62 -0
- package/esm/mcp-server/tools/pipeshubAgents.js.map +1 -0
- package/esm/mcp-server/tools/pipeshubChat.d.ts +6 -1
- package/esm/mcp-server/tools/pipeshubChat.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubChat.js +79 -23
- package/esm/mcp-server/tools/pipeshubChat.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubDirectory.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubDirectory.js +0 -1
- package/esm/mcp-server/tools/pipeshubDirectory.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubDownloadRecord.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubDownloadRecord.js +0 -3
- package/esm/mcp-server/tools/pipeshubDownloadRecord.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts +8 -0
- package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts.map +1 -0
- package/esm/mcp-server/tools/pipeshubGetRecordContent.js +63 -0
- package/esm/mcp-server/tools/pipeshubGetRecordContent.js.map +1 -0
- package/esm/mcp-server/tools/pipeshubSearch.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubSearch.js +8 -7
- package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubSources.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubSources.js +0 -4
- package/esm/mcp-server/tools/pipeshubSources.js.map +1 -1
- package/esm/mcp-server/tools.d.ts.map +1 -1
- package/esm/mcp-server/tools.js +13 -1
- package/esm/mcp-server/tools.js.map +1 -1
- package/esm/models/agentops.d.ts +119 -0
- package/esm/models/agentops.d.ts.map +1 -0
- package/esm/models/agentops.js +97 -0
- package/esm/models/agentops.js.map +1 -0
- package/esm/models/getrecordcontentop.d.ts +6 -0
- package/esm/models/getrecordcontentop.d.ts.map +1 -0
- package/esm/models/getrecordcontentop.js +5 -0
- package/esm/models/getrecordcontentop.js.map +1 -0
- package/esm/tool-names.d.ts.map +1 -1
- package/esm/tool-names.js +10 -2
- package/esm/tool-names.js.map +1 -1
- package/package.json +3 -2
- package/src/funcs/agentsListAgents.ts +141 -0
- package/src/funcs/agentsStreamConversation.ts +144 -0
- package/src/funcs/agentsStreamMessage.ts +147 -0
- package/src/funcs/connectorGetRecordContent.ts +179 -0
- package/src/funcs/conversationsStreamConversation.ts +2 -4
- package/src/funcs/conversationsStreamMessage.ts +0 -8
- package/src/funcs/userGroupsGetAllUserGroups.ts +1 -1
- package/src/hooks/registration.ts +2 -1
- package/src/hooks/request-context.ts +47 -0
- package/src/hooks/requestid.ts +62 -0
- package/src/mcp-server/instructions.ts +106 -0
- package/src/mcp-server/prompts/pipeshubAssistant.ts +37 -0
- package/src/mcp-server/server.ts +18 -64
- package/src/mcp-server/tools/_helpers.ts +216 -1
- package/src/mcp-server/tools/pipeshubAgents.ts +68 -0
- package/src/mcp-server/tools/pipeshubChat.ts +78 -23
- package/src/mcp-server/tools/pipeshubDirectory.ts +0 -1
- package/src/mcp-server/tools/pipeshubDownloadRecord.ts +0 -4
- package/src/mcp-server/tools/pipeshubGetRecordContent.ts +67 -0
- package/src/mcp-server/tools/pipeshubSearch.ts +8 -7
- package/src/mcp-server/tools/pipeshubSources.ts +0 -5
- package/src/mcp-server/tools.ts +14 -1
- package/src/models/agentops.ts +160 -0
- package/src/models/getrecordcontentop.ts +11 -0
- package/src/tool-names.ts +10 -2
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// Curated PipesHub chat tool. Hand-written; replaces the two generated
|
|
2
|
-
// tools `pipeshub-chat` (create) and `pipeshub-chat-continue` (follow-up)
|
|
3
|
-
// with a single tool that branches on whether `conversationId` was given.
|
|
4
|
-
//
|
|
5
|
-
// Wire transport: SSE. The non-streaming `/conversations/create` endpoint
|
|
6
|
-
// is broken in production today, so we drive
|
|
7
1
|
// `POST /conversations/stream` (and
|
|
8
2
|
// `POST /conversations/{conversationId}/messages/stream` for follow-ups),
|
|
9
3
|
// accumulate the frames server-side, and hand the LLM a single trimmed
|
|
@@ -29,9 +23,12 @@
|
|
|
29
23
|
import * as z from "zod";
|
|
30
24
|
import { conversationsStreamConversation } from "../../funcs/conversationsStreamConversation.js";
|
|
31
25
|
import { conversationsStreamMessage } from "../../funcs/conversationsStreamMessage.js";
|
|
26
|
+
import { agentsStreamConversation } from "../../funcs/agentsStreamConversation.js";
|
|
27
|
+
import { agentsStreamMessage } from "../../funcs/agentsStreamMessage.js";
|
|
32
28
|
import { ToolDefinition } from "../tools.js";
|
|
33
29
|
import {
|
|
34
30
|
errorResult,
|
|
31
|
+
httpErrorResult,
|
|
35
32
|
iterateSSE,
|
|
36
33
|
jsonResult,
|
|
37
34
|
trimConversation,
|
|
@@ -68,16 +65,28 @@ const args = {
|
|
|
68
65
|
),
|
|
69
66
|
modelName: z.string().optional(),
|
|
70
67
|
modelFriendlyName: z.string().optional(),
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
+ "`
|
|
74
|
-
+ "
|
|
75
|
-
+ "
|
|
76
|
-
+ "
|
|
77
|
-
+ "
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
agentId: z.string().optional().describe(
|
|
69
|
+
"Optional PipesHub agent to converse with — the `agentId` from "
|
|
70
|
+
+ "`pipeshub_agents`. When set, this turn runs against that agent's "
|
|
71
|
+
+ "configuration (prompt, tools, knowledge). On follow-up turns pass the "
|
|
72
|
+
+ "SAME `agentId` together with the `conversationId` returned by the "
|
|
73
|
+
+ "previous call. Omit for a plain (non-agent) conversation. If unsure "
|
|
74
|
+
+ "which agent to use, call `pipeshub_agents` first to see the options.",
|
|
75
|
+
),
|
|
76
|
+
chatMode: z.enum([
|
|
77
|
+
"internal_search",
|
|
78
|
+
"web_search",
|
|
79
|
+
"auto",
|
|
80
|
+
"quick",
|
|
81
|
+
"verification",
|
|
82
|
+
"deep",
|
|
83
|
+
]).optional().describe(
|
|
84
|
+
"Response strategy. The valid values depend on whether `agentId` is set:\n"
|
|
85
|
+
+ "- WITHOUT `agentId` (plain chat): `internal_search` — answer from the "
|
|
86
|
+
+ "org's indexed knowledge (default) — or `web_search` — answer from the "
|
|
87
|
+
+ "live web.\n"
|
|
88
|
+
+ "- WITH `agentId` (agent chat): `auto` (let the agent decide; default), "
|
|
89
|
+
+ "`quick`, `verification`, or `deep`.",
|
|
81
90
|
),
|
|
82
91
|
};
|
|
83
92
|
|
|
@@ -91,6 +100,9 @@ the user asks about their documents, files, knowledge base, company policies,
|
|
|
91
100
|
or anything that could plausibly be answered by content in their PipesHub-indexed
|
|
92
101
|
sources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).
|
|
93
102
|
Grounds the answer in the user's actual data and returns citations.
|
|
103
|
+
Answers come from a few retrieved passages, not whole documents — for
|
|
104
|
+
any task needing a document's full content, use
|
|
105
|
+
\`pipeshub_get_record_content\` instead.
|
|
94
106
|
|
|
95
107
|
**Web search** (\`chatMode: "web_search"\`): Use when the user asks about
|
|
96
108
|
current events, public information, or anything unlikely to be in the org's
|
|
@@ -98,8 +110,11 @@ internal knowledge base. Pass \`chatMode: "web_search"\` and this tool will
|
|
|
98
110
|
search the public web instead.
|
|
99
111
|
|
|
100
112
|
**When to pick this over other tools:**
|
|
101
|
-
- "
|
|
102
|
-
|
|
113
|
+
- "Summarize <doc>" / "key points of <doc>" / "what does <document> say
|
|
114
|
+
about X?" → NOT this tool. Use \`pipeshub_search\` →
|
|
115
|
+
\`pipeshub_get_record_content\`: answering for a specific document
|
|
116
|
+
requires its full content, and chat only sees a few retrieved
|
|
117
|
+
passages, never the whole document.
|
|
103
118
|
- "What's our policy on Y?" → \`pipeshub_chat\` (internal_search)
|
|
104
119
|
- "What's in the news about Z?" → \`pipeshub_chat\` (web_search)
|
|
105
120
|
- "What is the latest version of <library>?" → \`pipeshub_chat\` (web_search)
|
|
@@ -134,8 +149,42 @@ cited document, take \`citations[*].recordId\` and call
|
|
|
134
149
|
const fetchOptions = { signal: ctx.signal };
|
|
135
150
|
let response: Response;
|
|
136
151
|
|
|
137
|
-
if (args.
|
|
138
|
-
//
|
|
152
|
+
if (args.agentId) {
|
|
153
|
+
// Agent conversation. Agent chatMode vocabulary defaults to `auto`.
|
|
154
|
+
const agentChatMode = args.chatMode ?? "auto";
|
|
155
|
+
if (args.conversationId) {
|
|
156
|
+
// Continue an existing agent conversation.
|
|
157
|
+
const [result] = await agentsStreamMessage(client, {
|
|
158
|
+
agentKey: args.agentId,
|
|
159
|
+
conversationId: args.conversationId,
|
|
160
|
+
body: {
|
|
161
|
+
query: args.query,
|
|
162
|
+
modelKey: args.modelKey,
|
|
163
|
+
modelName: args.modelName,
|
|
164
|
+
modelFriendlyName: args.modelFriendlyName,
|
|
165
|
+
chatMode: agentChatMode,
|
|
166
|
+
},
|
|
167
|
+
}, { fetchOptions }).$inspect();
|
|
168
|
+
if (!result.ok) return errorResult(result.error.message);
|
|
169
|
+
response = result.value;
|
|
170
|
+
} else {
|
|
171
|
+
// Start a new agent conversation.
|
|
172
|
+
const [result] = await agentsStreamConversation(client, {
|
|
173
|
+
agentKey: args.agentId,
|
|
174
|
+
body: {
|
|
175
|
+
query: args.query,
|
|
176
|
+
filters: args.filters,
|
|
177
|
+
modelKey: args.modelKey,
|
|
178
|
+
modelName: args.modelName,
|
|
179
|
+
modelFriendlyName: args.modelFriendlyName,
|
|
180
|
+
chatMode: agentChatMode,
|
|
181
|
+
},
|
|
182
|
+
}, { fetchOptions }).$inspect();
|
|
183
|
+
if (!result.ok) return errorResult(result.error.message);
|
|
184
|
+
response = result.value;
|
|
185
|
+
}
|
|
186
|
+
} else if (args.conversationId) {
|
|
187
|
+
// Continue an existing (non-agent) conversation.
|
|
139
188
|
const [result] = await conversationsStreamMessage(client, {
|
|
140
189
|
conversationId: args.conversationId,
|
|
141
190
|
body: {
|
|
@@ -143,25 +192,31 @@ cited document, take \`citations[*].recordId\` and call
|
|
|
143
192
|
modelKey: args.modelKey,
|
|
144
193
|
modelName: args.modelName,
|
|
145
194
|
modelFriendlyName: args.modelFriendlyName,
|
|
146
|
-
chatMode: args.chatMode,
|
|
195
|
+
chatMode: args.chatMode ?? "internal_search",
|
|
147
196
|
},
|
|
148
197
|
}, { fetchOptions }).$inspect();
|
|
149
198
|
if (!result.ok) return errorResult(result.error.message);
|
|
150
199
|
response = result.value;
|
|
151
200
|
} else {
|
|
152
|
-
// Start a new conversation.
|
|
201
|
+
// Start a new (non-agent) conversation.
|
|
153
202
|
const [result] = await conversationsStreamConversation(client, {
|
|
154
203
|
query: args.query,
|
|
155
204
|
filters: args.filters,
|
|
156
205
|
modelKey: args.modelKey,
|
|
157
206
|
modelName: args.modelName,
|
|
158
207
|
modelFriendlyName: args.modelFriendlyName,
|
|
159
|
-
chatMode: args.chatMode,
|
|
208
|
+
chatMode: args.chatMode ?? "internal_search",
|
|
160
209
|
}, { fetchOptions }).$inspect();
|
|
161
210
|
if (!result.ok) return errorResult(result.error.message);
|
|
162
211
|
response = result.value;
|
|
163
212
|
}
|
|
164
213
|
|
|
214
|
+
// The streaming funcs accept any HTTP status as a "successful request",
|
|
215
|
+
// so explicitly reject non-2xx (auth, not-found, server errors) with a
|
|
216
|
+
// clear message before we try to read the body as SSE.
|
|
217
|
+
const httpErr = await httpErrorResult(response, "PipesHub chat request");
|
|
218
|
+
if (httpErr) return httpErr;
|
|
219
|
+
|
|
165
220
|
// Drain the SSE stream. We only need the terminal `complete` (or
|
|
166
221
|
// `error`) frame; everything else is observability and ignored.
|
|
167
222
|
let finalConversation: any = null;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// Curated download tool. Hand-written; replaces the generated
|
|
2
|
-
// `pipeshub-download-record`. Pipes raw bytes back through formatResult
|
|
3
|
-
// so binary downloads (PDFs, images) are returned correctly.
|
|
4
|
-
|
|
5
1
|
import * as z from "zod";
|
|
6
2
|
import { recordsStreamRecordBuffer } from "../../funcs/recordsStreamRecordBuffer.js";
|
|
7
3
|
import { formatResult, ToolDefinition } from "../tools.js";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { connectorGetRecordContent } from "../../funcs/connectorGetRecordContent.js";
|
|
3
|
+
import { ToolDefinition } from "../tools.js";
|
|
4
|
+
import { errorResult, httpErrorResult, readJson } from "./_helpers.js";
|
|
5
|
+
|
|
6
|
+
const args = {
|
|
7
|
+
recordId: z.string().min(1).describe(
|
|
8
|
+
"Record identifier — usually a UUID for connector-sourced records or "
|
|
9
|
+
+ "a 24-character ObjectId for uploaded records. Get it from a chat "
|
|
10
|
+
+ "citation (`citations[*].recordId`) or from a `pipeshub_search` hit.",
|
|
11
|
+
),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const tool$pipeshubGetRecordContent: ToolDefinition<typeof args> = {
|
|
15
|
+
name: "pipeshub_get_record_content",
|
|
16
|
+
description:
|
|
17
|
+
`Read a record's full parsed content by \`recordId\` — the only way to
|
|
18
|
+
see a document's COMPLETE text.
|
|
19
|
+
|
|
20
|
+
Use it whenever the answer depends on a document's full content — any
|
|
21
|
+
task where missing a part could make the answer wrong: summarize / key
|
|
22
|
+
points / action items; extract or list ALL of something; check whether
|
|
23
|
+
or where the doc mentions X; translate, rewrite, outline, or review the
|
|
24
|
+
doc; compare named docs (fetch each); any question scoped to one named
|
|
25
|
+
document. \`pipeshub_chat\` cannot do these — it only sees a few
|
|
26
|
+
retrieved passages, never the whole document. Get the \`recordId\` from
|
|
27
|
+
a \`pipeshub_search\` top hit or a chat citation.
|
|
28
|
+
|
|
29
|
+
Judge by the user's INTENT, not their keywords: they need not say
|
|
30
|
+
"summarize", "key points", or "extract". Reason about what a good
|
|
31
|
+
answer requires — if it would need the whole document (e.g. "what's
|
|
32
|
+
this doc about?", "walk me through the report", "anything in here
|
|
33
|
+
about Y?"), that is a full-content task, so call this tool.
|
|
34
|
+
|
|
35
|
+
Returns a single \`content\` string: a short metadata header (title,
|
|
36
|
+
source, key fields, and a pre-generated summary) followed by the
|
|
37
|
+
record's full parsed text — paragraphs, tables, and lists in reading
|
|
38
|
+
order. For a record with no extractable content, \`content\` is the
|
|
39
|
+
literal \`No record found\`. Use \`pipeshub_download_record\` only when
|
|
40
|
+
you need the original file bytes.`,
|
|
41
|
+
scopes: ["read"],
|
|
42
|
+
annotations: {
|
|
43
|
+
title: "Get a record's full parsed content",
|
|
44
|
+
destructiveHint: false,
|
|
45
|
+
idempotentHint: true,
|
|
46
|
+
openWorldHint: false,
|
|
47
|
+
readOnlyHint: true,
|
|
48
|
+
},
|
|
49
|
+
args,
|
|
50
|
+
tool: async (client, args, ctx) => {
|
|
51
|
+
const [result] = await connectorGetRecordContent(client, {
|
|
52
|
+
recordId: args.recordId,
|
|
53
|
+
}, { fetchOptions: { signal: ctx.signal } }).$inspect();
|
|
54
|
+
if (!result.ok) return errorResult(result.error.message);
|
|
55
|
+
|
|
56
|
+
// The SDK func uses errorCodes:[], so any non-2xx comes back as an
|
|
57
|
+
// ok=false Response — surface it as an error rather than parsing it.
|
|
58
|
+
const httpErr = await httpErrorResult(result.value, "Get record content");
|
|
59
|
+
if (httpErr) return httpErr;
|
|
60
|
+
|
|
61
|
+
// Success: the endpoint returns { content: <string> }. Hand the LLM the
|
|
62
|
+
// plain text (real newlines), not the JSON wrapper.
|
|
63
|
+
const parsed = await readJson<{ content?: string }>(result.value);
|
|
64
|
+
if (!parsed.ok) return parsed.result;
|
|
65
|
+
return { content: [{ type: "text", text: parsed.value.content ?? "" }] };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Curated semantic-search tool. Hand-written; replaces the generated
|
|
2
|
-
// `pipeshub-search` tool. Trims the response to the fields the LLM
|
|
3
|
-
// actually needs (recordId, recordName, score, snippet, webUrl).
|
|
4
1
|
|
|
5
2
|
import * as z from "zod";
|
|
6
3
|
import { semanticSearchSearch } from "../../funcs/semanticSearchSearch.js";
|
|
@@ -26,12 +23,16 @@ export const tool$pipeshubSearch: ToolDefinition<typeof args> = {
|
|
|
26
23
|
description:
|
|
27
24
|
`Vector / semantic search across the org's indexed documents.
|
|
28
25
|
|
|
29
|
-
**Use this
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
does the retrieval internally and grounds the answer in
|
|
26
|
+
**Use this when the user wants to LOCATE a document** — by name, topic,
|
|
27
|
+
or a phrase to grep for — and to resolve it to a \`recordId\`. For
|
|
28
|
+
open-ended questions across many documents, use \`pipeshub_chat\`
|
|
29
|
+
instead, which does the retrieval internally and grounds the answer in
|
|
30
|
+
citations.
|
|
33
31
|
|
|
34
32
|
Typical uses:
|
|
33
|
+
- Resolve a doc name / topic into a \`recordId\` for
|
|
34
|
+
\`pipeshub_get_record_content\` — step 1 of any full-document task
|
|
35
|
+
(summarize, extract, review, "what does the doc say?").
|
|
35
36
|
- Resolve a filename / phrase into a \`recordId\` for
|
|
36
37
|
\`pipeshub_download_record\`.
|
|
37
38
|
- Show the user a ranked list of matching files when they ask "find /
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// Curated discovery tool. Hand-written; merges two generated tools
|
|
2
|
-
// (`pipeshub-list-sources` for connectors+KB and `pipeshub-list-models`
|
|
3
|
-
// for available LLMs / embedding models) into one tool, since the LLM
|
|
4
|
-
// almost always wants both before the first chat turn.
|
|
5
|
-
|
|
6
1
|
import * as z from "zod";
|
|
7
2
|
import { knowledgeHubGetKnowledgeHubRootNodes } from "../../funcs/knowledgeHubGetKnowledgeHubRootNodes.js";
|
|
8
3
|
import { aiModelsProvidersGetAvailableModelsByType } from "../../funcs/aiModelsProvidersGetAvailableModelsByType.js";
|
package/src/mcp-server/tools.ts
CHANGED
|
@@ -75,9 +75,22 @@ export async function formatResult(
|
|
|
75
75
|
content = data == null
|
|
76
76
|
? []
|
|
77
77
|
: [{ type: "audio", data, mimeType: contentType }];
|
|
78
|
-
} else
|
|
78
|
+
} else if (
|
|
79
|
+
contentType.startsWith("text/")
|
|
80
|
+
|| contentType.includes("json")
|
|
81
|
+
|| contentType.includes("xml")
|
|
82
|
+
|| contentType.includes("yaml")
|
|
83
|
+
) {
|
|
79
84
|
const text = await response.text();
|
|
80
85
|
content = [{ type: "text", text }];
|
|
86
|
+
} else {
|
|
87
|
+
const blob = await valueToBase64(await response.arrayBuffer());
|
|
88
|
+
content = blob == null
|
|
89
|
+
? []
|
|
90
|
+
: [{
|
|
91
|
+
type: "resource",
|
|
92
|
+
resource: { uri: response.url, mimeType: contentType, blob },
|
|
93
|
+
}];
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
return response.ok ? { content } : { content, isError: true };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Request models for the agent
|
|
2
|
+
// endpoints that the vendored SDK does not yet cover:
|
|
3
|
+
// - GET /agents (listAgents)
|
|
4
|
+
// - POST /agents/{agentKey}/conversations/stream (streamAgentConversation)
|
|
5
|
+
// - POST /agents/{agentKey}/conversations/{conversationId}/messages/stream
|
|
6
|
+
// (streamAgentConversationMessage)
|
|
7
|
+
// Mirrors the shapes in the canonical OpenAPI spec; reuses the existing
|
|
8
|
+
// `Filters` model. Unknown fields are stripped server-side, so the shared
|
|
9
|
+
// body schema is safe to reuse for both create and follow-up turns.
|
|
10
|
+
|
|
11
|
+
import * as z from "zod";
|
|
12
|
+
import { Filters, Filters$zodSchema } from "./filters.js";
|
|
13
|
+
|
|
14
|
+
/** Query params for `GET /agents`. */
|
|
15
|
+
export type ListAgentsRequest = {
|
|
16
|
+
page?: number | undefined;
|
|
17
|
+
limit?: number | undefined;
|
|
18
|
+
search?: string | undefined;
|
|
19
|
+
sortBy?: string | undefined;
|
|
20
|
+
sortOrder?: "asc" | "desc" | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ListAgentsRequest$zodSchema: z.ZodType<ListAgentsRequest> = z
|
|
24
|
+
.object({
|
|
25
|
+
page: z.int().optional(),
|
|
26
|
+
limit: z.int().optional(),
|
|
27
|
+
search: z.string().optional(),
|
|
28
|
+
sortBy: z.string().optional(),
|
|
29
|
+
sortOrder: z.enum(["asc", "desc"]).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Shared body for the agent conversation stream endpoints. `query` is the
|
|
34
|
+
* only required field; everything else is an optional override / routing hint.
|
|
35
|
+
* `chatMode` for agents is one of `auto | quick | verification | deep`.
|
|
36
|
+
*/
|
|
37
|
+
export type AgentStreamConversationBody = {
|
|
38
|
+
query: string;
|
|
39
|
+
recordIds?: Array<string> | undefined;
|
|
40
|
+
filters?: Filters | undefined;
|
|
41
|
+
chatMode?: string | undefined;
|
|
42
|
+
modelKey?: string | undefined;
|
|
43
|
+
modelName?: string | undefined;
|
|
44
|
+
modelFriendlyName?: string | undefined;
|
|
45
|
+
tools?: Array<string> | undefined;
|
|
46
|
+
timezone?: string | undefined;
|
|
47
|
+
currentTime?: string | undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const AgentStreamConversationBody$zodSchema: z.ZodType<
|
|
51
|
+
AgentStreamConversationBody
|
|
52
|
+
> = z.object({
|
|
53
|
+
query: z.string().min(1),
|
|
54
|
+
recordIds: z.array(z.string()).optional(),
|
|
55
|
+
filters: Filters$zodSchema.optional(),
|
|
56
|
+
chatMode: z.string().optional(),
|
|
57
|
+
modelKey: z.string().optional(),
|
|
58
|
+
modelName: z.string().optional(),
|
|
59
|
+
modelFriendlyName: z.string().optional(),
|
|
60
|
+
tools: z.array(z.string()).optional(),
|
|
61
|
+
timezone: z.string().optional(),
|
|
62
|
+
currentTime: z.string().optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** `POST /agents/{agentKey}/conversations/stream` — path param + body. */
|
|
66
|
+
export type AgentStreamCreateConversationRequest = {
|
|
67
|
+
agentKey: string;
|
|
68
|
+
body: AgentStreamConversationBody;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const AgentStreamCreateConversationRequest$zodSchema: z.ZodType<
|
|
72
|
+
AgentStreamCreateConversationRequest
|
|
73
|
+
> = z.object({
|
|
74
|
+
agentKey: z.string().min(1),
|
|
75
|
+
body: AgentStreamConversationBody$zodSchema,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* `POST /agents/{agentKey}/conversations/{conversationId}/messages/stream` —
|
|
80
|
+
* two path params + body.
|
|
81
|
+
*/
|
|
82
|
+
export type AgentAddMessageStreamRequest = {
|
|
83
|
+
agentKey: string;
|
|
84
|
+
conversationId: string;
|
|
85
|
+
body: AgentStreamConversationBody;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const AgentAddMessageStreamRequest$zodSchema: z.ZodType<
|
|
89
|
+
AgentAddMessageStreamRequest
|
|
90
|
+
> = z.object({
|
|
91
|
+
agentKey: z.string().min(1),
|
|
92
|
+
conversationId: z.string(),
|
|
93
|
+
body: AgentStreamConversationBody$zodSchema,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── Response models ──────────────────────────────────────────────────────────
|
|
97
|
+
// Validated subsets of the agent responses (only the fields the curated tools
|
|
98
|
+
// surface; zod strips the rest). Types are inferred from the schemas.
|
|
99
|
+
//
|
|
100
|
+
// As of the enriched `GET /agents`, the list item carries `toolsets` and
|
|
101
|
+
// `knowledge` directly, so the curated tool no longer needs a per-agent detail
|
|
102
|
+
// fetch.
|
|
103
|
+
|
|
104
|
+
const AgentWebSearch$zodSchema = z.object({
|
|
105
|
+
provider: z.string().optional(),
|
|
106
|
+
providerKey: z.string().optional(),
|
|
107
|
+
providerLabel: z.string().optional(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const AgentToolsetTool$zodSchema = z.object({
|
|
111
|
+
name: z.string().nullish(),
|
|
112
|
+
fullName: z.string().nullish(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const AgentToolset$zodSchema = z.object({
|
|
116
|
+
name: z.string().nullish(),
|
|
117
|
+
displayName: z.string().nullish(),
|
|
118
|
+
instanceName: z.string().nullish(),
|
|
119
|
+
tools: z.array(AgentToolsetTool$zodSchema).default([]),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const AgentKnowledge$zodSchema = z.object({
|
|
123
|
+
name: z.string().nullish(),
|
|
124
|
+
displayName: z.string().nullish(),
|
|
125
|
+
type: z.string().nullish(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* One item of the enriched `GET /agents` list — includes `toolsets` and
|
|
130
|
+
* `knowledge` (the routing-critical capability signal). `default([])` keeps it
|
|
131
|
+
* working against an older list endpoint that doesn't return them.
|
|
132
|
+
*/
|
|
133
|
+
export const AgentSummary$zodSchema = z.object({
|
|
134
|
+
_key: z.string(),
|
|
135
|
+
name: z.string(),
|
|
136
|
+
description: z.string().nullish(),
|
|
137
|
+
systemPrompt: z.string().nullish(),
|
|
138
|
+
startMessage: z.string().nullish(),
|
|
139
|
+
tags: z.array(z.string()).default([]),
|
|
140
|
+
webSearch: AgentWebSearch$zodSchema.nullish(),
|
|
141
|
+
isActive: z.boolean().nullish(),
|
|
142
|
+
toolsets: z.array(AgentToolset$zodSchema).default([]),
|
|
143
|
+
knowledge: z.array(AgentKnowledge$zodSchema).default([]),
|
|
144
|
+
});
|
|
145
|
+
export type AgentSummary = z.infer<typeof AgentSummary$zodSchema>;
|
|
146
|
+
|
|
147
|
+
const AgentListPagination$zodSchema = z.object({
|
|
148
|
+
totalItems: z.number().nullish(),
|
|
149
|
+
total: z.number().nullish(),
|
|
150
|
+
hasNext: z.boolean().nullish(),
|
|
151
|
+
hasNextPage: z.boolean().nullish(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** Envelope of `GET /agents`. */
|
|
155
|
+
export const AgentListEnvelope$zodSchema = z.object({
|
|
156
|
+
success: z.boolean().nullish(),
|
|
157
|
+
agents: z.array(AgentSummary$zodSchema).default([]),
|
|
158
|
+
pagination: AgentListPagination$zodSchema.nullish(),
|
|
159
|
+
});
|
|
160
|
+
export type AgentListEnvelope = z.infer<typeof AgentListEnvelope$zodSchema>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
export type GetRecordContentRequest = { recordId: string };
|
|
4
|
+
|
|
5
|
+
export const GetRecordContentRequest$zodSchema: z.ZodType<
|
|
6
|
+
GetRecordContentRequest
|
|
7
|
+
> = z.object({
|
|
8
|
+
recordId: z.string().describe(
|
|
9
|
+
"Record ID to fetch. Obtain it from a `pipeshub_search` result (`hits[*].recordId`) or a chat citation (`citations[*].recordId`).",
|
|
10
|
+
),
|
|
11
|
+
});
|
package/src/tool-names.ts
CHANGED
|
@@ -6,18 +6,26 @@ export const toolNames: Array<{ name: string; description: string }>= [
|
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
8
|
"name": "pipeshub_chat",
|
|
9
|
-
"description": "**Primary chat tool — handles both internal knowledge queries and web search.**\n\n**Internal search** (default, `chatMode: \"internal_search\"`): Use whenever\nthe user asks about their documents, files, knowledge base, company policies,\nor anything that could plausibly be answered by content in their PipesHub-indexed\nsources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).\nGrounds the answer in the user's actual data and returns citations.\n\n**Web search** (`chatMode: \"web_search\"`): Use when the user asks about\ncurrent events, public information, or anything unlikely to be in the org's\ninternal knowledge base. Pass `chatMode: \"web_search\"` and this tool will\nsearch the public web instead.\n\n**When to pick this over other tools:**\n- \"
|
|
9
|
+
"description": "**Primary chat tool — handles both internal knowledge queries and web search.**\n\n**Internal search** (default, `chatMode: \"internal_search\"`): Use whenever\nthe user asks about their documents, files, knowledge base, company policies,\nor anything that could plausibly be answered by content in their PipesHub-indexed\nsources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).\nGrounds the answer in the user's actual data and returns citations.\nAnswers come from a few retrieved passages, not whole documents — for\nany task needing a document's full content, use\n`pipeshub_get_record_content` instead.\n\n**Web search** (`chatMode: \"web_search\"`): Use when the user asks about\ncurrent events, public information, or anything unlikely to be in the org's\ninternal knowledge base. Pass `chatMode: \"web_search\"` and this tool will\nsearch the public web instead.\n\n**When to pick this over other tools:**\n- \"Summarize <doc>\" / \"key points of <doc>\" / \"what does <document> say\n about X?\" → NOT this tool. Use `pipeshub_search` →\n `pipeshub_get_record_content`: answering for a specific document\n requires its full content, and chat only sees a few retrieved\n passages, never the whole document.\n- \"What's our policy on Y?\" → `pipeshub_chat` (internal_search)\n- \"What's in the news about Z?\" → `pipeshub_chat` (web_search)\n- \"What is the latest version of <library>?\" → `pipeshub_chat` (web_search)\n- \"Find / locate the file named X\" → `pipeshub_search` (then\n `pipeshub_download_record` if the user wants the bytes).\n\n**Conversation lifecycle** — one tool, both start and continue:\n\n- **First turn**: omit `conversationId`. The server creates a new\n conversation; capture `conversationId` from the response.\n- **Follow-up turn**: pass the `conversationId` from the previous\n response. Server-side context is preserved — do NOT replay earlier\n messages, and `filters` is ignored on follow-ups (set once at\n creation).\n\nOnly re-omit `conversationId` (start a fresh conversation) when the\nuser explicitly asks to start over / clear context.\n\nThe response contains the AI's `answer` plus `citations`. To download a\ncited document, take `citations[*].recordId` and call\n`pipeshub_download_record`."
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"name": "pipeshub_search",
|
|
13
|
-
"description": "Vector / semantic search across the org's indexed documents.\n\n**Use this
|
|
13
|
+
"description": "Vector / semantic search across the org's indexed documents.\n\n**Use this when the user wants to LOCATE a document** — by name, topic,\nor a phrase to grep for — and to resolve it to a `recordId`. For\nopen-ended questions across many documents, use `pipeshub_chat`\ninstead, which does the retrieval internally and grounds the answer in\ncitations.\n\nTypical uses:\n- Resolve a doc name / topic into a `recordId` for\n `pipeshub_get_record_content` — step 1 of any full-document task\n (summarize, extract, review, \"what does the doc say?\").\n- Resolve a filename / phrase into a `recordId` for\n `pipeshub_download_record`.\n- Show the user a ranked list of matching files when they ask \"find /\n search for X\".\n\nThe response is trimmed to one row per hit:\n`{ recordId, recordName, score, snippet, mimeType, webUrl, ... }`.\nHighest `score` first; multiple hits may share the same `recordId`\n(different chunks of the same record)."
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"name": "pipeshub_download_record",
|
|
17
17
|
"description": "Stream the binary content of a single record from PipesHub.\n\nTypical sources for the `recordId`:\n- A chat citation:\n `pipeshub_chat` response → `citations[*].recordId`.\n- A search result:\n `pipeshub_search` response → `hits[*].recordId` /\n `uniqueRecords[*].recordId`.\n\nResponse `Content-Type` is forwarded from the upstream service —\n`application/pdf`, `application/octet-stream`, etc. Binary content is\nreturned base64-encoded; text content is returned inline."
|
|
18
18
|
},
|
|
19
|
+
{
|
|
20
|
+
"name": "pipeshub_get_record_content",
|
|
21
|
+
"description": "Read a record's full parsed content by `recordId` — the only way to\nsee a document's COMPLETE text.\n\nUse it whenever the answer depends on a document's full content — any\ntask where missing a part could make the answer wrong: summarize / key\npoints / action items; extract or list ALL of something; check whether\nor where the doc mentions X; translate, rewrite, outline, or review the\ndoc; compare named docs (fetch each); any question scoped to one named\ndocument. `pipeshub_chat` cannot do these — it only sees a few\nretrieved passages, never the whole document. Get the `recordId` from\na `pipeshub_search` top hit or a chat citation.\n\nJudge by the user's INTENT, not their keywords: they need not say\n\"summarize\", \"key points\", or \"extract\". Reason about what a good\nanswer requires — if it would need the whole document (e.g. \"what's\nthis doc about?\", \"walk me through the report\", \"anything in here\nabout Y?\"), that is a full-content task, so call this tool.\n\nReturns a single `content` string: a short metadata header (title,\nsource, key fields, and a pre-generated summary) followed by the\nrecord's full parsed text — paragraphs, tables, and lists in reading\norder. For a record with no extractable content, `content` is the\nliteral `No record found`. Use `pipeshub_download_record` only when\nyou need the original file bytes."
|
|
22
|
+
},
|
|
19
23
|
{
|
|
20
24
|
"name": "pipeshub_directory",
|
|
21
25
|
"description": "Look up people, groups, and teams in PipesHub. One tool with five\nactions — pick the right `action`:\n\n- `whoami` — who is the caller? Use this whenever you need the\n authenticated user's own id, email, or full name (e.g. before\n `get_user` on themselves).\n- `list_users` — search / page through org users.\n- `get_user` — full `User` document for one user (requires `userId`).\n- `list_groups` — list user groups with `userCount`.\n- `list_my_teams` — teams the caller belongs to, with capability flags\n (`canEdit` / `canDelete` / `canManageMembers`).\n\nOutput shape varies by action; see each action's docs above."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "pipeshub_agents",
|
|
29
|
+
"description": "List the PipesHub **agents** configured for this org, each with its\ncapabilities.\n\nAgents are specialized assistants (custom system prompt, tools, knowledge\nscope). To converse with one, take its `agentId` and pass it to\n`pipeshub_chat`'s `agentId` argument.\n\nEach agent is returned as:\n`{ agentId, name, description, systemPrompt, startMessage, tags, webSearch,\nisActive, toolsets, knowledge }`.\n- `toolsets` — what the agent can DO: each `{ name, tools }` where `name`\n is the connector (e.g. `jira`, `gmail`) and `tools` are the runnable\n tool ids (e.g. `jira.create_issue`, `gmail.send_email`).\n- `knowledge` — what the agent can READ: each `{ name, type }` (e.g.\n `Confluence-2` / `Confluence`).\n\n**Route on `toolsets`/`knowledge`, not the name** — names and descriptions\nare often generic or misleading. Match the request to the agent whose tools can\nactually perform it (e.g. \"create a Jira ticket\" → the agent whose toolset is\n`jira` and whose tools include `jira.create_issue`). If NO agent has a tool\nfor the requested action, say so — don't force an unrelated agent.\n\nThe list **may be empty** (no agents configured). For plain Q&A when no\nspecific agent is needed, use `pipeshub_chat` WITHOUT `agentId` and pick a\n`chatMode`: `internal_search` (org's indexed knowledge) or `web_search`\n(live web). Use `agentId` everywhere an agent is referenced."
|
|
22
30
|
}
|
|
23
31
|
];
|