@salesforce/graphiti 11.15.0 → 11.16.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/CHANGELOG.md +10 -0
- package/dist/intent/build-aggregate.js +6 -0
- package/dist/intent/build-aggregate.js.map +1 -1
- package/dist/intent/build-output.d.ts +15 -1
- package/dist/intent/build-output.js +15 -1
- package/dist/intent/build-output.js.map +1 -1
- package/dist/lib/errors.d.ts +54 -1
- package/dist/lib/errors.js +104 -8
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/introspect.js +10 -3
- package/dist/lib/introspect.js.map +1 -1
- package/dist/lib/prime-schema.d.ts +10 -0
- package/dist/lib/prime-schema.js +31 -4
- package/dist/lib/prime-schema.js.map +1 -1
- package/dist/lib/query-builder.d.ts +11 -0
- package/dist/lib/query-builder.js +174 -12
- package/dist/lib/query-builder.js.map +1 -1
- package/dist/mcp/tools/sf-gql-connect.js +1 -1
- package/dist/mcp/tools/sf-gql-connect.js.map +1 -1
- package/dist/schemas/tool-adapter.d.ts +17 -1
- package/dist/schemas/tool-adapter.js +29 -6
- package/dist/schemas/tool-adapter.js.map +1 -1
- package/package.json +1 -1
- package/src/intent/__tests__/build-aggregate.spec.ts +24 -0
- package/src/intent/__tests__/build-output.spec.ts +24 -1
- package/src/intent/build-aggregate.ts +6 -0
- package/src/intent/build-output.ts +15 -1
- package/src/lib/__tests__/query-builder.spec.ts +388 -0
- package/src/lib/errors.ts +125 -2
- package/src/lib/introspect.ts +10 -2
- package/src/lib/prime-schema.ts +32 -4
- package/src/lib/query-builder.ts +176 -11
- package/src/mcp/tools/__tests__/error-surface.contract.spec.ts +50 -1
- package/src/mcp/tools/sf-gql-connect.ts +1 -1
- package/src/schemas/__tests__/tool-adapter.spec.ts +162 -1
- package/src/schemas/tool-adapter.ts +51 -7
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { UserInputError } from "./errors.js";
|
|
7
|
+
import { assertGraphqlName, GRAPHQL_NAME_RE } from "./graphql-name.js";
|
|
7
8
|
import { getChildren, getEffectiveArgs } from "./session.js";
|
|
8
9
|
/**
|
|
9
10
|
* Renders a QuerySession's selection tree into a properly formatted GraphQL query string.
|
|
11
|
+
*
|
|
12
|
+
* @throws if any emitted GraphQL Name (operation name, variable name, field
|
|
13
|
+
* name, alias, inline-fragment type condition, or directive name) is not a
|
|
14
|
+
* valid GraphQL Name, if a variable's type reference has a non-Name innermost
|
|
15
|
+
* NamedType, or if a `{`/`[`-prefixed argument/default value is not valid JSON
|
|
16
|
+
* — the W-23204027 render-layer fail-safe. This fires only on a programmer
|
|
17
|
+
* error (a builder/CLI path that stored a raw identifier without its own guard)
|
|
18
|
+
* or a hostile input that slipped past the per-builder guards; every legitimate
|
|
19
|
+
* value passes. Name violations throw an `Error` whose message matches
|
|
20
|
+
* USER_INPUT_RE; the JSON-literal violation throws a typed `UserInputError` —
|
|
21
|
+
* both classify as UserInput at the MCP boundary (`runTool`).
|
|
10
22
|
*/
|
|
11
23
|
export function renderQuery(session) {
|
|
12
24
|
const parts = [];
|
|
@@ -14,14 +26,41 @@ export function renderQuery(session) {
|
|
|
14
26
|
const varDefs = session.variables.length > 0
|
|
15
27
|
? `(${session.variables
|
|
16
28
|
.map((v) => {
|
|
29
|
+
// Fail-safe: the variable NAME is a GraphQL Name position emitted
|
|
30
|
+
// verbatim as `$<name>`. The TYPE is also emitted verbatim, but it
|
|
31
|
+
// is not a bare Name — it carries type-reference syntax (`!`, `[]`)
|
|
32
|
+
// — so it is guarded structurally by assertGraphqlType, which walks
|
|
33
|
+
// the wrappers and asserts only the innermost NamedType.
|
|
34
|
+
assertGraphqlName(v.name, "renderQuery", "variableName");
|
|
35
|
+
assertGraphqlType(v.type, "renderQuery");
|
|
17
36
|
let def = `$${v.name}: ${v.type}`;
|
|
37
|
+
// W-23204027 (PR #694 review): the default VALUE is a value position,
|
|
38
|
+
// not a Name — it must be formatted like any other arg value, not
|
|
39
|
+
// concatenated raw. Raw emission both breaks legitimate output (a
|
|
40
|
+
// multi-word string default renders `= Acme Corp`, which fails to
|
|
41
|
+
// parse) and is a live selection-set/operation injection sink: a
|
|
42
|
+
// default of `5) { stolen { Id } } query Decoy($z: Int` (reachable
|
|
43
|
+
// via `sf_gql_raw`'s `var $x <path> '<default>'`) would otherwise
|
|
44
|
+
// render a second, attacker-controlled operation. formatArgValue
|
|
45
|
+
// quotes strings, passes through numbers/enums/bools/`$refs`, and
|
|
46
|
+
// routes `{`/`[` defaults through jsonToGraphQL, which either emits
|
|
47
|
+
// an arg-key-guarded input-object literal (valid JSON) or throws
|
|
48
|
+
// (invalid JSON — see Round 3 fix in jsonToGraphQL). No variant
|
|
49
|
+
// reaches raw emission.
|
|
18
50
|
if (v.defaultValue !== undefined)
|
|
19
|
-
def += ` = ${v.defaultValue}`;
|
|
51
|
+
def += ` = ${formatArgValue(v.defaultValue)}`;
|
|
20
52
|
return def;
|
|
21
53
|
})
|
|
22
54
|
.join(", ")})`
|
|
23
55
|
: "";
|
|
24
56
|
const operationKeyword = session.operation === "aggregate" ? "query" : session.operation;
|
|
57
|
+
// Fail-safe: operationName is a GraphQL Name position emitted verbatim in the
|
|
58
|
+
// operation header. Guarded at every builder, but a deserialized/migrated
|
|
59
|
+
// session (loadSession reads it from disk with no re-validation) or a future
|
|
60
|
+
// builder could bypass that — so backstop it here too.
|
|
61
|
+
if (session.operationName) {
|
|
62
|
+
assertGraphqlName(session.operationName, "renderQuery", "operationName");
|
|
63
|
+
}
|
|
25
64
|
const operationName = session.operationName ? ` ${session.operationName}` : "";
|
|
26
65
|
const operationBody = renderChildren(session, null, 1);
|
|
27
66
|
if (operationBody) {
|
|
@@ -34,6 +73,61 @@ export function renderQuery(session) {
|
|
|
34
73
|
}
|
|
35
74
|
return parts.join("\n");
|
|
36
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* W-23204027 render-layer fail-safe for GraphQL argument KEYS. An argument key
|
|
78
|
+
* is emitted verbatim as `<key>: <value>` in three places — a field's argument
|
|
79
|
+
* names (renderField), a directive's argument names (renderDirective), and the
|
|
80
|
+
* keys of a nested input-object literal (valueToGraphQL). The last is
|
|
81
|
+
* attacker-reachable today: `sf_gql_list` / `sf_gql_aggregate` accept a `filter`
|
|
82
|
+
* / `orderBy` typed as `z.record(z.unknown())` (no charset on keys), the builder
|
|
83
|
+
* `JSON.stringify`s it into an arg value, and valueToGraphQL then emits each
|
|
84
|
+
* input field name / operator as a key. A key such as
|
|
85
|
+
* `Name: {eq:"x"} }) { edges { node { Id } } } evilAlias: accounts(where: { Industry`
|
|
86
|
+
* would otherwise render a fully parseable, schema-valid second connection —
|
|
87
|
+
* a silent selection-set injection. Keys are NOT validated at the builder or
|
|
88
|
+
* zod layer, so this render-layer assert is the only universal choke point
|
|
89
|
+
* (everything funnels through the renderer). Every legitimate key — operators
|
|
90
|
+
* (eq/ne/and/or/not/…), SObject field API names incl. `Custom__c`, connection
|
|
91
|
+
* args — is a valid GraphQL Name, so this never fires on real input.
|
|
92
|
+
*/
|
|
93
|
+
function assertArgumentKey(key, emitter) {
|
|
94
|
+
assertGraphqlName(key, emitter, "argumentKey");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* W-23204027 render-layer fail-safe for a variable's TYPE reference. Unlike a
|
|
98
|
+
* Name position, a type carries GraphQL type-reference syntax (`!` for
|
|
99
|
+
* non-null, `[...]` for lists) and so cannot be a bare `assertGraphqlName` —
|
|
100
|
+
* that would wrongly reject legitimate types like `Int!` or `[ID!]!`. Instead
|
|
101
|
+
* we walk the type-reference grammar structurally (October 2021 §2.11): strip a
|
|
102
|
+
* trailing non-null `!`, unwrap a `[ ... ]` list wrapper (recursing on the inner
|
|
103
|
+
* type), and finally assert the innermost NamedType is a valid GraphQL Name.
|
|
104
|
+
*
|
|
105
|
+
* This is a defense-in-depth backstop, not a live-reachable sink today: every
|
|
106
|
+
* `addVariable` call site derives the type from schema inference,
|
|
107
|
+
* `createInputTypeName`, or a hardcoded scalar — none accept a raw type from the
|
|
108
|
+
* agent (the CLI `var`/`define` verbs set only the NAME, never the type). It
|
|
109
|
+
* guards the residual paths a Name backstop would otherwise miss: a
|
|
110
|
+
* deserialized/migrated session (`loadSession` does no type re-validation) or a
|
|
111
|
+
* future builder that stores a raw type. Without it, a type such as
|
|
112
|
+
* `Int) { evil { id } } query Decoy($z: Int` emitted verbatim as `$v: <type>`
|
|
113
|
+
* breaks out into a second operation with no default value needed.
|
|
114
|
+
*/
|
|
115
|
+
function assertGraphqlType(type, emitter) {
|
|
116
|
+
let inner = type.trim();
|
|
117
|
+
// Peel any number of non-null / list wrappers from the outside in.
|
|
118
|
+
while (true) {
|
|
119
|
+
if (inner.endsWith("!")) {
|
|
120
|
+
inner = inner.slice(0, -1).trim();
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (inner.startsWith("[") && inner.endsWith("]")) {
|
|
124
|
+
inner = inner.slice(1, -1).trim();
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
assertGraphqlName(inner, emitter, "variableType");
|
|
130
|
+
}
|
|
37
131
|
function renderChildren(session, parentId, depth) {
|
|
38
132
|
const _indent = " ".repeat(depth);
|
|
39
133
|
const lines = [];
|
|
@@ -50,15 +144,29 @@ function renderChildren(session, parentId, depth) {
|
|
|
50
144
|
function renderField(session, node, depth) {
|
|
51
145
|
const indent = " ".repeat(depth);
|
|
52
146
|
let line = indent;
|
|
53
|
-
//
|
|
147
|
+
// W-23204027 render-layer fail-safe: assert every emitted GraphQL Name is
|
|
148
|
+
// valid, making "the renderer never emits an injectable identifier" a
|
|
149
|
+
// system-wide invariant layered UNDER the per-builder guards (W-22735537),
|
|
150
|
+
// not replacing them. Fires only on a programmer error — a future builder
|
|
151
|
+
// that calls selectLeaf/selectDottedFieldPath and forgets its assert — so
|
|
152
|
+
// failing loud is correct. The message matches USER_INPUT_RE, so the MCP
|
|
153
|
+
// path classifies it as UserInput rather than crashing. Argument *keys* are
|
|
154
|
+
// covered too: they are emitted verbatim as `<key>: <value>` here and in
|
|
155
|
+
// renderDirective/valueToGraphQL, so a malicious filter/orderBy key (a
|
|
156
|
+
// z.record(z.unknown()) with no charset at the schema boundary — see
|
|
157
|
+
// assertArgumentKey) can otherwise break out of the argument object into
|
|
158
|
+
// the selection set. That sink is NOT closed at the builder or zod layer.
|
|
54
159
|
if (node.alias) {
|
|
160
|
+
assertGraphqlName(node.alias, "renderField", "alias");
|
|
55
161
|
line += `${node.alias}: `;
|
|
56
162
|
}
|
|
163
|
+
assertGraphqlName(node.fieldName, "renderField", "fieldName");
|
|
57
164
|
line += node.fieldName;
|
|
58
165
|
// Arguments
|
|
59
166
|
const argEntries = Object.entries(getEffectiveArgs(session, node));
|
|
60
167
|
if (argEntries.length > 0) {
|
|
61
168
|
const argParts = argEntries.map(([name, value]) => {
|
|
169
|
+
assertArgumentKey(name, "renderField");
|
|
62
170
|
return `${name}: ${formatArgValue(value)}`;
|
|
63
171
|
});
|
|
64
172
|
line += `(${argParts.join(", ")})`;
|
|
@@ -78,6 +186,9 @@ function renderField(session, node, depth) {
|
|
|
78
186
|
}
|
|
79
187
|
function renderInlineFragment(session, frag, depth) {
|
|
80
188
|
const indent = " ".repeat(depth);
|
|
189
|
+
// W-23204027 render-layer fail-safe (see renderField): a type condition is a
|
|
190
|
+
// GraphQL Name position emitted verbatim after `... on `.
|
|
191
|
+
assertGraphqlName(frag.onType, "renderInlineFragment", "onType");
|
|
81
192
|
let line = `${indent}... on ${frag.onType}`;
|
|
82
193
|
for (const dir of frag.directives) {
|
|
83
194
|
line += ` ${renderDirective(dir)}`;
|
|
@@ -92,11 +203,19 @@ function renderInlineFragment(session, frag, depth) {
|
|
|
92
203
|
return line;
|
|
93
204
|
}
|
|
94
205
|
function renderDirective(dir) {
|
|
206
|
+
// W-23204027 render-layer fail-safe (see renderField): a directive name is a
|
|
207
|
+
// GraphQL Name position emitted verbatim after `@`. The only directive the
|
|
208
|
+
// declarative/MCP surface adds is `@optional` (a valid Name), so this never
|
|
209
|
+
// fires on legitimate output.
|
|
210
|
+
assertGraphqlName(dir.name, "renderDirective", "directiveName");
|
|
95
211
|
const argEntries = Object.entries(dir.args);
|
|
96
212
|
if (argEntries.length === 0) {
|
|
97
213
|
return `@${dir.name}`;
|
|
98
214
|
}
|
|
99
|
-
const argParts = argEntries.map(([name, value]) =>
|
|
215
|
+
const argParts = argEntries.map(([name, value]) => {
|
|
216
|
+
assertArgumentKey(name, "renderDirective");
|
|
217
|
+
return `${name}: ${formatArgValue(value)}`;
|
|
218
|
+
});
|
|
100
219
|
return `@${dir.name}(${argParts.join(", ")})`;
|
|
101
220
|
}
|
|
102
221
|
/**
|
|
@@ -123,9 +242,23 @@ function formatArgValue(value) {
|
|
|
123
242
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
124
243
|
return jsonToGraphQL(trimmed);
|
|
125
244
|
}
|
|
126
|
-
// Quoted string — pass through
|
|
127
|
-
|
|
128
|
-
|
|
245
|
+
// Quoted string — pass through ONLY when it is a single, well-formed string
|
|
246
|
+
// literal. W-23204027 (PR #694 review): a bare `startsWith('"') && endsWith('"')`
|
|
247
|
+
// check is an injection hole — a payload like
|
|
248
|
+
// `"a") { stolen } query Y($q: String = "b"` also starts and ends with a quote
|
|
249
|
+
// yet breaks out of the value into a second operation. JSON.parse yielding a
|
|
250
|
+
// string proves the whole token is ONE literal (interior quotes are escaped);
|
|
251
|
+
// anything else falls through to JSON.stringify, which re-encodes it as a safe
|
|
252
|
+
// single literal.
|
|
253
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
254
|
+
try {
|
|
255
|
+
if (typeof JSON.parse(trimmed) === "string")
|
|
256
|
+
return trimmed;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Not a single well-formed literal — re-encode below.
|
|
260
|
+
}
|
|
261
|
+
}
|
|
129
262
|
// Default: a string literal. JSON.stringify produces a spec-valid GraphQL string
|
|
130
263
|
// literal — it escapes line terminators and control chars (\n \r \t \b \f) and
|
|
131
264
|
// quotes/backslashes — so values like "a\nb" don't render a raw newline that
|
|
@@ -138,14 +271,34 @@ function formatArgValue(value) {
|
|
|
138
271
|
* GraphQL uses unquoted keys: { Status: { ne: "Closed" } }
|
|
139
272
|
*/
|
|
140
273
|
function jsonToGraphQL(jsonStr) {
|
|
274
|
+
let parsed;
|
|
141
275
|
try {
|
|
142
|
-
|
|
143
|
-
return valueToGraphQL(parsed);
|
|
276
|
+
parsed = JSON.parse(jsonStr);
|
|
144
277
|
}
|
|
145
278
|
catch {
|
|
146
|
-
//
|
|
147
|
-
|
|
279
|
+
// W-23204027 (PR #694 review, Round 3): REJECT — do NOT return the raw
|
|
280
|
+
// string. `formatArgValue` only routes here when the value starts with `{`
|
|
281
|
+
// or `[`, so a JSON.parse failure means it is a `{`/`[`-prefixed string
|
|
282
|
+
// that is NOT well-formed JSON — never legitimate GraphQL. Every real
|
|
283
|
+
// producer of a `{`/`[` value delivers valid JSON (the builders
|
|
284
|
+
// `JSON.stringify` filter/orderBy; the CLI `set`/`assign` path
|
|
285
|
+
// JSON-validates `{`/`[` literals in `validateLiteralAssignment` before
|
|
286
|
+
// storing), so this rejects zero legitimate flows. Returning it verbatim
|
|
287
|
+
// bypassed valueToGraphQL's `assertArgumentKey`, making it a live
|
|
288
|
+
// selection-set/operation injection sink: a variable default of
|
|
289
|
+
// `{ minRevenue: 0 }) { edges { node { id } } } } query Decoy($z: Filter`
|
|
290
|
+
// (unquoted key ⇒ invalid JSON ⇒ this catch; reachable via `sf_gql_raw`'s
|
|
291
|
+
// `var $x <path> '<default>'`) rendered a second, attacker-controlled
|
|
292
|
+
// operation. Throw a typed UserInputError (NOT a bare Error whose text
|
|
293
|
+
// would miss USER_INPUT_RE and misclassify as Internal) so `runTool`
|
|
294
|
+
// classifies it UserInput.
|
|
295
|
+
throw new UserInputError(`jsonToGraphQL: value beginning with '{' or '[' is not valid JSON and cannot be rendered as a GraphQL literal: ${jsonStr.slice(0, 60)}`);
|
|
148
296
|
}
|
|
297
|
+
// valueToGraphQL runs OUTSIDE the try: it enforces the W-23204027 arg-key
|
|
298
|
+
// fail-safe by throwing on a malicious input-object key, and that assertion
|
|
299
|
+
// must propagate — not be swallowed and fall back to emitting the raw
|
|
300
|
+
// (injectable) string.
|
|
301
|
+
return valueToGraphQL(parsed);
|
|
149
302
|
}
|
|
150
303
|
function valueToGraphQL(value) {
|
|
151
304
|
if (value === null || value === undefined)
|
|
@@ -173,7 +326,16 @@ function valueToGraphQL(value) {
|
|
|
173
326
|
}
|
|
174
327
|
if (typeof value === "object") {
|
|
175
328
|
const entries = Object.entries(value);
|
|
176
|
-
const parts = entries.map(([k, v]) =>
|
|
329
|
+
const parts = entries.map(([k, v]) => {
|
|
330
|
+
// The live arg-key injection sink: filter/orderBy objects are
|
|
331
|
+
// JSON.stringify'd into an arg value by the builders, so every input
|
|
332
|
+
// field name / operator here is an attacker-controllable key emitted
|
|
333
|
+
// verbatim into a GraphQL input-object literal. Guard it (see
|
|
334
|
+
// assertArgumentKey). Array elements never reach this branch as keys —
|
|
335
|
+
// the Array case above emits `[...]` with no `<key>:`.
|
|
336
|
+
assertArgumentKey(k, "valueToGraphQL");
|
|
337
|
+
return `${k}: ${valueToGraphQL(v)}`;
|
|
338
|
+
});
|
|
177
339
|
return `{ ${parts.join(", ")} }`;
|
|
178
340
|
}
|
|
179
341
|
return String(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../../src/lib/query-builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"query-builder.js","sourceRoot":"","sources":["../../src/lib/query-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { runTool } from "../../schemas/tool-adapter.js";
|
|
|
9
9
|
const inputSchema = CONNECT_INPUT.shape;
|
|
10
10
|
export function registerSfGqlConnectTool(server, opts = {}) {
|
|
11
11
|
server.registerTool("sf_gql_connect", {
|
|
12
|
-
description: "Connect to a Salesforce org and prime its GraphQL schema cache. With forceRefresh, re-download the schema and coherently clear all caches so subsequent tools see freshly-deployed metadata. Concurrent refreshes coalesce into a single introspection. Returns { org, instanceUrl, refreshed, cached, durationMs, warnings? } — not the standard ToolOutput envelope. If a refresh fails but a usable cached schema survives, returns refreshed:false with a staleness warning instead of erroring. Error convention (all sf_gql_* tools): on failure the isError text is prefixed with a category — `UserInput:` (fix the request), `Auth:` (re-authenticate the org), `Schema:` (introspection/cache problem), or `Internal:` (unexpected) — so you can decide whether to fix inputs, re-auth, or retry.",
|
|
12
|
+
description: "Connect to a Salesforce org and prime its GraphQL schema cache. With forceRefresh, re-download the schema and coherently clear all caches so subsequent tools see freshly-deployed metadata. Concurrent refreshes coalesce into a single introspection. Returns { org, instanceUrl, refreshed, cached, durationMs, warnings? } — not the standard ToolOutput envelope. If a refresh fails but a usable cached schema survives, returns refreshed:false with a staleness warning instead of erroring. Error convention (all sf_gql_* tools): on failure the isError text is prefixed with a category — `UserInput:` (fix the request), `Auth:` (re-authenticate the org), `Schema:` (introspection/cache problem), or `Internal:` (unexpected) — so you can decide whether to fix inputs, re-auth, or retry. A `Schema:` error additionally ends with a retryability token: `[retry=now]` (retry immediately — e.g. a priming-lock timeout, where no live org round-trip occurred) or `[retry=backoff]` (the org was unreachable and an automatic retry already failed — wait with increasing backoff, e.g. 2^n seconds capped around 8s, and retry serially; do not fan out concurrent retries against an org that is already failing, and note some conditions such as an org API rate limit may take longer than a few seconds to clear); ABSENCE of any `[retry=...]` token means the failure is permanent (404, missing/malformed `__schema`, GraphQL errors in the body, or no cached schema) — do not retry, fix the request or re-prime via sf_gql_connect. This token convention applies to every sf_gql_* tool, not just this one. Example error text: `Schema: introspection request failed [retry=backoff]`; extract the disposition with the end-anchored regex ` /\\s\\[retry=(now|backoff)\\]$/` (no match ⇒ permanent).",
|
|
13
13
|
inputSchema,
|
|
14
14
|
}, async (args) => runTool(() => buildConnect(args, opts)));
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sf-gql-connect.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-connect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAIxD,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC;AAExC,MAAM,UAAU,wBAAwB,CACvC,MAAiB,EACjB,OAAgC,EAAE;IAElC,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,WAAW,EACV,
|
|
1
|
+
{"version":3,"file":"sf-gql-connect.js","sourceRoot":"","sources":["../../../src/mcp/tools/sf-gql-connect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAIxD,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC;AAExC,MAAM,UAAU,wBAAwB,CACvC,MAAiB,EACjB,OAAgC,EAAE;IAElC,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,WAAW,EACV,wuDAAwuD;QACzuD,WAAW;KACX,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACvD,CAAC;AACH,CAAC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
|
+
import { type RetryHint } from "../lib/errors.js";
|
|
6
7
|
/**
|
|
7
8
|
* Shared MCP tool adapter (W-22697673). Wraps an intent invocation so that any
|
|
8
9
|
* throw becomes a sanitized, category-prefixed error envelope instead of leaking
|
|
@@ -20,6 +21,17 @@
|
|
|
20
21
|
* untyped throws. The sanitized message is returned for EVERY category — not
|
|
21
22
|
* just Internal — because typed Auth/Schema errors embed cache/lock paths and
|
|
22
23
|
* wrapped jsforce/@salesforce/core causes embed `~/.sfdx/...` paths.
|
|
24
|
+
*
|
|
25
|
+
* Retryability hint (W-23148365): a `Schema:` error — and ONLY a `Schema:` error —
|
|
26
|
+
* additionally ends with a closed-set token telling the host whether to retry:
|
|
27
|
+
* ` [retry=now]` (retry immediately; no live org round-trip occurred — a priming-
|
|
28
|
+
* lock timeout, or a refresh where a usable cached schema survives), ` [retry=backoff]`
|
|
29
|
+
* (the introspection request failed transiently AND already exhausted the
|
|
30
|
+
* connection layer's one built-in retry — wait briefly), or NO token (permanent:
|
|
31
|
+
* 4xx, malformed/absent `__schema`, GraphQL errors in the body, no cached schema —
|
|
32
|
+
* don't retry; fix the request, re-auth, or re-prime). The `<Category>: ` prefix is
|
|
33
|
+
* unchanged. The disposition comes from the typed error's stamped `retry` field,
|
|
34
|
+
* with a defensive `cause`-chain fallback (`classifyCause`) for untyped throws.
|
|
23
35
|
*/
|
|
24
36
|
export type ErrorCategory = "UserInput" | "Auth" | "Schema" | "Internal";
|
|
25
37
|
interface ToolTextResult {
|
|
@@ -51,9 +63,13 @@ export declare const PATH_MARKERS: {
|
|
|
51
63
|
* carries the `u` flag so they match (and escape) as a single code point.
|
|
52
64
|
*/
|
|
53
65
|
export declare function neutralizeControlChars(s: string): string;
|
|
54
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Classify a thrown error into a category, a Schema-only retry hint, and a
|
|
68
|
+
* sanitized message text. `retry` is `"no"` for every non-Schema category.
|
|
69
|
+
*/
|
|
55
70
|
export declare function classifyError(e: unknown): {
|
|
56
71
|
category: ErrorCategory;
|
|
72
|
+
retry: RetryHint;
|
|
57
73
|
text: string;
|
|
58
74
|
};
|
|
59
75
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
6
|
import os from "node:os";
|
|
7
|
-
import { AuthError, SchemaError, UserInputError } from "../lib/errors.js";
|
|
7
|
+
import { AuthError, classifyCause, SchemaError, UserInputError, } from "../lib/errors.js";
|
|
8
8
|
import { graphitiHome, schemaDir } from "../lib/introspect.js";
|
|
9
9
|
import { SchemaRefreshError } from "../lib/prime-schema.js";
|
|
10
10
|
import { MutationContextError } from "../lib/walker.js";
|
|
@@ -141,19 +141,38 @@ function categoryOf(e, message) {
|
|
|
141
141
|
return "Schema";
|
|
142
142
|
return "Internal";
|
|
143
143
|
}
|
|
144
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Retryability disposition for a Schema-category error (W-23148365). The typed
|
|
146
|
+
* SchemaError/SchemaRefreshError carry an authoritative `retry` stamped at the
|
|
147
|
+
* throw site; for an untyped throw that reached the Schema bucket via the
|
|
148
|
+
* SCHEMA_RE heuristic we fall back to inspecting its `cause`. Only ever called
|
|
149
|
+
* for the Schema category — Auth/UserInput/Internal are uniformly `"no"`.
|
|
150
|
+
*/
|
|
151
|
+
function retryHintFor(e) {
|
|
152
|
+
if (e instanceof SchemaError || e instanceof SchemaRefreshError)
|
|
153
|
+
return e.retry;
|
|
154
|
+
if (typeof e === "object" && e !== null && "cause" in e) {
|
|
155
|
+
return classifyCause(e.cause);
|
|
156
|
+
}
|
|
157
|
+
return "no";
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Classify a thrown error into a category, a Schema-only retry hint, and a
|
|
161
|
+
* sanitized message text. `retry` is `"no"` for every non-Schema category.
|
|
162
|
+
*/
|
|
145
163
|
export function classifyError(e) {
|
|
146
164
|
const message = e instanceof Error ? e.message : String(e);
|
|
147
165
|
const category = categoryOf(e, message);
|
|
166
|
+
const retry = category === "Schema" ? retryHintFor(e) : "no";
|
|
148
167
|
const safeMessage = sanitizePaths(message);
|
|
149
168
|
if (category !== "Internal")
|
|
150
|
-
return { category, text: safeMessage };
|
|
169
|
+
return { category, retry, text: safeMessage };
|
|
151
170
|
// Internal: unexpected. Attach a truncated, path-stripped stack for the host.
|
|
152
171
|
const frames = truncatedStack(e);
|
|
153
172
|
const text = frames.length
|
|
154
173
|
? `${safeMessage}\n${frames.map((f) => ` ${f}`).join("\n")}`
|
|
155
174
|
: safeMessage;
|
|
156
|
-
return { category, text };
|
|
175
|
+
return { category, retry, text };
|
|
157
176
|
}
|
|
158
177
|
/**
|
|
159
178
|
* Run an MCP tool's intent invocation. Returns the success envelope
|
|
@@ -169,13 +188,17 @@ export async function runTool(fn) {
|
|
|
169
188
|
return { content: [{ type: "text", text: stripLineSeparators(JSON.stringify(output)) }] };
|
|
170
189
|
}
|
|
171
190
|
catch (e) {
|
|
172
|
-
const { category, text } = classifyError(e);
|
|
191
|
+
const { category, retry, text } = classifyError(e);
|
|
173
192
|
if (category === "Internal") {
|
|
174
193
|
console.error("[graphiti-mcp] Internal tool error:", e);
|
|
175
194
|
}
|
|
195
|
+
// Append the retry hint as an end-anchored token (W-23148365). Only a
|
|
196
|
+
// Schema error ever carries a non-"no" hint; the `<Category>: ` prefix is
|
|
197
|
+
// unchanged so existing host parses (startsWith / split) still work.
|
|
198
|
+
const suffix = retry === "no" ? "" : ` [retry=${retry}]`;
|
|
176
199
|
return {
|
|
177
200
|
isError: true,
|
|
178
|
-
content: [{ type: "text", text: stripLineSeparators(`${category}: ${text}`) }],
|
|
201
|
+
content: [{ type: "text", text: stripLineSeparators(`${category}: ${text}${suffix}`) }],
|
|
179
202
|
};
|
|
180
203
|
}
|
|
181
204
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-adapter.js","sourceRoot":"","sources":["../../src/schemas/tool-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"tool-adapter.js","sourceRoot":"","sources":["../../src/schemas/tool-adapter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -757,6 +757,30 @@ describe("intent/build-aggregate", () => {
|
|
|
757
757
|
},
|
|
758
758
|
);
|
|
759
759
|
|
|
760
|
+
// W-23204027: `aggregations[].field` was the second unguarded sink (the
|
|
761
|
+
// schema boundary types it as `z.string()` with no charset). It must be
|
|
762
|
+
// rejected at the builder layer like `alias`/`groupBy`, not just by the
|
|
763
|
+
// renderField fail-safe. `Amount } injectedAlias: Name { value` is the
|
|
764
|
+
// selection-set-breakout payload class from W-22735537.
|
|
765
|
+
it.each([
|
|
766
|
+
"Amount } injectedAlias: Name { value",
|
|
767
|
+
"}}__schema{types{name",
|
|
768
|
+
"1foo",
|
|
769
|
+
"foo bar",
|
|
770
|
+
] as const)("aggregation field '%s' is rejected as not a valid GraphQL Name", async (field) => {
|
|
771
|
+
await expect(
|
|
772
|
+
buildAggregate(
|
|
773
|
+
{
|
|
774
|
+
org: ORG,
|
|
775
|
+
object: "Order",
|
|
776
|
+
groupBy: [],
|
|
777
|
+
aggregations: [{ function: "sum", field }],
|
|
778
|
+
},
|
|
779
|
+
noopPrimeDeps(),
|
|
780
|
+
),
|
|
781
|
+
).rejects.toThrow(/buildAggregate: field .* is not a valid GraphQL Name/);
|
|
782
|
+
});
|
|
783
|
+
|
|
760
784
|
it("rejects an operationName that is not a valid GraphQL Name", async () => {
|
|
761
785
|
await expect(
|
|
762
786
|
buildAggregate(
|
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
import { buildSchema } from "graphql";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
|
-
import { createSession, selectLeaf } from "../../lib/session.js";
|
|
9
|
+
import { createSession, type FieldProjectionNode, selectLeaf } from "../../lib/session.js";
|
|
10
10
|
import { buildOutput } from "../build-output.js";
|
|
11
11
|
|
|
12
12
|
const schema = buildSchema(`
|
|
13
13
|
type Query { ping: String }
|
|
14
14
|
`);
|
|
15
15
|
|
|
16
|
+
const argSchema = buildSchema(`
|
|
17
|
+
type Query { accounts(first: Int): String }
|
|
18
|
+
`);
|
|
19
|
+
|
|
16
20
|
describe("intent/build-output", () => {
|
|
17
21
|
it("renders query, no warnings on valid query", () => {
|
|
18
22
|
const session = createSession("test", "query");
|
|
@@ -52,4 +56,23 @@ describe("intent/build-output", () => {
|
|
|
52
56
|
expect(out.types).toMatch(/\/\/ Type generation failed:/);
|
|
53
57
|
expect(out.warnings.some((w) => w.startsWith("Codegen:"))).toBe(true);
|
|
54
58
|
});
|
|
59
|
+
|
|
60
|
+
// W-23204027 (PR #694 review): buildOutput's docstring no longer claims "Never
|
|
61
|
+
// throws". renderQuery runs OUTSIDE the try/catch and enforces the render-layer
|
|
62
|
+
// fail-safe, so a hostile identifier/arg-key that slipped past the per-builder
|
|
63
|
+
// guards throws instead of emitting an injectable name. This is caught and
|
|
64
|
+
// classified UserInput at the MCP boundary (runTool), but direct callers must
|
|
65
|
+
// be prepared for it — and it must NOT be swallowed into warnings[], which would
|
|
66
|
+
// let an injected selection reach the output. This test pins that contract.
|
|
67
|
+
it("propagates the render-layer fail-safe throw (does not swallow it into warnings)", () => {
|
|
68
|
+
const session = createSession("test", "query");
|
|
69
|
+
selectLeaf(session, ["accounts"]);
|
|
70
|
+
const node = session.nodes.find(
|
|
71
|
+
(n): n is FieldProjectionNode => n.kind === "field" && n.fieldName === "accounts",
|
|
72
|
+
)!;
|
|
73
|
+
// A raw, injectable argument key (as sf_gql_raw's `set <path> @args/<key>`
|
|
74
|
+
// could plant) — never a valid GraphQL Name.
|
|
75
|
+
node.args["first) { stolen } evil("] = "1";
|
|
76
|
+
expect(() => buildOutput(session, argSchema)).toThrow(/is not a valid GraphQL Name/);
|
|
77
|
+
});
|
|
55
78
|
});
|
|
@@ -117,6 +117,12 @@ export async function buildAggregate(spec: AggregateSpec, deps?: PrimeDeps): Pro
|
|
|
117
117
|
assertGraphqlName(agg.alias, "buildAggregate", "alias");
|
|
118
118
|
}
|
|
119
119
|
const field = agg.field ?? "Id";
|
|
120
|
+
// W-23204027: `aggregations[].field` is `z.string()` with no charset at the
|
|
121
|
+
// schema boundary, so guard it per-builder like `alias`/`groupBy field` —
|
|
122
|
+
// it flows raw into createSiblingFieldInstance → node.fieldName → rendered
|
|
123
|
+
// verbatim. The renderField fail-safe backstops this; this is the matching
|
|
124
|
+
// builder-layer defense-in-depth (parity with W-22735537).
|
|
125
|
+
assertGraphqlName(field, "buildAggregate", "field");
|
|
120
126
|
const key = agg.alias ?? defaultKey(agg.function, field);
|
|
121
127
|
if (seenKeys.has(key)) {
|
|
122
128
|
throw new Error(
|
|
@@ -27,11 +27,25 @@ const OPTIONAL_DIRECTIVE_UNKNOWN_MARKER = 'Unknown directive "@optional"';
|
|
|
27
27
|
* Render → validate → codegen → assemble. Shared finalizer for every typed
|
|
28
28
|
* intent function (`buildList`, `buildDetail`, …).
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* Validation and codegen never throw — their failure modes surface as entries in
|
|
31
|
+
* `warnings[]`:
|
|
31
32
|
* - `Validation: <msg>` — non-schema-level errors from `graphql-js validate()`.
|
|
32
33
|
* - `Validation: schema check skipped (<msg>)` — `validate()` itself crashed.
|
|
33
34
|
* - `Codegen: <msg>` — `generateTypes()` threw; `types` becomes `// Type generation failed: <msg>`.
|
|
34
35
|
*
|
|
36
|
+
* `renderQuery`, however, CAN throw — by design. It runs before (outside) the
|
|
37
|
+
* try/catch and enforces the W-23204027 render-layer fail-safe: if any emitted
|
|
38
|
+
* GraphQL Name (operation/variable/field name, alias, type condition, directive
|
|
39
|
+
* name) or argument key is not a valid GraphQL Name, it throws rather than emit
|
|
40
|
+
* an injectable identifier. This fires only on a programmer error or a hostile
|
|
41
|
+
* input that slipped past the per-builder guards (e.g. a `filter`/`orderBy` key —
|
|
42
|
+
* `z.record(z.unknown())` with no charset). At the MCP boundary `runTool`
|
|
43
|
+
* catches it and the message (`… is not a valid GraphQL Name`) is classified as
|
|
44
|
+
* `UserInput`, so failing loud is safe there. Direct callers (e.g. an eval
|
|
45
|
+
* harness) that bypass that boundary must be prepared for the throw. Do NOT move
|
|
46
|
+
* `renderQuery` into the try below — that would swallow the fail-safe and let an
|
|
47
|
+
* injected selection reach the output.
|
|
48
|
+
*
|
|
35
49
|
* Schema-level errors (e.g. "Input Object type X must define one or more fields"
|
|
36
50
|
* raised by malformed UIAPI schemas, not by the user's query) are filtered out
|
|
37
51
|
* per FR-9.2.
|