@sellable/mcp 0.1.754 → 0.1.756
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/refill-run-loop.js
CHANGED
|
@@ -56,6 +56,11 @@ const DEFAULT_BUDGETS = {
|
|
|
56
56
|
schedulerWaitRunBudgetMs: 300_000,
|
|
57
57
|
maxInvocationWallClockMs: 220_000,
|
|
58
58
|
};
|
|
59
|
+
// Keep this transport marker aligned with
|
|
60
|
+
// src/lib/workflow-tables/refill-run-state.ts. The backend intentionally
|
|
61
|
+
// refuses to promote unversioned legacy creditTrust packets into scheduling
|
|
62
|
+
// authority, even when they contain a fresh provider response.
|
|
63
|
+
const REFILL_CREDIT_TRUST_VERSION = 2;
|
|
59
64
|
// Mirrors RefillDoneReason in src/lib/workflow-tables/refill-target-plan.ts,
|
|
60
65
|
// plus loop-authored terminals the planner never emits:
|
|
61
66
|
// - abandoned_stale_checkpoint (fix 110-20d): a resumed wait the loop declares
|
|
@@ -2316,6 +2321,7 @@ async function gateBootstrap(input, deps, ctx) {
|
|
|
2316
2321
|
const pinnedRunState = pinInitialSenderLaneChains(plan, ctx.runState);
|
|
2317
2322
|
const nextRunState = mergeRunState(pinnedRunState, {
|
|
2318
2323
|
creditTrust: {
|
|
2324
|
+
version: REFILL_CREDIT_TRUST_VERSION,
|
|
2319
2325
|
refreshedAt: (deps.now?.() ?? new Date()).toISOString(),
|
|
2320
2326
|
senderIds: [...refreshedSenderIds],
|
|
2321
2327
|
attemptedSenderIds: [...attemptedSenderIds],
|
package/dist/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { compileAgentToolPolicy, createAgentMcpAuthorizationHandlers, } from "./
|
|
|
8
8
|
import { getConfig, getConfiguredCredentialIntent } from "./auth.js";
|
|
9
9
|
import { MCP_RUNTIME_IDENTITY } from "./runtime-identity.js";
|
|
10
10
|
import { getSkillByName, listSkills } from "./skills.js";
|
|
11
|
+
import { toMcpToolResult } from "./tool-result-envelope.js";
|
|
11
12
|
import { getAuthStatus } from "./tools/auth.js";
|
|
12
13
|
import { handleAddColumn, handleCommitBlueprint, } from "./tools/blueprint-commit.js";
|
|
13
14
|
import { bootstrapCreateCampaign } from "./tools/bootstrap.js";
|
|
@@ -63,7 +64,6 @@ import { attachRecommendedSequence, attachSequence, createWorkflowTable, } from
|
|
|
63
64
|
import { setupEvergreenCampaigns } from "./tools/setup-evergreen-campaigns.js";
|
|
64
65
|
import { exportTableCsv, listTables } from "./tools/tables.js";
|
|
65
66
|
import { handleVerifyTableRow } from "./tools/verify-row.js";
|
|
66
|
-
import { sanitizeWatchUrlsForMcpResult } from "./tools/watch-url-security.js";
|
|
67
67
|
import { getCampaignWaterfall, setCampaignWaterfallOrder, } from "./tools/waterfalls.js";
|
|
68
68
|
import { exportWorkspaceCsv } from "./tools/workspace-export.js";
|
|
69
69
|
import { addTeammate, createWorkspace, getActiveWorkspace, getWorkspace, listWorkspaces, setActiveWorkspace, } from "./tools/workspaces.js";
|
|
@@ -134,11 +134,6 @@ function formatSubskillPromptText(result) {
|
|
|
134
134
|
: "";
|
|
135
135
|
return `${header}${result.prompt}${footer}`;
|
|
136
136
|
}
|
|
137
|
-
function isStructuredToolResult(result) {
|
|
138
|
-
return (!!result &&
|
|
139
|
-
typeof result === "object" &&
|
|
140
|
-
Array.isArray(result.content));
|
|
141
|
-
}
|
|
142
137
|
const agentApprovalPort = new HttpAgentApprovalEffectPort();
|
|
143
138
|
const agentMcpAuthorization = createAgentMcpAuthorizationHandlers({
|
|
144
139
|
allTools,
|
|
@@ -901,18 +896,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
901
896
|
default:
|
|
902
897
|
throw new Error(`Unknown tool: ${name}`);
|
|
903
898
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return {
|
|
909
|
-
content: [
|
|
910
|
-
{
|
|
911
|
-
type: "text",
|
|
912
|
-
text: JSON.stringify(safeResult),
|
|
913
|
-
},
|
|
914
|
-
],
|
|
915
|
-
};
|
|
899
|
+
// A tool that DECLARES an `outputSchema` must answer with `structuredContent`,
|
|
900
|
+
// or the client discards the whole response. `toMcpToolResult` owns that,
|
|
901
|
+
// keyed on the tool's own declaration. See tool-result-envelope.ts.
|
|
902
|
+
return toMcpToolResult(name, result);
|
|
916
903
|
}
|
|
917
904
|
catch (error) {
|
|
918
905
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MCP WIRE RESULT. One place shapes it, because the CONTRACT that governs it is
|
|
3
|
+
* declared per tool and enforced by the CLIENT, not by us.
|
|
4
|
+
*
|
|
5
|
+
* A tool that declares an `outputSchema` and answers without `structuredContent` has
|
|
6
|
+
* its ENTIRE response thrown away by the client, whatever the server actually said:
|
|
7
|
+
*
|
|
8
|
+
* - python `mcp/client/session.py`: `if not result.isError:
|
|
9
|
+
* _validate_tool_result(...)`, which raises
|
|
10
|
+
* `Tool <name> has an output schema but did not return structured content`;
|
|
11
|
+
* - typescript `@modelcontextprotocol/sdk` `client/index.js:499-520`: the same
|
|
12
|
+
* rule, then ajv validation of the payload against that schema.
|
|
13
|
+
*
|
|
14
|
+
* That is not a theoretical hazard. It took the three `integrations_*` tools down on
|
|
15
|
+
* the live customer runtime for every Slack turn — success and refusal alike, since
|
|
16
|
+
* neither sets `isError` — and three consecutive failures then tripped Hermes' MCP
|
|
17
|
+
* circuit breaker, which reported the whole server as "unreachable".
|
|
18
|
+
*
|
|
19
|
+
* The declaration and the payload therefore cannot be maintained in two places by
|
|
20
|
+
* two conventions. `inbox.ts`, `senders.ts`, and `runtime-identity.ts` each hand-built
|
|
21
|
+
* `{ content, structuredContent }` correctly and `integrations.ts` returned a bare
|
|
22
|
+
* envelope; keying off the tool's own declaration removes the chance to get it wrong.
|
|
23
|
+
*/
|
|
24
|
+
export type McpToolTextContent = {
|
|
25
|
+
type: "text";
|
|
26
|
+
text: string;
|
|
27
|
+
};
|
|
28
|
+
export type McpToolResult = {
|
|
29
|
+
content: McpToolTextContent[];
|
|
30
|
+
structuredContent?: unknown;
|
|
31
|
+
isError?: boolean;
|
|
32
|
+
};
|
|
33
|
+
export declare function isStructuredToolResult(result: unknown): result is McpToolResult;
|
|
34
|
+
export declare function toMcpToolResult(name: string, result: unknown): McpToolResult;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { allTools } from "./tools/registry.js";
|
|
2
|
+
import { sanitizeWatchUrlsForMcpResult } from "./tools/watch-url-security.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read off the REAL tool table, so a tool that adds an `outputSchema` is covered the
|
|
5
|
+
* moment it is registered and cannot be forgotten here.
|
|
6
|
+
*/
|
|
7
|
+
const OUTPUT_SCHEMA_TOOL_NAMES = new Set(allTools
|
|
8
|
+
.filter((tool) => tool.outputSchema)
|
|
9
|
+
.map((tool) => tool.name));
|
|
10
|
+
export function isStructuredToolResult(result) {
|
|
11
|
+
return (!!result &&
|
|
12
|
+
typeof result === "object" &&
|
|
13
|
+
Array.isArray(result.content));
|
|
14
|
+
}
|
|
15
|
+
function isPlainObject(value) {
|
|
16
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
export function toMcpToolResult(name, result) {
|
|
19
|
+
const safeResult = sanitizeWatchUrlsForMcpResult(result);
|
|
20
|
+
if (isStructuredToolResult(safeResult)) {
|
|
21
|
+
return safeResult;
|
|
22
|
+
}
|
|
23
|
+
const content = [
|
|
24
|
+
{ type: "text", text: JSON.stringify(safeResult) },
|
|
25
|
+
];
|
|
26
|
+
if (!OUTPUT_SCHEMA_TOOL_NAMES.has(name)) {
|
|
27
|
+
return { content };
|
|
28
|
+
}
|
|
29
|
+
if (!isPlainObject(safeResult)) {
|
|
30
|
+
// `structuredContent` must be an OBJECT, so a non-object can never satisfy an
|
|
31
|
+
// object schema. FAIL CLOSED as an error the client will still deliver, instead
|
|
32
|
+
// of a well-formed response the client discards wholesale — the operator gets
|
|
33
|
+
// the payload and a visible error rather than a bare protocol complaint.
|
|
34
|
+
return { content, isError: true };
|
|
35
|
+
}
|
|
36
|
+
return { content, structuredContent: safeResult };
|
|
37
|
+
}
|
|
@@ -243,11 +243,36 @@ export const integrationsToolDefinitions = [
|
|
|
243
243
|
},
|
|
244
244
|
},
|
|
245
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;
|
|
246
263
|
function envelope(value) {
|
|
264
|
+
const outcome = value.outcome ?? (value.ok ? "ok" : "refused");
|
|
265
|
+
const attribution = value.attribution ?? (value.ok ? "policy" : "sellable");
|
|
247
266
|
return {
|
|
248
267
|
ok: value.ok,
|
|
249
|
-
outcome:
|
|
250
|
-
|
|
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",
|
|
251
276
|
error: value.error ?? null,
|
|
252
277
|
guidance: value.guidance ?? null,
|
|
253
278
|
result: value.result ?? null,
|