@sonisoft/now-sdk-ext-mcp 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/common/annotations.d.ts +14 -9
- package/dist/common/annotations.d.ts.map +1 -1
- package/dist/common/annotations.js +39 -54
- package/dist/common/annotations.js.map +1 -1
- package/dist/common/connection.d.ts.map +1 -1
- package/dist/common/connection.js +6 -2
- package/dist/common/connection.js.map +1 -1
- package/dist/common/guard.d.ts +18 -0
- package/dist/common/guard.d.ts.map +1 -0
- package/dist/common/guard.js +124 -0
- package/dist/common/guard.js.map +1 -0
- package/dist/common/logging.d.ts +48 -0
- package/dist/common/logging.d.ts.map +1 -0
- package/dist/common/logging.js +65 -0
- package/dist/common/logging.js.map +1 -0
- package/dist/common/progress.d.ts.map +1 -1
- package/dist/common/progress.js +5 -3
- package/dist/common/progress.js.map +1 -1
- package/dist/common/tool-packages.d.ts +3 -1
- package/dist/common/tool-packages.d.ts.map +1 -1
- package/dist/common/tool-packages.js +8 -4
- package/dist/common/tool-packages.js.map +1 -1
- package/dist/index.js +82 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
@@ -398,7 +427,17 @@ Since stdout is reserved for JSON-RPC, **never use `console.log()` in server cod
|
|
|
398
427
|
|
|
399
428
|
- **`console.error()`** — writes to stderr, which is safe and visible in the MCP Inspector's Notifications pane and in Claude Desktop's log files (`~/Library/Logs/Claude/mcp*.log`).
|
|
400
429
|
- **MCP Inspector** — run the server under the inspector to see all JSON-RPC messages and stderr output in real time.
|
|
401
|
-
- **File logging** —
|
|
430
|
+
- **File logging** — off by default. This server's working directory is chosen by whoever launched it, so it writes no files unless asked. Configure with environment variables:
|
|
431
|
+
|
|
432
|
+
| Variable | Effect |
|
|
433
|
+
| --- | --- |
|
|
434
|
+
| `NEX_LOG_FILE` | `1`/`true` writes a log file to `$XDG_STATE_HOME/now-sdk-ext/logs` (`~/.local/state/...`) |
|
|
435
|
+
| `NEX_LOG_DIR` | Write log files to this directory instead. Implies `NEX_LOG_FILE` |
|
|
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 |
|
|
439
|
+
|
|
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.
|
|
402
441
|
|
|
403
442
|
### Code Conventions
|
|
404
443
|
|
|
@@ -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
|
-
|
|
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;
|
|
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 =
|
|
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:
|
|
83
|
-
run_atf_test_suite:
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
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
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/common/annotations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/common/connection.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAEnB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/common/connection.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAEnB,MAAM,4BAA4B,CAAC;AA+BpC;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,kBAAkB,CAAC,CAgD7B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAKtD;AAED,0EAA0E;AAC1E,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAeD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACpE,OAAO,CAIT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC,CAAC,CAeZ"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getCredentials } from "@servicenow/sdk-cli/dist/auth/index.js";
|
|
2
2
|
import { ServiceNowInstance, } from "@sonisoft/now-sdk-ext-core";
|
|
3
|
+
import { getLogger } from "./logging.js";
|
|
4
|
+
const log = getLogger("connection");
|
|
3
5
|
/** Cache TTL — 30 minutes. ServiceNow sessions typically expire after idle time. */
|
|
4
6
|
const CACHE_TTL_MS = 30 * 60 * 1000;
|
|
5
7
|
const instanceCache = new Map();
|
|
@@ -36,7 +38,7 @@ export async function getServiceNowInstance(authAlias) {
|
|
|
36
38
|
return cached.instance;
|
|
37
39
|
}
|
|
38
40
|
// TTL expired — evict and create fresh
|
|
39
|
-
|
|
41
|
+
log.debug("Cache TTL expired, refreshing session", { alias: resolvedAlias });
|
|
40
42
|
instanceCache.delete(resolvedAlias);
|
|
41
43
|
}
|
|
42
44
|
const credential = await getCredentials(resolvedAlias);
|
|
@@ -134,7 +136,9 @@ export async function withConnectionRetry(authAlias, operation) {
|
|
|
134
136
|
}
|
|
135
137
|
catch (error) {
|
|
136
138
|
if (isRetryableError(error)) {
|
|
137
|
-
|
|
139
|
+
// The error went into the message before, unredacted — an auth or HTTP failure
|
|
140
|
+
// here carries a live session. As metadata it goes through redaction.
|
|
141
|
+
log.warn("Retryable error, refreshing session and retrying", { error });
|
|
138
142
|
clearInstance(authAlias);
|
|
139
143
|
const freshInstance = await getServiceNowInstance(authAlias);
|
|
140
144
|
return await operation(freshInstance);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/common/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EACL,kBAAkB,GAEnB,MAAM,4BAA4B,CAAC;AAQpC,oFAAoF;AACpF,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAsB,CAAC;AAEpD;;;GAGG;AACH,SAAS,YAAY,CAAC,SAAkB;IACtC,MAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACxD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,uDAAuD;YACrD,oFAAoF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,SAAkB;IAElB,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;QAC1C,IAAI,GAAG,GAAG,YAAY,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;QACD,uCAAuC;QACvC,
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/common/connection.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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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging setup for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Two things make this different from the CLI.
|
|
5
|
+
*
|
|
6
|
+
* First, there are no flags — an MCP server is launched by a client with an argv it
|
|
7
|
+
* does not control — so configuration is environment only: NEX_LOG_FILE, NEX_LOG_DIR,
|
|
8
|
+
* NEX_LOG_LEVEL, read by core itself.
|
|
9
|
+
*
|
|
10
|
+
* Second, this process is the reason file logging had to become opt-in at all. The
|
|
11
|
+
* server's working directory is chosen by whoever launched it, and core used to create
|
|
12
|
+
* ./logs/ there on import, unconditionally, without this server ever asking to log
|
|
13
|
+
* anything (NEX-3).
|
|
14
|
+
*
|
|
15
|
+
* stdout is the JSON-RPC transport. Everything here goes to stderr, and nothing in this
|
|
16
|
+
* process may ever write to fd 1.
|
|
17
|
+
*/
|
|
18
|
+
import { Logger } from "@sonisoft/now-sdk-ext-core";
|
|
19
|
+
/**
|
|
20
|
+
* Configures process-wide logging. Call once, before anything constructs a manager.
|
|
21
|
+
*
|
|
22
|
+
* Explicit rather than relying on core reading the environment on its own: the
|
|
23
|
+
* stderr-only guarantee is this server's to make, so it should be stated here where it
|
|
24
|
+
* can be read and tested, not inherited by accident.
|
|
25
|
+
*/
|
|
26
|
+
export declare function initLogging(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Returns a named logger.
|
|
29
|
+
*
|
|
30
|
+
* Safe to call before `initLogging()`, and several modules do: ES modules evaluate
|
|
31
|
+
* every import before the importing module's own top-level statements, so the
|
|
32
|
+
* `const log = getLogger(...)` in connection.ts, progress.ts and tool-packages.ts all
|
|
33
|
+
* run BEFORE the `initLogging()` call in index.ts, even though it sits above them in
|
|
34
|
+
* source order.
|
|
35
|
+
*
|
|
36
|
+
* That is fine because nothing is decided at construction time. `Logger` only stores a
|
|
37
|
+
* label; core builds the underlying winston logger on the first WRITE, resolves config
|
|
38
|
+
* lazily at that point, and rebuilds when configuration changes. So a logger built
|
|
39
|
+
* during import picks up whatever `initLogging()` sets, provided nothing logs during
|
|
40
|
+
* module evaluation — and nothing does.
|
|
41
|
+
*
|
|
42
|
+
* This is not left to inspection: the "still reports startup breadcrumbs on stderr"
|
|
43
|
+
* case in test/unit/common/logging.test.ts only passes if `consoleLevel: info` from
|
|
44
|
+
* `initLogging()` reached a logger that was constructed before it ran. Core's default
|
|
45
|
+
* is `warn`, which would suppress those lines.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getLogger(name: string): Logger;
|
|
48
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/common/logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,EAAoB,MAAM,4BAA4B,CAAC;AAItE;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAelC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging setup for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Two things make this different from the CLI.
|
|
5
|
+
*
|
|
6
|
+
* First, there are no flags — an MCP server is launched by a client with an argv it
|
|
7
|
+
* does not control — so configuration is environment only: NEX_LOG_FILE, NEX_LOG_DIR,
|
|
8
|
+
* NEX_LOG_LEVEL, read by core itself.
|
|
9
|
+
*
|
|
10
|
+
* Second, this process is the reason file logging had to become opt-in at all. The
|
|
11
|
+
* server's working directory is chosen by whoever launched it, and core used to create
|
|
12
|
+
* ./logs/ there on import, unconditionally, without this server ever asking to log
|
|
13
|
+
* anything (NEX-3).
|
|
14
|
+
*
|
|
15
|
+
* stdout is the JSON-RPC transport. Everything here goes to stderr, and nothing in this
|
|
16
|
+
* process may ever write to fd 1.
|
|
17
|
+
*/
|
|
18
|
+
import { Logger, configureLogging } from "@sonisoft/now-sdk-ext-core";
|
|
19
|
+
let configured = false;
|
|
20
|
+
/**
|
|
21
|
+
* Configures process-wide logging. Call once, before anything constructs a manager.
|
|
22
|
+
*
|
|
23
|
+
* Explicit rather than relying on core reading the environment on its own: the
|
|
24
|
+
* stderr-only guarantee is this server's to make, so it should be stated here where it
|
|
25
|
+
* can be read and tested, not inherited by accident.
|
|
26
|
+
*/
|
|
27
|
+
export function initLogging() {
|
|
28
|
+
if (configured) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
configured = true;
|
|
32
|
+
configureLogging({
|
|
33
|
+
// stderr IS this server's log channel — an operator reading `docker logs` or a
|
|
34
|
+
// client's server pane sees only this. `warn` (core's default, right for a CLI
|
|
35
|
+
// where the terminal is the user's) would silence the startup breadcrumbs that
|
|
36
|
+
// say which tool package loaded and whether credentials resolved.
|
|
37
|
+
consoleLevel: process.env.NEX_LOG_LEVEL?.trim() || "info",
|
|
38
|
+
// File logging stays off unless NEX_LOG_FILE/NEX_LOG_DIR asks for it. Core
|
|
39
|
+
// resolves those; naming them here would override an operator's NEX_LOG_FILE=0.
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns a named logger.
|
|
44
|
+
*
|
|
45
|
+
* Safe to call before `initLogging()`, and several modules do: ES modules evaluate
|
|
46
|
+
* every import before the importing module's own top-level statements, so the
|
|
47
|
+
* `const log = getLogger(...)` in connection.ts, progress.ts and tool-packages.ts all
|
|
48
|
+
* run BEFORE the `initLogging()` call in index.ts, even though it sits above them in
|
|
49
|
+
* source order.
|
|
50
|
+
*
|
|
51
|
+
* That is fine because nothing is decided at construction time. `Logger` only stores a
|
|
52
|
+
* label; core builds the underlying winston logger on the first WRITE, resolves config
|
|
53
|
+
* lazily at that point, and rebuilds when configuration changes. So a logger built
|
|
54
|
+
* during import picks up whatever `initLogging()` sets, provided nothing logs during
|
|
55
|
+
* module evaluation — and nothing does.
|
|
56
|
+
*
|
|
57
|
+
* This is not left to inspection: the "still reports startup breadcrumbs on stderr"
|
|
58
|
+
* case in test/unit/common/logging.test.ts only passes if `consoleLevel: info` from
|
|
59
|
+
* `initLogging()` reached a logger that was constructed before it ran. Core's default
|
|
60
|
+
* is `warn`, which would suppress those lines.
|
|
61
|
+
*/
|
|
62
|
+
export function getLogger(name) {
|
|
63
|
+
return new Logger(name);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../../src/common/logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB;;;;;;GAMG;AACH,MAAM,UAAU,WAAW;IACvB,IAAI,UAAU,EAAE,CAAC;QACb,OAAO;IACX,CAAC;IACD,UAAU,GAAG,IAAI,CAAC;IAElB,gBAAgB,CAAC;QACb,+EAA+E;QAC/E,+EAA+E;QAC/E,+EAA+E;QAC/E,kEAAkE;QAClE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,MAAM;QACzD,2EAA2E;QAC3E,gFAAgF;KACnF,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IAClC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/common/progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/common/progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAM5F,MAAM,MAAM,SAAS,GAAG,mBAAmB,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;AAE/E,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CA8B3F"}
|
package/dist/common/progress.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* left undefined. Inventing a percentage from a message count would be a lie that
|
|
17
17
|
* gets less accurate the longer an operation runs.
|
|
18
18
|
*/
|
|
19
|
+
import { getLogger } from "./logging.js";
|
|
20
|
+
const log = getLogger("progress");
|
|
19
21
|
/**
|
|
20
22
|
* Returns an `onProgress` callback to hand to core, or `undefined` when the client
|
|
21
23
|
* did not request progress.
|
|
@@ -48,9 +50,9 @@ export function progressReporter(extra) {
|
|
|
48
50
|
message,
|
|
49
51
|
},
|
|
50
52
|
}).catch((error) => {
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
53
|
+
// stderr, never stdout: stdout is the JSON-RPC transport. Via the logger
|
|
54
|
+
// so the error is redacted rather than stringified into the message.
|
|
55
|
+
log.debug("Failed to send progress notification", { error });
|
|
54
56
|
});
|
|
55
57
|
};
|
|
56
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/common/progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/common/progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;AAOlC;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IACzD,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC;IAC1C,2EAA2E;IAC3E,oEAAoE;IACpE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,4EAA4E;IAC5E,uEAAuE;IACvE,uBAAuB;IACvB,MAAM,IAAI,GAAG,KAAK,CAAC;IACnB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,OAAO,CAAC,OAAe,EAAE,EAAE;QACvB,IAAI,IAAI,CAAC,CAAC;QACV,4EAA4E;QAC5E,yEAAyE;QACzE,KAAK,IAAI,CAAC,gBAAgB,CAAC;YACvB,MAAM,EAAE,wBAAwB;YAChC,MAAM,EAAE;gBACJ,aAAa,EAAE,KAAK;gBACpB,QAAQ,EAAE,IAAI;gBACd,OAAO;aACV;SACJ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,yEAAyE;YACzE,qEAAqE;YACrE,GAAG,CAAC,KAAK,CAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -31,7 +31,9 @@ export declare function resolveToolPackage(selection: string | undefined): Resol
|
|
|
31
31
|
* Reports the resolution on stderr.
|
|
32
32
|
*
|
|
33
33
|
* stderr, never stdout — stdout is the JSON-RPC transport, and a stray byte
|
|
34
|
-
* there breaks the client's parser.
|
|
34
|
+
* there breaks the client's parser. Goes through the logger so it obeys
|
|
35
|
+
* NEX_LOG_LEVEL and reaches the log file when one is configured; these are the
|
|
36
|
+
* breadcrumbs that explain why a client sees the tools it sees.
|
|
35
37
|
*/
|
|
36
38
|
export declare function reportResolution(resolved: ResolvedPackage): void;
|
|
37
39
|
//# sourceMappingURL=tool-packages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-packages.d.ts","sourceRoot":"","sources":["../../src/common/tool-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"tool-packages.d.ts","sourceRoot":"","sources":["../../src/common/tool-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH,MAAM,WAAW,eAAe;IAC5B,gDAAgD;IAChD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,+DAA+D;IAC/D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,sFAAsF;IACtF,QAAQ,EAAE,OAAO,CAAC;CACrB;AAgBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,CAuEjF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAuBhE"}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { TOOL_PACKAGES, DEFAULT_PACKAGE } from "../config/tool-packages.js";
|
|
10
10
|
import { TOOL_ANNOTATIONS } from "./annotations.js";
|
|
11
11
|
import { allToolNames } from "../tools/registry.js";
|
|
12
|
+
import { getLogger } from "./logging.js";
|
|
13
|
+
const log = getLogger("tool-packages");
|
|
12
14
|
/** Sentinel in a package's `tools` meaning "everything". */
|
|
13
15
|
const ALL = "*";
|
|
14
16
|
/** Sentinel meaning "every tool annotated readOnlyHint: true". */
|
|
@@ -108,21 +110,23 @@ export function resolveToolPackage(selection) {
|
|
|
108
110
|
* Reports the resolution on stderr.
|
|
109
111
|
*
|
|
110
112
|
* stderr, never stdout — stdout is the JSON-RPC transport, and a stray byte
|
|
111
|
-
* there breaks the client's parser.
|
|
113
|
+
* there breaks the client's parser. Goes through the logger so it obeys
|
|
114
|
+
* NEX_LOG_LEVEL and reaches the log file when one is configured; these are the
|
|
115
|
+
* breadcrumbs that explain why a client sees the tools it sees.
|
|
112
116
|
*/
|
|
113
117
|
export function reportResolution(resolved) {
|
|
114
118
|
const { names, tools, unknownPackages, unknownTools, fellBack } = resolved;
|
|
115
119
|
if (unknownPackages.length > 0) {
|
|
116
120
|
const available = Object.keys(TOOL_PACKAGES).sort().join(", ");
|
|
117
|
-
|
|
121
|
+
log.warn(`Unknown tool package(s): ${unknownPackages.join(", ")}. ` +
|
|
118
122
|
`Available: ${available}.` +
|
|
119
123
|
(fellBack ? ` Falling back to "${DEFAULT_PACKAGE}".` : ""));
|
|
120
124
|
}
|
|
121
125
|
if (unknownTools.length > 0) {
|
|
122
|
-
|
|
126
|
+
log.warn(`${unknownTools.length} tool(s) named by a package do not exist ` +
|
|
123
127
|
`on this server and were skipped: ${unknownTools.join(", ")}. ` +
|
|
124
128
|
`This is expected for tools from work that has not landed yet.`);
|
|
125
129
|
}
|
|
126
|
-
|
|
130
|
+
log.info(`Active tool package(s): ${names.join(", ")} — ${tools.length} of ${allToolNames().length} tools registered`);
|
|
127
131
|
}
|
|
128
132
|
//# sourceMappingURL=tool-packages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-packages.js","sourceRoot":"","sources":["../../src/common/tool-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,GAAG,GAAG,GAAG,CAAC;AAEhB,kEAAkE;AAClE,MAAM,QAAQ,GAAG,WAAW,CAAC;AAe7B;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,KAAkB;IACrC,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;SACjE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAA6B;IAC5D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;SAC9B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEjC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACH,KAAK,EAAE,CAAC,eAAe,CAAC;YACxB,KAAK,EAAE,YAAY,EAAE;YACrB,eAAe,EAAE,EAAE;YACnB,YAAY,EAAE,EAAE;YAChB,QAAQ,EAAE,KAAK;SAClB,CAAC;IACN,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC3B,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,uEAAuE;QACvE,4EAA4E;QAC5E,kDAAkD;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC;YACjE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC;YACrB,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3B,SAAS;QACb,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACf,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC3B,aAAa,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACzD,CAAC;iBAAM,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACJ,8DAA8D;gBAC9D,0DAA0D;gBAC1D,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;QACL,CAAC;IACL,CAAC;IAED,wEAAwE;IACxE,yEAAyE;IACzE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACH,KAAK,EAAE,CAAC,eAAe,CAAC;YACxB,KAAK,EAAE,YAAY,EAAE;YACrB,eAAe;YACf,YAAY,EAAE,EAAE;YAChB,QAAQ,EAAE,IAAI;SACjB,CAAC;IACN,CAAC;IAED,OAAO;QACH,KAAK;QACL,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE;QAC3B,eAAe;QACf,YAAY,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,EAAE;QACzC,QAAQ,EAAE,KAAK;KAClB,CAAC;AACN,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"tool-packages.js","sourceRoot":"","sources":["../../src/common/tool-packages.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
// anything can read credentials. An MCP server is always launched as a
|
|
4
4
|
// non-interactive child process, where the keyring cannot be unlocked.
|
|
5
5
|
import "./common/credstore-boot.js";
|
|
6
|
+
// Second, and before anything constructs a core manager: core's loggers are field
|
|
7
|
+
// initializers, and until NEX-3 merely constructing one created ./logs/ in whatever
|
|
8
|
+
// directory the client happened to launch this server from.
|
|
9
|
+
import { flushLogs } from "@sonisoft/now-sdk-ext-core";
|
|
10
|
+
import { initLogging, getLogger } from "./common/logging.js";
|
|
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();
|
|
6
17
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
18
|
import { registerServiceNowResources } from "./resources/servicenow.js";
|
|
8
19
|
import { TOOL_REGISTRY } from "./tools/registry.js";
|
|
@@ -10,6 +21,7 @@ import { registerListToolPackagesTool } from "./tools/tool-packages.js";
|
|
|
10
21
|
import { resolveToolPackage, reportResolution } from "./common/tool-packages.js";
|
|
11
22
|
import { readServerVersion } from "./common/version.js";
|
|
12
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
const log = getLogger("mcp");
|
|
13
25
|
const server = new McpServer({
|
|
14
26
|
name: "now-sdk-ext-mcp",
|
|
15
27
|
version: readServerVersion(),
|
|
@@ -24,6 +36,11 @@ const server = new McpServer({
|
|
|
24
36
|
resources: {},
|
|
25
37
|
},
|
|
26
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.
|
|
27
44
|
registerServiceNowResources(server);
|
|
28
45
|
// Tools are registered through the registry rather than 86 direct calls, so a
|
|
29
46
|
// package can select a subset. Resolution happens once, before registration —
|
|
@@ -31,29 +48,87 @@ registerServiceNowResources(server);
|
|
|
31
48
|
// the tools in tools/list.
|
|
32
49
|
const activePackage = resolveToolPackage(process.env.MCP_TOOL_PACKAGE);
|
|
33
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);
|
|
34
55
|
for (const name of activePackage.tools) {
|
|
35
|
-
TOOL_REGISTRY[name](
|
|
56
|
+
TOOL_REGISTRY[name](guarded);
|
|
36
57
|
}
|
|
37
58
|
// Always registered, whatever the package. A filtered session otherwise has no
|
|
38
59
|
// way to tell "this server cannot do that" from "that tool is filtered out of
|
|
39
60
|
// this session".
|
|
40
|
-
registerListToolPackagesTool(
|
|
61
|
+
registerListToolPackagesTool(guarded, activePackage);
|
|
41
62
|
// Prevent the process from crashing silently on unexpected errors.
|
|
42
63
|
// Log to stderr (stdout is reserved for JSON-RPC).
|
|
64
|
+
// Through the logger, not console.error: an uncaught error here can be an auth or
|
|
65
|
+
// HTTP failure carrying a live session, and only the logger redacts.
|
|
43
66
|
process.on("uncaughtException", (error) => {
|
|
44
|
-
|
|
67
|
+
log.error("Uncaught exception", { error });
|
|
45
68
|
});
|
|
46
69
|
process.on("unhandledRejection", (reason) => {
|
|
47
|
-
|
|
70
|
+
log.error("Unhandled rejection", { reason });
|
|
48
71
|
});
|
|
72
|
+
/** Longest a shutdown will wait on the logger before giving up on it. */
|
|
73
|
+
const FLUSH_TIMEOUT_MS = 2000;
|
|
74
|
+
/**
|
|
75
|
+
* Flushes buffered log records, then exits — but never lets the flush prevent the exit.
|
|
76
|
+
*
|
|
77
|
+
* Awaiting `flushLogs()` unguarded would make termination depend on that promise
|
|
78
|
+
* settling. Stuck I/O would then turn a graceful SIGTERM into a process that has to be
|
|
79
|
+
* SIGKILLed, which is strictly worse than the lost log lines this exists to save.
|
|
80
|
+
* `unref` so a pending timer cannot itself hold the process open.
|
|
81
|
+
*
|
|
82
|
+
* The rejection is swallowed on purpose: `void p.finally(cb)` re-throws after running
|
|
83
|
+
* the callback, so a failing flush would surface as an unhandled rejection during
|
|
84
|
+
* shutdown. Exiting regardless is the intent, so say so rather than rely on
|
|
85
|
+
* `process.exit` winning the race against the rejection being reported.
|
|
86
|
+
*/
|
|
87
|
+
async function flushAndExit(code) {
|
|
88
|
+
let timer;
|
|
89
|
+
try {
|
|
90
|
+
await Promise.race([
|
|
91
|
+
flushLogs(),
|
|
92
|
+
new Promise((resolve) => {
|
|
93
|
+
timer = setTimeout(resolve, FLUSH_TIMEOUT_MS);
|
|
94
|
+
timer.unref?.();
|
|
95
|
+
}),
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Nothing useful to do while shutting down; the exit below still happens.
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
// Tidy even though process.exit follows: leaving it dangling would be one more
|
|
103
|
+
// thing to reason about if this ever grows a path that does not exit.
|
|
104
|
+
if (timer)
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
process.exit(code);
|
|
108
|
+
}
|
|
109
|
+
// A client stops this server by signalling it, which is the ORDINARY way it exits —
|
|
110
|
+
// not an error path. Winston's file transport buffers, so without a flush here the
|
|
111
|
+
// last records before shutdown are lost whenever NEX_LOG_FILE is on, which is exactly
|
|
112
|
+
// when someone is reading the file to find out what happened.
|
|
113
|
+
//
|
|
114
|
+
// `once`, so a second signal from an impatient client still terminates immediately
|
|
115
|
+
// rather than queueing another flush.
|
|
116
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
117
|
+
process.once(signal, () => {
|
|
118
|
+
void flushAndExit(0);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
49
121
|
// Start the server on stdio transport
|
|
50
122
|
async function main() {
|
|
51
123
|
const transport = new StdioServerTransport();
|
|
52
124
|
await server.connect(transport);
|
|
53
|
-
|
|
125
|
+
log.info("now-sdk-ext-mcp server running on stdio");
|
|
54
126
|
}
|
|
55
127
|
main().catch((error) => {
|
|
56
|
-
|
|
57
|
-
|
|
128
|
+
log.error("Fatal error starting server", { error });
|
|
129
|
+
// Flush before exiting: winston buffers, and this is the line that explains why
|
|
130
|
+
// the server is not there. Same timeout guard — a stuck flush must not leave a
|
|
131
|
+
// failed startup hanging instead of exiting non-zero.
|
|
132
|
+
void flushAndExit(1);
|
|
58
133
|
});
|
|
59
134
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,iFAAiF;AACjF,uEAAuE;AACvE,uEAAuE;AACvE,OAAO,4BAA4B,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;
|
|
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.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "MCP server for interacting with ServiceNow instances via @sonisoft/now-sdk-ext-core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"servicenow",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@servicenow/sdk-cli": "4.9.2",
|
|
48
48
|
"@servicenow/sdk-cli-core": "3.0.3",
|
|
49
49
|
"@servicenow/sdk-core": "4.9.2",
|
|
50
|
-
"@sonisoft/now-sdk-ext-core": "^
|
|
50
|
+
"@sonisoft/now-sdk-ext-core": "^6.1.0",
|
|
51
51
|
"@sonisoft/sn-credstore": "^1.0.0",
|
|
52
52
|
"zod": "^3.23.8"
|
|
53
53
|
},
|