@sellable/mcp 0.1.753 → 0.1.755
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/dist/agent-integration-request-context.d.ts +78 -0
- package/dist/agent-integration-request-context.js +74 -0
- package/dist/agent-tool-names.d.ts +1 -1
- package/dist/agent-tool-names.js +3 -0
- package/dist/api.d.ts +15 -0
- package/dist/api.js +37 -12
- package/dist/server.js +56 -21
- package/dist/tool-result-envelope.d.ts +34 -0
- package/dist/tool-result-envelope.js +37 -0
- package/dist/tools/integrations.d.ts +322 -0
- package/dist/tools/integrations.js +502 -0
- package/dist/tools/registry.d.ts +155 -0
- package/dist/tools/registry.js +8 -3
- package/package.json +1 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { getApi, SellableApiError } from "../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* The THREE provider-neutral managed-integration tools.
|
|
4
|
+
*
|
|
5
|
+
* ## PROVIDER TOOL NAMES ARE ARGUMENTS. That is the architecture in one sentence.
|
|
6
|
+
*
|
|
7
|
+
* `gmail-find-email` is a `toolName` STRING passed at call time. Nothing writes it
|
|
8
|
+
* into a config file, a Hermes profile, a `tools.include` list, or an MCP tool name.
|
|
9
|
+
* A source scan in `tests/mcp/integration-tools.test.ts` asserts no provider action
|
|
10
|
+
* key appears under `packages/sellable-install/**` or in any generated config, so
|
|
11
|
+
* the deleted materialization design cannot creep back.
|
|
12
|
+
*
|
|
13
|
+
* Three problems disappear from this phase as a consequence, worth recording so
|
|
14
|
+
* nobody re-solves them: the `profile-materializer` tool-name regex (no hyphens,
|
|
15
|
+
* 128 chars, sorted, duplicate-free) never has to accept a hyphenated provider key;
|
|
16
|
+
* the global-flat-list mutation hazard has no list to mutate; and the context-bloat
|
|
17
|
+
* problem of materializing N provider tools into every prompt does not exist.
|
|
18
|
+
*
|
|
19
|
+
* ## THE ACTOR IS NOT A TOOL INPUT
|
|
20
|
+
*
|
|
21
|
+
* No `inputSchema` here carries a requester, a channel, or an actor field, and this
|
|
22
|
+
* module DESIGNS NO ACTOR MECHANISM — it CONSUMES plan 141-04 T2's. The trusted
|
|
23
|
+
* triple arrives from `boundary.context` (which the MCP context proxy injects only
|
|
24
|
+
* after REJECTING any model-supplied `_meta`) and `server.ts` passes it here, where
|
|
25
|
+
* it becomes an explicit versioned BODY field the route re-verifies against
|
|
26
|
+
* `WorkspaceSlackIdentity`. A model-supplied actor claim is worthless, so the model
|
|
27
|
+
* is never offered a place to put one.
|
|
28
|
+
*
|
|
29
|
+
* ## WHY `registry.ts` AND `server.ts` ARE SHARED, NOT OWNED
|
|
30
|
+
*
|
|
31
|
+
* Phase 142 plan 142-05 adds three CONNECT tools to the same two files. Phase 141
|
|
32
|
+
* merges first, so 142's edits are additive to this plan's landed set. Neither plan
|
|
33
|
+
* may claim exclusive ownership of `registry.ts` or `server.ts`, and neither may
|
|
34
|
+
* reorder the other's entries.
|
|
35
|
+
*
|
|
36
|
+
* Registering three tools also grows `SELLABLE_MCP_TOOL_NAMES`, and
|
|
37
|
+
* `src/lib/sellable-agent/worker-broker.ts:832-837` computes `toolInclude` from it
|
|
38
|
+
* into the runtime revision manifest — so `toolsHash` moves ONCE. That is expected:
|
|
39
|
+
* plan 141-04 T1's drift test classifies a moved `toolsHash` as `RELOAD_MCP`, a
|
|
40
|
+
* one-time installer-version event exercised by plan 141-09's canary. It is NOT a
|
|
41
|
+
* per-connect event; 141-04's zero-drift proof is about connects.
|
|
42
|
+
*
|
|
43
|
+
* Release versioning is not tool registration: `mcp/sellable/package.json` is
|
|
44
|
+
* UNTOUCHED here and plan 141-09 T2 owns the `@sellable/mcp` WIP version.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* THE PUBLISHED RESULT ENVELOPE — a cross-phase contract, not prose.
|
|
48
|
+
*
|
|
49
|
+
* Plan 142-05 T1 carries `SA142-52`, which requires the connect tools' envelopes to
|
|
50
|
+
* MIRROR this one key-for-key, and instructs its executor to STOP and report rather
|
|
51
|
+
* than guess if the contract "is not importable from the MCP package". So it is
|
|
52
|
+
* exported, frozen, and structurally asserted against every handler result.
|
|
53
|
+
*
|
|
54
|
+
* The key set is STABLE across success and failure — `error` and `guidance` are
|
|
55
|
+
* present as `null` rather than omitted — which is what makes a structural
|
|
56
|
+
* comparison possible at all.
|
|
57
|
+
*
|
|
58
|
+
* | key | meaning |
|
|
59
|
+
* | ------------- | -------------------------------------------------------------- |
|
|
60
|
+
* | `ok` | the success/failure DISCRIMINATOR |
|
|
61
|
+
* | `outcome` | snake_case: `ok`, `refused`, `approval_required`, `catalog_incomplete`, `not_connected`, `upstream_degraded` |
|
|
62
|
+
* | `attribution` | snake_case: `provider`, `configuration`, `policy`, `sellable` |
|
|
63
|
+
* | `error` | the snake_case code, forwarded VERBATIM from the route |
|
|
64
|
+
* | `guidance` | one human sentence, or `null` |
|
|
65
|
+
* | `result` | the payload |
|
|
66
|
+
*
|
|
67
|
+
* `approval_required` and `catalog_incomplete` are RESULTS inside this envelope, not
|
|
68
|
+
* errors: the Agent must be able to explain them, not just fail.
|
|
69
|
+
*/
|
|
70
|
+
export const INTEGRATIONS_RESULT_ENVELOPE_KEYS = Object.freeze([
|
|
71
|
+
"ok",
|
|
72
|
+
"outcome",
|
|
73
|
+
"attribution",
|
|
74
|
+
"error",
|
|
75
|
+
"guidance",
|
|
76
|
+
"result",
|
|
77
|
+
]);
|
|
78
|
+
const SELECTOR_PATTERN = /^[a-z0-9_-]+$/i;
|
|
79
|
+
const LIST_PATH = "/api/v3/sellable-agent/integrations/tools/list";
|
|
80
|
+
const DESCRIBE_PATH = "/api/v3/sellable-agent/integrations/tools/describe";
|
|
81
|
+
const CALL_PATH = "/api/v3/sellable-agent/integrations/tools/call";
|
|
82
|
+
/**
|
|
83
|
+
* The client-side deadline, in milliseconds.
|
|
84
|
+
*
|
|
85
|
+
* The backend routes export `maxDuration` 20 s (the two reads) and 40 s (the call),
|
|
86
|
+
* so these sit just above them: the server's own truthful error wins the race and
|
|
87
|
+
* the client bound only fires when the server never answered at all. Before this
|
|
88
|
+
* plan there was NO client-side timeout anywhere in `api.ts`, which meant a hung
|
|
89
|
+
* backend hung the whole Slack turn.
|
|
90
|
+
*/
|
|
91
|
+
const READ_TIMEOUT_MS = 25_000;
|
|
92
|
+
const CALL_TIMEOUT_MS = 45_000;
|
|
93
|
+
const SELECTOR_PROPERTIES = {
|
|
94
|
+
appSlug: {
|
|
95
|
+
type: "string",
|
|
96
|
+
minLength: 1,
|
|
97
|
+
maxLength: 64,
|
|
98
|
+
description: "The connected app, e.g. 'gmail'. Use this when the workspace has exactly one connected account for the app; if it has more than one, the call is refused as ambiguous and you must pass accountLabel instead.",
|
|
99
|
+
},
|
|
100
|
+
bindingId: {
|
|
101
|
+
type: "string",
|
|
102
|
+
minLength: 1,
|
|
103
|
+
maxLength: 64,
|
|
104
|
+
description: "An exact integration binding id, as returned by a previous integrations_list_tools call.",
|
|
105
|
+
},
|
|
106
|
+
accountLabel: {
|
|
107
|
+
type: "string",
|
|
108
|
+
minLength: 1,
|
|
109
|
+
maxLength: 120,
|
|
110
|
+
description: "The human label the user gave the connected account. This is the ONLY way to tell two connections over the same inbox apart, because the provider's own account name can be byte-identical for both.",
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
const ENVELOPE_OUTPUT_SCHEMA = {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
ok: { type: "boolean" },
|
|
117
|
+
outcome: {
|
|
118
|
+
type: "string",
|
|
119
|
+
enum: [
|
|
120
|
+
"ok",
|
|
121
|
+
"refused",
|
|
122
|
+
"approval_required",
|
|
123
|
+
"catalog_incomplete",
|
|
124
|
+
"not_connected",
|
|
125
|
+
"upstream_degraded",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
attribution: {
|
|
129
|
+
type: "string",
|
|
130
|
+
enum: ["provider", "configuration", "policy", "sellable"],
|
|
131
|
+
},
|
|
132
|
+
error: { type: ["string", "null"] },
|
|
133
|
+
guidance: { type: ["string", "null"] },
|
|
134
|
+
result: {},
|
|
135
|
+
},
|
|
136
|
+
required: ["ok", "outcome", "attribution", "error", "guidance", "result"],
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
};
|
|
139
|
+
export const integrationsToolDefinitions = [
|
|
140
|
+
{
|
|
141
|
+
name: "integrations_list_tools",
|
|
142
|
+
description: "List the provider actions this Agent is allowed to run against a connected app, for one connected account. Returns only reviewed, policy-allowed actions plus a COUNT of the ones that are not allowed, so you can truthfully say there are actions you cannot run without naming them. Call this before integrations_describe_tool or integrations_call_tool. A 'catalog_incomplete' outcome means the provider catalog could not be read completely — it does NOT mean the app has no usable actions.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: SELECTOR_PROPERTIES,
|
|
146
|
+
required: [],
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
},
|
|
149
|
+
outputSchema: ENVELOPE_OUTPUT_SCHEMA,
|
|
150
|
+
annotations: {
|
|
151
|
+
title: "List allowed integration actions",
|
|
152
|
+
readOnlyHint: true,
|
|
153
|
+
destructiveHint: false,
|
|
154
|
+
idempotentHint: true,
|
|
155
|
+
openWorldHint: true,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "integrations_describe_tool",
|
|
160
|
+
description: "Describe one allowed provider action's arguments before calling it: per argument you get its type, whether it is required, its default, and its category. For an argument whose valid values come from a remote lookup you get either the resolved option list or an explicit marker telling you to ask the user or narrow the query. The connected account is pinned server-side and is never an argument you supply. Use this instead of guessing at an argument shape.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
...SELECTOR_PROPERTIES,
|
|
165
|
+
toolName: {
|
|
166
|
+
type: "string",
|
|
167
|
+
minLength: 1,
|
|
168
|
+
maxLength: 128,
|
|
169
|
+
description: "The provider action key, exactly as returned by integrations_list_tools, e.g. 'gmail-find-email'.",
|
|
170
|
+
},
|
|
171
|
+
propName: {
|
|
172
|
+
type: "string",
|
|
173
|
+
minLength: 1,
|
|
174
|
+
maxLength: 128,
|
|
175
|
+
description: "Resolve the option list for just this one argument, instead of every argument.",
|
|
176
|
+
},
|
|
177
|
+
optionQuery: {
|
|
178
|
+
type: "string",
|
|
179
|
+
minLength: 1,
|
|
180
|
+
maxLength: 200,
|
|
181
|
+
description: "A server-side filter applied while resolving that argument's options, so a large list can be narrowed rather than truncated.",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
required: ["toolName"],
|
|
185
|
+
additionalProperties: false,
|
|
186
|
+
},
|
|
187
|
+
outputSchema: ENVELOPE_OUTPUT_SCHEMA,
|
|
188
|
+
annotations: {
|
|
189
|
+
title: "Describe an allowed integration action",
|
|
190
|
+
// Resolving options is a READ: `components/configure` creates no provider
|
|
191
|
+
// state.
|
|
192
|
+
readOnlyHint: true,
|
|
193
|
+
destructiveHint: false,
|
|
194
|
+
idempotentHint: true,
|
|
195
|
+
openWorldHint: true,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "integrations_call_tool",
|
|
200
|
+
description: "Run one allowed provider action against a connected account. The exact account is resolved server-side from the binding — you never pass an account id, and one passed in the arguments is rejected. An action outside the reviewed allowlist is refused permanently: that is a policy decision, not a validation problem, so do not retry it with different arguments. An 'approval_required' outcome means a human must approve this exact call on the Integrations page first.",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
...SELECTOR_PROPERTIES,
|
|
205
|
+
toolName: {
|
|
206
|
+
type: "string",
|
|
207
|
+
minLength: 1,
|
|
208
|
+
maxLength: 128,
|
|
209
|
+
description: "The provider action key, exactly as returned by integrations_list_tools.",
|
|
210
|
+
},
|
|
211
|
+
arguments: {
|
|
212
|
+
type: "object",
|
|
213
|
+
description: "The action's arguments, using the exact names integrations_describe_tool returned. The connected account, the authorization, and the provider user id are all set server-side and are stripped if present here.",
|
|
214
|
+
additionalProperties: true,
|
|
215
|
+
},
|
|
216
|
+
approvalId: {
|
|
217
|
+
type: "string",
|
|
218
|
+
minLength: 1,
|
|
219
|
+
maxLength: 128,
|
|
220
|
+
description: "The approval reference returned by a previous approval_required outcome for these EXACT arguments.",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
required: ["toolName"],
|
|
224
|
+
additionalProperties: false,
|
|
225
|
+
},
|
|
226
|
+
outputSchema: ENVELOPE_OUTPUT_SCHEMA,
|
|
227
|
+
annotations: {
|
|
228
|
+
title: "Run an allowed integration action",
|
|
229
|
+
/**
|
|
230
|
+
* DECLARED HONESTLY as `false`, even though every v1-allowlisted Gmail
|
|
231
|
+
* action is `readOnlyHint: true`.
|
|
232
|
+
*
|
|
233
|
+
* This tool is PROVIDER-NEUTRAL and its allowlist is DATA: the moment a
|
|
234
|
+
* mutating action is promoted on any binding, a `true` here becomes a lie
|
|
235
|
+
* that no test would catch, because nothing would have changed in this file.
|
|
236
|
+
* A false negative on an annotation costs a little caution; a false positive
|
|
237
|
+
* costs a silent write.
|
|
238
|
+
*/
|
|
239
|
+
readOnlyHint: false,
|
|
240
|
+
destructiveHint: false,
|
|
241
|
+
idempotentHint: false,
|
|
242
|
+
openWorldHint: true,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
/**
|
|
247
|
+
* The two CLOSED enums, read off the declared `outputSchema` so there is one source.
|
|
248
|
+
*
|
|
249
|
+
* `outcome` and `attribution` are validated by the MCP CLIENT against
|
|
250
|
+
* `ENVELOPE_OUTPUT_SCHEMA` (`@modelcontextprotocol/sdk` runs ajv over
|
|
251
|
+
* `structuredContent`; the python SDK does the same via `jsonschema`). An off-enum
|
|
252
|
+
* value therefore does not degrade the envelope — it makes the client reject the
|
|
253
|
+
* WHOLE response, so the operator loses the refusal AND its reason.
|
|
254
|
+
*
|
|
255
|
+
* `toolError` forwards the route's fields VERBATIM, and off-enum values exist
|
|
256
|
+
* server-side already (`attribution: "app"` in
|
|
257
|
+
* `src/lib/sellable-agent/integration-connect-copy.ts`). So an unrecognized value is
|
|
258
|
+
* normalized FAIL-CLOSED to a refusal by `sellable`, while the route's `error` code —
|
|
259
|
+
* the field that actually carries the reason — is still forwarded verbatim.
|
|
260
|
+
*/
|
|
261
|
+
const ENVELOPE_OUTCOMES = ENVELOPE_OUTPUT_SCHEMA.properties.outcome.enum;
|
|
262
|
+
const ENVELOPE_ATTRIBUTIONS = ENVELOPE_OUTPUT_SCHEMA.properties.attribution.enum;
|
|
263
|
+
function envelope(value) {
|
|
264
|
+
const outcome = value.outcome ?? (value.ok ? "ok" : "refused");
|
|
265
|
+
const attribution = value.attribution ?? (value.ok ? "policy" : "sellable");
|
|
266
|
+
return {
|
|
267
|
+
ok: value.ok,
|
|
268
|
+
outcome: ENVELOPE_OUTCOMES.includes(outcome)
|
|
269
|
+
? outcome
|
|
270
|
+
: value.ok
|
|
271
|
+
? "ok"
|
|
272
|
+
: "refused",
|
|
273
|
+
attribution: ENVELOPE_ATTRIBUTIONS.includes(attribution)
|
|
274
|
+
? attribution
|
|
275
|
+
: "sellable",
|
|
276
|
+
error: value.error ?? null,
|
|
277
|
+
guidance: value.guidance ?? null,
|
|
278
|
+
result: value.result ?? null,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Errors are RETURNED, never thrown, and the route's `error` is forwarded VERBATIM.
|
|
283
|
+
*
|
|
284
|
+
* No MCP-side error-code list is exported: this helper forwards whatever the route
|
|
285
|
+
* said, so a hand-copied list would be a second maintained list the MCP never
|
|
286
|
+
* produces. Copied in shape from `company-info.ts:96-125`.
|
|
287
|
+
*/
|
|
288
|
+
function toolError(action, error) {
|
|
289
|
+
if (error instanceof SellableApiError) {
|
|
290
|
+
let forwarded = {};
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(error.body);
|
|
293
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
294
|
+
forwarded = parsed;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// Plain-text body; fall through to the generic shape below.
|
|
299
|
+
}
|
|
300
|
+
return envelope({
|
|
301
|
+
ok: false,
|
|
302
|
+
outcome: typeof forwarded.outcome === "string" ? forwarded.outcome : "refused",
|
|
303
|
+
attribution: typeof forwarded.attribution === "string"
|
|
304
|
+
? forwarded.attribution
|
|
305
|
+
: error.isAuthError
|
|
306
|
+
? "policy"
|
|
307
|
+
: "sellable",
|
|
308
|
+
error: typeof forwarded.error === "string"
|
|
309
|
+
? forwarded.error
|
|
310
|
+
: `integration_${action}_failed`,
|
|
311
|
+
guidance: typeof forwarded.guidance === "string"
|
|
312
|
+
? forwarded.guidance
|
|
313
|
+
: (error.guidance ?? null),
|
|
314
|
+
result: forwarded.detail ?? null,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
318
|
+
return envelope({
|
|
319
|
+
ok: false,
|
|
320
|
+
outcome: "refused",
|
|
321
|
+
attribution: "sellable",
|
|
322
|
+
error: `integration_${action}_failed`,
|
|
323
|
+
guidance: reason,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/** Bound validation that REJECTS before any API call is made. */
|
|
327
|
+
function validateSelector(input) {
|
|
328
|
+
const selector = {};
|
|
329
|
+
for (const key of ["appSlug", "bindingId"]) {
|
|
330
|
+
const value = input[key];
|
|
331
|
+
if (value === undefined || value === null)
|
|
332
|
+
continue;
|
|
333
|
+
if (typeof value !== "string" ||
|
|
334
|
+
value.length < 1 ||
|
|
335
|
+
value.length > 64 ||
|
|
336
|
+
!SELECTOR_PATTERN.test(value)) {
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
envelope: envelope({
|
|
340
|
+
ok: false,
|
|
341
|
+
error: "integration_selector_rejected",
|
|
342
|
+
guidance: `${key} must be 1-64 characters of letters, digits, hyphen, or underscore.`,
|
|
343
|
+
}),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
selector[key] = value;
|
|
347
|
+
}
|
|
348
|
+
const label = input.accountLabel;
|
|
349
|
+
if (label !== undefined && label !== null) {
|
|
350
|
+
if (typeof label !== "string" || label.length < 1 || label.length > 120) {
|
|
351
|
+
return {
|
|
352
|
+
ok: false,
|
|
353
|
+
envelope: envelope({
|
|
354
|
+
ok: false,
|
|
355
|
+
error: "integration_selector_rejected",
|
|
356
|
+
guidance: "accountLabel must be 1-120 characters.",
|
|
357
|
+
}),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
selector.accountLabel = label;
|
|
361
|
+
}
|
|
362
|
+
if (Object.keys(selector).length === 0) {
|
|
363
|
+
return {
|
|
364
|
+
ok: false,
|
|
365
|
+
envelope: envelope({
|
|
366
|
+
ok: false,
|
|
367
|
+
error: "integration_selector_required",
|
|
368
|
+
guidance: "Pass appSlug, bindingId, or accountLabel to say which connected account to use.",
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { ok: true, selector };
|
|
373
|
+
}
|
|
374
|
+
function validateToolName(value) {
|
|
375
|
+
if (typeof value !== "string" ||
|
|
376
|
+
value.length < 1 ||
|
|
377
|
+
value.length > 128 ||
|
|
378
|
+
!SELECTOR_PATTERN.test(value)) {
|
|
379
|
+
return {
|
|
380
|
+
ok: false,
|
|
381
|
+
envelope: envelope({
|
|
382
|
+
ok: false,
|
|
383
|
+
error: "integration_tool_name_rejected",
|
|
384
|
+
guidance: "toolName must be a provider action key of 1-128 characters, exactly as integrations_list_tools returned it.",
|
|
385
|
+
}),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return { ok: true, toolName: value };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The actor travels as EXPLICIT BODY FIELDS. `server.ts` supplies the verified
|
|
392
|
+
* triple; the model has no way to reach these keys because no `inputSchema`
|
|
393
|
+
* declares them.
|
|
394
|
+
*/
|
|
395
|
+
function actorBody(actor) {
|
|
396
|
+
if (!actor)
|
|
397
|
+
return {};
|
|
398
|
+
return {
|
|
399
|
+
slackRequesterId: actor.requesterId,
|
|
400
|
+
slackChannelId: actor.channelId,
|
|
401
|
+
providerRequestId: actor.providerRequestId,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function normalizeRouteResult(value) {
|
|
405
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
406
|
+
return envelope({
|
|
407
|
+
ok: false,
|
|
408
|
+
error: "integration_response_malformed",
|
|
409
|
+
guidance: "The Sellable integrations API returned an unexpected shape. Retry after refreshing the Sellable MCP package.",
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const record = value;
|
|
413
|
+
const { ok, outcome, attribution, error, guidance, terminal, status, ...rest } = record;
|
|
414
|
+
void terminal;
|
|
415
|
+
void status;
|
|
416
|
+
return envelope({
|
|
417
|
+
ok: ok === true,
|
|
418
|
+
outcome: typeof outcome === "string" ? outcome : undefined,
|
|
419
|
+
attribution: typeof attribution === "string" ? attribution : undefined,
|
|
420
|
+
error: typeof error === "string" ? error : null,
|
|
421
|
+
guidance: typeof guidance === "string" ? guidance : null,
|
|
422
|
+
result: Object.keys(rest).length ? rest : null,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
export async function integrationsListTools(input = {}, actor, agentEffectId) {
|
|
426
|
+
const selector = validateSelector(input);
|
|
427
|
+
if (!selector.ok)
|
|
428
|
+
return selector.envelope;
|
|
429
|
+
try {
|
|
430
|
+
const response = await getApi().post(LIST_PATH, { ...selector.selector, ...actorBody(actor) }, {
|
|
431
|
+
timeoutMs: READ_TIMEOUT_MS,
|
|
432
|
+
...(agentEffectId ? { agentEffectId } : {}),
|
|
433
|
+
});
|
|
434
|
+
return normalizeRouteResult(response);
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
return toolError("list_tools", error);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
export async function integrationsDescribeTool(input = {}, actor, agentEffectId) {
|
|
441
|
+
const selector = validateSelector(input);
|
|
442
|
+
if (!selector.ok)
|
|
443
|
+
return selector.envelope;
|
|
444
|
+
const tool = validateToolName(input.toolName);
|
|
445
|
+
if (!tool.ok)
|
|
446
|
+
return tool.envelope;
|
|
447
|
+
try {
|
|
448
|
+
const response = await getApi().post(DESCRIBE_PATH, {
|
|
449
|
+
...selector.selector,
|
|
450
|
+
toolName: tool.toolName,
|
|
451
|
+
...(typeof input.propName === "string" && input.propName
|
|
452
|
+
? { propName: input.propName }
|
|
453
|
+
: {}),
|
|
454
|
+
...(typeof input.optionQuery === "string" && input.optionQuery
|
|
455
|
+
? { optionQuery: input.optionQuery }
|
|
456
|
+
: {}),
|
|
457
|
+
...actorBody(actor),
|
|
458
|
+
}, {
|
|
459
|
+
timeoutMs: READ_TIMEOUT_MS,
|
|
460
|
+
...(agentEffectId ? { agentEffectId } : {}),
|
|
461
|
+
});
|
|
462
|
+
return normalizeRouteResult(response);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
return toolError("describe_tool", error);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
export async function integrationsCallTool(input = {}, actor, agentEffectId) {
|
|
469
|
+
const selector = validateSelector(input);
|
|
470
|
+
if (!selector.ok)
|
|
471
|
+
return selector.envelope;
|
|
472
|
+
const tool = validateToolName(input.toolName);
|
|
473
|
+
if (!tool.ok)
|
|
474
|
+
return tool.envelope;
|
|
475
|
+
const args = input.arguments;
|
|
476
|
+
if (args !== undefined &&
|
|
477
|
+
(args === null || typeof args !== "object" || Array.isArray(args))) {
|
|
478
|
+
return envelope({
|
|
479
|
+
ok: false,
|
|
480
|
+
error: "integration_arguments_rejected",
|
|
481
|
+
guidance: "arguments must be an object keyed by the argument names integrations_describe_tool returned.",
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const response = await getApi().post(CALL_PATH, {
|
|
486
|
+
...selector.selector,
|
|
487
|
+
toolName: tool.toolName,
|
|
488
|
+
...(args ? { arguments: args } : {}),
|
|
489
|
+
...(typeof input.approvalId === "string" && input.approvalId
|
|
490
|
+
? { approvalId: input.approvalId }
|
|
491
|
+
: {}),
|
|
492
|
+
...actorBody(actor),
|
|
493
|
+
}, {
|
|
494
|
+
timeoutMs: CALL_TIMEOUT_MS,
|
|
495
|
+
...(agentEffectId ? { agentEffectId } : {}),
|
|
496
|
+
});
|
|
497
|
+
return normalizeRouteResult(response);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
return toolError("call_tool", error);
|
|
501
|
+
}
|
|
502
|
+
}
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -8623,6 +8623,161 @@ export declare const allTools: ({
|
|
|
8623
8623
|
openWorldHint: boolean;
|
|
8624
8624
|
idempotentHint: boolean;
|
|
8625
8625
|
};
|
|
8626
|
+
} | {
|
|
8627
|
+
name: string;
|
|
8628
|
+
description: string;
|
|
8629
|
+
inputSchema: {
|
|
8630
|
+
type: string;
|
|
8631
|
+
properties: {
|
|
8632
|
+
toolName: {
|
|
8633
|
+
type: string;
|
|
8634
|
+
minLength: number;
|
|
8635
|
+
maxLength: number;
|
|
8636
|
+
description: string;
|
|
8637
|
+
};
|
|
8638
|
+
propName: {
|
|
8639
|
+
type: string;
|
|
8640
|
+
minLength: number;
|
|
8641
|
+
maxLength: number;
|
|
8642
|
+
description: string;
|
|
8643
|
+
};
|
|
8644
|
+
optionQuery: {
|
|
8645
|
+
type: string;
|
|
8646
|
+
minLength: number;
|
|
8647
|
+
maxLength: number;
|
|
8648
|
+
description: string;
|
|
8649
|
+
};
|
|
8650
|
+
appSlug: {
|
|
8651
|
+
readonly type: "string";
|
|
8652
|
+
readonly minLength: 1;
|
|
8653
|
+
readonly maxLength: 64;
|
|
8654
|
+
readonly description: "The connected app, e.g. 'gmail'. Use this when the workspace has exactly one connected account for the app; if it has more than one, the call is refused as ambiguous and you must pass accountLabel instead.";
|
|
8655
|
+
};
|
|
8656
|
+
bindingId: {
|
|
8657
|
+
readonly type: "string";
|
|
8658
|
+
readonly minLength: 1;
|
|
8659
|
+
readonly maxLength: 64;
|
|
8660
|
+
readonly description: "An exact integration binding id, as returned by a previous integrations_list_tools call.";
|
|
8661
|
+
};
|
|
8662
|
+
accountLabel: {
|
|
8663
|
+
readonly type: "string";
|
|
8664
|
+
readonly minLength: 1;
|
|
8665
|
+
readonly maxLength: 120;
|
|
8666
|
+
readonly description: "The human label the user gave the connected account. This is the ONLY way to tell two connections over the same inbox apart, because the provider's own account name can be byte-identical for both.";
|
|
8667
|
+
};
|
|
8668
|
+
};
|
|
8669
|
+
required: string[];
|
|
8670
|
+
additionalProperties: boolean;
|
|
8671
|
+
};
|
|
8672
|
+
outputSchema: {
|
|
8673
|
+
readonly type: "object";
|
|
8674
|
+
readonly properties: {
|
|
8675
|
+
readonly ok: {
|
|
8676
|
+
readonly type: "boolean";
|
|
8677
|
+
};
|
|
8678
|
+
readonly outcome: {
|
|
8679
|
+
readonly type: "string";
|
|
8680
|
+
readonly enum: readonly ["ok", "refused", "approval_required", "catalog_incomplete", "not_connected", "upstream_degraded"];
|
|
8681
|
+
};
|
|
8682
|
+
readonly attribution: {
|
|
8683
|
+
readonly type: "string";
|
|
8684
|
+
readonly enum: readonly ["provider", "configuration", "policy", "sellable"];
|
|
8685
|
+
};
|
|
8686
|
+
readonly error: {
|
|
8687
|
+
readonly type: readonly ["string", "null"];
|
|
8688
|
+
};
|
|
8689
|
+
readonly guidance: {
|
|
8690
|
+
readonly type: readonly ["string", "null"];
|
|
8691
|
+
};
|
|
8692
|
+
readonly result: {};
|
|
8693
|
+
};
|
|
8694
|
+
readonly required: readonly ["ok", "outcome", "attribution", "error", "guidance", "result"];
|
|
8695
|
+
readonly additionalProperties: false;
|
|
8696
|
+
};
|
|
8697
|
+
annotations: {
|
|
8698
|
+
title: string;
|
|
8699
|
+
readOnlyHint: boolean;
|
|
8700
|
+
destructiveHint: boolean;
|
|
8701
|
+
idempotentHint: boolean;
|
|
8702
|
+
openWorldHint: boolean;
|
|
8703
|
+
};
|
|
8704
|
+
} | {
|
|
8705
|
+
name: string;
|
|
8706
|
+
description: string;
|
|
8707
|
+
inputSchema: {
|
|
8708
|
+
type: string;
|
|
8709
|
+
properties: {
|
|
8710
|
+
toolName: {
|
|
8711
|
+
type: string;
|
|
8712
|
+
minLength: number;
|
|
8713
|
+
maxLength: number;
|
|
8714
|
+
description: string;
|
|
8715
|
+
};
|
|
8716
|
+
arguments: {
|
|
8717
|
+
type: string;
|
|
8718
|
+
description: string;
|
|
8719
|
+
additionalProperties: boolean;
|
|
8720
|
+
};
|
|
8721
|
+
approvalId: {
|
|
8722
|
+
type: string;
|
|
8723
|
+
minLength: number;
|
|
8724
|
+
maxLength: number;
|
|
8725
|
+
description: string;
|
|
8726
|
+
};
|
|
8727
|
+
appSlug: {
|
|
8728
|
+
readonly type: "string";
|
|
8729
|
+
readonly minLength: 1;
|
|
8730
|
+
readonly maxLength: 64;
|
|
8731
|
+
readonly description: "The connected app, e.g. 'gmail'. Use this when the workspace has exactly one connected account for the app; if it has more than one, the call is refused as ambiguous and you must pass accountLabel instead.";
|
|
8732
|
+
};
|
|
8733
|
+
bindingId: {
|
|
8734
|
+
readonly type: "string";
|
|
8735
|
+
readonly minLength: 1;
|
|
8736
|
+
readonly maxLength: 64;
|
|
8737
|
+
readonly description: "An exact integration binding id, as returned by a previous integrations_list_tools call.";
|
|
8738
|
+
};
|
|
8739
|
+
accountLabel: {
|
|
8740
|
+
readonly type: "string";
|
|
8741
|
+
readonly minLength: 1;
|
|
8742
|
+
readonly maxLength: 120;
|
|
8743
|
+
readonly description: "The human label the user gave the connected account. This is the ONLY way to tell two connections over the same inbox apart, because the provider's own account name can be byte-identical for both.";
|
|
8744
|
+
};
|
|
8745
|
+
};
|
|
8746
|
+
required: string[];
|
|
8747
|
+
additionalProperties: boolean;
|
|
8748
|
+
};
|
|
8749
|
+
outputSchema: {
|
|
8750
|
+
readonly type: "object";
|
|
8751
|
+
readonly properties: {
|
|
8752
|
+
readonly ok: {
|
|
8753
|
+
readonly type: "boolean";
|
|
8754
|
+
};
|
|
8755
|
+
readonly outcome: {
|
|
8756
|
+
readonly type: "string";
|
|
8757
|
+
readonly enum: readonly ["ok", "refused", "approval_required", "catalog_incomplete", "not_connected", "upstream_degraded"];
|
|
8758
|
+
};
|
|
8759
|
+
readonly attribution: {
|
|
8760
|
+
readonly type: "string";
|
|
8761
|
+
readonly enum: readonly ["provider", "configuration", "policy", "sellable"];
|
|
8762
|
+
};
|
|
8763
|
+
readonly error: {
|
|
8764
|
+
readonly type: readonly ["string", "null"];
|
|
8765
|
+
};
|
|
8766
|
+
readonly guidance: {
|
|
8767
|
+
readonly type: readonly ["string", "null"];
|
|
8768
|
+
};
|
|
8769
|
+
readonly result: {};
|
|
8770
|
+
};
|
|
8771
|
+
readonly required: readonly ["ok", "outcome", "attribution", "error", "guidance", "result"];
|
|
8772
|
+
readonly additionalProperties: false;
|
|
8773
|
+
};
|
|
8774
|
+
annotations: {
|
|
8775
|
+
title: string;
|
|
8776
|
+
readOnlyHint: boolean;
|
|
8777
|
+
destructiveHint: boolean;
|
|
8778
|
+
idempotentHint: boolean;
|
|
8779
|
+
openWorldHint: boolean;
|
|
8780
|
+
};
|
|
8626
8781
|
} | {
|
|
8627
8782
|
name: string;
|
|
8628
8783
|
description: string;
|