@sonisoft/now-sdk-ext-mcp 4.6.0 → 4.7.1

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 CHANGED
@@ -266,6 +266,35 @@ This MCP server wraps the same core library used by the CLI:
266
266
 
267
267
  When adding new MCP tools, reference the corresponding CLI command in `now-sdk-ext-cli/src/commands/` for the expected behavior and data flow.
268
268
 
269
+ ## Restricting what can be changed
270
+
271
+ Instance changes are **permitted by default**. Set `NEX_POLICY_DENY` in the server's
272
+ environment to refuse them:
273
+
274
+ ```json
275
+ {
276
+ "mcpServers": {
277
+ "now-sdk-ext": {
278
+ "command": "now-sdk-ext-mcp",
279
+ "env": { "NEX_POLICY_DENY": "all" }
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ Refused tools return an error result explaining that nothing was changed; read-only
286
+ tools are unaffected. There is deliberately **no tool parameter** to grant permission —
287
+ on this surface the caller is the model, so a parameter it can set would not be a
288
+ control.
289
+
290
+ > `NEX_POLICY_DENY` only holds when set somewhere the model cannot write. The config
291
+ > file above usually lives **in the workspace**, and an agent with file-write access can
292
+ > edit it. For a real lockdown set it in the environment that launches the client — a
293
+ > shell profile, a systemd unit, or a container env.
294
+ >
295
+ > This is a guardrail, not a security boundary. Anything holding the credential can
296
+ > reach the instance directly.
297
+
269
298
  ## Contributing
270
299
 
271
300
  ### Testing
@@ -405,6 +434,8 @@ Since stdout is reserved for JSON-RPC, **never use `console.log()` in server cod
405
434
  | `NEX_LOG_FILE` | `1`/`true` writes a log file to `$XDG_STATE_HOME/now-sdk-ext/logs` (`~/.local/state/...`) |
406
435
  | `NEX_LOG_DIR` | Write log files to this directory instead. Implies `NEX_LOG_FILE` |
407
436
  | `NEX_LOG_LEVEL` | `error`, `warn`, `info` (default), `http`, `verbose`, `debug`, `silly` |
437
+ | `NEX_POLICY_DENY` | `write`, `execute`, or `all` — refuses matching instance changes. Malformed values **fail closed** |
438
+ | `NEX_POLICY_ALLOW` | Grants verbs. Inert while changes are permitted by default |
408
439
 
409
440
  Diagnostics always go to **stderr**, never stdout — stdout carries JSON-RPC. Credential material is stripped from both metadata and message text before anything is written.
410
441
 
@@ -1,3 +1,4 @@
1
+ import { Requirement, Target, Verb } from "@sonisoft/now-sdk-ext-core";
1
2
  /**
2
3
  * Tool annotations — the machine-readable risk classification for every tool.
3
4
  *
@@ -41,16 +42,20 @@ export interface ToolAnnotations {
41
42
  destructiveHint?: boolean;
42
43
  idempotentHint?: boolean;
43
44
  openWorldHint?: boolean;
45
+ /**
46
+ * What permission this tool needs. NOT part of MCP — `annotationsFor` strips both
47
+ * of these before the annotations go over the wire.
48
+ *
49
+ * Carried here rather than in a second table because the completeness test already
50
+ * guarantees every registered tool appears in TOOL_ANNOTATIONS, so a new tool
51
+ * cannot arrive without a classification. A parallel table would drift.
52
+ */
53
+ verbs?: readonly Verb[];
54
+ target?: Target;
44
55
  }
45
56
  export declare const TOOL_ANNOTATIONS: Record<string, ToolAnnotations>;
46
- /**
47
- * Annotations for a tool.
48
- *
49
- * Throws on an unknown name rather than returning a permissive default. An
50
- * unannotated tool would silently inherit the spec defaults — `readOnlyHint`
51
- * false, `destructiveHint` TRUE — which is safe, but it would also mean a new
52
- * tool could ship with no deliberate classification and nobody would notice.
53
- * Failing at registration makes that impossible.
54
- */
57
+ /** What permission a tool needs. Throws on an unknown name — see `lookup`. */
58
+ export declare function requirementFor(toolName: string): Requirement;
59
+ /** The annotations sent to the client. */
55
60
  export declare function annotationsFor(toolName: string): ToolAnnotations;
56
61
  //# sourceMappingURL=annotations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/common/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,WAAW,eAAe;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2BD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAwJ5D,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAchE"}
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/common/annotations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,WAAW,eAAe;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAoCD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAwJ5D,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAM5D;AA2BD,0CAA0C;AAC1C,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAKhE"}
@@ -1,51 +1,13 @@
1
- /**
2
- * Tool annotations — the machine-readable risk classification for every tool.
3
- *
4
- * Until now the only signal that a tool deletes records rather than reads them
5
- * was prose in its description ("IMPORTANT: This PERMANENTLY DELETES..."). A
6
- * host client cannot act on prose, so it had to prompt for all 86 tools equally
7
- * — including `query_table` and `count_records`, which cannot change anything.
8
- * Prompting uniformly trains people to click through, which is worse than not
9
- * prompting at all.
10
- *
11
- * Kept as ONE table rather than inlined at 86 registration sites, because
12
- * NEX-40 (role-based tool filtering) needs exactly the same central metadata.
13
- * Building it twice would guarantee the two drift.
14
- *
15
- * These are HINTS, not guarantees. The authority on what a tool may do is the
16
- * ServiceNow ACLs of the authenticated user; this only describes intent.
17
- *
18
- * ---
19
- *
20
- * Classification rules used here, since the spec defaults are easy to misread:
21
- *
22
- * - `readOnlyHint: true` — cannot modify anything. destructive/idempotent are
23
- * meaningless alongside it and are omitted.
24
- * - `destructiveHint` — DEFAULTS TO TRUE, so it must be set false explicitly for
25
- * writes that only add. Reserved for operations that can lose or overwrite
26
- * data that already existed.
27
- * - `idempotentHint` — repeating the call with identical arguments leaves the
28
- * same end state. True for "set this field to X"; false for "append a comment"
29
- * or "create a record", which accumulate.
30
- * - `openWorldHint` — TRUE FOR EVERY TOOL HERE, and set explicitly rather than
31
- * left to the spec's default. All of them talk to a live ServiceNow instance,
32
- * which is exactly what the spec means by an open world. It was tempting to use
33
- * it to single out arbitrary code execution, but that would have been wrong in
34
- * both directions: the spec default is already `true`, so the marking would
35
- * have been a no-op, and leaving it off the other 80-odd would have implied
36
- * they were closed-world when they are not. MCP has no hint for "runs
37
- * caller-supplied logic" — the tool description carries that.
38
- */
39
1
  /** Cannot modify anything. */
40
- const READ = { readOnlyHint: true, openWorldHint: true };
2
+ const READ = { verbs: [], target: "instance", readOnlyHint: true, openWorldHint: true };
41
3
  /** Adds something new; nothing pre-existing is lost. Repeating accumulates. */
42
- const CREATE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
4
+ const CREATE = { verbs: ["write"], target: "instance", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
43
5
  /** Sets state to a given value. Nothing is destroyed; repeating is a no-op. */
44
- const SET = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true };
6
+ const SET = { verbs: ["write"], target: "instance", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true };
45
7
  /** Overwrites or removes data that already existed. Repeating settles. */
46
- const OVERWRITE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true };
8
+ const OVERWRITE = { verbs: ["write"], target: "instance", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true };
47
9
  /** Overwrites or removes, and repeating does NOT settle. */
48
- const OVERWRITE_ONCE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
10
+ const OVERWRITE_ONCE = { verbs: ["write"], target: "instance", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
49
11
  /**
50
12
  * Runs caller-supplied logic — a script or a flow someone else authored.
51
13
  *
@@ -54,7 +16,15 @@ const OVERWRITE_ONCE = { readOnlyHint: false, destructiveHint: true, idempotentH
54
16
  * tools are in that category; the warning that actually reaches the model is in
55
17
  * the tool description.
56
18
  */
57
- const ARBITRARY = OVERWRITE_ONCE;
19
+ const ARBITRARY = {
20
+ ...OVERWRITE_ONCE,
21
+ // The MCP hints stay identical to OVERWRITE_ONCE — the protocol has no hint for
22
+ // "unbounded effect". What differs is the permission: running caller-supplied or
23
+ // third-party logic needs `execute`, and it can write, so it needs both.
24
+ verbs: ["execute", "write"],
25
+ };
26
+ /** Writes to the LOCAL filesystem. Read-only with respect to the instance. */
27
+ const LOCAL_WRITE = { ...OVERWRITE, target: "local" };
58
28
  export const TOOL_ANNOTATIONS = {
59
29
  // ---- meta: reports this server's own configuration, touches no instance.
60
30
  // Registered outside TOOL_REGISTRY because it is always available regardless
@@ -79,8 +49,8 @@ export const TOOL_ANNOTATIONS = {
79
49
  // ---- ATF: tests execute real logic against the instance and routinely
80
50
  // create and modify records as part of running.
81
51
  find_atf_tests: READ,
82
- run_atf_test: OVERWRITE_ONCE,
83
- run_atf_test_suite: OVERWRITE_ONCE,
52
+ run_atf_test: ARBITRARY,
53
+ run_atf_test_suite: ARBITRARY,
84
54
  // ---- attachments
85
55
  list_attachments: READ,
86
56
  get_attachment_info: READ,
@@ -158,7 +128,7 @@ export const TOOL_ANNOTATIONS = {
158
128
  // Writes to the LOCAL filesystem and will overwrite an existing file. Read-only
159
129
  // with respect to ServiceNow, but "read-only" means the environment, not the
160
130
  // instance.
161
- pull_script: OVERWRITE,
131
+ pull_script: LOCAL_WRITE,
162
132
  // Overwrites the script record on the instance.
163
133
  push_script: OVERWRITE,
164
134
  // ---- tasks: field updates and appends
@@ -185,16 +155,24 @@ export const TOOL_ANNOTATIONS = {
185
155
  // Imports can update existing records, matched by sys_id.
186
156
  import_records_xml: OVERWRITE_ONCE,
187
157
  };
158
+ /** What permission a tool needs. Throws on an unknown name — see `lookup`. */
159
+ export function requirementFor(toolName) {
160
+ const annotations = lookup(toolName);
161
+ return {
162
+ verbs: annotations.verbs ?? ["write"],
163
+ target: annotations.target ?? "instance",
164
+ };
165
+ }
188
166
  /**
189
- * Annotations for a tool.
167
+ * The raw table entry, including the non-MCP fields.
190
168
  *
191
- * Throws on an unknown name rather than returning a permissive default. An
192
- * unannotated tool would silently inherit the spec defaults — `readOnlyHint`
193
- * false, `destructiveHint` TRUE — which is safe, but it would also mean a new
194
- * tool could ship with no deliberate classification and nobody would notice.
195
- * Failing at registration makes that impossible.
169
+ * Throws on an unknown name rather than returning a permissive default. An unannotated
170
+ * tool would silently inherit the spec defaults — `readOnlyHint` false,
171
+ * `destructiveHint` TRUE — which is safe on the wire, but it would also mean a new tool
172
+ * could ship with no deliberate permission classification and nobody would notice.
173
+ * Because the guard calls this at REGISTRATION, failing here stops the server booting.
196
174
  */
197
- export function annotationsFor(toolName) {
175
+ function lookup(toolName) {
198
176
  // hasOwnProperty, not bracket access: TOOL_ANNOTATIONS["constructor"] would
199
177
  // otherwise return the Object constructor — truthy — and this would hand back
200
178
  // a function instead of throwing.
@@ -207,4 +185,11 @@ export function annotationsFor(toolName) {
207
185
  }
208
186
  return annotations;
209
187
  }
188
+ /** The annotations sent to the client. */
189
+ export function annotationsFor(toolName) {
190
+ // `verbs` and `target` are ours, not MCP's. Strip them here so nothing
191
+ // non-standard reaches the client — the wire shape is unchanged by this feature.
192
+ const { verbs: _verbs, target: _target, ...wire } = lookup(toolName);
193
+ return wire;
194
+ }
210
195
  //# sourceMappingURL=annotations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/common/annotations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/common/annotations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /** Installs the ladder for this server process. Call once, at startup. */
3
+ export declare function initPolicy(): void;
4
+ /**
5
+ * Wraps a server so every tool registered through it is permission-checked.
6
+ *
7
+ * A Proxy over `registerTool` rather than a change to `ToolRegistrar`. That signature is
8
+ * `(server) => void` across all 87 registrations, and `registry.test.ts` calls each
9
+ * registrar with a bare `{registerTool}` fake — changing it would mean 87 edits and a
10
+ * broken test for no benefit. Wrapping the server leaves both untouched, and because
11
+ * `annotations.test.ts` already regexes on the exact `server.registerTool(` call shape,
12
+ * a new tool cannot be added in a way that avoids this.
13
+ *
14
+ * Refusals are RETURNED as `isError` results, never thrown: a throw becomes a protocol
15
+ * error, while a result is something the model can read and act on.
16
+ */
17
+ export declare function guardServer(server: McpServer): McpServer;
18
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/common/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8BpE,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,IAAI,CA6BjC;AAgCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAkDxD"}
@@ -0,0 +1,124 @@
1
+ import { allowFromEnvironment, checkRequirement, denyFromEnvironment, grantLayer, installPolicy, setRemediationWriter, } from "@sonisoft/now-sdk-ext-core";
2
+ import { requirementFor } from "./annotations.js";
3
+ import { getLogger } from "./logging.js";
4
+ /**
5
+ * Permission enforcement for tool calls.
6
+ *
7
+ * Changes are PERMITTED by default, matching the CLI. Configuration is environment-only
8
+ * because an MCP server does not control its own argv — whoever launches it sets
9
+ * `NEX_POLICY_DENY`, and that is the layer the model cannot reach.
10
+ *
11
+ * There is deliberately NO per-tool "allow" parameter. On this surface the caller IS
12
+ * the model, so a parameter it can set is not a control — and with a permissive default
13
+ * it would grant nothing it did not already have. The same reasoning that kept
14
+ * `--allow-write` off the CLI.
15
+ */
16
+ const log = getLogger("policy");
17
+ /** Installs the ladder for this server process. Call once, at startup. */
18
+ export function initPolicy() {
19
+ const layers = [];
20
+ // 0. Operator lockdown, and the only rung the model cannot reach.
21
+ //
22
+ // The guarantee has a precise limit worth stating: it holds only when set
23
+ // somewhere the model cannot write. An MCP server is launched from a config file
24
+ // — often .mcp.json inside the workspace — and an agent with file-write access
25
+ // can edit that. Set it in a shell profile, systemd unit, or container env.
26
+ const envDeny = denyFromEnvironment(process.env, (m) => log.warn(m));
27
+ if (envDeny)
28
+ layers.push(envDeny);
29
+ // 1. Grants from the environment. Inert while the default is permissive; kept so
30
+ // the ladder does not need rebuilding if the default ever flips.
31
+ const envAllow = allowFromEnvironment(process.env, (m) => log.warn(m));
32
+ if (envAllow)
33
+ layers.push(envAllow);
34
+ // 2. THE DEFAULT. Delete this layer to make the server deny-by-default.
35
+ layers.push(grantLayer("default (changes permitted)", ["write", "execute"]));
36
+ installPolicy(layers);
37
+ setRemediationWriter((_verbs, missing, layer) => {
38
+ const what = missing === "execute" ? "Running scripts or flows" : "Changing instance data";
39
+ return `${what} is not permitted in this session (${layer}).`;
40
+ });
41
+ if (envDeny) {
42
+ log.info("Instance changes are restricted by the environment", { layer: envDeny.name });
43
+ }
44
+ }
45
+ function refusalResult(toolName, verbs, layer) {
46
+ const what = verbs.includes("execute")
47
+ ? "run scripts, flows or tests"
48
+ : "change instance data";
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ // States the situation without naming an escape hatch. There is nothing
54
+ // the model can pass to get past this, and implying otherwise would just
55
+ // produce a retry loop.
56
+ text: `Refused: this session is not permitted to ${what}.\n\n` +
57
+ `The tool "${toolName}" was not called and nothing was changed. ` +
58
+ `This restriction is set in the server's environment (${layer}) and ` +
59
+ `cannot be changed from a tool call. Read-only tools are unaffected.`,
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ }
65
+ /**
66
+ * Wraps a server so every tool registered through it is permission-checked.
67
+ *
68
+ * A Proxy over `registerTool` rather than a change to `ToolRegistrar`. That signature is
69
+ * `(server) => void` across all 87 registrations, and `registry.test.ts` calls each
70
+ * registrar with a bare `{registerTool}` fake — changing it would mean 87 edits and a
71
+ * broken test for no benefit. Wrapping the server leaves both untouched, and because
72
+ * `annotations.test.ts` already regexes on the exact `server.registerTool(` call shape,
73
+ * a new tool cannot be added in a way that avoids this.
74
+ *
75
+ * Refusals are RETURNED as `isError` results, never thrown: a throw becomes a protocol
76
+ * error, while a result is something the model can read and act on.
77
+ */
78
+ export function guardServer(server) {
79
+ return new Proxy(server, {
80
+ get(target, property, receiver) {
81
+ // Function results are rebound to `target` below, which sidesteps the
82
+ // classic Proxy-vs-#private brand-check failure for METHODS. Non-function
83
+ // properties are still read with `receiver = proxy`, so if a future SDK
84
+ // version adds an accessor that touches a real `#private` field, reading it
85
+ // through the guard would throw. Nothing does today — TS `private` alone is
86
+ // not a real private field — but it is a hard failure to trace back here.
87
+ const value = Reflect.get(target, property, receiver);
88
+ if (property !== "registerTool" || typeof value !== "function") {
89
+ return typeof value === "function" ? value.bind(target) : value;
90
+ }
91
+ const original = value;
92
+ return function registerToolGuarded(name, config, handler) {
93
+ // Throws for an unclassified tool, at REGISTRATION time — so a tool
94
+ // without a permission classification cannot start the server, rather
95
+ // than failing on first call in front of a user.
96
+ const requirement = requirementFor(name);
97
+ const guarded = async (...args) => {
98
+ const decision = checkRequirement(requirement);
99
+ if (!decision.allowed) {
100
+ log.warn("Refused a tool call", {
101
+ tool: name,
102
+ verbs: decision.verbs,
103
+ decidingLayer: decision.decidingLayer,
104
+ });
105
+ return refusalResult(name, decision.verbs, decision.decidingLayer);
106
+ }
107
+ // Logged so a session's mutations are reconstructable afterwards.
108
+ // Reads are not logged — they are the overwhelming majority and
109
+ // would drown the signal.
110
+ if (requirement.verbs.length > 0 && requirement.target === "instance") {
111
+ log.info("Permitted a change", {
112
+ tool: name,
113
+ verbs: requirement.verbs,
114
+ grantedBy: decision.decidingLayer,
115
+ });
116
+ }
117
+ return handler(...args);
118
+ };
119
+ return original.call(target, name, config, guarded);
120
+ };
121
+ },
122
+ });
123
+ }
124
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/common/guard.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -9,6 +9,11 @@ import "./common/credstore-boot.js";
9
9
  import { flushLogs } from "@sonisoft/now-sdk-ext-core";
10
10
  import { initLogging, getLogger } from "./common/logging.js";
11
11
  initLogging();
12
+ // Third, before any tool is registered: the permission ladder. Changes are permitted
13
+ // by default; NEX_POLICY_DENY in the server's environment is what restricts them, and
14
+ // it is the only layer the model cannot reach.
15
+ import { guardServer, initPolicy } from "./common/guard.js";
16
+ initPolicy();
12
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
18
  import { registerServiceNowResources } from "./resources/servicenow.js";
14
19
  import { TOOL_REGISTRY } from "./tools/registry.js";
@@ -31,6 +36,11 @@ const server = new McpServer({
31
36
  resources: {},
32
37
  },
33
38
  });
39
+ // Intentionally the RAW server, not the guard. MCP resources are representational and
40
+ // read-only — the ServiceNow ones expose scope, update-set and schema — so there is
41
+ // nothing here for a permission check to refuse. Stated explicitly because the loop
42
+ // below deliberately does use the guard, and the difference should not read as an
43
+ // oversight in a security-relevant path.
34
44
  registerServiceNowResources(server);
35
45
  // Tools are registered through the registry rather than 86 direct calls, so a
36
46
  // package can select a subset. Resolution happens once, before registration —
@@ -38,13 +48,17 @@ registerServiceNowResources(server);
38
48
  // the tools in tools/list.
39
49
  const activePackage = resolveToolPackage(process.env.MCP_TOOL_PACKAGE);
40
50
  reportResolution(activePackage);
51
+ // Registered through the guard, not the server directly. The Proxy intercepts
52
+ // registerTool and wraps each handler with a permission check — a tool cannot opt out,
53
+ // and an unclassified tool fails at startup rather than on first use.
54
+ const guarded = guardServer(server);
41
55
  for (const name of activePackage.tools) {
42
- TOOL_REGISTRY[name](server);
56
+ TOOL_REGISTRY[name](guarded);
43
57
  }
44
58
  // Always registered, whatever the package. A filtered session otherwise has no
45
59
  // way to tell "this server cannot do that" from "that tool is filtered out of
46
60
  // this session".
47
- registerListToolPackagesTool(server, activePackage);
61
+ registerListToolPackagesTool(guarded, activePackage);
48
62
  // Prevent the process from crashing silently on unexpected errors.
49
63
  // Log to stderr (stdout is reserved for JSON-RPC).
50
64
  // Through the logger, not console.error: an uncaught error here can be an auth or
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonisoft/now-sdk-ext-mcp",
3
- "version": "4.6.0",
3
+ "version": "4.7.1",
4
4
  "description": "MCP server for interacting with ServiceNow instances via @sonisoft/now-sdk-ext-core",
5
5
  "keywords": [
6
6
  "servicenow",
@@ -38,6 +38,7 @@
38
38
  "test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPatterns=\"unit\" --forceExit --coverage --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false",
39
39
  "test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPatterns=\"integration\" --forceExit --watchAll=false",
40
40
  "test:all": "node --experimental-vm-modules node_modules/.bin/jest --forceExit --verbose=true --silent=false",
41
+ "eval:credstore": "node test/evals/credstore-mcp.eval.mjs",
41
42
  "lint": "tsc --noEmit",
42
43
  "prepare": "git config core.hooksPath .githooks && sh scripts/setup-gitleaks.sh"
43
44
  },
@@ -47,8 +48,8 @@
47
48
  "@servicenow/sdk-cli": "4.9.2",
48
49
  "@servicenow/sdk-cli-core": "3.0.3",
49
50
  "@servicenow/sdk-core": "4.9.2",
50
- "@sonisoft/now-sdk-ext-core": "^6.0.0",
51
- "@sonisoft/sn-credstore": "^1.0.0",
51
+ "@sonisoft/now-sdk-ext-core": "^6.1.0",
52
+ "@sonisoft/sn-credstore": "1.1.0",
52
53
  "zod": "^3.23.8"
53
54
  },
54
55
  "devDependencies": {