@jini-ai/mcp 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +97 -22
- package/dist/agent-install/install.d.ts +20 -4
- package/dist/agent-install/install.js +11 -11
- package/dist/bin/serve.d.ts +25 -13
- package/dist/bin/serve.js +42 -21
- package/dist/client/client.d.ts +28 -8
- package/dist/client/client.js +25 -21
- package/dist/core/config.d.ts +57 -18
- package/dist/core/config.js +76 -48
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.js +1 -2
- package/dist/core/install-info.d.ts +4 -1
- package/dist/core/install-info.js +5 -5
- package/dist/core/tokens.d.ts +39 -13
- package/dist/core/tokens.js +54 -24
- package/dist/federation/adapter.http.d.ts +67 -0
- package/dist/federation/adapter.http.js +414 -0
- package/dist/federation/approvals/call-confirmation.d.ts +23 -0
- package/dist/federation/approvals/call-confirmation.js +124 -0
- package/dist/federation/approvals/index.d.ts +4 -0
- package/dist/federation/approvals/index.js +4 -0
- package/dist/federation/approvals/ports.d.ts +107 -0
- package/dist/federation/approvals/ports.js +2 -0
- package/dist/federation/approvals/revocation.d.ts +61 -0
- package/dist/federation/approvals/revocation.js +52 -0
- package/dist/federation/bootstrap.d.ts +100 -0
- package/dist/federation/bootstrap.js +182 -0
- package/dist/federation/config.d.ts +68 -0
- package/dist/federation/config.js +40 -0
- package/dist/federation/index.d.ts +14 -0
- package/dist/federation/index.js +14 -0
- package/dist/federation/mcp-protocol.d.ts +115 -0
- package/dist/federation/mcp-protocol.js +151 -0
- package/dist/federation/messages.d.ts +54 -0
- package/dist/federation/messages.js +35 -0
- package/dist/federation/ports.d.ts +287 -0
- package/dist/federation/ports.js +5 -0
- package/dist/federation/presets.d.ts +66 -0
- package/dist/federation/presets.js +30 -0
- package/dist/federation/refusal-notice.d.ts +198 -0
- package/dist/federation/refusal-notice.js +193 -0
- package/dist/federation/registrations.d.ts +171 -0
- package/dist/federation/registrations.js +181 -0
- package/dist/federation/reload.d.ts +112 -0
- package/dist/federation/reload.js +110 -0
- package/dist/federation/stdio/adapter.stdio.d.ts +157 -0
- package/dist/federation/stdio/adapter.stdio.js +526 -0
- package/dist/federation/stdio/default-connect.d.ts +29 -0
- package/dist/federation/stdio/default-connect.js +92 -0
- package/dist/federation/stdio/index.d.ts +4 -0
- package/dist/federation/stdio/index.js +4 -0
- package/dist/federation/stdio/stdio-launch-resolver.d.ts +139 -0
- package/dist/federation/stdio/stdio-launch-resolver.js +227 -0
- package/dist/federation/testing/adapter.memory.d.ts +177 -0
- package/dist/federation/testing/adapter.memory.js +189 -0
- package/dist/federation/testing/index.d.ts +3 -0
- package/dist/federation/testing/index.js +3 -0
- package/dist/federation/testing/tool-approvals.memory.d.ts +27 -0
- package/dist/federation/testing/tool-approvals.memory.js +38 -0
- package/dist/federation/tool-approvals.d.ts +97 -0
- package/dist/federation/tool-approvals.js +42 -0
- package/dist/federation/trust.d.ts +471 -0
- package/dist/federation/trust.js +707 -0
- package/dist/index.d.ts +175 -2
- package/dist/index.js +172 -2
- package/dist/server/daemon-client.d.ts +16 -4
- package/dist/server/daemon-client.js +41 -19
- package/dist/server/ports.d.ts +9 -0
- package/dist/server/ports.js +2 -0
- package/dist/server/resource-protocol.d.ts +14 -4
- package/dist/server/resource-protocol.js +6 -6
- package/dist/server/resources/active-resource.js +3 -3
- package/dist/server/sdk-transport.d.ts +11 -0
- package/dist/server/sdk-transport.js +31 -0
- package/dist/server/tool-protocol.d.ts +31 -9
- package/dist/server/tool-protocol.js +20 -18
- package/dist/server/tool-server.d.ts +60 -43
- package/dist/server/tool-server.js +51 -27
- package/dist/server/tools/component-catalog-tools.js +7 -7
- package/dist/server/tools/delegated-tool.d.ts +7 -4
- package/dist/server/tools/delegated-tool.js +14 -15
- package/dist/server/tools/run-tools.js +15 -15
- package/dist/server/tools/tool-catalog-tools.js +7 -7
- package/dist/tools/ask-choice/index.d.ts +123 -0
- package/dist/tools/ask-choice/index.js +98 -0
- package/dist/tools/ask-choice/pending.d.ts +11 -0
- package/dist/tools/ask-choice/pending.js +34 -0
- package/dist/tools/ask-choice/question.d.ts +16 -0
- package/dist/tools/ask-choice/question.js +60 -0
- package/dist/tools/ask-choice/schema.d.ts +91 -0
- package/dist/tools/ask-choice/schema.js +27 -0
- package/dist/tools/ask-choice/types.d.ts +106 -0
- package/dist/tools/ask-choice/types.js +2 -0
- package/package.json +43 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @jini-ai/mcp
|
|
2
|
+
|
|
3
|
+
## 0.4.0 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
### BREAKING
|
|
6
|
+
|
|
7
|
+
- OAuth implementations and the CLI sanitizer dependency were retired; core text and platform filesystem/network entries are canonical. Approval fingerprint domains, wire codes and messages are host-supplied; approval tenancy is optional scope.
|
|
8
|
+
- Distribution includes runtime output, release documentation and required assets only. Process records and per-job neutrality checks are no longer part of the package surface.
|
|
9
|
+
|
|
10
|
+
## Unreleased
|
|
11
|
+
|
|
12
|
+
- Enforce fresh consent for destructive federated calls across both remembered scopes. Destructive
|
|
13
|
+
cards offer no remember buttons, matching chat grants cannot bypass them, and forged remember
|
|
14
|
+
choices are not saved. Keep ordinary, non-destructive remembered approvals and fingerprint bytes.
|
|
15
|
+
|
|
16
|
+
- Repair package compilation against strict optional properties and current object-call contracts,
|
|
17
|
+
including the test fixtures. Omit absent HTTP signals and approval/revocation scopes; unscoped
|
|
18
|
+
memory approval records omit `scope`. Preserve supplied values, assertions and security guards.
|
|
19
|
+
|
|
20
|
+
- Use core/primitives path containment after host path resolution. Legal managed directory names beginning with `..` are now accepted; traversal and sibling-prefix escapes remain refused.
|
|
21
|
+
|
|
22
|
+
- **BREAKING:** Federation prose uses required host messages (`defaultFederationMessages` is
|
|
23
|
+
neutral English); fingerprint domains and error namespaces are required wire arguments.
|
|
24
|
+
Missing confirmation channels derive `${errorCode}_NO_CONFIRMATION_CHANNEL`. Opaque optional
|
|
25
|
+
scope replaces workspace-shaped repository/roster/webhook inputs; approval-duration fields stay.
|
|
26
|
+
- **BREAKING:** Bootstrap/reload use core Logger and approval/token clocks use core Clock.nowMs().
|
|
27
|
+
The private secret writer is removed; stores use platform's async atomic writer at 0600 with
|
|
28
|
+
owner-only verification, file/directory fsync and parent creation. Directory-sync errors can
|
|
29
|
+
follow successful replacement. Default info logs use console.info with `agent-daemon` prefix.
|
|
30
|
+
- Remove the CLI runtime dependency, including executable URL resolution. Sanitization imports
|
|
31
|
+
core/text; loopback checks use platform/net. Fingerprint and key byte compatibility are pinned
|
|
32
|
+
in tests. The stdio stderr scrubber retains its lowercase marker/minimum length contract.
|
|
33
|
+
|
|
34
|
+
- Align trust comments with protected-action-only confirmation and preserve the distinct HTTP 401
|
|
35
|
+
and 403 outcomes. Count UTF-8 result/response bytes, cap aggregate serialized images, bound shutdown
|
|
36
|
+
DELETE and all stdio connection phases, pin install data roots, redact diagnostic URLs and use
|
|
37
|
+
declared run statuses. Regression tests are written, not run (owner directive).
|
|
38
|
+
|
|
39
|
+
- Isolate HTTP 401 challenge-hook failures through the injected logger and bound observer waits by
|
|
40
|
+
the existing request timeout and caller cancellation. Preserve typed authentication failures for
|
|
41
|
+
auth-revocation handlers; hooks may consume the request signal through optional second arguments.
|
|
42
|
+
Regression tests are written and not run (owner directive).
|
|
43
|
+
|
|
44
|
+
- Finish pass: reconcile `./federation`, `./federation/approvals`, `./federation/stdio`,
|
|
45
|
+
`./federation/testing`, and `./tools/ask-choice` with their entry metadata, source barrels,
|
|
46
|
+
injected ports, and generalized characterization suites. Add declarations/import conditions
|
|
47
|
+
to the existing `./bin` executable entry. The core dependency is declared; lockfile refresh
|
|
48
|
+
remains with the coordinator. Tests, typechecks, build, and packing are not run (owner directive).
|
|
49
|
+
- BREAKING: effectful lifecycle methods (`run`, `close`, `reload`, `noteActivity`, `dispose`),
|
|
50
|
+
preset reset, and empty memory-store construction take an empty required object. SDK callbacks
|
|
51
|
+
and zero-argument getters retain their ABI. Question-shape errors take `{ message }, { cause? }`;
|
|
52
|
+
their messages and error classification are preserved. No exports are renamed in this pass.
|
|
53
|
+
Correlation-id generators and idle/request-work callbacks receive an empty object; clocks,
|
|
54
|
+
URL/connection resolvers, list getters, and native scheduler/SDK callbacks keep their ABI.
|
|
55
|
+
- Security defaults: reject explicitly invalid transport/auth modes instead of inferring a policy.
|
|
56
|
+
Incomplete configured stdio toolchains now refuse launch; hosts can explicitly opt into
|
|
57
|
+
`allowIdentityFallback: true` with a warning. Unconfigured ordinary launches remain identity
|
|
58
|
+
launches. Protocol bytes, approval fingerprints, timeouts, environment inheritance, and version
|
|
59
|
+
remain unchanged. New regression tests describe these changes and await execution.
|
|
60
|
+
|
|
61
|
+
- Add `./tools/ask-choice`: a product-neutral human-question tool factory with injected
|
|
62
|
+
policy, presentation, result copy, exchange storage, and pending-question storage.
|
|
63
|
+
Includes an optional clock/ID-injected single-use answer-ticket adapter, generalized
|
|
64
|
+
characterization tests, and a package source neutrality guard. Existing exports and
|
|
65
|
+
the package version are unchanged. Verification deferred by owner directive.
|
|
66
|
+
|
|
67
|
+
- Add `./federation`, `./federation/stdio`, `./federation/testing`, and
|
|
68
|
+
`./federation/approvals`, completing the new subpaths alongside `./tools/ask-choice`.
|
|
69
|
+
Federation injects client identity, permission, session, credential, resolver, human
|
|
70
|
+
confirmation, approval storage, and current-roster ports. Preserve the approval fingerprint
|
|
71
|
+
format and pinned vector. Declare the core dependency and per-entry runtimes.
|
|
72
|
+
- BREAKING: public helpers and factories take required objects and optional settings objects.
|
|
73
|
+
Tool handlers take `{ args, ctx }`, resource readers take `{ ctx }`, and ask-choice handlers
|
|
74
|
+
take `{ ctx }`. Daemon GET/POST helpers and error constructors use named objects.
|
|
75
|
+
Server/transport, federation callback, channel, and resolver ports use named objects;
|
|
76
|
+
cancellation and HTTP body options are second objects. Config/token stores inject filesystem
|
|
77
|
+
and clock ports. `requireString({ value, name })` returns the string; `isAgentSlug(args)`
|
|
78
|
+
narrows `args.value`; `serve({}, deps)` uses `{ text }`/`{ code }` error/exit ports.
|
|
79
|
+
Persisted data and MCP wire formats remain unchanged.
|
|
80
|
+
- BREAKING: retire the duplicated MCP OAuth implementation and its root exports. Use the
|
|
81
|
+
independent OAuth package through host credential ports; MCP does not import that engine.
|
|
82
|
+
The root API test now asserts the retired exports remain absent.
|
|
83
|
+
- Complete root type exports and reconcile core registry calls, delegated gateway construction,
|
|
84
|
+
daemon proxy callers, test fixtures, and package-wide source neutrality checks. No version bump.
|
|
85
|
+
Verification is not run (owner directive).
|
|
86
|
+
|
|
87
|
+
## 0.1.2
|
|
88
|
+
|
|
89
|
+
### Patch Changes
|
|
90
|
+
|
|
91
|
+
- Add top-level `main`/`types` fields alongside the existing `exports` map. A consumer on
|
|
92
|
+
TypeScript's classic `moduleResolution: "node"` (node10) — which ignores `package.json#exports`
|
|
93
|
+
entirely — could not resolve this package's types at all (`TS2307: Cannot find module`) even
|
|
94
|
+
after the previous exports-map fix restored `require()` at runtime; type resolution and runtime
|
|
95
|
+
resolution are separate algorithms. Verified against a real external consumer (whose
|
|
96
|
+
tsconfig uses this legacy resolution mode): adding these two fields, with its tsconfig completely
|
|
97
|
+
unchanged, made the error disappear. Also fixes absolute-path `require()` (distinct from a bare
|
|
98
|
+
specifier, which already worked) for the same reason — `main` was previously absent.
|
|
99
|
+
|
|
100
|
+
Purely additive: every modern resolver (Node's own runtime `exports` resolution, TypeScript's
|
|
101
|
+
`bundler`/`node16`/`nodenext`) prefers `exports` over `main`/`types` when both are present, so
|
|
102
|
+
this changes nothing for a consumer already on a modern resolver.
|
|
103
|
+
|
|
104
|
+
- Updated dependencies
|
|
105
|
+
- @jini-ai/cli@0.1.2
|
|
106
|
+
- @jini-ai/platform@0.1.2
|
|
107
|
+
|
|
108
|
+
## 0.1.1
|
|
109
|
+
|
|
110
|
+
### Patch Changes
|
|
111
|
+
|
|
112
|
+
- Add a `"default"` export condition to every published package's `exports` map — every one of
|
|
113
|
+
them lacked it, which meant `require()` failed with `ERR_PACKAGE_PATH_NOT_EXPORTED` for any
|
|
114
|
+
CommonJS consumer (found via a real external integration attempt; Node needs `require(esm)`
|
|
115
|
+
support, i.e. Node >=22.12, for this to resolve).
|
|
116
|
+
|
|
117
|
+
`@jini-ai/agent-runtime`:
|
|
118
|
+
|
|
119
|
+
- **New**: `RuntimeBuildOptions.permissionMode` (`'bypass' | 'restricted'`) lets a caller opt a
|
|
120
|
+
run OUT of the auto-approve-every-permission-prompt flag every def with one
|
|
121
|
+
(`bypassPermissions` / `--yolo` / `--dangerously-skip-permissions`) previously pushed
|
|
122
|
+
unconditionally, with no way to turn it off. Omitting it keeps today's default (bypass)
|
|
123
|
+
behavior unchanged.
|
|
124
|
+
- **New**: `ClaudeStreamEvent`, `CopilotStreamEvent`, and `QoderEvent` are now real exported
|
|
125
|
+
discriminated unions instead of `Record<string, unknown>` — a real external consumer guessed a
|
|
126
|
+
nonexistent field name (`event.text` instead of the actual `event.delta`) against the old
|
|
127
|
+
untyped sink and silently lost every streamed token with no compile or runtime error.
|
|
128
|
+
- Fixed a doc/implementation mismatch in `claude-stream.ts`: the module doc claimed `tool_result`
|
|
129
|
+
events carry `{ tool_use_id, content, is_error }`; the actual emitted shape is
|
|
130
|
+
`{ toolUseId, content, isError }`.
|
|
131
|
+
|
|
132
|
+
`@jini-ai/daemon`: `AgentExecutorRunInput.permissionMode` forwards the new
|
|
133
|
+
`RuntimeBuildOptions.permissionMode` through to `buildArgs`, so a host can actually reach the new
|
|
134
|
+
opt-out from the daemon's real run-input surface, not just from `@jini-ai/agent-runtime` in
|
|
135
|
+
isolation.
|
|
136
|
+
|
|
137
|
+
`@jini-ai/agentic`: `setAtPointer` no longer throws on a malformed (e.g. missing leading `/`)
|
|
138
|
+
`updateDataModel` path — degrades to a no-op like its sibling `getAtPointer`, matching this
|
|
139
|
+
package's own "a bad binding must not crash the renderer" contract. That path is agent-authored
|
|
140
|
+
wire data with no error boundary above it in any host, so the uncaught throw could unmount an
|
|
141
|
+
entire chat UI from ~40 bytes of malformed input.
|
|
142
|
+
|
|
143
|
+
`@jini-ai/chat-react`: a local (client-resolved) A2UI button action is no longer a silent no-op —
|
|
144
|
+
`A2uiSurfaceCard` now surfaces the resolved value. New `ExtEventErrorBoundary` confines a
|
|
145
|
+
`kind: 'ext'` event group's renderer to its own card instead of letting a render/effect-phase
|
|
146
|
+
throw from agent-controlled content unmount the whole chat root (there was no error boundary
|
|
147
|
+
anywhere in this package or its hosts before this).
|
|
148
|
+
|
|
149
|
+
- Updated dependencies
|
|
150
|
+
- @jini-ai/platform@0.1.1
|
|
151
|
+
- @jini-ai/cli@0.1.1
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Everything a Jini host needs on both sides of the Model Context Protocol. On the
|
|
|
4
4
|
a generic mechanism for hosting a bounded set of MCP tools and read-only resources over stdio, plus
|
|
5
5
|
the kernel run tools (`start_run`, `get_run`, `cancel_run`, `list_agents`) already defined against
|
|
6
6
|
it, and a bootable `jini-mcp` binary an MCP client can spawn directly. On the **client/config** side:
|
|
7
|
-
a validated `mcp.json` schema with sanitizing read/write,
|
|
8
|
-
store, and installation planning that registers an MCP server into external coding agents. Nothing
|
|
7
|
+
a validated `mcp.json` schema with sanitizing read/write, a token
|
|
8
|
+
store with host-owned OAuth orchestration, and installation planning that registers an MCP server into external coding agents. Nothing
|
|
9
9
|
here assumes a product layout — a caller injects the daemon URL, the tool list, and the fetch
|
|
10
10
|
implementation.
|
|
11
11
|
|
|
@@ -15,12 +15,13 @@ implementation.
|
|
|
15
15
|
npm install @jini-ai/mcp
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
No peer dependencies. `@modelcontextprotocol/sdk`, `undici`, `@
|
|
18
|
+
No peer dependencies. `@modelcontextprotocol/sdk`, `undici`, `@cfworker/json-schema`,
|
|
19
|
+
`@jini-ai/core`, `@jini-ai/cli`, and `@jini-ai/platform`
|
|
19
20
|
are regular dependencies, installed automatically. Nothing here needs a native compile.
|
|
20
21
|
|
|
21
22
|
## What you get
|
|
22
23
|
|
|
23
|
-
**Hosting an MCP server** — `createMcpToolServer(
|
|
24
|
+
**Hosting an MCP server** — `createMcpToolServer(requiredArgs, optionalArgs)` returns a handle whose `run({})` serves your
|
|
24
25
|
`McpToolDef[]` (and optional `McpResourceDef[]`) over this process's stdin/stdout. Duplicate tool
|
|
25
26
|
names or resource URIs throw at construction time. Supporting pieces: `buildToolIndex`,
|
|
26
27
|
`handleToolCall`, `toolsToList`, `buildResourceIndex`, `handleResourceRead`, `resourcesToList`,
|
|
@@ -29,9 +30,10 @@ names or resource URIs throw at construction time. Supporting pieces: `buildTool
|
|
|
29
30
|
|
|
30
31
|
**Kernel tool and resource definitions** — `RUN_TOOLS` plus its members `startRunTool`, `getRunTool`,
|
|
31
32
|
`cancelRunTool`, `listAgentsTool`, `getActiveContextTool`; `KERNEL_RESOURCES` and
|
|
32
|
-
`activeContextResource`; and `createExecuteDelegatedToolTool(
|
|
33
|
+
`activeContextResource`; and `createExecuteDelegatedToolTool({ runId }, optionalArgs)` for the MCP-callback delegated
|
|
33
34
|
tool-execution path. Daemon HTTP access for those definitions goes through `getDaemonJson` /
|
|
34
|
-
`postDaemonJson`, which
|
|
35
|
+
`postDaemonJson`, which bound response bytes and translate an internal `DaemonResponseTooLargeError`
|
|
36
|
+
into a redacted public error rather than buffering an unbounded body.
|
|
35
37
|
|
|
36
38
|
**The `jini-mcp` binary** — `package.json` declares `"bin": { "jini-mcp": "./dist/bin/serve.js" }`.
|
|
37
39
|
One process serves exactly one run for its lifetime: the spawning daemon injects the run id into the
|
|
@@ -45,21 +47,48 @@ trusting a model-supplied one. The daemon URL comes from an environment variable
|
|
|
45
47
|
`inferMcpAuthModeForUrl`, and `isManagedProjectCwd`. Per-agent config emitters:
|
|
46
48
|
`buildClaudeMcpJson`, `buildAcpMcpServers`, `buildOpenCodeMcpConfigContent`.
|
|
47
49
|
|
|
48
|
-
**OAuth
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
one-call `beginAuth`.
|
|
50
|
+
**Authentication** — OAuth is supplied by the host through credential and challenge ports.
|
|
51
|
+
The earlier in-package OAuth implementation has been retired. MCP neither imports an OAuth
|
|
52
|
+
engine nor retries a challenged request automatically.
|
|
52
53
|
|
|
53
54
|
**Token store** — `readTokensFile`, `sanitizeTokensFile`, `getToken`, `setToken`, `clearToken`,
|
|
54
55
|
`readAllTokens`, `isTokenExpired`.
|
|
55
56
|
|
|
56
|
-
**Installing into external agents** — `AGENT_SLUGS` / `isAgentSlug`, `planAgentInstall(
|
|
57
|
+
**Installing into external agents** — `AGENT_SLUGS` / `isAgentSlug`, `planAgentInstall({ slug, spec, ctx })`
|
|
57
58
|
returning a discriminated `InstallPlan` (`CliInstallPlan` | `JsonInstallPlan` | `ManualInstallPlan`),
|
|
58
59
|
and `applyJsonInstall` / `removeJsonInstall` to execute the JSON-file variant. Plus
|
|
59
60
|
`buildMcpInstallPayload` for the install-info payload a UI renders.
|
|
60
61
|
|
|
61
62
|
**Client-side runtime helpers** — `extractRelativeRefs`, `isTextualMime`.
|
|
62
63
|
|
|
64
|
+
## Argument convention (breaking)
|
|
65
|
+
|
|
66
|
+
Public APIs take a required argument object and, when applicable, an optional settings object.
|
|
67
|
+
For example: `writeMcpConfig({ dataDir, body }, { filesystem })`,
|
|
68
|
+
`buildClaudeMcpJson({ servers }, { tokens })`, and
|
|
69
|
+
`handleToolCall({ name, tools, ctx }, { rawArgs })`.
|
|
70
|
+
Tool handlers receive `{ args, ctx }`; resource readers receive `{ ctx }`.
|
|
71
|
+
`requireString({ value, name })` returns the validated string, and
|
|
72
|
+
`isAgentSlug(args)` narrows `args.value` on a successful guard.
|
|
73
|
+
The binary's library entry point is `serve({}, deps)`; its error/exit ports take
|
|
74
|
+
`{ text }` and `{ code }`. SDK-facing signatures stay inside the adapters.
|
|
75
|
+
|
|
76
|
+
Daemon helpers use `getDaemonJson({ baseUrl, route }, options)` and
|
|
77
|
+
`postDaemonJson({ baseUrl, route, body }, options)`. Error constructors use `{ message, status }`
|
|
78
|
+
or `{ limitBytes }`. See [API.md](https://github.com/AINSEP/Jini/blob/main/packages/mcp/API.md) for the current contracts and the package integration
|
|
79
|
+
report for source provenance and deferred host rewiring.
|
|
80
|
+
|
|
81
|
+
Effectful lifecycle methods use empty objects: `handle.run({})`, `session.close({})`,
|
|
82
|
+
`channel.close({})`, `coordinator.reload({})`, `idle.noteActivity({})`, and `idle.dispose({})`.
|
|
83
|
+
SDK callbacks and zero-argument getters retain their ABI. Config sanitization rejects supplied
|
|
84
|
+
invalid transport/authentication modes. A configured but incomplete bundled stdio toolchain refuses
|
|
85
|
+
launch unless the host explicitly sets `{ allowIdentityFallback: true }` in resolver options;
|
|
86
|
+
allowed fallback carries a warning. Both toolchain variables absent selects ordinary identity
|
|
87
|
+
launching. See [API.md](https://github.com/AINSEP/Jini/blob/main/packages/mcp/API.md) for the security and port contracts.
|
|
88
|
+
|
|
89
|
+
Optional approval/revocation scopes and HTTP cancellation signals are omitted when absent.
|
|
90
|
+
Supplied scopes and signals are forwarded unchanged; unscoped memory approval records omit `scope`.
|
|
91
|
+
|
|
63
92
|
## Usage
|
|
64
93
|
|
|
65
94
|
```ts
|
|
@@ -80,10 +109,9 @@ const greetTool: McpToolDef = {
|
|
|
80
109
|
properties: { name: { type: 'string' } },
|
|
81
110
|
required: ['name'],
|
|
82
111
|
},
|
|
83
|
-
handler: async (args) => {
|
|
84
|
-
const who = args.name;
|
|
85
|
-
|
|
86
|
-
return okResult(`hello ${who}`);
|
|
112
|
+
handler: async ({ args }) => {
|
|
113
|
+
const who = requireString({ value: args.name, name: 'name' });
|
|
114
|
+
return okResult({ payload: `hello ${who}` });
|
|
87
115
|
},
|
|
88
116
|
};
|
|
89
117
|
|
|
@@ -91,12 +119,13 @@ const server = createMcpToolServer({
|
|
|
91
119
|
name: 'example-mcp',
|
|
92
120
|
version: '1.0.0',
|
|
93
121
|
tools: [...RUN_TOOLS, greetTool],
|
|
94
|
-
resources: KERNEL_RESOURCES,
|
|
95
122
|
resolveBaseUrl: () => process.env.EXAMPLE_DAEMON_URL ?? 'http://127.0.0.1:4173',
|
|
123
|
+
}, {
|
|
124
|
+
resources: KERNEL_RESOURCES,
|
|
96
125
|
instructions: 'Use start_run to launch work; poll get_run for status.',
|
|
97
126
|
});
|
|
98
127
|
|
|
99
|
-
await server.run(); // serves over this process's stdio until idle-exit
|
|
128
|
+
await server.run({}); // serves over this process's stdio until idle-exit
|
|
100
129
|
```
|
|
101
130
|
|
|
102
131
|
Read `McpToolDef` in `src/server/` before writing your own tool — the exact `handler` and
|
|
@@ -109,16 +138,62 @@ Read `McpToolDef` in `src/server/` before writing your own tool — the exact `h
|
|
|
109
138
|
without spawning a process or opening a socket. The tool and resource lists are yours — `RUN_TOOLS`
|
|
110
139
|
and `KERNEL_RESOURCES` are convenience defaults, not a fixed set. `createExecuteDelegatedToolTool`
|
|
111
140
|
takes its run scoping from the caller. Fixed and not replaceable: the MCP wire framing itself (that
|
|
112
|
-
is `@modelcontextprotocol/sdk`'s job), the config sanitizers' validation rules, and the
|
|
113
|
-
step ordering.
|
|
141
|
+
is `@modelcontextprotocol/sdk`'s job), the config sanitizers' validation rules, and the MCP handshake's step ordering.
|
|
114
142
|
|
|
115
143
|
## Runtime
|
|
116
144
|
|
|
117
|
-
`jini.runtime: "node"` — stdio streams, `node:fs` for the config and token stores, `node:crypto` for
|
|
118
|
-
|
|
145
|
+
`jini.runtime: "node"` — stdio streams, `node:fs` for the config and token stores, `node:crypto` for approval fingerprints. Universal entries are the ask-choice tool and
|
|
146
|
+
federation test doubles; federation and approvals require Node for hashing. The stdio entry
|
|
147
|
+
adds process, filesystem, and environment adapters.
|
|
119
148
|
ESM only — ships `"type": "module"` with no CommonJS `require` build.
|
|
120
149
|
|
|
121
150
|
## Provenance
|
|
122
151
|
|
|
123
|
-
See
|
|
152
|
+
See the archived provenance ledger for per-file provenance and scope decisions. Apache-2.0,
|
|
124
153
|
inherited from Open Design — see the repo `NOTICE`.
|
|
154
|
+
|
|
155
|
+
## Federation entries
|
|
156
|
+
|
|
157
|
+
`@jini-ai/mcp/federation` exposes admission/trust, protocol/HTTP sessions, bootstrap, reload,
|
|
158
|
+
fingerprints, and approval/revocation orchestration. Import process adapters and launch resolvers
|
|
159
|
+
from `@jini-ai/mcp/federation/stdio`, and in-memory sessions, scripted transports, and approval
|
|
160
|
+
stores from `@jini-ai/mcp/federation/testing`. The focused approval entry is
|
|
161
|
+
`@jini-ai/mcp/federation/approvals`. These stay separate from the root barrel to avoid naming
|
|
162
|
+
collisions and accidental process-adapter loading.
|
|
163
|
+
|
|
164
|
+
The host supplies product name, client information, credential policy, permission evaluator,
|
|
165
|
+
confirmation UI/exchanges, and current connection roster. A revocation gate checks the current
|
|
166
|
+
row on every call; the host composes its credential checks, including preset credentials.
|
|
167
|
+
See [API.md](https://github.com/AINSEP/Jini/blob/main/packages/mcp/API.md) for concrete object-argument examples.
|
|
168
|
+
|
|
169
|
+
All test, compiler, build, and pack verification is deferred under the owner directive.
|
|
170
|
+
|
|
171
|
+
## Design decisions
|
|
172
|
+
|
|
173
|
+
- [Remote annotations can narrow authority but cannot grant it](docs/decisions/DR-001-federation-admission.md).
|
|
174
|
+
- [Preserve structured protocol resources without exposing human secrets](docs/decisions/DR-002-protocol-resource-boundary.md).
|
|
175
|
+
- [Federation presets compose trusted ports](docs/decisions/DR-003-trusted-federation-composition.md).
|
|
176
|
+
- [Parked human exchanges need explicit deadlines](docs/decisions/DR-004-parked-human-exchange-deadlines.md).
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Federation hosts supply `messages`, `errorCode` and `fingerprintDomain` explicitly. Import
|
|
180
|
+
`defaultFederationMessages` as neutral English or replace the full object with host wording.
|
|
181
|
+
Optional opaque `scope` travels separately from approval duration through store and webhook
|
|
182
|
+
ports. Existing approvals retain their fingerprint and storage-key bytes when the host supplies
|
|
183
|
+
its existing domain and partition. Logger and clock ports come from `@jini-ai/core/primitives`.
|
|
184
|
+
See `API.md` for the breaking signatures and the shared atomic writer's durability semantics.
|
|
185
|
+
MCP imports core text sanitization directly and has no dependency on the CLI shell.
|
|
186
|
+
|
|
187
|
+
## Managed project containment
|
|
188
|
+
|
|
189
|
+
Managed-project detection resolves real/lexical paths as before, then uses the
|
|
190
|
+
browser-safe `pathContains` helper from `@jini-ai/core/primitives`. Comparison
|
|
191
|
+
follows the host's path case policy and respects directory boundaries. A legal
|
|
192
|
+
project directory beginning with `..` is accepted; escaping the managed root
|
|
193
|
+
or selecting the root itself is still refused. Platform remains required for
|
|
194
|
+
other Node integrations, not for this pure containment check.
|
|
195
|
+
|
|
196
|
+
Permanent deletion always needs a fresh one-call confirmation. Destructive cards offer neither
|
|
197
|
+
chat nor always approval; matching remembered grants cannot skip them, and forged remember choices
|
|
198
|
+
are discarded. Write-shaped protected calls also remain unrememberable. The host must keep its
|
|
199
|
+
persisted fingerprint domain stable when adopting federation; changing it invalidates saved grants.
|
|
@@ -18,7 +18,11 @@ export type AgentSlug = (typeof AGENT_SLUGS)[number];
|
|
|
18
18
|
* @param value The string to test.
|
|
19
19
|
* @returns `true` if `value` is a valid `AgentSlug`, `false` otherwise.
|
|
20
20
|
*/
|
|
21
|
-
export declare function isAgentSlug(
|
|
21
|
+
export declare function isAgentSlug(args: {
|
|
22
|
+
value: string;
|
|
23
|
+
}): args is {
|
|
24
|
+
value: AgentSlug;
|
|
25
|
+
};
|
|
22
26
|
/** The resolved stdio launch spec — mirrors the relevant fields of
|
|
23
27
|
* McpInstallPayload (command/args/env). */
|
|
24
28
|
export interface McpLaunchSpec {
|
|
@@ -78,7 +82,13 @@ export type InstallPlan = CliInstallPlan | JsonInstallPlan | ManualInstallPlan;
|
|
|
78
82
|
* Build the registration plan for one agent. Pure — all IO happens in the
|
|
79
83
|
* executor. Throws on an unknown slug so callers surface a clear error.
|
|
80
84
|
*/
|
|
81
|
-
export declare function planAgentInstall(slug
|
|
85
|
+
export declare function planAgentInstall({ slug, spec, ctx }: {
|
|
86
|
+
slug: AgentSlug;
|
|
87
|
+
spec: McpLaunchSpec;
|
|
88
|
+
ctx: PlanContext;
|
|
89
|
+
}, { env }?: {
|
|
90
|
+
env?: NodeJS.ProcessEnv;
|
|
91
|
+
}): InstallPlan;
|
|
82
92
|
/**
|
|
83
93
|
* Merge one server entry into existing config text without disturbing the
|
|
84
94
|
* rest. Missing intermediate objects are created. Returns pretty JSON with
|
|
@@ -86,12 +96,18 @@ export declare function planAgentInstall(slug: AgentSlug, spec: McpLaunchSpec, c
|
|
|
86
96
|
* (caller decides whether to bail or back up), or if `plan.keyPath` /
|
|
87
97
|
* `plan.serverKey` contains a dangerous object-path segment (CR-007).
|
|
88
98
|
*/
|
|
89
|
-
export declare function applyJsonInstall(existingText
|
|
99
|
+
export declare function applyJsonInstall({ existingText, plan }: {
|
|
100
|
+
existingText: string | null;
|
|
101
|
+
plan: JsonInstallPlan;
|
|
102
|
+
}): string;
|
|
90
103
|
/**
|
|
91
104
|
* Remove the server entry. Returns the new text, or null when the file did
|
|
92
105
|
* not exist / had nothing to remove (caller treats null as a no-op). Throws
|
|
93
106
|
* if `plan.keyPath` / `plan.serverKey` contains a dangerous object-path
|
|
94
107
|
* segment (CR-007).
|
|
95
108
|
*/
|
|
96
|
-
export declare function removeJsonInstall(existingText
|
|
109
|
+
export declare function removeJsonInstall({ existingText, plan }: {
|
|
110
|
+
existingText: string | null;
|
|
111
|
+
plan: JsonInstallPlan;
|
|
112
|
+
}): string | null;
|
|
97
113
|
//# sourceMappingURL=install.d.ts.map
|
|
@@ -57,8 +57,8 @@ export const AGENT_SLUGS = [
|
|
|
57
57
|
* @param value The string to test.
|
|
58
58
|
* @returns `true` if `value` is a valid `AgentSlug`, `false` otherwise.
|
|
59
59
|
*/
|
|
60
|
-
export function isAgentSlug(
|
|
61
|
-
return AGENT_SLUGS.includes(value);
|
|
60
|
+
export function isAgentSlug(args) {
|
|
61
|
+
return AGENT_SLUGS.includes(args.value);
|
|
62
62
|
}
|
|
63
63
|
// RESIDUAL RISK (CR-006 / SEC-RB-002): `-e`/`--env KEY=VALUE` is claude's,
|
|
64
64
|
// codex's, and kimi's own documented `mcp add` CLI contract for passing
|
|
@@ -102,7 +102,7 @@ function jsonEntry(spec, extra = {}, envKey = 'env') {
|
|
|
102
102
|
* Build the registration plan for one agent. Pure — all IO happens in the
|
|
103
103
|
* executor. Throws on an unknown slug so callers surface a clear error.
|
|
104
104
|
*/
|
|
105
|
-
export function planAgentInstall(slug, spec, ctx) {
|
|
105
|
+
export function planAgentInstall({ slug, spec, ctx }, { env = process.env } = {}) {
|
|
106
106
|
const { home, platform, serverName } = ctx;
|
|
107
107
|
switch (slug) {
|
|
108
108
|
// ----- CLI-driven agents (idempotent via the agent's own tool) -----
|
|
@@ -172,7 +172,7 @@ export function planAgentInstall(slug, spec, ctx) {
|
|
|
172
172
|
return {
|
|
173
173
|
kind: 'json',
|
|
174
174
|
slug,
|
|
175
|
-
configPath: clineConfigPath(home, platform),
|
|
175
|
+
configPath: clineConfigPath(home, platform, env.APPDATA),
|
|
176
176
|
keyPath: ['mcpServers'],
|
|
177
177
|
serverKey: serverName,
|
|
178
178
|
entry: jsonEntry(spec, { disabled: false, autoApprove: [] }),
|
|
@@ -219,7 +219,7 @@ export function planAgentInstall(slug, spec, ctx) {
|
|
|
219
219
|
return {
|
|
220
220
|
kind: 'json',
|
|
221
221
|
slug,
|
|
222
|
-
configPath: traeConfigPath(home, platform),
|
|
222
|
+
configPath: traeConfigPath(home, platform, env.APPDATA),
|
|
223
223
|
keyPath: ['mcpServers'],
|
|
224
224
|
serverKey: serverName,
|
|
225
225
|
entry: jsonEntry(spec),
|
|
@@ -263,23 +263,23 @@ export function planAgentInstall(slug, spec, ctx) {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
-
function clineConfigPath(home, platform) {
|
|
266
|
+
function clineConfigPath(home, platform, suppliedAppData) {
|
|
267
267
|
const rel = path.join('globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
|
|
268
268
|
if (platform === 'darwin') {
|
|
269
269
|
return path.join(home, 'Library', 'Application Support', 'Code', 'User', rel);
|
|
270
270
|
}
|
|
271
271
|
if (platform === 'win32') {
|
|
272
|
-
const appData =
|
|
272
|
+
const appData = suppliedAppData ?? path.join(home, 'AppData', 'Roaming');
|
|
273
273
|
return path.join(appData, 'Code', 'User', rel);
|
|
274
274
|
}
|
|
275
275
|
return path.join(home, '.config', 'Code', 'User', rel);
|
|
276
276
|
}
|
|
277
|
-
function traeConfigPath(home, platform) {
|
|
277
|
+
function traeConfigPath(home, platform, suppliedAppData) {
|
|
278
278
|
if (platform === 'darwin') {
|
|
279
279
|
return path.join(home, 'Library', 'Application Support', 'Trae', 'User', 'mcp.json');
|
|
280
280
|
}
|
|
281
281
|
if (platform === 'win32') {
|
|
282
|
-
const appData =
|
|
282
|
+
const appData = suppliedAppData ?? path.join(home, 'AppData', 'Roaming');
|
|
283
283
|
return path.join(appData, 'Trae', 'User', 'mcp.json');
|
|
284
284
|
}
|
|
285
285
|
return path.join(home, '.config', 'Trae', 'User', 'mcp.json');
|
|
@@ -317,7 +317,7 @@ function assertSafeJsonInstallPlan(plan) {
|
|
|
317
317
|
* (caller decides whether to bail or back up), or if `plan.keyPath` /
|
|
318
318
|
* `plan.serverKey` contains a dangerous object-path segment (CR-007).
|
|
319
319
|
*/
|
|
320
|
-
export function applyJsonInstall(existingText, plan) {
|
|
320
|
+
export function applyJsonInstall({ existingText, plan }) {
|
|
321
321
|
assertSafeJsonInstallPlan(plan);
|
|
322
322
|
const root = parseJsonObject(existingText, plan.configPath);
|
|
323
323
|
let cursor = root;
|
|
@@ -337,7 +337,7 @@ export function applyJsonInstall(existingText, plan) {
|
|
|
337
337
|
* if `plan.keyPath` / `plan.serverKey` contains a dangerous object-path
|
|
338
338
|
* segment (CR-007).
|
|
339
339
|
*/
|
|
340
|
-
export function removeJsonInstall(existingText, plan) {
|
|
340
|
+
export function removeJsonInstall({ existingText, plan }) {
|
|
341
341
|
assertSafeJsonInstallPlan(plan);
|
|
342
342
|
if (existingText == null || existingText.trim() === '')
|
|
343
343
|
return null;
|
package/dist/bin/serve.d.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The bootable `jini-mcp` binary (`package.json`'s `"bin": { "jini-mcp": "./dist/bin/serve.js" }`)
|
|
6
6
|
* — the real executable entry point gap 3's MCP-callback continuation-transport spike needed and
|
|
7
|
-
* did not have (see `packages/daemon/
|
|
8
|
-
* addition, and this package's own dated section in `
|
|
7
|
+
* did not have (see `packages/daemon/archived provenance ledger`'s "run/chat orchestration gap 3, part 1"
|
|
8
|
+
* addition, and this package's own dated section in `archived provenance ledger`). `../server/tool-server.js`'s
|
|
9
9
|
* `createMcpToolServer` was always designed to run as *this process's own* stdio — meant to be a
|
|
10
10
|
* script a client (Claude Code) spawns as its own child subprocess and talks to over that child's
|
|
11
11
|
* stdin/stdout — but until this file, nothing in this package actually was that script.
|
|
12
12
|
*
|
|
13
|
-
* Follows
|
|
13
|
+
* Follows the CLI entry point's established shape for a bootable, side-effect-guarded, `#!/usr/bin/env
|
|
14
14
|
* node` entry point: a named, fully-dependency-injected `serve()` export (importable for tests, or
|
|
15
15
|
* for a consumer building its own bin wrapper around it) plus a bottom-of-file guard that only
|
|
16
16
|
* calls it for real when this module is the actual process entrypoint — mirrors that file's own
|
|
@@ -29,19 +29,18 @@
|
|
|
29
29
|
* other tool this server hosts (`RUN_TOOLS`, `../server/tools/run-tools.js`) is run-agnostic and
|
|
30
30
|
* needed no such scoping.
|
|
31
31
|
*
|
|
32
|
-
* **Daemon-URL resolution**
|
|
33
|
-
* `resolveDaemonUrl` (
|
|
32
|
+
* **Daemon-URL resolution** follows the spawning host's environment: no baked-in default, just
|
|
33
|
+
* `resolveDaemonUrl` (the CLI transport) wired to a `--daemon-url`-shaped precedence chain — except this
|
|
34
34
|
* process has no argv to parse (an MCP client launches it with a fixed `command`/`args`/`env`, not
|
|
35
35
|
* interactive flags), so only the env-var step applies: {@link DAEMON_URL_ENV_VAR}. When unset,
|
|
36
36
|
* `resolveDaemonUrl` throws, and this file's own top-level error boundary turns that into a clean
|
|
37
37
|
* stderr message + non-zero exit rather than an unhandled rejection.
|
|
38
38
|
*/
|
|
39
39
|
import type { Readable, Writable } from 'node:stream';
|
|
40
|
-
import { resolveDaemonUrl as resolveDaemonUrlDefault } from '@jini-ai/cli';
|
|
41
40
|
import { createMcpToolServer as createMcpToolServerDefault } from '../server/tool-server.js';
|
|
42
41
|
/** The one run this process is scoped to for its entire lifetime. Set by the spawning daemon, never by the MCP client. */
|
|
43
42
|
export declare const RUN_ID_ENV_VAR = "JINI_RUN_ID";
|
|
44
|
-
/** Checked (only) when resolving the daemon HTTP base URL — see `resolveDaemonUrl`'s own docs; matches
|
|
43
|
+
/** Checked (only) when resolving the daemon HTTP base URL — see `resolveDaemonUrl`'s own docs; matches the CLI entry point's identically-named constant. */
|
|
45
44
|
export declare const DAEMON_URL_ENV_VAR = "JINI_DAEMON_URL";
|
|
46
45
|
/**
|
|
47
46
|
* Bearer credential for this process's daemon callbacks, set by the spawning daemon when it issued
|
|
@@ -72,15 +71,19 @@ export interface ServeDeps {
|
|
|
72
71
|
/** @default process.env */
|
|
73
72
|
readonly env?: NodeJS.ProcessEnv;
|
|
74
73
|
/** @default process.stderr.write */
|
|
75
|
-
readonly writeErr?: (
|
|
74
|
+
readonly writeErr?: (args: {
|
|
75
|
+
text: string;
|
|
76
|
+
}) => void;
|
|
76
77
|
/** @default process.exit; inject for tests (must not return) */
|
|
77
|
-
readonly exit?: (
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
readonly exit?: (args: {
|
|
79
|
+
code: number;
|
|
80
|
+
}) => never;
|
|
81
|
+
/** @default the real environment-only daemon URL resolution */
|
|
82
|
+
readonly resolveDaemonUrl?: (required: Record<string, never>, options: McpDaemonUrlOptions) => Promise<string>;
|
|
80
83
|
/** @default the real createMcpToolServer */
|
|
81
84
|
readonly createMcpToolServer?: typeof createMcpToolServerDefault;
|
|
82
85
|
/** Threaded into `execute_delegated_tool`'s per-call correlation id. @default node:crypto randomUUID */
|
|
83
|
-
readonly generateToolUseId?: () => string;
|
|
86
|
+
readonly generateToolUseId?: (required: Record<string, never>) => string;
|
|
84
87
|
/** Threaded into every tool's daemon call. @default the global fetch */
|
|
85
88
|
readonly fetchImpl?: typeof fetch;
|
|
86
89
|
/** @default process.stdin */
|
|
@@ -88,6 +91,15 @@ export interface ServeDeps {
|
|
|
88
91
|
/** @default process.stdout */
|
|
89
92
|
readonly stdout?: Writable;
|
|
90
93
|
}
|
|
94
|
+
/** This executable has no flags or discovery phase: its spawning host supplies the environment. */
|
|
95
|
+
export interface McpDaemonUrlOptions {
|
|
96
|
+
flagUrl: null;
|
|
97
|
+
env: NodeJS.ProcessEnv;
|
|
98
|
+
envVarName: string;
|
|
99
|
+
warn: (required: {
|
|
100
|
+
message: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
}
|
|
91
103
|
/**
|
|
92
104
|
* Assembles and runs the `jini-mcp` stdio MCP server: `RUN_ID_ENV_VAR` (required — this process
|
|
93
105
|
* has no meaningful default run to serve) plus `DAEMON_URL_ENV_VAR` resolution, then hosts
|
|
@@ -96,5 +108,5 @@ export interface ServeDeps {
|
|
|
96
108
|
* idle-exit window elapses (`createMcpToolServer`'s own default) — never bare-throws: every error
|
|
97
109
|
* path below writes a message to `writeErr` and calls `exit(1)` instead.
|
|
98
110
|
*/
|
|
99
|
-
export declare function serve(deps?: ServeDeps): Promise<void>;
|
|
111
|
+
export declare function serve(_requiredArgs: Record<string, never>, deps?: ServeDeps): Promise<void>;
|
|
100
112
|
//# sourceMappingURL=serve.d.ts.map
|