@jini-ai/mcp 0.4.4 → 0.6.10
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
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# @jini-ai/mcp
|
|
2
2
|
|
|
3
|
+
## 0.6.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4f52784: Add a stable `./bin` export subpath resolving to the `jini-mcp` executable.
|
|
8
|
+
|
|
9
|
+
The package declared a `bin` entry for `dist/bin/serve.js` but an `exports` map containing only
|
|
10
|
+
`"."`, so a consumer that needed the executable's path in order to spawn it as a CLI-injected MCP
|
|
11
|
+
server could not ask for one: `require.resolve('@jini-ai/mcp/dist/bin/serve.js')` fails with
|
|
12
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`. Consumers were resolving the root export and walking to a sibling
|
|
13
|
+
path by hand, hardcoding this package's private build layout.
|
|
14
|
+
|
|
15
|
+
`require.resolve('@jini-ai/mcp/bin')` now returns that path directly. The subpath deliberately
|
|
16
|
+
declares only a `default` condition — it is meant to be resolved to a path and handed to `spawn`,
|
|
17
|
+
not imported (`serve.ts` uses top-level await behind its `isMainModule` guard, so it is not
|
|
18
|
+
`require()`-able). Purely additive: the `"."` export is unchanged.
|
|
19
|
+
|
|
20
|
+
- 8ff5653: Enforce a tool's declared `inputSchema` server-side before its handler runs.
|
|
21
|
+
|
|
22
|
+
`createMcpToolServer` wires tools through the MCP SDK's low-level `Server.setRequestHandler`, not
|
|
23
|
+
the schema-validating `McpServer.registerTool()` helper — the former never checked a real client's
|
|
24
|
+
`tools/call` arguments against the tool's own declared `inputSchema`. A wire-level test (a real SDK
|
|
25
|
+
`Client` and `Server` over `InMemoryTransport`, not the existing `FakeTransport` suite, which calls
|
|
26
|
+
handlers directly either way and so cannot see this) proved schema-violating arguments — wrong
|
|
27
|
+
type, an undeclared property under `additionalProperties: false` — reached every handler completely
|
|
28
|
+
unvalidated.
|
|
29
|
+
|
|
30
|
+
`handleToolCall` now validates `rawArgs` against `tool.inputSchema` via the MCP SDK's own
|
|
31
|
+
`@modelcontextprotocol/sdk/validation/cfworker` provider (no `eval`/codegen, matching this package's
|
|
32
|
+
Node CLI runtime) before invoking the handler, returning the same `{isError:true}` shape as an
|
|
33
|
+
unknown-tool-name or thrown-error result rather than ever calling the handler with unvalidated
|
|
34
|
+
input. Each tool's compiled validator is cached (`WeakMap` keyed by the `McpToolDef` instance, stable
|
|
35
|
+
for a server's whole lifetime) since compiling one is real work the SDK's validator does not cache
|
|
36
|
+
internally. Adds `@cfworker/json-schema` (already an optional peer of the installed SDK version) as
|
|
37
|
+
a direct dependency.
|
|
38
|
+
|
|
39
|
+
- 3fe4d14: Add default-off delegated-result v1 text markers for generic, read-only and typed gateways. The stdio host can opt in with JINI_DELEGATED_RESULT_MARKERS=1.
|
|
40
|
+
- 9098d22: Describe site-guide discovery as an optional host capability in the generic MCP instructions.
|
|
41
|
+
- 4f52784: Add a `sidecar-strict` security mode and per-run MCP credential propagation.
|
|
42
|
+
|
|
43
|
+
For a daemon whose threat model is **another process running as the same OS user** rather than a
|
|
44
|
+
remote attacker, the existing `jini-local` mode is a no-op: `registerApiBearerAuthMiddleware`
|
|
45
|
+
short-circuits for any loopback peer before it reads the `Authorization` header, and a `127.0.0.1`
|
|
46
|
+
bind keeps remote hosts out while doing nothing about a co-resident process. A consumer that spawns a
|
|
47
|
+
Jini daemon holding real authority — starting agent runs, executing tools against a real database —
|
|
48
|
+
previously had to write its own middleware.
|
|
49
|
+
|
|
50
|
+
- `@jini-ai/http-kit` gains `requireStrictBearerToken`: fail-closed 503 when the named token env var
|
|
51
|
+
is unset, 401 on mismatch, **no loopback exemption and no disable flag**. Its `tokenEnvVar` is
|
|
52
|
+
required with no default, so this package never names a host's secret.
|
|
53
|
+
- `composeJiniKernel` gains `security: { mode: 'sidecar-strict', host, tokenEnvVar, exemptPaths? }`.
|
|
54
|
+
Purely additive — `host` and `jini-local` are unchanged by construction, since the modes are arms of
|
|
55
|
+
a discriminated union. The strict gate mounts ahead of the JSON body parser, so a caller it rejects
|
|
56
|
+
never has its body parsed.
|
|
57
|
+
- `@jini-ai/daemon`'s `McpJsonInjectionOptions` gains `credential?: (runId) => string | Promise<string>`
|
|
58
|
+
— a **resolver, not a string**, because injection options are built once before any run exists and a
|
|
59
|
+
boot-wide shared secret would defeat the point of scoping a credential to a run. It is delivered to
|
|
60
|
+
the child as `JINI_DAEMON_TOKEN`.
|
|
61
|
+
- `@jini-ai/mcp`'s `jini-mcp` reads that variable and attaches `Authorization` to every daemon call.
|
|
62
|
+
Optional throughout: with no credential, request headers and `.mcp.json` output are byte-identical
|
|
63
|
+
to before.
|
|
64
|
+
|
|
65
|
+
Also generalized: both existing bearer gates now compare tokens in constant time (`timingSafeEqual`)
|
|
66
|
+
and share one header-parsing helper, closing a timing side channel and removing a duplicated regex.
|
|
67
|
+
|
|
68
|
+
- Updated dependencies [22f0619]
|
|
69
|
+
- Updated dependencies [82ed2d3]
|
|
70
|
+
- Updated dependencies [3fe4d14]
|
|
71
|
+
- Updated dependencies [5a3de86]
|
|
72
|
+
- @jini-ai/core@0.6.10
|
|
73
|
+
- @jini-ai/protocol@0.6.10
|
|
74
|
+
|
|
3
75
|
## 0.4.4 — 2026-10-22
|
|
4
76
|
|
|
5
77
|
- 2026-10-22: Typed tools from the launched bridge, concrete describe schemas, list-changed, diagnostic error codes, coverage.
|
package/dist/bin/serve.js
CHANGED
|
@@ -148,6 +148,7 @@ function buildDelegatedGatewayOptions({ env }, deps) {
|
|
|
148
148
|
const rawTimeout = env[DELEGATED_TOOL_TIMEOUT_ENV_VAR];
|
|
149
149
|
return definedOnly({
|
|
150
150
|
generateToolUseId: deps.generateToolUseId,
|
|
151
|
+
delegatedResultMarkers: env.JINI_DELEGATED_RESULT_MARKERS === '1' ? true : undefined,
|
|
151
152
|
// Parsed, not validated: `createExecuteDelegatedToolTool` already rejects a non-finite or
|
|
152
153
|
// non-positive value back to the default, so `NaN` from a malformed env var lands there too.
|
|
153
154
|
delegatedToolTimeoutMs: rawTimeout === undefined ? undefined : Number(rawTimeout),
|
package/dist/federation/trust.js
CHANGED
|
@@ -334,17 +334,17 @@ export function writeShapedSchemaInputNames({ schema }) {
|
|
|
334
334
|
walk(schema, 0);
|
|
335
335
|
return sortedUnique(found);
|
|
336
336
|
}
|
|
337
|
-
/** R3 action-name rules, applied to the snake_cased remote name. Kept as named module constants
|
|
337
|
+
/** R3 action-name rules, applied to the snake_cased remote name (camelCase split, `-` read as `_`). Kept as named module constants
|
|
338
338
|
* (none carries the `g` flag, so `.test` is stateless) so {@link federatedCallConfirmationForAction}
|
|
339
339
|
* reads as the ordered policy it is. */
|
|
340
|
-
const DELIVERY_ACTION = /(?:send|deliver|invite|reply|forward).*(?:email|mail|newsletter|message|sms|invitation)|(?:email|mail|newsletter|message|sms).*(?:send|deliver|reply|forward)|(?:post|publish).*(?:message|newsletter)/;
|
|
340
|
+
const DELIVERY_ACTION = /(?:send|deliver|invite|reply|forward|broadcast).*(?:email|mail|newsletter|message|sms|invitation|broadcast)|(?:email|mail|newsletter|message|sms).*(?:send|deliver|reply|forward)|(?:post|publish).*(?:message|newsletter)/;
|
|
341
341
|
const SYSTEM_PROMPT_ACTION = /(?:set|update|edit|change).*(?:system_prompt|system_instruction)/;
|
|
342
342
|
const ASSISTANT_PREFIX = /^(?:assistant|agent)_/;
|
|
343
343
|
const READ_ACTION = /^(?:get|list|read|search|describe)_/;
|
|
344
344
|
const ASSISTANT_ACCESS_ACTION = /(?:assistant|agent).*(?:instruction|privacy|permission|access|policy)/;
|
|
345
345
|
const SQL_ACTION = /(?:execute_sql|apply_migration|run_query|query_database)/;
|
|
346
346
|
const REVERSIBLE_REMOVAL_ACTION = /(?:^|_)(?:trash|archive|tombstone|unpublish)(?:_|$)/;
|
|
347
|
-
const PERMANENT_REMOVAL_ACTION = /(?:^|_)(?:delete|purge|drop|destroy|truncate)(?:_|$)/;
|
|
347
|
+
const PERMANENT_REMOVAL_ACTION = /(?:^|_)(?:delete|purge|drop|destroy|truncate|remove|revoke)(?:_|$)/;
|
|
348
348
|
/**
|
|
349
349
|
* Owner confirmation policy (2026-10-01). Ordinary writes run immediately; permanent deletion,
|
|
350
350
|
* delivery to people and changes to the assistant's own access or instructions require a card.
|
|
@@ -358,7 +358,8 @@ const PERMANENT_REMOVAL_ACTION = /(?:^|_)(?:delete|purge|drop|destroy|truncate)(
|
|
|
358
358
|
* @complexity O(n) in the serialized input size.
|
|
359
359
|
*/
|
|
360
360
|
export function federatedCallConfirmationForAction({ remoteName, annotations, args }) {
|
|
361
|
-
|
|
361
|
+
// `-` separates words like `_` does (`remove-domain`, `send-broadcast`): MCP servers use both.
|
|
362
|
+
const name = remoteName.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/-/g, "_");
|
|
362
363
|
const declaredDestructive = annotations?.destructiveHint === true;
|
|
363
364
|
if (DELIVERY_ACTION.test(name) || SYSTEM_PROMPT_ACTION.test(name))
|
|
364
365
|
return "confirm";
|
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module @jini-ai/mcp/server/tools/delegated-tool
|
|
3
|
-
*
|
|
4
|
-
* `execute_delegated_tool` and its read-only companion `execute_readonly_delegated_tool` — the
|
|
5
|
-
* MCP-callback half of gap 3's continuation transport (see
|
|
6
|
-
* `packages/daemon/archived provenance ledger`'s "run/chat orchestration gap 3, part 1" addition and this
|
|
7
|
-
* package's own dated section in `archived provenance ledger` for the rest of the spike). The swarm-consensus
|
|
8
|
-
* Final Recommendation asked for exactly this: "inject the already-shipped MCP host into one
|
|
9
|
-
* MCP-capable CLI's launch config, prove a tool round-trip through the existing
|
|
10
|
-
* `delegated-tool-bridge.ts`." Where `../tools/run-tools.js`'s tools proxy generic
|
|
11
|
-
* run-lifecycle HTTP endpoints, this tool proxies the new `POST /api/delegated-tool-calls`
|
|
12
|
-
* daemon route (`packages/http/src/delegated-tools.ts`), which is the one and only thing that
|
|
13
|
-
* calls into `createDelegatedToolBridge` (`packages/daemon/src/delegated-tool-bridge.ts`) — the
|
|
14
|
-
* same `ToolExecutor` deny-by-default gate every other tool-execution path in this codebase
|
|
15
|
-
* already uses. No parallel authorization mechanism is introduced anywhere on this path.
|
|
16
|
-
*
|
|
17
|
-
* Unlike every other tool this package ships (`run-tools.ts`'s five tools, plain static
|
|
18
|
-
* objects), this one is a **factory**. The MCP server subprocess a `claude` run spawns via
|
|
19
|
-
* `.mcp.json` (see `../../bin/serve.js`) is scoped to exactly one `runId` for its entire
|
|
20
|
-
* lifetime — the daemon injected that run id into the subprocess's own environment at spawn
|
|
21
|
-
* time, before the model ever said a word. `runId` is therefore closed over at construction
|
|
22
|
-
* time, not accepted as a per-call tool argument: a model has no legitimate way to pick, or
|
|
23
|
-
* need to know, which run it is currently inside, and letting it supply one would open a
|
|
24
|
-
* confused-deputy path (one run's MCP subprocess executing a tool call "as" a different run).
|
|
25
|
-
* `toolUseId` is likewise generated per call (`randomUUID` by default), not model-supplied — a
|
|
26
|
-
* model has no legitimate reason to choose its own correlation id either.
|
|
27
|
-
*
|
|
28
|
-
* TWO defs, ONE code path. `createDelegatedGateway` builds both; the factories differ only in the
|
|
29
|
-
* name/title/description they present and in whether the request carries `requireReadOnly: true`.
|
|
30
|
-
* The read-only companion exists because MCP annotations are static per tool declaration — a
|
|
31
|
-
* surface that promises "this only reads" can only be a second declaration, never a per-call mode —
|
|
32
|
-
* and it ENFORCES that promise daemon-side rather than asserting it, because a `readOnlyHint: true`
|
|
33
|
-
* tool that could still reach writes would launder a write past its caller's own safety gate. See
|
|
34
|
-
* {@link createExecuteReadonlyDelegatedToolTool} for the failure that motivated it.
|
|
35
|
-
*/
|
|
36
1
|
import { type ExposedToolDescriptor } from '@jini-ai/core';
|
|
37
2
|
import { type McpToolDef } from '../tool-protocol.js';
|
|
38
3
|
/**
|
|
@@ -82,6 +47,8 @@ export interface CreateExecuteDelegatedToolToolOptions {
|
|
|
82
47
|
* @default DEFAULT_DELEGATED_TOOL_TIMEOUT_MS (6 min)
|
|
83
48
|
*/
|
|
84
49
|
readonly delegatedToolTimeoutMs?: number;
|
|
50
|
+
/** Emit v1 metadata only when the host has enabled daemon/runtime echo correlation. Default off. */
|
|
51
|
+
readonly delegatedResultMarkers?: boolean;
|
|
85
52
|
}
|
|
86
53
|
/**
|
|
87
54
|
* Builds the `execute_delegated_tool` tool def for one specific `runId`. A fresh MCP server
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* tool that could still reach writes would launder a write past its caller's own safety gate. See
|
|
34
34
|
* {@link createExecuteReadonlyDelegatedToolTool} for the failure that motivated it.
|
|
35
35
|
*/
|
|
36
|
+
import { delegatedResultMarker } from '@jini-ai/protocol';
|
|
36
37
|
import { batchOutcomeFor, isReadOnlyTool } from '@jini-ai/core';
|
|
37
38
|
import { ToolSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
38
39
|
import { randomUUID } from 'node:crypto';
|
|
@@ -154,9 +155,10 @@ function createDelegatedGateway(variant, { runId }, options) {
|
|
|
154
155
|
},
|
|
155
156
|
handler: async ({ args, ctx }) => {
|
|
156
157
|
const toolId = requireString({ value: args.toolId, name: 'toolId' });
|
|
158
|
+
const toolUseId = generateToolUseId({});
|
|
157
159
|
const body = {
|
|
158
160
|
runId,
|
|
159
|
-
toolUseId
|
|
161
|
+
toolUseId,
|
|
160
162
|
toolId: toolId,
|
|
161
163
|
// The schema tells the model to omit `input` for a tool that takes none, but most tool
|
|
162
164
|
// handlers refuse an absent input ("input must be an object"). Sending `{}` makes the
|
|
@@ -168,7 +170,11 @@ function createDelegatedGateway(variant, { runId }, options) {
|
|
|
168
170
|
...daemonCallOptions({ ctx }),
|
|
169
171
|
timeoutMs,
|
|
170
172
|
});
|
|
171
|
-
|
|
173
|
+
const output = unwrapMcpContentEnvelope(data.result);
|
|
174
|
+
// Only an unwrapped MCP envelope can correlate against the bridge's saved content digest.
|
|
175
|
+
if (!options.delegatedResultMarkers || !isRecord(output) || !Array.isArray(output.content))
|
|
176
|
+
return output;
|
|
177
|
+
return { ...output, content: [delegatedResultMarker({ toolUseId }), ...output.content] };
|
|
172
178
|
},
|
|
173
179
|
};
|
|
174
180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "Model Context Protocol adapters: server config schema and IO, an MCP client, and agent-side MCP server installation.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -83,11 +83,12 @@
|
|
|
83
83
|
"registry": "https://registry.npmjs.org"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@jini-ai/core": "^0.
|
|
86
|
+
"@jini-ai/core": "^0.6.10",
|
|
87
|
+
"@jini-ai/protocol": "^0.6.10"
|
|
87
88
|
},
|
|
88
89
|
"peerDependencies": {
|
|
89
90
|
"@cfworker/json-schema": "^4.1.1",
|
|
90
|
-
"@jini-ai/platform": "^0.
|
|
91
|
+
"@jini-ai/platform": "^0.6.10",
|
|
91
92
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
92
93
|
},
|
|
93
94
|
"peerDependenciesMeta": {
|
|
@@ -104,9 +105,9 @@
|
|
|
104
105
|
"devDependencies": {
|
|
105
106
|
"@cfworker/json-schema": "^4.1.1",
|
|
106
107
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
107
|
-
"@jini-ai/core": "^0.
|
|
108
|
-
"@jini-ai/
|
|
109
|
-
"@jini-ai/
|
|
108
|
+
"@jini-ai/core": "^0.6.10",
|
|
109
|
+
"@jini-ai/daemon": "^0.6.10",
|
|
110
|
+
"@jini-ai/platform": "^0.6.10"
|
|
110
111
|
},
|
|
111
112
|
"scripts": {
|
|
112
113
|
"build": "tsc -p tsconfig.json && chmod +x dist/bin/serve.js",
|