@jini-ai/mcp 0.4.1 → 0.4.3
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 +6 -0
- package/README.md +1 -0
- package/dist/agent-install/chrome-devtools.d.ts +4 -0
- package/dist/agent-install/chrome-devtools.js +7 -0
- package/dist/agent-install/index.d.ts +1 -0
- package/dist/agent-install/index.js +1 -0
- package/dist/bin/serve.js +6 -2
- package/dist/federation/bootstrap.d.ts +18 -0
- package/dist/federation/bootstrap.js +4 -4
- package/dist/federation/ports.d.ts +25 -0
- package/dist/federation/ports.js +19 -0
- package/dist/federation/presets.d.ts +16 -17
- package/dist/federation/presets.js +35 -28
- package/dist/federation/testing/adapter.memory.d.ts +4 -3
- package/dist/federation/testing/adapter.memory.js +3 -2
- package/dist/federation/testing/tool-approvals.memory.js +8 -0
- package/dist/federation/tool-approvals.d.ts +3 -0
- package/dist/index.d.ts +6 -177
- package/dist/index.js +6 -180
- package/dist/server/daemon-client.d.ts +2 -3
- package/dist/server/daemon-client.js +8 -27
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/resources/active-resource.d.ts +1 -2
- package/dist/server/resources/active-resource.js +4 -24
- package/dist/server/resources/site-guide-resource.d.ts +15 -0
- package/dist/server/resources/site-guide-resource.js +25 -0
- package/dist/server/tool-server.d.ts +5 -23
- package/dist/server/tool-server.js +1 -1
- package/dist/server/tools/run-tools.js +3 -12
- package/package.json +22 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @jini-ai/mcp
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Tovu clean-up release (2026-10-08): code Tovu moved into Jini, plus the Jini clean-up (see the commit log since 0.4.1).
|
|
8
|
+
|
|
3
9
|
## 0.4.1 — 2026-10-06
|
|
4
10
|
|
|
5
11
|
- The delegated tool gateways send input `{}` when the model omits it or sends `null`, so a no-input call no longer fails before a retry.
|
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ engine nor retries a challenged request automatically.
|
|
|
58
58
|
returning a discriminated `InstallPlan` (`CliInstallPlan` | `JsonInstallPlan` | `ManualInstallPlan`),
|
|
59
59
|
and `applyJsonInstall` / `removeJsonInstall` to execute the JSON-file variant. Plus
|
|
60
60
|
`buildMcpInstallPayload` for the install-info payload a UI renders.
|
|
61
|
+
For an explicit UI check, pass `CHROME_DEVTOOLS_MCP_PRESET` to `planAgentInstall` with `ctx.serverName: "chrome-devtools"`; it pins 1.10.1 and disables usage statistics and CrUX. See the [on-demand recipe](../diagnostics/docs/ui-performance-check.md).
|
|
61
62
|
|
|
62
63
|
**Client-side runtime helpers** — `extractRelativeRefs`, `isTextualMime`.
|
|
63
64
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Explicit on-demand launch preset; importing it never registers or launches a browser. */
|
|
2
|
+
export const CHROME_DEVTOOLS_MCP_PRESET = {
|
|
3
|
+
command: 'npx',
|
|
4
|
+
args: ['-y', 'chrome-devtools-mcp@1.10.1', '--no-usage-statistics', '--no-performance-crux'],
|
|
5
|
+
env: {},
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=chrome-devtools.js.map
|
package/dist/bin/serve.js
CHANGED
|
@@ -7,6 +7,7 @@ import { TOOL_CATALOG_TOOLS } from '../server/tools/tool-catalog-tools.js';
|
|
|
7
7
|
import { COMPONENT_CATALOG_TOOLS } from '../server/tools/component-catalog-tools.js';
|
|
8
8
|
import { createExecuteDelegatedToolTool, createExecuteReadonlyDelegatedToolTool } from '../server/tools/delegated-tool.js';
|
|
9
9
|
import { KERNEL_RESOURCES } from '../server/resources/active-resource.js';
|
|
10
|
+
import { createSiteGuideResource } from '../server/resources/site-guide-resource.js';
|
|
10
11
|
/** The one run this process is scoped to for its entire lifetime. Set by the spawning daemon, never by the MCP client. */
|
|
11
12
|
export const RUN_ID_ENV_VAR = 'JINI_RUN_ID';
|
|
12
13
|
/** Checked (only) when resolving the daemon HTTP base URL — see `resolveDaemonUrl`'s own docs; matches the CLI entry point's identically-named constant. */
|
|
@@ -40,7 +41,10 @@ const SERVER_NAME = 'jini-mcp';
|
|
|
40
41
|
const SERVER_VERSION = '0.0.0';
|
|
41
42
|
const INSTRUCTIONS = 'Tools proxying this host\'s Jini daemon over loopback HTTP, scoped to the run that spawned this MCP server process. ' +
|
|
42
43
|
'Use execute_delegated_tool to invoke a Jini-registered tool (never an agent-vendor-specific tool name) against that run — ' +
|
|
43
|
-
'every call is routed through the daemon\'s deny-by-default ToolExecutor gate.'
|
|
44
|
+
'every call is routed through the daemon\'s deny-by-default ToolExecutor gate. ' +
|
|
45
|
+
'When the host provides site_guide, consult jini://site-guide or call site_guide with operation overview ' +
|
|
46
|
+
'through execute_readonly_delegated_tool; follow with explain for the relevant topic ids. ' +
|
|
47
|
+
'If the host does not provide site_guide, report that availability gap.';
|
|
44
48
|
// Keep the bin's environment-only assembly here; a general CLI resolver would introduce a
|
|
45
49
|
// runtime dependency on another shell. No discovery/default URL is selected for a run child.
|
|
46
50
|
async function resolveDaemonUrlDefault(_required, options) {
|
|
@@ -120,7 +124,7 @@ export async function serve(_requiredArgs, deps = {}) {
|
|
|
120
124
|
: undefined;
|
|
121
125
|
const requiredArgs = { name: SERVER_NAME, version: SERVER_VERSION, tools, resolveBaseUrl };
|
|
122
126
|
const serverOptions = {
|
|
123
|
-
resources: KERNEL_RESOURCES,
|
|
127
|
+
resources: [...KERNEL_RESOURCES, createSiteGuideResource({ runId }, delegatedGatewayOptions)],
|
|
124
128
|
instructions: INSTRUCTIONS,
|
|
125
129
|
...(authHeaders !== undefined ? { authHeaders } : {}),
|
|
126
130
|
...(deps.fetchImpl !== undefined ? { fetchImpl: deps.fetchImpl } : {}),
|
|
@@ -2,6 +2,7 @@ import { type Logger } from "@jini-ai/core/primitives";
|
|
|
2
2
|
import type { ToolRegistry } from "@jini-ai/core";
|
|
3
3
|
import type { ResolvedFederatedConnection } from "./config.js";
|
|
4
4
|
import type { McpSessionPort } from "./ports.js";
|
|
5
|
+
import { type FederatedMcpPresetRegistry } from "./presets.js";
|
|
5
6
|
import { type FederationDeps } from "./registrations.js";
|
|
6
7
|
import type { FederatedAdmissionReport } from "./trust.js";
|
|
7
8
|
export interface AttachFederatedToolsResult {
|
|
@@ -74,6 +75,7 @@ export interface AttachFederatedToolsResult {
|
|
|
74
75
|
* @complexity O(c · t) in connections and their advertised tools.
|
|
75
76
|
*/
|
|
76
77
|
export interface AttachFederatedMcpToolsParams {
|
|
78
|
+
readonly presetRegistry?: FederatedMcpPresetRegistry;
|
|
77
79
|
registry: ToolRegistry;
|
|
78
80
|
deps: FederationDeps;
|
|
79
81
|
connections?: readonly ResolvedFederatedConnection[] | undefined;
|
|
@@ -97,4 +99,20 @@ export interface AttachFederatedMcpToolsParams {
|
|
|
97
99
|
export type AttachFederatedMcpToolsRequiredArgs = Pick<AttachFederatedMcpToolsParams, "registry" | "deps" | "connect">;
|
|
98
100
|
export type AttachFederatedMcpToolsOptions = Omit<AttachFederatedMcpToolsParams, "registry" | "deps" | "connect">;
|
|
99
101
|
export declare function attachFederatedMcpTools(requiredArgs: AttachFederatedMcpToolsRequiredArgs, optionalArgs?: AttachFederatedMcpToolsOptions): Promise<AttachFederatedToolsResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Asks every registered preset for a connection, in registration order.
|
|
104
|
+
*
|
|
105
|
+
* Errors are isolated PER PRESET rather than abandoning the whole resolution pass: an enabled-but-
|
|
106
|
+
* invalid Supabase config must not also disable a correctly-configured second vendor that happens to
|
|
107
|
+
* be registered after it. Each failure is loud — the operator meant to have that connection and does
|
|
108
|
+
* not — and still non-fatal, because host app's own assistant is unaffected either way.
|
|
109
|
+
*
|
|
110
|
+
* A preset that declines (`null`) is silent: "not configured" is the expected default state, and
|
|
111
|
+
* logging it every boot would train operators to ignore this channel.
|
|
112
|
+
*/
|
|
113
|
+
export declare function resolveFederatedMcpPresets({ env, logger, registry }: {
|
|
114
|
+
env: NodeJS.ProcessEnv;
|
|
115
|
+
logger: Pick<Logger, "warn">;
|
|
116
|
+
registry: FederatedMcpPresetRegistry;
|
|
117
|
+
}, _optional?: {}): ResolvedFederatedConnection[];
|
|
100
118
|
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createConsoleLogger } from "@jini-ai/core/primitives";
|
|
2
|
-
import {
|
|
2
|
+
import { defaultFederatedMcpPresetRegistry } from "./presets.js";
|
|
3
3
|
import { federateSession } from "./registrations.js";
|
|
4
4
|
/**
|
|
5
5
|
* @file The composition root for outbound MCP federation: the one function
|
|
@@ -56,7 +56,7 @@ function withPresetOrigin(connection) {
|
|
|
56
56
|
function resolveFederationAttachInputs(params) {
|
|
57
57
|
const logger = params.logger ?? consoleLogger;
|
|
58
58
|
const connect = params.connect;
|
|
59
|
-
const presetConnections = params.connections ??
|
|
59
|
+
const presetConnections = params.connections ?? resolveFederatedMcpPresets({ env: params.env ?? process.env, logger, registry: params.presetRegistry ?? defaultFederatedMcpPresetRegistry });
|
|
60
60
|
const connections = [
|
|
61
61
|
...presetConnections.map((connection) => ({ connection: withPresetOrigin(connection), isPreset: true })),
|
|
62
62
|
...(params.extraConnections ?? []).map((connection) => ({ connection, isPreset: false })),
|
|
@@ -162,9 +162,9 @@ async function attachOneFederatedConnection(params) {
|
|
|
162
162
|
* A preset that declines (`null`) is silent: "not configured" is the expected default state, and
|
|
163
163
|
* logging it every boot would train operators to ignore this channel.
|
|
164
164
|
*/
|
|
165
|
-
function
|
|
165
|
+
export function resolveFederatedMcpPresets({ env, logger, registry }, _optional = {}) {
|
|
166
166
|
const connections = [];
|
|
167
|
-
for (const preset of
|
|
167
|
+
for (const preset of registry.list({})) {
|
|
168
168
|
try {
|
|
169
169
|
const connection = preset.resolve({ env: env });
|
|
170
170
|
if (connection)
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The typed boundary for OUTBOUND MCP federation: the host daemon acting as a client of
|
|
3
|
+
* third-party servers. Inbound MCP injection hands an agent access to the host's own catalog;
|
|
4
|
+
* outbound federation accepts another party's tool surface, so its trust question is different.
|
|
5
|
+
*
|
|
6
|
+
* Why a declared port rather than `@modelcontextprotocol/sdk`: the SDK is not a dependency and
|
|
7
|
+
* would add a transitive tree to the boot path for a small JSON-RPC surface. A real adapter and
|
|
8
|
+
* a memory double share one interface instead.
|
|
9
|
+
*
|
|
10
|
+
* Layered seams are deliberate. McpSessionPort is the outer seam for trust and registration tests
|
|
11
|
+
* without protocol I/O. McpStdioChannel and McpHttpExchange are the inner seams, so the real
|
|
12
|
+
* client's framing, handshake, pagination, cancellation and timeouts run against scripted bytes.
|
|
13
|
+
* A fake at the outer seam alone would test the fake, which would not satisfy the constraint of
|
|
14
|
+
* exercising protocol behavior without a live third-party server. Both transports share protocol
|
|
15
|
+
* logic above those seams; adding a hosted transport does not redesign the federation boundary.
|
|
16
|
+
*
|
|
17
|
+
* All remote names, descriptions, schemas, annotations and results are untrusted input. These
|
|
18
|
+
* declarations type them; trust.ts vets them before they reach a registry or model.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Remote behavior hints are self-declared by the party being classified. A host's native risk
|
|
22
|
+
* catalog refuses to let a tool downgrade its own risk by editing one word; letting a remote
|
|
23
|
+
* server change its annotations would make the same mistake with a party the host cannot control.
|
|
24
|
+
* Hints are carried and logged, but trust.ts only lets them make a tool LESS available, never more.
|
|
25
|
+
*/
|
|
1
26
|
export interface RemoteToolAnnotations {
|
|
2
27
|
readonly title?: string | undefined;
|
|
3
28
|
readonly readOnlyHint?: boolean | undefined;
|
package/dist/federation/ports.js
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The typed boundary for OUTBOUND MCP federation: the host daemon acting as a client of
|
|
3
|
+
* third-party servers. Inbound MCP injection hands an agent access to the host's own catalog;
|
|
4
|
+
* outbound federation accepts another party's tool surface, so its trust question is different.
|
|
5
|
+
*
|
|
6
|
+
* Why a declared port rather than `@modelcontextprotocol/sdk`: the SDK is not a dependency and
|
|
7
|
+
* would add a transitive tree to the boot path for a small JSON-RPC surface. A real adapter and
|
|
8
|
+
* a memory double share one interface instead.
|
|
9
|
+
*
|
|
10
|
+
* Layered seams are deliberate. McpSessionPort is the outer seam for trust and registration tests
|
|
11
|
+
* without protocol I/O. McpStdioChannel and McpHttpExchange are the inner seams, so the real
|
|
12
|
+
* client's framing, handshake, pagination, cancellation and timeouts run against scripted bytes.
|
|
13
|
+
* A fake at the outer seam alone would test the fake, which would not satisfy the constraint of
|
|
14
|
+
* exercising protocol behavior without a live third-party server. Both transports share protocol
|
|
15
|
+
* logic above those seams; adding a hosted transport does not redesign the federation boundary.
|
|
16
|
+
*
|
|
17
|
+
* All remote names, descriptions, schemas, annotations and results are untrusted input. These
|
|
18
|
+
* declarations type them; trust.ts vets them before they reach a registry or model.
|
|
19
|
+
*/
|
|
1
20
|
/** Narrows a {@link McpLaunchSpec} to its hosted arm. */
|
|
2
21
|
export function isHttpLaunchSpec(input) {
|
|
3
22
|
return "url" in input.spec;
|
|
@@ -44,23 +44,22 @@ export interface FederatedMcpPreset {
|
|
|
44
44
|
readonly presetId: string;
|
|
45
45
|
readonly resolve: FederatedMcpPresetResolver;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* preserved
|
|
56
|
-
*/
|
|
57
|
-
export declare function
|
|
47
|
+
export interface FederatedMcpPresetRegistry {
|
|
48
|
+
register(required: {
|
|
49
|
+
preset: FederatedMcpPreset;
|
|
50
|
+
}, optional?: Record<string, never>): void;
|
|
51
|
+
list(required: Record<string, never>, optional?: Record<string, never>): readonly FederatedMcpPreset[];
|
|
52
|
+
reset(required: Record<string, never>, optional?: Record<string, never>): void;
|
|
53
|
+
}
|
|
54
|
+
/** Each host owns one ordered registry. Replacement retains order; snapshots cannot mutate it.
|
|
55
|
+
* The preserved reset rationale below describes the legacy module registry; reset now clears
|
|
56
|
+
* only this instance. The published default registry is a compatibility adapter. */
|
|
57
|
+
export declare function createFederatedMcpPresetRegistry(_required: Record<string, never>, _optional?: Record<string, never>): FederatedMcpPresetRegistry;
|
|
58
|
+
/** Compatibility owner for published callers; hosts inject their own instance at boot. */
|
|
59
|
+
export declare const defaultFederatedMcpPresetRegistry: FederatedMcpPresetRegistry;
|
|
60
|
+
export declare function registerFederatedMcpPreset(required: {
|
|
58
61
|
preset: FederatedMcpPreset;
|
|
59
|
-
}): void;
|
|
60
|
-
/** Registration order. `bootstrap.ts` resolves in this order, and that order decides which
|
|
61
|
-
* connection claims a contested tool id first (R1). */
|
|
62
|
+
}, optional?: {}): void;
|
|
62
63
|
export declare function listFederatedMcpPresets(): readonly FederatedMcpPreset[];
|
|
63
|
-
|
|
64
|
-
* `resetPageHeadRegistry`, though that one is no longer test-only — see its own doc). */
|
|
65
|
-
export declare function resetFederatedMcpPresetsForTests(_required: Record<string, never>): void;
|
|
64
|
+
export declare function resetFederatedMcpPresetsForTests(required: Record<string, never>): void;
|
|
66
65
|
//# sourceMappingURL=presets.d.ts.map
|
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
/** Each host owns one ordered registry. Replacement retains order; snapshots cannot mutate it.
|
|
2
|
+
* The preserved reset rationale below describes the legacy module registry; reset now clears
|
|
3
|
+
* only this instance. The published default registry is a compatibility adapter. */
|
|
4
|
+
export function createFederatedMcpPresetRegistry(_required, _optional = {}) {
|
|
5
|
+
let presets = [];
|
|
6
|
+
return {
|
|
7
|
+
/**
|
|
8
|
+
* Registers a preset, called once at composition-root boot by the preset's own module.
|
|
9
|
+
*
|
|
10
|
+
* Re-registering the same `presetId` REPLACES the earlier entry rather than appending. Unlike a
|
|
11
|
+
* `<head>` contributor, a duplicate here would resolve to two connections with the same
|
|
12
|
+
* `connectionId`, and `trust.ts` R1's collision assertion would then drop the second one whole —
|
|
13
|
+
* producing a confusing "registered, then refused for shadowing itself" log for what is really just
|
|
14
|
+
* a module imported from two places. Last registration wins, and registration order is otherwise
|
|
15
|
+
* preserved so a replacement does not silently reorder connections.
|
|
16
|
+
*/
|
|
17
|
+
register({ preset }) {
|
|
18
|
+
const existing = presets.findIndex(candidate => candidate.presetId === preset.presetId);
|
|
19
|
+
if (existing >= 0)
|
|
20
|
+
presets[existing] = preset;
|
|
21
|
+
else
|
|
22
|
+
presets.push(preset);
|
|
23
|
+
},
|
|
24
|
+
/** Registration order. `bootstrap.ts` resolves in this order, and that order decides which
|
|
25
|
+
* connection claims a contested tool id first (R1). */
|
|
26
|
+
list: () => Object.freeze(presets.slice()),
|
|
27
|
+
/** Test-only reset of the module-level registry (mirrors `page-head.ts`'s
|
|
28
|
+
* `resetPageHeadRegistry`, though that one is no longer test-only — see its own doc). */
|
|
29
|
+
reset: () => { presets = []; },
|
|
30
|
+
};
|
|
29
31
|
}
|
|
32
|
+
/** Compatibility owner for published callers; hosts inject their own instance at boot. */
|
|
33
|
+
export const defaultFederatedMcpPresetRegistry = createFederatedMcpPresetRegistry({});
|
|
34
|
+
export function registerFederatedMcpPreset(required, optional = {}) { defaultFederatedMcpPresetRegistry.register(required, optional); }
|
|
35
|
+
export function listFederatedMcpPresets() { return defaultFederatedMcpPresetRegistry.list({}); }
|
|
36
|
+
export function resetFederatedMcpPresetsForTests(required) { defaultFederatedMcpPresetRegistry.reset(required); }
|
|
30
37
|
//# sourceMappingURL=presets.js.map
|
|
@@ -50,16 +50,17 @@ export declare class InMemoryMcpSession implements McpSessionPort {
|
|
|
50
50
|
constructor({ tools }: {
|
|
51
51
|
tools: readonly RemoteToolDescriptor[];
|
|
52
52
|
}, options?: {
|
|
53
|
-
/** Defaults to echoing the call back, which is enough for most wiring assertions.
|
|
53
|
+
/** Defaults to echoing the call back, which is enough for most wiring assertions.
|
|
54
|
+
* The injected remote receives the same optional cancellation object as a real session. */
|
|
54
55
|
onCall?: ((required: {
|
|
55
56
|
name: string;
|
|
56
57
|
args: Record<string, unknown>;
|
|
57
|
-
}) => RemoteToolResult | Promise<RemoteToolResult>) | undefined;
|
|
58
|
+
}, options?: McpToolCallOptions) => RemoteToolResult | Promise<RemoteToolResult>) | undefined;
|
|
58
59
|
/** Overrides `listTools` wholesale, for the "the remote is down at connect time" cases. */
|
|
59
60
|
onListTools?: (() => Promise<RemoteToolDescriptor[]>) | undefined;
|
|
60
61
|
});
|
|
61
62
|
listTools(): Promise<RemoteToolDescriptor[]>;
|
|
62
|
-
callTool(request: McpToolCallRequiredArgs,
|
|
63
|
+
callTool(request: McpToolCallRequiredArgs, options?: McpToolCallOptions): Promise<RemoteToolResult>;
|
|
63
64
|
close(_required: Record<string, never>): Promise<void>;
|
|
64
65
|
}
|
|
65
66
|
/** One JSON-RPC message the fake channel received from the client under test. */
|
|
@@ -51,9 +51,10 @@ export class InMemoryMcpSession {
|
|
|
51
51
|
return this.listToolsBehaviour();
|
|
52
52
|
return [...this.tools];
|
|
53
53
|
}
|
|
54
|
-
async callTool(request,
|
|
54
|
+
async callTool(request, options = {}) {
|
|
55
55
|
this.calls.push({ name: request.name, arguments: request.arguments });
|
|
56
|
-
|
|
56
|
+
// The injected remote must see cancellation too, so wiring tests can exercise the real ABI.
|
|
57
|
+
return this.handler({ name: request.name, args: request.arguments }, options);
|
|
57
58
|
}
|
|
58
59
|
async close(_required) {
|
|
59
60
|
this.closed = true;
|
|
@@ -30,6 +30,14 @@ export function createInMemoryConversationToolApprovalStore(_required) {
|
|
|
30
30
|
async has(key) {
|
|
31
31
|
return rows.get(keyOf(key)) === key.fingerprint;
|
|
32
32
|
},
|
|
33
|
+
async hasIdentity(key) {
|
|
34
|
+
for (const [encoded, fingerprint] of rows) {
|
|
35
|
+
const [, principalId, connectionId, toolName] = JSON.parse(encoded);
|
|
36
|
+
if (principalId === key.principalId && connectionId === key.connectionId && toolName === key.toolName && fingerprint === key.fingerprint)
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
},
|
|
33
41
|
async grant({ key }) {
|
|
34
42
|
rows.set(keyOf(key), key.fingerprint);
|
|
35
43
|
},
|
|
@@ -89,6 +89,9 @@ export interface ConversationToolApprovalKey {
|
|
|
89
89
|
export interface ConversationToolApprovalStore {
|
|
90
90
|
/** True only for this conversation AND this person AND this exact fingerprint. */
|
|
91
91
|
has(key: ConversationToolApprovalKey): Promise<boolean>;
|
|
92
|
+
/** Native escalation consent can outlive the invoking chat, while remaining person/identity bound.
|
|
93
|
+
* Hosts namespace connectionId by workspace and behavior; deletion of all granting chats revokes it. */
|
|
94
|
+
hasIdentity?(key: Omit<ConversationToolApprovalKey, "conversationId">): Promise<boolean>;
|
|
92
95
|
grant(input: {
|
|
93
96
|
key: ConversationToolApprovalKey;
|
|
94
97
|
grantedAt: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,180 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Re-exports are explicit and named (no `export *`) so the public surface is
|
|
9
|
-
* visible here and name collisions surface at build time.
|
|
10
|
-
|
|
11
|
-
* Archived provenance rationale:
|
|
12
|
-
* ## What was dropped (and why)
|
|
13
|
-
*
|
|
14
|
-
* - **`live-artifacts/` (entire subdir)** — the OD per-run "live artifacts" MCP
|
|
15
|
-
* tool surface. An OD-product feature, out of scope per the port brief.
|
|
16
|
-
* - **The `od mcp` stdio server** in `client/client.ts` — `runMcpStdio`,
|
|
17
|
-
* `TOOL_DEFS`, `handleMcpToolCall`, and every tool handler (`get_artifact`,
|
|
18
|
-
* `create_project`, `start_run`, project/active-context resolution, studio deep
|
|
19
|
-
* links, run polling, …). This is not a generic MCP client: it is an
|
|
20
|
-
* OD-product MCP **server** hardwired to Open Design's REST API
|
|
21
|
-
* (`/api/projects`, `/api/skills`, `/api/design-systems`, `/api/runs`, …) and
|
|
22
|
-
* concepts (design systems, skills, plugins, artifacts, runs, studio, the
|
|
23
|
-
* `od://` resource scheme). It also imports the OD-only `@open-design/contracts`
|
|
24
|
-
* package (`buildProjectRawFileUrl`), the OD daemon-internal
|
|
25
|
-
* `../../artifacts/create.js`, and the `@modelcontextprotocol/sdk` — none of
|
|
26
|
-
* which exist in this repo. Faithfully porting it would mean importing OD
|
|
27
|
-
* product surface, so it was dropped and only its generic primitives salvaged.
|
|
28
|
-
* - **`MCP_TEMPLATES` catalog + template types + `templateId`** in `config.ts` —
|
|
29
|
-
* a large, design-tool-tilted curated catalog of third-party MCP servers with
|
|
30
|
-
* OD marketing prose. Zero functional coupling; dropped for neutrality (same
|
|
31
|
-
* rationale `@jini/sqlite` used to drop the OD "templates" table).
|
|
32
|
-
*
|
|
33
|
-
* ### The `list_agents` decision
|
|
34
|
-
*
|
|
35
|
-
* Investigated before building anything, per the task brief. `@jini/agent-runtime`'s
|
|
36
|
-
* barrel (`packages/agent-runtime/src/index.ts` → `export * from './registry.js'`)
|
|
37
|
-
* already exports `AGENT_DEFS: RuntimeAgentDef[]` — the exact static, in-memory
|
|
38
|
-
* list of every registered agent def (`registry.ts`'s `BASE_AGENT_DEFS`, 24
|
|
39
|
-
* entries). `RuntimeAgentDef` has plain `id`/`name` fields (plus a large surface
|
|
40
|
-
* of CLI-spawn internals: `bin`, `buildArgs`, `env`, `listModels`, etc. — no
|
|
41
|
-
* `available`/live-detection field at all). No HTTP projection of this list
|
|
42
|
-
* existed anywhere in `packages/http/src/*.ts` before this pass. Building
|
|
43
|
-
* `GET /api/agents` returning `{agents: [{id, name}]}` needed no design judgment
|
|
44
|
-
* call beyond "project the two safe fields" — no timeout/caching/probing
|
|
45
|
-
* decisions, unlike OD's own `list_agents` (`listAgents`, mcp.ts:1034-1057),
|
|
46
|
-
* which filters on a *live-detected* `available: boolean` field (OD's
|
|
47
|
-
* `/api/agents` route actually probes each agent binary for installation/version
|
|
48
|
-
* at request time — a real design decision this pass deliberately did not need
|
|
49
|
-
* to make, since Jini has no such probing route or field to project). So it was
|
|
50
|
-
* built: `packages/http/src/agents.ts`'s `agentListRoute` (`GET /api/agents`,
|
|
51
|
-
* `AgentsHttpDeps.listAgents: () => readonly {id, name}[]` injected — DI,
|
|
52
|
-
* matching `daemon-status.ts`/`active-context.ts`'s convention, so `@jini/http`
|
|
53
|
-
* does not take on a new `@jini/agent-runtime` package dependency for a
|
|
54
|
-
* two-field projection) plus `@jini/mcp`'s `list_agents` tool
|
|
55
|
-
* (`server/tools/run-tools.ts`) proxying it. OD's `includeUnavailable` flag and
|
|
56
|
-
* per-agent `installUrl`/`modelsCount` enrichment were **not** ported — those
|
|
57
|
-
* exist only because OD's route does live probing, which this pass's route does
|
|
58
|
-
* not attempt.
|
|
59
|
-
*
|
|
60
|
-
* ### What was explicitly excluded (and why)
|
|
61
|
-
*
|
|
62
|
-
* Per the task brief, only the mechanism plus tools mapping to real kernel
|
|
63
|
-
* primitives were ported. The other 13 of OD's 18 `TOOL_DEFS` were **not**
|
|
64
|
-
* ported — all are OD's project/file/artifact/skill/plugin model, none of which
|
|
65
|
-
* exists in the Jini kernel (extraction-plan §2.1's "no Project, Conversation,
|
|
66
|
-
* Brand, DesignSystem, Plugin/marketplace, or Automation noun" holds here
|
|
67
|
-
* exactly as it did for `@jini/cli`'s own `mcp` row):
|
|
68
|
-
* `list_projects`, `get_artifact`, `get_project`, `get_file`, `search_files`,
|
|
69
|
-
* `list_files`, `create_artifact`, `write_file`, `delete_file`, `delete_project`,
|
|
70
|
-
* `create_project`, `list_skills`, `list_plugins`. Also not ported: the
|
|
71
|
-
* `ListResourcesRequestSchema`/`ReadResourceRequestSchema` resource surface
|
|
72
|
-
* (`od://focus/active`, `od://skills/...`, `od://design-systems/...` — resources
|
|
73
|
-
* require a skill/design-system store this kernel doesn't have), the studio
|
|
74
|
-
* deep-link builder (`buildStudioUrl`, `getDefaultConversationId`,
|
|
75
|
-
* `getWebBaseUrl`), the project-name-resolution cache (`resolveProjectId`,
|
|
76
|
-
* `fetchProjectList`, `resolveProjectArg`), and the SSE-transcript reassembly
|
|
77
|
-
* (`fetchRunAgentMessage`) — every one of these exists only in service of a
|
|
78
|
-
* dropped tool.
|
|
79
|
-
*
|
|
80
|
-
* ### Design decision: the hosting-mechanism shape
|
|
81
|
-
*
|
|
82
|
-
* `createMcpToolServer({name, version, tools, resolveBaseUrl, instructions?,
|
|
83
|
-
* idleMs?, fetchImpl?, stdin?, stdout?, createServer?, createTransport?})`
|
|
84
|
-
* returns `{run(): Promise<void>}`. Construction (tool-name-uniqueness
|
|
85
|
-
* validation via `buildToolIndex`) is synchronous and side-effect-free; all I/O
|
|
86
|
-
* — resolving the daemon URL, connecting the transport, serving requests —
|
|
87
|
-
* happens in `run()`. This is genuinely new kernel-adjacent surface (a new
|
|
88
|
-
* package capability, not a file port), justified per extraction-plan §7's
|
|
89
|
-
* two-consumer rule by the user's explicit request plus the stated expectation
|
|
90
|
-
* that Zana/Open-Marketing will each want their own product-specific MCP
|
|
91
|
-
* server built on the same hosting mechanism later — so the mechanism accepts
|
|
92
|
-
* an arbitrary, caller-registered `McpToolDef[]`, not the 3-5 tools this pass
|
|
93
|
-
* ships as its first real, working example.
|
|
94
|
-
*
|
|
95
|
-
* `McpServerLike`/`McpTransportLike` (in `tool-server.ts`) are narrow structural
|
|
96
|
-
* interfaces covering only what this module calls on a `Server` instance
|
|
97
|
-
* (`setRequestHandler`, `connect`) and a transport (`onmessage`, `onclose`,
|
|
98
|
-
* `close`) — not literal re-exports of the SDK's own types, which can't be
|
|
99
|
-
* satisfied by a plain object (the SDK's `Server` class has private fields, so
|
|
100
|
-
* it's nominally, not structurally, typed). The default `createServer`/
|
|
101
|
-
* `createTransport` factories construct the real SDK classes and cast through
|
|
102
|
-
* `unknown` to these narrower interfaces (the same `as unknown as X` pattern
|
|
103
|
-
* `core/oauth.ts` already uses to bridge undici's types); a caller may override
|
|
104
|
-
* either factory to inject a test double. This is what makes `run()`'s full
|
|
105
|
-
* orchestration — activity wrapping, message/close composition, the stdin-close
|
|
106
|
-
* race, idle-triggered shutdown — deterministically unit-testable without a
|
|
107
|
-
* real subprocess or real timers (see `tool-server.test.ts`), while one
|
|
108
|
-
* dedicated test (`'wires the real @modelcontextprotocol/sdk Server +
|
|
109
|
-
* StdioServerTransport when no factories are injected'`) still exercises the
|
|
110
|
-
* true default path end-to-end with real `node:stream` `PassThrough` streams,
|
|
111
|
-
* proving the DI seam isn't hiding a real-SDK integration bug.
|
|
112
|
-
*
|
|
113
|
-
* **Why `daemon-client.ts` is not `@jini/cli`'s `getJsonFromDaemon`/
|
|
114
|
-
* `postJsonToDaemon`.** The architectural brief called for reusing
|
|
115
|
-
* `@jini/cli`'s already-hardened daemon-fetch helpers "the way
|
|
116
|
-
* `packages/cli/src/run-command.ts` already does." Checked directly: those two
|
|
117
|
-
* functions map every failure — network-unreachable, non-2xx, oversized
|
|
118
|
-
* response — onto `process.exit()` (via `exitWithStructuredError`), which is
|
|
119
|
-
* correct for a one-shot CLI invocation but fatal for a long-lived stdio MCP
|
|
120
|
-
* server, where a single failed tool call must return an `{isError:true}` MCP
|
|
121
|
-
* result and the process must keep serving the next call. Routing around this
|
|
122
|
-
* by injecting an `exit` callback that throws instead of exiting was considered
|
|
123
|
-
* and rejected: it would require also capturing/re-parsing whatever
|
|
124
|
-
* `postJsonToDaemon` had already written through its `write` callback to
|
|
125
|
-
* reconstruct an error message, which is more convoluted than porting OD's own
|
|
126
|
-
* already-throw-based `getJson`/`postJson` mechanism with the same bounded-I/O
|
|
127
|
-
* hardening added. What **is** reused directly from `@jini/cli` (both pure,
|
|
128
|
-
* non-`process.exit` functions): `sanitizeUntrustedText` (redacts daemon-
|
|
129
|
-
* supplied error text before it reaches an MCP result) and, per the
|
|
130
|
-
* architecture note about `daemon-url.ts`, `resolveDaemonUrl`/
|
|
131
|
-
* `sanitizeDaemonUrlForDisplay` are available for a caller's `resolveBaseUrl`
|
|
132
|
-
* implementation to use directly (this package does not re-wrap them — a
|
|
133
|
-
* caller passes `() => resolveDaemonUrl({...})` straight into
|
|
134
|
-
* `createMcpToolServer`'s `resolveBaseUrl` option; no `packages/mcp`-local
|
|
135
|
-
* daemon-URL-resolution equivalent was built, since one already exists and nothing
|
|
136
|
-
* about MCP's use case differs from a CLI command's). This cross-import
|
|
137
|
-
* (`@jini/mcp` -> `@jini/cli`) is allowed by `scripts/check-engine-boundaries.ts`'s
|
|
138
|
-
* R7 rule: R7 only restricts a **locked** package (the extraction-plan §3
|
|
139
|
-
* fourteen) importing an **unlocked** one below `"stable"` status; `@jini/mcp`
|
|
140
|
-
* is itself unlocked (`UNLOCKED.md`), so it importing the locked, stable
|
|
141
|
-
* `@jini/cli` package is unrestricted. Confirmed via `pnpm guard` after wiring
|
|
142
|
-
* the dependency (see below) — zero violations.
|
|
143
|
-
*
|
|
144
|
-
* No SSRF hardening (`assertSafePublicUrl`/`createValidatingLookup`, the
|
|
145
|
-
* pattern `core/oauth.ts` uses for its own outbound fetches) was added to
|
|
146
|
-
* `daemon-client.ts` — deliberately: `assertSafePublicUrl` actively *rejects*
|
|
147
|
-
* loopback/private addresses, which is exactly where a real daemon lives. The
|
|
148
|
-
* daemon URL here is a caller-resolved, typically-loopback target the user
|
|
149
|
-
* already trusts enough to run (the same trust boundary `@jini/cli/http.ts`
|
|
150
|
-
* already accepts for the identical "fetch my own daemon" concern), not an
|
|
151
|
-
* attacker- or remote-metadata-controlled URL the way an external MCP server's
|
|
152
|
-
* OAuth endpoints are.
|
|
153
|
-
*
|
|
154
|
-
* ### `daemonCallOptions(ctx)` — why a helper for two fields
|
|
155
|
-
*
|
|
156
|
-
* Nine call sites across `tools/` and `resources/` each wrote `{ fetchImpl: ctx.fetchImpl }` inline. Adding
|
|
157
|
-
* a second field that must travel with the first creates a failure mode that is *not* a compile error: miss
|
|
158
|
-
* one site and that single tool 401s while its neighbours keep working, which reaches a user as "some of
|
|
159
|
-
* these tools just don't do anything". Every site now routes through one mapping, so a newly added tool is
|
|
160
|
-
* authenticated by construction.
|
|
161
|
-
*
|
|
162
|
-
* The guarantee is a test rather than a convention. `server/__tests__/auth-propagation.test.ts` enumerates
|
|
163
|
-
* the **real** registries (`RUN_TOOLS`, `TOOL_CATALOG_TOOLS`, `createExecuteDelegatedToolTool`,
|
|
164
|
-
* `KERNEL_RESOURCES` — 8 tools + 1 resource), drives each handler through a spy `fetch`, and asserts the
|
|
165
|
-
* header on every request each one makes; a matching pass asserts **no** `Authorization` key at all when no
|
|
166
|
-
* credential was issued. It also asserts the surface list itself, so emptying or renaming a registry cannot
|
|
167
|
-
* make the loops vacuously pass.
|
|
168
|
-
*
|
|
169
|
-
* That test was verified to actually catch the bug it exists for: reverting one call site to the inline form
|
|
170
|
-
* fails it, naming the offending tool and URL.
|
|
171
|
-
*
|
|
172
|
-
* No transport change was needed — `daemon-client.ts`'s `DaemonRequestOptions` already carried `headers`.
|
|
173
|
-
*
|
|
174
|
-
* **Verified, personally, this session**: `pnpm --filter @jini-ai/mcp run typecheck` clean; full suite
|
|
175
|
-
* **332/332 passing** (18 files), including 21 new propagation tests and 4 new `serve()` credential tests.
|
|
176
|
-
* The pre-existing tool/resource suites, which assert the exact options object passed to the daemon client,
|
|
177
|
-
* pass **unmodified** — the evidence that the no-credential path is byte-identical.
|
|
3
|
+
* Import capabilities through the public barrel or declared stable subpaths; internal source
|
|
4
|
+
* paths remain private so the core/client/install/hosting split can move without consumer churn.
|
|
5
|
+
* Explicit named exports expose the public surface and catch collisions. Core binding, hosting,
|
|
6
|
+
* daemon I/O and auth propagation are documented at their respective owners below.
|
|
178
7
|
*/
|
|
179
8
|
export { inferMcpAuthModeForUrl, sanitizeMcpServer, sanitizeMcpConfig, readMcpConfig, writeMcpConfig, isManagedProjectCwd, buildClaudeMcpJson, buildAcpMcpServers, buildOpenCodeMcpConfigContent, } from './core/index.js';
|
|
180
9
|
export type { McpTransport, McpAuthMode, McpServerConfig, McpConfig, AcpMcpServer, OpenCodeConfigBuildOptions, } from './core/index.js';
|
|
@@ -185,13 +14,13 @@ export type { BuildMcpInstallPayloadInputs, McpInstallPayload } from './core/ind
|
|
|
185
14
|
export { createMcpIdleExitController, extractRelativeRefs, isTextualMime, } from './client/index.js';
|
|
186
15
|
export { cancelRunTool, createMcpToolServer, daemonCallOptions, errorResult, getActiveContextTool, getDaemonJson, getRunTool, listAgentsTool, okResult, postDaemonJson, requireString, RUN_TOOLS, startRunTool, toolsToList, buildToolIndex, handleToolCall, DaemonResponseTooLargeError, } from './server/index.js';
|
|
187
16
|
export type { DaemonRequestOptions, McpServerLike, McpToolContext, McpToolDef, McpToolServerHandle, McpToolServerOptions, McpTransportLike, } from './server/index.js';
|
|
188
|
-
export { activeContextResource, buildResourceIndex, handleResourceRead, KERNEL_RESOURCES, resourcesToList, } from './server/index.js';
|
|
17
|
+
export { activeContextResource, createSiteGuideResource, buildResourceIndex, handleResourceRead, KERNEL_RESOURCES, resourcesToList, } from './server/index.js';
|
|
189
18
|
export type { McpResourceDef, McpResourceReadResult } from './server/index.js';
|
|
190
19
|
export { searchToolsTool, describeToolTool, TOOL_CATALOG_TOOLS } from './server/index.js';
|
|
191
20
|
export { searchComponentsTool, describeComponentTool, COMPONENT_CATALOG_TOOLS } from './server/index.js';
|
|
192
21
|
export { createExecuteDelegatedToolTool, createExecuteReadonlyDelegatedToolTool, DEFAULT_DELEGATED_TOOL_TIMEOUT_MS, } from './server/index.js';
|
|
193
22
|
export type { CreateExecuteDelegatedToolToolOptions } from './server/index.js';
|
|
194
|
-
export { AGENT_SLUGS, isAgentSlug, planAgentInstall, applyJsonInstall, removeJsonInstall, } from './agent-install/index.js';
|
|
23
|
+
export { CHROME_DEVTOOLS_MCP_PRESET, AGENT_SLUGS, isAgentSlug, planAgentInstall, applyJsonInstall, removeJsonInstall, } from './agent-install/index.js';
|
|
195
24
|
export type { AgentSlug, McpLaunchSpec, PlanContext, CliInstallPlan, JsonInstallPlan, ManualInstallPlan, InstallPlan, } from './agent-install/index.js';
|
|
196
25
|
export type { McpConfigFilesystemPort } from './core/config.js';
|
|
197
26
|
export type { McpTokenClockOptions, McpTokenStoreOptions } from './core/tokens.js';
|
package/dist/index.js
CHANGED
|
@@ -1,184 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Re-exports are explicit and named (no `export *`) so the public surface is
|
|
9
|
-
* visible here and name collisions surface at build time.
|
|
10
|
-
|
|
11
|
-
* Archived provenance rationale:
|
|
12
|
-
* ## What was dropped (and why)
|
|
13
|
-
*
|
|
14
|
-
* - **`live-artifacts/` (entire subdir)** — the OD per-run "live artifacts" MCP
|
|
15
|
-
* tool surface. An OD-product feature, out of scope per the port brief.
|
|
16
|
-
* - **The `od mcp` stdio server** in `client/client.ts` — `runMcpStdio`,
|
|
17
|
-
* `TOOL_DEFS`, `handleMcpToolCall`, and every tool handler (`get_artifact`,
|
|
18
|
-
* `create_project`, `start_run`, project/active-context resolution, studio deep
|
|
19
|
-
* links, run polling, …). This is not a generic MCP client: it is an
|
|
20
|
-
* OD-product MCP **server** hardwired to Open Design's REST API
|
|
21
|
-
* (`/api/projects`, `/api/skills`, `/api/design-systems`, `/api/runs`, …) and
|
|
22
|
-
* concepts (design systems, skills, plugins, artifacts, runs, studio, the
|
|
23
|
-
* `od://` resource scheme). It also imports the OD-only `@open-design/contracts`
|
|
24
|
-
* package (`buildProjectRawFileUrl`), the OD daemon-internal
|
|
25
|
-
* `../../artifacts/create.js`, and the `@modelcontextprotocol/sdk` — none of
|
|
26
|
-
* which exist in this repo. Faithfully porting it would mean importing OD
|
|
27
|
-
* product surface, so it was dropped and only its generic primitives salvaged.
|
|
28
|
-
* - **`MCP_TEMPLATES` catalog + template types + `templateId`** in `config.ts` —
|
|
29
|
-
* a large, design-tool-tilted curated catalog of third-party MCP servers with
|
|
30
|
-
* OD marketing prose. Zero functional coupling; dropped for neutrality (same
|
|
31
|
-
* rationale `@jini/sqlite` used to drop the OD "templates" table).
|
|
32
|
-
*
|
|
33
|
-
* ### The `list_agents` decision
|
|
34
|
-
*
|
|
35
|
-
* Investigated before building anything, per the task brief. `@jini/agent-runtime`'s
|
|
36
|
-
* barrel (`packages/agent-runtime/src/index.ts` → `export * from './registry.js'`)
|
|
37
|
-
* already exports `AGENT_DEFS: RuntimeAgentDef[]` — the exact static, in-memory
|
|
38
|
-
* list of every registered agent def (`registry.ts`'s `BASE_AGENT_DEFS`, 24
|
|
39
|
-
* entries). `RuntimeAgentDef` has plain `id`/`name` fields (plus a large surface
|
|
40
|
-
* of CLI-spawn internals: `bin`, `buildArgs`, `env`, `listModels`, etc. — no
|
|
41
|
-
* `available`/live-detection field at all). No HTTP projection of this list
|
|
42
|
-
* existed anywhere in `packages/http/src/*.ts` before this pass. Building
|
|
43
|
-
* `GET /api/agents` returning `{agents: [{id, name}]}` needed no design judgment
|
|
44
|
-
* call beyond "project the two safe fields" — no timeout/caching/probing
|
|
45
|
-
* decisions, unlike OD's own `list_agents` (`listAgents`, mcp.ts:1034-1057),
|
|
46
|
-
* which filters on a *live-detected* `available: boolean` field (OD's
|
|
47
|
-
* `/api/agents` route actually probes each agent binary for installation/version
|
|
48
|
-
* at request time — a real design decision this pass deliberately did not need
|
|
49
|
-
* to make, since Jini has no such probing route or field to project). So it was
|
|
50
|
-
* built: `packages/http/src/agents.ts`'s `agentListRoute` (`GET /api/agents`,
|
|
51
|
-
* `AgentsHttpDeps.listAgents: () => readonly {id, name}[]` injected — DI,
|
|
52
|
-
* matching `daemon-status.ts`/`active-context.ts`'s convention, so `@jini/http`
|
|
53
|
-
* does not take on a new `@jini/agent-runtime` package dependency for a
|
|
54
|
-
* two-field projection) plus `@jini/mcp`'s `list_agents` tool
|
|
55
|
-
* (`server/tools/run-tools.ts`) proxying it. OD's `includeUnavailable` flag and
|
|
56
|
-
* per-agent `installUrl`/`modelsCount` enrichment were **not** ported — those
|
|
57
|
-
* exist only because OD's route does live probing, which this pass's route does
|
|
58
|
-
* not attempt.
|
|
59
|
-
*
|
|
60
|
-
* ### What was explicitly excluded (and why)
|
|
61
|
-
*
|
|
62
|
-
* Per the task brief, only the mechanism plus tools mapping to real kernel
|
|
63
|
-
* primitives were ported. The other 13 of OD's 18 `TOOL_DEFS` were **not**
|
|
64
|
-
* ported — all are OD's project/file/artifact/skill/plugin model, none of which
|
|
65
|
-
* exists in the Jini kernel (extraction-plan §2.1's "no Project, Conversation,
|
|
66
|
-
* Brand, DesignSystem, Plugin/marketplace, or Automation noun" holds here
|
|
67
|
-
* exactly as it did for `@jini/cli`'s own `mcp` row):
|
|
68
|
-
* `list_projects`, `get_artifact`, `get_project`, `get_file`, `search_files`,
|
|
69
|
-
* `list_files`, `create_artifact`, `write_file`, `delete_file`, `delete_project`,
|
|
70
|
-
* `create_project`, `list_skills`, `list_plugins`. Also not ported: the
|
|
71
|
-
* `ListResourcesRequestSchema`/`ReadResourceRequestSchema` resource surface
|
|
72
|
-
* (`od://focus/active`, `od://skills/...`, `od://design-systems/...` — resources
|
|
73
|
-
* require a skill/design-system store this kernel doesn't have), the studio
|
|
74
|
-
* deep-link builder (`buildStudioUrl`, `getDefaultConversationId`,
|
|
75
|
-
* `getWebBaseUrl`), the project-name-resolution cache (`resolveProjectId`,
|
|
76
|
-
* `fetchProjectList`, `resolveProjectArg`), and the SSE-transcript reassembly
|
|
77
|
-
* (`fetchRunAgentMessage`) — every one of these exists only in service of a
|
|
78
|
-
* dropped tool.
|
|
79
|
-
*
|
|
80
|
-
* ### Design decision: the hosting-mechanism shape
|
|
81
|
-
*
|
|
82
|
-
* `createMcpToolServer({name, version, tools, resolveBaseUrl, instructions?,
|
|
83
|
-
* idleMs?, fetchImpl?, stdin?, stdout?, createServer?, createTransport?})`
|
|
84
|
-
* returns `{run(): Promise<void>}`. Construction (tool-name-uniqueness
|
|
85
|
-
* validation via `buildToolIndex`) is synchronous and side-effect-free; all I/O
|
|
86
|
-
* — resolving the daemon URL, connecting the transport, serving requests —
|
|
87
|
-
* happens in `run()`. This is genuinely new kernel-adjacent surface (a new
|
|
88
|
-
* package capability, not a file port), justified per extraction-plan §7's
|
|
89
|
-
* two-consumer rule by the user's explicit request plus the stated expectation
|
|
90
|
-
* that Zana/Open-Marketing will each want their own product-specific MCP
|
|
91
|
-
* server built on the same hosting mechanism later — so the mechanism accepts
|
|
92
|
-
* an arbitrary, caller-registered `McpToolDef[]`, not the 3-5 tools this pass
|
|
93
|
-
* ships as its first real, working example.
|
|
94
|
-
*
|
|
95
|
-
* `McpServerLike`/`McpTransportLike` (in `tool-server.ts`) are narrow structural
|
|
96
|
-
* interfaces covering only what this module calls on a `Server` instance
|
|
97
|
-
* (`setRequestHandler`, `connect`) and a transport (`onmessage`, `onclose`,
|
|
98
|
-
* `close`) — not literal re-exports of the SDK's own types, which can't be
|
|
99
|
-
* satisfied by a plain object (the SDK's `Server` class has private fields, so
|
|
100
|
-
* it's nominally, not structurally, typed). The default `createServer`/
|
|
101
|
-
* `createTransport` factories construct the real SDK classes and cast through
|
|
102
|
-
* `unknown` to these narrower interfaces (the same `as unknown as X` pattern
|
|
103
|
-
* `core/oauth.ts` already uses to bridge undici's types); a caller may override
|
|
104
|
-
* either factory to inject a test double. This is what makes `run()`'s full
|
|
105
|
-
* orchestration — activity wrapping, message/close composition, the stdin-close
|
|
106
|
-
* race, idle-triggered shutdown — deterministically unit-testable without a
|
|
107
|
-
* real subprocess or real timers (see `tool-server.test.ts`), while one
|
|
108
|
-
* dedicated test (`'wires the real @modelcontextprotocol/sdk Server +
|
|
109
|
-
* StdioServerTransport when no factories are injected'`) still exercises the
|
|
110
|
-
* true default path end-to-end with real `node:stream` `PassThrough` streams,
|
|
111
|
-
* proving the DI seam isn't hiding a real-SDK integration bug.
|
|
112
|
-
*
|
|
113
|
-
* **Why `daemon-client.ts` is not `@jini/cli`'s `getJsonFromDaemon`/
|
|
114
|
-
* `postJsonToDaemon`.** The architectural brief called for reusing
|
|
115
|
-
* `@jini/cli`'s already-hardened daemon-fetch helpers "the way
|
|
116
|
-
* `packages/cli/src/run-command.ts` already does." Checked directly: those two
|
|
117
|
-
* functions map every failure — network-unreachable, non-2xx, oversized
|
|
118
|
-
* response — onto `process.exit()` (via `exitWithStructuredError`), which is
|
|
119
|
-
* correct for a one-shot CLI invocation but fatal for a long-lived stdio MCP
|
|
120
|
-
* server, where a single failed tool call must return an `{isError:true}` MCP
|
|
121
|
-
* result and the process must keep serving the next call. Routing around this
|
|
122
|
-
* by injecting an `exit` callback that throws instead of exiting was considered
|
|
123
|
-
* and rejected: it would require also capturing/re-parsing whatever
|
|
124
|
-
* `postJsonToDaemon` had already written through its `write` callback to
|
|
125
|
-
* reconstruct an error message, which is more convoluted than porting OD's own
|
|
126
|
-
* already-throw-based `getJson`/`postJson` mechanism with the same bounded-I/O
|
|
127
|
-
* hardening added. What **is** reused directly from `@jini/cli` (both pure,
|
|
128
|
-
* non-`process.exit` functions): `sanitizeUntrustedText` (redacts daemon-
|
|
129
|
-
* supplied error text before it reaches an MCP result) and, per the
|
|
130
|
-
* architecture note about `daemon-url.ts`, `resolveDaemonUrl`/
|
|
131
|
-
* `sanitizeDaemonUrlForDisplay` are available for a caller's `resolveBaseUrl`
|
|
132
|
-
* implementation to use directly (this package does not re-wrap them — a
|
|
133
|
-
* caller passes `() => resolveDaemonUrl({...})` straight into
|
|
134
|
-
* `createMcpToolServer`'s `resolveBaseUrl` option; no `packages/mcp`-local
|
|
135
|
-
* daemon-URL-resolution equivalent was built, since one already exists and nothing
|
|
136
|
-
* about MCP's use case differs from a CLI command's). This cross-import
|
|
137
|
-
* (`@jini/mcp` -> `@jini/cli`) is allowed by `scripts/check-engine-boundaries.ts`'s
|
|
138
|
-
* R7 rule: R7 only restricts a **locked** package (the extraction-plan §3
|
|
139
|
-
* fourteen) importing an **unlocked** one below `"stable"` status; `@jini/mcp`
|
|
140
|
-
* is itself unlocked (`UNLOCKED.md`), so it importing the locked, stable
|
|
141
|
-
* `@jini/cli` package is unrestricted. Confirmed via `pnpm guard` after wiring
|
|
142
|
-
* the dependency (see below) — zero violations.
|
|
143
|
-
*
|
|
144
|
-
* No SSRF hardening (`assertSafePublicUrl`/`createValidatingLookup`, the
|
|
145
|
-
* pattern `core/oauth.ts` uses for its own outbound fetches) was added to
|
|
146
|
-
* `daemon-client.ts` — deliberately: `assertSafePublicUrl` actively *rejects*
|
|
147
|
-
* loopback/private addresses, which is exactly where a real daemon lives. The
|
|
148
|
-
* daemon URL here is a caller-resolved, typically-loopback target the user
|
|
149
|
-
* already trusts enough to run (the same trust boundary `@jini/cli/http.ts`
|
|
150
|
-
* already accepts for the identical "fetch my own daemon" concern), not an
|
|
151
|
-
* attacker- or remote-metadata-controlled URL the way an external MCP server's
|
|
152
|
-
* OAuth endpoints are.
|
|
153
|
-
*
|
|
154
|
-
* ### `daemonCallOptions(ctx)` — why a helper for two fields
|
|
155
|
-
*
|
|
156
|
-
* Nine call sites across `tools/` and `resources/` each wrote `{ fetchImpl: ctx.fetchImpl }` inline. Adding
|
|
157
|
-
* a second field that must travel with the first creates a failure mode that is *not* a compile error: miss
|
|
158
|
-
* one site and that single tool 401s while its neighbours keep working, which reaches a user as "some of
|
|
159
|
-
* these tools just don't do anything". Every site now routes through one mapping, so a newly added tool is
|
|
160
|
-
* authenticated by construction.
|
|
161
|
-
*
|
|
162
|
-
* The guarantee is a test rather than a convention. `server/__tests__/auth-propagation.test.ts` enumerates
|
|
163
|
-
* the **real** registries (`RUN_TOOLS`, `TOOL_CATALOG_TOOLS`, `createExecuteDelegatedToolTool`,
|
|
164
|
-
* `KERNEL_RESOURCES` — 8 tools + 1 resource), drives each handler through a spy `fetch`, and asserts the
|
|
165
|
-
* header on every request each one makes; a matching pass asserts **no** `Authorization` key at all when no
|
|
166
|
-
* credential was issued. It also asserts the surface list itself, so emptying or renaming a registry cannot
|
|
167
|
-
* make the loops vacuously pass.
|
|
168
|
-
*
|
|
169
|
-
* That test was verified to actually catch the bug it exists for: reverting one call site to the inline form
|
|
170
|
-
* fails it, naming the offending tool and URL.
|
|
171
|
-
*
|
|
172
|
-
* No transport change was needed — `daemon-client.ts`'s `DaemonRequestOptions` already carried `headers`.
|
|
173
|
-
*
|
|
174
|
-
* **Verified, personally, this session**: `pnpm --filter @jini-ai/mcp run typecheck` clean; full suite
|
|
175
|
-
* **332/332 passing** (18 files), including 21 new propagation tests and 4 new `serve()` credential tests.
|
|
176
|
-
* The pre-existing tool/resource suites, which assert the exact options object passed to the daemon client,
|
|
177
|
-
* pass **unmodified** — the evidence that the no-credential path is byte-identical.
|
|
3
|
+
* Import capabilities through the public barrel or declared stable subpaths; internal source
|
|
4
|
+
* paths remain private so the core/client/install/hosting split can move without consumer churn.
|
|
5
|
+
* Explicit named exports expose the public surface and catch collisions. Core binding, hosting,
|
|
6
|
+
* daemon I/O and auth propagation are documented at their respective owners below.
|
|
178
7
|
*/
|
|
179
|
-
// The original root-only boundary above describes the initial core/client/server split.
|
|
180
|
-
// Stable public subpaths now also host federation, approvals, stdio, testing, and ask-choice;
|
|
181
|
-
// internal source paths remain private so consumers can follow their declared runtime entry.
|
|
182
8
|
// ── core: config schema + IO ────────────────────────────────────────────────
|
|
183
9
|
export { inferMcpAuthModeForUrl, sanitizeMcpServer, sanitizeMcpConfig, readMcpConfig, writeMcpConfig, isManagedProjectCwd, buildClaudeMcpJson, buildAcpMcpServers, buildOpenCodeMcpConfigContent, } from './core/index.js';
|
|
184
10
|
// ── core: token store ───────────────────────────────────────────────────────
|
|
@@ -190,7 +16,7 @@ export { createMcpIdleExitController, extractRelativeRefs, isTextualMime, } from
|
|
|
190
16
|
// ── server: the generic MCP tool-hosting mechanism + kernel-run tool defs ───
|
|
191
17
|
export { cancelRunTool, createMcpToolServer, daemonCallOptions, errorResult, getActiveContextTool, getDaemonJson, getRunTool, listAgentsTool, okResult, postDaemonJson, requireString, RUN_TOOLS, startRunTool, toolsToList, buildToolIndex, handleToolCall, DaemonResponseTooLargeError, } from './server/index.js';
|
|
192
18
|
// ── server: the generic MCP resource surface + kernel resource defs ────────
|
|
193
|
-
export { activeContextResource, buildResourceIndex, handleResourceRead, KERNEL_RESOURCES, resourcesToList, } from './server/index.js';
|
|
19
|
+
export { activeContextResource, createSiteGuideResource, buildResourceIndex, handleResourceRead, KERNEL_RESOURCES, resourcesToList, } from './server/index.js';
|
|
194
20
|
// ── server: the tool-catalog discovery defs (search_tools / describe_tool) ──
|
|
195
21
|
export { searchToolsTool, describeToolTool, TOOL_CATALOG_TOOLS } from './server/index.js';
|
|
196
22
|
// ── server: the component-catalog discovery defs (search_components / describe_component) ──
|
|
@@ -198,6 +24,6 @@ export { searchComponentsTool, describeComponentTool, COMPONENT_CATALOG_TOOLS }
|
|
|
198
24
|
// ── server: gap 3's MCP-callback delegated-tool-execution def ──────────────
|
|
199
25
|
export { createExecuteDelegatedToolTool, createExecuteReadonlyDelegatedToolTool, DEFAULT_DELEGATED_TOOL_TIMEOUT_MS, } from './server/index.js';
|
|
200
26
|
// ── agent-install: register an MCP server into external agents ──────────────
|
|
201
|
-
export { AGENT_SLUGS, isAgentSlug, planAgentInstall, applyJsonInstall, removeJsonInstall, } from './agent-install/index.js';
|
|
27
|
+
export { CHROME_DEVTOOLS_MCP_PRESET, AGENT_SLUGS, isAgentSlug, planAgentInstall, applyJsonInstall, removeJsonInstall, } from './agent-install/index.js';
|
|
202
28
|
export { DaemonHttpError } from './server/daemon-client.js';
|
|
203
29
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Thrown for a non-2xx daemon response.
|
|
3
|
-
*
|
|
4
|
-
* other failure instead of pattern-matching the message.
|
|
2
|
+
* Thrown for a non-2xx daemon response. `status` lets a tool distinguish an unsupported route (404)
|
|
3
|
+
* from other failures without pattern-matching the public error message.
|
|
5
4
|
*/
|
|
6
5
|
export declare class DaemonHttpError extends Error {
|
|
7
6
|
readonly status: number;
|
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp/server/daemon-client
|
|
3
|
+
* Bounded-I/O JSON GET/POST against a caller-resolved trusted daemon base URL.
|
|
4
|
+
* A long-lived MCP server must report a failed call and keep serving, so this transport throws
|
|
5
|
+
* redacted errors for handleToolCall to convert rather than using the CLI transport's process.exit.
|
|
6
|
+
* Bounded reads, timeouts and redaction follow CR-004/SEC-RB-009.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* (`../client/client.js`'s `createMcpIdleExitController` is the other half of
|
|
7
|
-
* the mechanism; this module is the network half).
|
|
8
|
-
*
|
|
9
|
-
* Deliberately NOT the CLI transport's `getJsonFromDaemon`/`postJsonToDaemon`
|
|
10
|
-
* (`packages/cli/src/http.ts`): those map a failure onto `process.exit`,
|
|
11
|
-
* which is the right contract for a one-shot CLI invocation but wrong here —
|
|
12
|
-
* a stdio MCP server is a long-lived process serving many tool calls, and one
|
|
13
|
-
* failed call must return an MCP `{isError:true}` result and keep serving the
|
|
14
|
-
* next call, not terminate the process. This module ports the same
|
|
15
|
-
* bounded-read / timeout / redaction posture `http.ts` established
|
|
16
|
-
* (CR-004/SEC-RB-009 — see `ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
|
|
17
|
-
* `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`) as
|
|
18
|
-
* a throw-based primitive instead: a failed request throws a plain,
|
|
19
|
-
* already-redacted `Error` whose message a tool handler's caller
|
|
20
|
-
* (`handleToolCall` in `./tool-protocol.js`) turns into that MCP error result.
|
|
21
|
-
*
|
|
22
|
-
* No SSRF hardening here: the
|
|
23
|
-
* target is a caller-resolved, typically-loopback daemon the user already
|
|
24
|
-
* trusts enough to run — not an attacker- or server-metadata-controlled
|
|
25
|
-
* remote URL the way a configured external MCP server's OAuth endpoints are.
|
|
26
|
-
* This mirrors the CLI HTTP transport's own posture for the identical "fetch my
|
|
27
|
-
* own daemon" concern (no `assertSafePublicUrl` there either).
|
|
8
|
+
* This is a trusted, typically-loopback daemon URL, not attacker-controlled server metadata or
|
|
9
|
+
* an external MCP OAuth endpoint; public-URL SSRF validation is therefore outside this contract.
|
|
28
10
|
*/
|
|
29
11
|
import { sanitizeUntrustedText } from '@jini-ai/core/text';
|
|
30
12
|
/** Request deadline. Generous enough for a slow tool call, short enough that a stalled daemon doesn't hang a stdio server turn forever. */
|
|
@@ -32,9 +14,8 @@ const DEFAULT_TIMEOUT_MS = 15_000;
|
|
|
32
14
|
/** Cap on a daemon response body. Comfortably larger than any real status/run envelope, small enough to bound worst-case memory from a hostile/broken daemon. */
|
|
33
15
|
const DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
34
16
|
/**
|
|
35
|
-
* Thrown for a non-2xx daemon response.
|
|
36
|
-
*
|
|
37
|
-
* other failure instead of pattern-matching the message.
|
|
17
|
+
* Thrown for a non-2xx daemon response. `status` lets a tool distinguish an unsupported route (404)
|
|
18
|
+
* from other failures without pattern-matching the public error message.
|
|
38
19
|
*/
|
|
39
20
|
export class DaemonHttpError extends Error {
|
|
40
21
|
status;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -19,4 +19,5 @@ export * from './tools/delegated-tool.js';
|
|
|
19
19
|
export * from './tools/tool-catalog-tools.js';
|
|
20
20
|
export * from './tools/component-catalog-tools.js';
|
|
21
21
|
export * from './resources/active-resource.js';
|
|
22
|
+
export * from './resources/site-guide-resource.js';
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/server/index.js
CHANGED
|
@@ -19,4 +19,5 @@ export * from './tools/delegated-tool.js';
|
|
|
19
19
|
export * from './tools/tool-catalog-tools.js';
|
|
20
20
|
export * from './tools/component-catalog-tools.js';
|
|
21
21
|
export * from './resources/active-resource.js';
|
|
22
|
+
export * from './resources/site-guide-resource.js';
|
|
22
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,8 +3,7 @@ import type { McpResourceDef } from '../resource-protocol.js';
|
|
|
3
3
|
* `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
|
|
4
4
|
* unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
|
|
5
5
|
* `active:false`; a resource is meant to be raw structured data a client attaches to context, not
|
|
6
|
-
* a model-facing tool result
|
|
7
|
-
* returned the raw `/api/active` body with no special-casing).
|
|
6
|
+
* a model-facing tool result.
|
|
8
7
|
*/
|
|
9
8
|
export declare const activeContextResource: McpResourceDef;
|
|
10
9
|
/** The full set of kernel resource defs this package ships, ready to pass as `createMcpToolServer`'s `resources` option. */
|
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp/server/resources/active-resource
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* — the same underlying data `../tools/run-tools.js`'s `getActiveContextTool`
|
|
7
|
-
* already proxies as a *tool*. This is not redundant: OD's origin
|
|
8
|
-
* (`apps/daemon/src/mcp.ts`) exposed the identical `/api/active` payload
|
|
9
|
-
* both ways — as the `get_active_context` tool (already ported, see
|
|
10
|
-
* `../tools/run-tools.js`) AND as the `od://focus/active` resource (its
|
|
11
|
-
* `ListResourcesRequestSchema`/`ReadResourceRequestSchema` handlers) —
|
|
12
|
-
* because tools and resources serve different MCP client affordances: a
|
|
13
|
-
* tool is invoked by the model mid-conversation, while a resource can be
|
|
14
|
-
* listed and attached to context by the user/client without any tool call.
|
|
15
|
-
* This module is that second affordance for the same primitive. See
|
|
16
|
-
* `archived provenance ledger`'s 2026-07-21 addition for why the rest of OD's resource
|
|
17
|
-
* surface (`od://skills/...`, `od://design-systems/...`) was NOT ported the
|
|
18
|
-
* same way (both require a Skill/DesignSystem noun this kernel doesn't have).
|
|
19
|
-
*
|
|
20
|
-
* Security posture matches every other tool/resource in this package: no
|
|
21
|
-
* separate authorization mechanism here — whatever `@jini-ai/http-kit`'s
|
|
22
|
-
* same-origin guard / bearer-auth middleware already enforces on
|
|
23
|
-
* `GET /api/active` is the only gate a read of this resource passes
|
|
24
|
-
* through.
|
|
3
|
+
* Read-only active-context resource over GET /api/active. Tools let the model request data during
|
|
4
|
+
* a conversation; resources let clients list and attach that data without invoking a tool.
|
|
5
|
+
* Target-route same-origin and bearer-auth gates remain authoritative, as for the proxy tools.
|
|
25
6
|
*/
|
|
26
7
|
import { getDaemonJson } from '../daemon-client.js';
|
|
27
8
|
import { daemonCallOptions } from '../tool-protocol.js';
|
|
@@ -29,8 +10,7 @@ import { daemonCallOptions } from '../tool-protocol.js';
|
|
|
29
10
|
* `jini://active` -> `GET /api/active`. Returns the raw daemon payload as formatted JSON text,
|
|
30
11
|
* unchanged — unlike `getActiveContextTool`, this does not add a conversational hint when
|
|
31
12
|
* `active:false`; a resource is meant to be raw structured data a client attaches to context, not
|
|
32
|
-
* a model-facing tool result
|
|
33
|
-
* returned the raw `/api/active` body with no special-casing).
|
|
13
|
+
* a model-facing tool result.
|
|
34
14
|
*/
|
|
35
15
|
export const activeContextResource = {
|
|
36
16
|
uri: 'jini://active',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { McpResourceDef } from '../resource-protocol.js';
|
|
2
|
+
import { type CreateExecuteDelegatedToolToolOptions } from '../tools/delegated-tool.js';
|
|
3
|
+
/** Expose a host's learning tool as an attachable resource through the EXISTING read-only gateway.
|
|
4
|
+
* The run identity is host-bound; bearer headers, cancellation and authorization follow the same
|
|
5
|
+
* path as tools/call. No direct registry read or privileged principal is introduced.
|
|
6
|
+
* @param input - The spawning run id, never supplied by the MCP client.
|
|
7
|
+
* @returns jini://site-guide, with a fresh permission-scoped overview on every read.
|
|
8
|
+
* @throws When the host lacks site_guide, authorization denies, or execution does not complete.
|
|
9
|
+
* @complexity O(1) plus one delegated read and result serialization.
|
|
10
|
+
* @example createSiteGuideResource({ runId });
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSiteGuideResource(input: {
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
}, options?: CreateExecuteDelegatedToolToolOptions): McpResourceDef;
|
|
15
|
+
//# sourceMappingURL=site-guide-resource.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createExecuteReadonlyDelegatedToolTool } from '../tools/delegated-tool.js';
|
|
2
|
+
/** Expose a host's learning tool as an attachable resource through the EXISTING read-only gateway.
|
|
3
|
+
* The run identity is host-bound; bearer headers, cancellation and authorization follow the same
|
|
4
|
+
* path as tools/call. No direct registry read or privileged principal is introduced.
|
|
5
|
+
* @param input - The spawning run id, never supplied by the MCP client.
|
|
6
|
+
* @returns jini://site-guide, with a fresh permission-scoped overview on every read.
|
|
7
|
+
* @throws When the host lacks site_guide, authorization denies, or execution does not complete.
|
|
8
|
+
* @complexity O(1) plus one delegated read and result serialization.
|
|
9
|
+
* @example createSiteGuideResource({ runId });
|
|
10
|
+
*/
|
|
11
|
+
export function createSiteGuideResource(input, options = {}) {
|
|
12
|
+
const gateway = createExecuteReadonlyDelegatedToolTool(input, options);
|
|
13
|
+
return {
|
|
14
|
+
uri: 'jini://site-guide', name: 'How this site works', mimeType: 'application/json',
|
|
15
|
+
description: 'Permission-filtered building blocks and live facts for the site hosting this run. Consult before planning or building; use site_guide explain through the delegated gateway for details.',
|
|
16
|
+
read: async ({ ctx }) => {
|
|
17
|
+
const result = await gateway.handler({ args: { toolId: 'site_guide', input: { operation: 'overview' } }, ctx });
|
|
18
|
+
if (typeof result !== 'object' || result === null || !('status' in result) || result.status !== 'completed' || !('output' in result)) {
|
|
19
|
+
throw new Error('site_guide resource read did not complete. Use the delegated tool result to inspect authorization or availability.');
|
|
20
|
+
}
|
|
21
|
+
return { text: JSON.stringify(result.output, null, 2) };
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=site-guide-resource.js.map
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp/server/tool-server
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* hardcoded 18-tool `TOOL_DEFS`, the `od://` resource scheme, the
|
|
9
|
-
* project/skill/plugin `instructions` prose — replaced by a caller-supplied,
|
|
10
|
-
* bounded, explicit `tools: readonly McpToolDef[]` list. A caller is the
|
|
11
|
-
* *first* user of this mechanism, not the only one it will ever support (see
|
|
12
|
-
* `archived provenance ledger`'s 2026-07-21 addition for the full design-decision note).
|
|
13
|
-
*
|
|
14
|
-
* `tools` capability is always advertised (`capabilities: {tools: {}}`).
|
|
15
|
-
* `resources` capability (`ListResourcesRequestSchema`/
|
|
16
|
-
* `ReadResourceRequestSchema`, mirroring the tools wiring one section down)
|
|
17
|
-
* is advertised only when the caller supplies at least one entry in the
|
|
18
|
-
* optional `resources` option — a 2026-07-21 addition once this package
|
|
19
|
-
* shipped its first genuinely portable resource (`../resources/
|
|
20
|
-
* active-resource.js`'s `jini://active`, see that file's module doc and
|
|
21
|
-
* `archived provenance ledger`'s 2026-07-21 addition for why the *rest* of OD's resource
|
|
22
|
-
* surface — `od://skills/...`, `od://design-systems/...` — still has no
|
|
23
|
-
* kernel equivalent and stays unported). A caller passing no `resources`
|
|
24
|
-
* gets the exact same `capabilities: {tools: {}}`-only server this module
|
|
25
|
-
* always produced.
|
|
3
|
+
* Generic stdio hosting for an explicit caller-supplied tool list. Construction validates unique
|
|
4
|
+
* names synchronously without I/O; run resolves the daemon URL, connects and serves until closure.
|
|
5
|
+
* Tools capability is always advertised; resources only when nonempty resources are supplied.
|
|
6
|
+
* Narrow object-shaped server/transport ports permit DI fakes without requiring SDK class instances
|
|
7
|
+
* with nominal private fields; default adapters translate those contracts to the real SDK.
|
|
26
8
|
*/
|
|
27
9
|
import type { Readable, Writable } from 'node:stream';
|
|
28
10
|
import type { McpTransportLike } from './ports.js';
|
|
@@ -5,7 +5,7 @@ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSche
|
|
|
5
5
|
import { createMcpIdleExitController } from '../client/client.js';
|
|
6
6
|
import { buildResourceIndex, handleResourceRead, resourcesToList } from './resource-protocol.js';
|
|
7
7
|
import { buildToolIndex, handleToolCall, toolsToList } from './tool-protocol.js';
|
|
8
|
-
/** Auto-exit an idle server after this long
|
|
8
|
+
/** Auto-exit an idle server after this long without tool activity. */
|
|
9
9
|
const DEFAULT_IDLE_MS = 30 * 60 * 1000;
|
|
10
10
|
// SDK objects remain behind these adapters; hosts implement the object-shaped ports above.
|
|
11
11
|
function defaultCreateServer({ info, options }) {
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @jini-ai/mcp/server/tools/run-tools
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* transport (`packages/http/src/runs.ts`) plus the generic active-resource
|
|
7
|
-
* channel (`packages/http/src/active-context.ts`) and the agent-def listing
|
|
8
|
-
* (`packages/http/src/agents.ts`). Each tool's `handler` does nothing but
|
|
9
|
-
* validate its own arguments and proxy a single HTTP call via
|
|
10
|
-
* `../daemon-client.js` — no separate authorization mechanism, no caching,
|
|
11
|
-
* no state: whatever `@jini-ai/http-kit`'s same-origin guard / bearer-auth
|
|
12
|
-
* middleware already enforces on the target route is the only gate a call
|
|
13
|
-
* here passes through (see `archived provenance ledger`'s 2026-07-21 addition for the
|
|
14
|
-
* full origin-mapping and what was deliberately not ported).
|
|
3
|
+
* Proxy tools for runs, active context and agent definitions. Each validates its arguments and
|
|
4
|
+
* delegates a single request to daemon-client; target-route same-origin and bearer-auth gates
|
|
5
|
+
* remain authoritative. See daemonCallOptions for shared auth and cancellation propagation.
|
|
15
6
|
*/
|
|
16
7
|
import { getDaemonJson, postDaemonJson } from '../daemon-client.js';
|
|
17
8
|
import { daemonCallOptions, requireString } from '../tool-protocol.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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,10 +83,29 @@
|
|
|
83
83
|
"registry": "https://registry.npmjs.org"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
+
"@jini-ai/core": "^0.4.3"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
90
|
+
"@jini-ai/platform": "^0.5.3",
|
|
91
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
92
|
+
},
|
|
93
|
+
"peerDependenciesMeta": {
|
|
94
|
+
"@cfworker/json-schema": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@jini-ai/platform": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@modelcontextprotocol/sdk": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {
|
|
86
105
|
"@cfworker/json-schema": "^4.1.1",
|
|
87
106
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
88
|
-
"@jini-ai/
|
|
89
|
-
"@jini-ai/
|
|
107
|
+
"@jini-ai/core": "^0.4.3",
|
|
108
|
+
"@jini-ai/platform": "^0.5.3"
|
|
90
109
|
},
|
|
91
110
|
"scripts": {
|
|
92
111
|
"build": "tsc -p tsconfig.json && chmod +x dist/bin/serve.js",
|