machine-bridge-mcp 3.0.0-beta.21 → 3.0.0-beta.26
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 +134 -0
- package/CONTRIBUTING.md +3 -3
- package/GOVERNANCE.md +2 -2
- package/README.md +24 -6
- package/browser-extension/manifest.json +1 -1
- package/docs/AGENT_CONTEXT.md +10 -7
- package/docs/ARCHITECTURE.md +35 -22
- package/docs/AUDIT.md +85 -1
- package/docs/CLIENTS.md +6 -2
- package/docs/ENGINEERING.md +31 -9
- package/docs/LOCAL_AUTOMATION.md +4 -2
- package/docs/LOGGING.md +8 -8
- package/docs/OPERATIONS.md +43 -17
- package/docs/PRIVACY.md +18 -4
- package/docs/PROJECT_STANDARDS.md +2 -2
- package/docs/RELEASING.md +35 -11
- package/docs/TESTING.md +36 -16
- package/docs/THREAT_MODEL.md +20 -5
- package/docs/TOOL_REFERENCE.md +18 -12
- package/docs/UPGRADING.md +32 -0
- package/package.json +15 -6
- package/scripts/check-plan.mjs +8 -0
- package/scripts/coverage-check.mjs +30 -1
- package/scripts/foreground-daemon-recovery.mjs +88 -0
- package/scripts/github-release.mjs +22 -16
- package/scripts/install-published-prerelease.mjs +7 -7
- package/scripts/official-mcp-conformance.mjs +243 -0
- package/scripts/persistent-activation-process.mjs +36 -0
- package/scripts/release-candidate-manifest.mjs +12 -0
- package/scripts/release-publication-guard.mjs +65 -0
- package/scripts/release-state.mjs +1 -1
- package/scripts/sbom-check.mjs +99 -0
- package/scripts/start-release-candidate.mjs +39 -13
- package/src/local/agent-context-projection.mjs +26 -7
- package/src/local/agent-context.mjs +25 -4
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/capability-observer.mjs +5 -0
- package/src/local/child-process-settlement.mjs +103 -0
- package/src/local/cli-activate.mjs +42 -5
- package/src/local/cli-service.mjs +55 -5
- package/src/local/cli.mjs +59 -10
- package/src/local/daemon-process.mjs +24 -3
- package/src/local/delegated-process-sandbox.mjs +1 -0
- package/src/local/execution-routing.mjs +231 -0
- package/src/local/git-service.mjs +3 -1
- package/src/local/job-runner.mjs +55 -19
- package/src/local/macos-trust-broker.mjs +7 -0
- package/src/local/managed-job-runner-claim.mjs +54 -0
- package/src/local/managed-job-runner.mjs +13 -2
- package/src/local/process-execution.mjs +2 -2
- package/src/local/process-identity.mjs +11 -0
- package/src/local/process-tree-ownership-types.d.ts +37 -0
- package/src/local/process-tree-ownership.mjs +49 -41
- package/src/local/process-tree.mjs +1 -1
- package/src/local/relay-call-recovery.mjs +40 -21
- package/src/local/runtime-activation.mjs +357 -38
- package/src/local/runtime-capabilities.mjs +22 -6
- package/src/local/runtime-diagnostics.mjs +9 -2
- package/src/local/runtime.mjs +18 -4
- package/src/local/service-convergence.mjs +33 -0
- package/src/local/service-owner.mjs +147 -0
- package/src/local/service-restart-handoff.mjs +22 -8
- package/src/local/service-runtime.mjs +145 -0
- package/src/local/service.mjs +143 -25
- package/src/local/state.mjs +104 -7
- package/src/local/stdio.mjs +139 -45
- package/src/local/system-network-route.mjs +76 -0
- package/src/local/tool-executor.mjs +24 -6
- package/src/local/tools.mjs +6 -5
- package/src/local/windows-service-convergence.mjs +49 -0
- package/src/local/windows-service.mjs +30 -53
- package/src/shared/mcp-protocol.d.mts +27 -0
- package/src/shared/mcp-protocol.mjs +256 -0
- package/src/shared/mcp-subscriptions.d.mts +4 -0
- package/src/shared/mcp-subscriptions.mjs +59 -0
- package/src/shared/relay-contract.json +1 -0
- package/src/shared/result-projection.d.mts +2 -1
- package/src/shared/result-projection.mjs +13 -2
- package/src/shared/server-metadata.json +11 -4
- package/src/shared/tool-argument-validation.d.mts +17 -0
- package/src/shared/tool-argument-validation.mjs +325 -0
- package/src/shared/tool-catalog.json +18 -12
- package/src/worker/durable-stream-calls.ts +12 -24
- package/src/worker/http.ts +36 -2
- package/src/worker/index.ts +181 -165
- package/src/worker/mcp-http-contract.ts +276 -0
- package/src/worker/mcp-jsonrpc.ts +12 -6
- package/src/worker/mcp-legacy-dispatch.ts +104 -0
- package/src/worker/mcp-modern-controller.ts +199 -0
- package/src/worker/mcp-modern-proxy.ts +126 -0
- package/src/worker/mcp-modern-stream.ts +71 -0
- package/src/worker/mcp-session.ts +12 -3
- package/src/worker/mcp-stream-proxy-contract.ts +67 -0
- package/src/worker/mcp-stream-proxy.ts +17 -60
- package/src/worker/mcp-tool-call-input.ts +23 -0
- package/src/worker/tool-catalog.ts +29 -1
- package/src/worker/tool-timeout.ts +53 -12
- package/src/worker/worker-mcp-config.ts +23 -0
- package/src/worker/worker-metadata.ts +10 -1
- package/src/worker/worker-runtime-config.ts +19 -0
- package/src/worker/worker-static-routes.ts +7 -2
- package/tsconfig.local.json +7 -1
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { waitForInactiveStatus } from "./service-convergence.mjs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from "./windows-launcher.mjs";
|
|
5
|
-
export {
|
|
6
|
-
windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent,
|
|
7
|
-
windowsLauncherPath, windowsTaskAction, writeWindowsLauncher,
|
|
8
|
-
} from "./windows-launcher.mjs";
|
|
2
|
+
import { stableWindowsStatus, waitForWindowsStatus, windowsStatusWaitOptions } from "./windows-service-convergence.mjs";
|
|
3
|
+
import { windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
|
|
4
|
+
export { windowsBatchArgument, windowsCommandLineArgument, windowsLauncherContent, windowsLauncherPath, windowsTaskAction, writeWindowsLauncher } from "./windows-launcher.mjs";
|
|
9
5
|
|
|
10
6
|
export const WINDOWS_TASK = "MachineBridgeMCP";
|
|
11
7
|
const WINDOWS_STATUS_SCRIPT = [
|
|
@@ -28,7 +24,9 @@ export async function installWindowsTask(spec, logger = console, options = {}) {
|
|
|
28
24
|
"/RL", "LIMITED",
|
|
29
25
|
"/F",
|
|
30
26
|
]);
|
|
31
|
-
const status = await waitForWindowsStatus(
|
|
27
|
+
const status = await waitForWindowsStatus(
|
|
28
|
+
() => statusWindowsTask({ ...options, run }), value => value.installed === true, options,
|
|
29
|
+
);
|
|
32
30
|
const ok = create?.code === 0 && status.installed === true;
|
|
33
31
|
if (ok) logger.info?.("Windows Scheduled Task installed for user logon");
|
|
34
32
|
else logger.warn?.("Windows Scheduled Task installation failed", { reason: windowsServiceFailureReason(create, status) });
|
|
@@ -51,35 +49,37 @@ export async function startWindowsTask(logger = console, options = {}) {
|
|
|
51
49
|
if (before.installed === null) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "task_status_unavailable", status: before };
|
|
52
50
|
if (before.installed === false) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, reason: "not_installed", status: before };
|
|
53
51
|
if (before.active === true) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
already_running: true,
|
|
63
|
-
reason: "already_running",
|
|
64
|
-
status: before,
|
|
65
|
-
};
|
|
52
|
+
const existing = await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options);
|
|
53
|
+
if (existing.stable) {
|
|
54
|
+
logger.info?.("Windows Scheduled Task is already running");
|
|
55
|
+
return {
|
|
56
|
+
ok: true, provider: "schtasks", task: WINDOWS_TASK, installed: true,
|
|
57
|
+
active_before: true, active: true, already_running: true, reason: "already_running", status: existing.status,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
66
60
|
}
|
|
67
61
|
const command = await run("schtasks", ["/Run", "/TN", WINDOWS_TASK]);
|
|
68
62
|
const after = await waitForWindowsStatus(
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
() => statusWindowsTask({ ...options, run }),
|
|
64
|
+
status => status.active === true || completedSince(before, status), options,
|
|
71
65
|
);
|
|
72
|
-
const
|
|
73
|
-
|
|
66
|
+
const stability = after.active === true
|
|
67
|
+
? await stableWindowsStatus(() => statusWindowsTask({ ...options, run }), options)
|
|
68
|
+
: { stable: false, status: after };
|
|
69
|
+
const observed = stability.status;
|
|
70
|
+
const completed = completedSince(before, observed);
|
|
71
|
+
const ok = stability.stable;
|
|
74
72
|
if (ok) logger.info?.("Windows Scheduled Task started");
|
|
75
|
-
else logger.warn?.("Windows Scheduled Task did not
|
|
73
|
+
else logger.warn?.("Windows Scheduled Task did not remain active after the start request");
|
|
76
74
|
return {
|
|
77
75
|
ok,
|
|
78
76
|
provider: "schtasks",
|
|
79
77
|
task: WINDOWS_TASK,
|
|
80
78
|
command,
|
|
81
|
-
status:
|
|
82
|
-
|
|
79
|
+
status: observed,
|
|
80
|
+
active_before: false,
|
|
81
|
+
active: observed?.active === true && stability.stable,
|
|
82
|
+
reason: stability.stable ? "started" : completed ? "completed_without_persistence" : "start_not_stable",
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
export async function restartWindowsTask(logger = console, options = {}) {
|
|
@@ -101,6 +101,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
|
|
|
101
101
|
installed: before.installed,
|
|
102
102
|
active_before: false,
|
|
103
103
|
active: false,
|
|
104
|
+
restore_required: false,
|
|
104
105
|
already_stopped: true,
|
|
105
106
|
reason: before.installed ? "already_stopped" : "not_installed",
|
|
106
107
|
status: before,
|
|
@@ -121,6 +122,7 @@ export async function stopWindowsTask(logger = console, options = {}) {
|
|
|
121
122
|
installed: after?.installed !== false,
|
|
122
123
|
active_before: true,
|
|
123
124
|
active: after?.active !== false,
|
|
125
|
+
restore_required: ok,
|
|
124
126
|
already_stopped: false,
|
|
125
127
|
command,
|
|
126
128
|
status: after,
|
|
@@ -134,8 +136,7 @@ export async function uninstallWindowsTask(logger = console, options = {}) {
|
|
|
134
136
|
if (!stopped.ok) return { ok: false, provider: "schtasks", task: WINDOWS_TASK, stop: stopped, reason: "stop_failed" };
|
|
135
137
|
const command = await run("schtasks", ["/Delete", "/TN", WINDOWS_TASK, "/F"]);
|
|
136
138
|
const status = await waitForWindowsStatus(
|
|
137
|
-
value => value.installed === false,
|
|
138
|
-
{ ...options, run },
|
|
139
|
+
() => statusWindowsTask({ ...options, run }), value => value.installed === false, options,
|
|
139
140
|
);
|
|
140
141
|
const ok = status.installed === false;
|
|
141
142
|
if (ok) logger.info?.("Windows Scheduled Task removed");
|
|
@@ -188,32 +189,8 @@ function windowsServiceFailureReason(command, status) {
|
|
|
188
189
|
return "task_installation_unverified";
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
async function waitForWindowsStatus(predicate, options) {
|
|
192
|
-
const attempts = boundedPositiveInteger(options.statusAttempts, 10);
|
|
193
|
-
const delayMs = boundedPositiveInteger(options.statusDelayMs, 100);
|
|
194
|
-
const sleep = typeof options.sleep === "function" ? options.sleep : milliseconds => new Promise(resolve => { setTimeout(resolve, milliseconds); });
|
|
195
|
-
let status = await statusWindowsTask(options);
|
|
196
|
-
for (let index = 1; index < attempts && !predicate(status); index += 1) {
|
|
197
|
-
await sleep(delayMs);
|
|
198
|
-
status = await statusWindowsTask(options);
|
|
199
|
-
}
|
|
200
|
-
return status;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function windowsStatusWaitOptions(options) {
|
|
204
|
-
return {
|
|
205
|
-
attempts: boundedPositiveInteger(options.statusAttempts, 10),
|
|
206
|
-
delayMs: boundedPositiveInteger(options.statusDelayMs, 100),
|
|
207
|
-
sleep: typeof options.sleep === "function" ? options.sleep : undefined,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
192
|
|
|
211
193
|
function requiredRun(run) {
|
|
212
194
|
if (typeof run !== "function") throw new Error("Windows service command runner is required");
|
|
213
195
|
return run;
|
|
214
196
|
}
|
|
215
|
-
|
|
216
|
-
function boundedPositiveInteger(value, fallback) {
|
|
217
|
-
const number = Number(value);
|
|
218
|
-
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
219
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const MCP_MODERN_PROTOCOL_VERSION: "2026-07-28";
|
|
2
|
+
export const MCP_LEGACY_PROTOCOL_VERSION: "2025-11-25";
|
|
3
|
+
export const MCP_MODERN_PROTOCOL_VERSIONS: readonly string[];
|
|
4
|
+
export const MCP_LEGACY_PROTOCOL_VERSIONS: readonly string[];
|
|
5
|
+
export const MCP_HEADER_MISMATCH: -32020;
|
|
6
|
+
export const MCP_UNSUPPORTED_PROTOCOL_VERSION: -32022;
|
|
7
|
+
export class McpProtocolError extends Error {
|
|
8
|
+
code: number;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
constructor(code: number, message: string, data?: unknown);
|
|
11
|
+
}
|
|
12
|
+
export function asMcpObject(value: unknown): Record<string, any>;
|
|
13
|
+
export function requestProtocolVersion(value: unknown): string;
|
|
14
|
+
export function isModernMcpRequest(value: unknown): boolean;
|
|
15
|
+
export function assertBoundedMcpJsonStructure(value: unknown, label?: string): void;
|
|
16
|
+
export function validateModernRequestMetadata(value: unknown, supportedVersions?: readonly string[]): {
|
|
17
|
+
version: string;
|
|
18
|
+
clientCapabilities: Record<string, unknown>;
|
|
19
|
+
clientInfo: Record<string, unknown> | null;
|
|
20
|
+
progressToken: unknown;
|
|
21
|
+
logLevel: unknown;
|
|
22
|
+
};
|
|
23
|
+
export function serverImplementation(input: { name: unknown; version: unknown; title?: unknown; description?: unknown }): Record<string, unknown>;
|
|
24
|
+
export function modernCompleteResult(fields?: Record<string, unknown>, serverInfo?: Record<string, unknown>): Record<string, unknown>;
|
|
25
|
+
export function modernCacheableResult(fields: Record<string, unknown>, options: { ttlMs: number; cacheScope: "public" | "private"; serverInfo?: Record<string, unknown> }): Record<string, unknown>;
|
|
26
|
+
export function modernDiscoverResult(input: { supportedVersions: readonly string[]; capabilities: Record<string, unknown>; instructions?: string; ttlMs?: number; serverInfo?: Record<string, unknown> }): Record<string, unknown>;
|
|
27
|
+
export function resultForProtocol(version: string, fields: Record<string, unknown>, options?: { serverInfo?: Record<string, unknown>; ttlMs?: number; cacheScope?: "public" | "private" }): Record<string, unknown>;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export const MCP_MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
2
|
+
export const MCP_LEGACY_PROTOCOL_VERSION = "2025-11-25";
|
|
3
|
+
export const MCP_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_PROTOCOL_VERSION]);
|
|
4
|
+
export const MCP_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_LEGACY_PROTOCOL_VERSION]);
|
|
5
|
+
export const MCP_HEADER_MISMATCH = -32020;
|
|
6
|
+
export const MCP_UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
7
|
+
const MCP_RESULT_COMPLETE = "complete";
|
|
8
|
+
|
|
9
|
+
const PROTOCOL_VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
10
|
+
const CLIENT_INFO_KEY = "io.modelcontextprotocol/clientInfo";
|
|
11
|
+
const CLIENT_CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
12
|
+
const SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
|
|
13
|
+
const LOG_LEVELS = new Set(["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]);
|
|
14
|
+
const META_KEY = /^(?:(?:[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)*[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?\/)?(?:[A-Za-z0-9](?:[A-Za-z0-9_.-]*[A-Za-z0-9])?)?$/;
|
|
15
|
+
const MAX_MCP_JSON_DEPTH = 32;
|
|
16
|
+
const MAX_MCP_JSON_NODES = 4096;
|
|
17
|
+
const MAX_MCP_JSON_KEY_LENGTH = 256;
|
|
18
|
+
|
|
19
|
+
export class McpProtocolError extends Error {
|
|
20
|
+
constructor(code, message, data) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "McpProtocolError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.data = data;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function asMcpObject(value) {
|
|
29
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requestMeta(value) {
|
|
33
|
+
return asMcpObject(asMcpObject(value).params)._meta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function requestProtocolVersion(value) {
|
|
37
|
+
const version = asMcpObject(requestMeta(value))[PROTOCOL_VERSION_KEY];
|
|
38
|
+
return typeof version === "string" ? version : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isModernMcpRequest(value) {
|
|
42
|
+
return requestProtocolVersion(value) !== "" || asMcpObject(value).method === "server/discover";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function validateModernRequestMetadata(value, supportedVersions = MCP_MODERN_PROTOCOL_VERSIONS) {
|
|
46
|
+
const params = asMcpObject(asMcpObject(value).params);
|
|
47
|
+
const meta = asMcpObject(params._meta);
|
|
48
|
+
assertBoundedMcpJsonStructure(meta, "request metadata");
|
|
49
|
+
const version = meta[PROTOCOL_VERSION_KEY];
|
|
50
|
+
if (typeof version !== "string" || !version) {
|
|
51
|
+
throw new McpProtocolError(-32602, `Missing required request metadata: ${PROTOCOL_VERSION_KEY}`);
|
|
52
|
+
}
|
|
53
|
+
if (!supportedVersions.includes(version)) {
|
|
54
|
+
throw new McpProtocolError(MCP_UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", {
|
|
55
|
+
supported: [...supportedVersions],
|
|
56
|
+
requested: boundedProtocolVersion(version),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
validateMetaKeys(meta);
|
|
60
|
+
const clientCapabilities = meta[CLIENT_CAPABILITIES_KEY];
|
|
61
|
+
if (!isPlainObject(clientCapabilities)) {
|
|
62
|
+
throw new McpProtocolError(-32602, `Missing required request metadata: ${CLIENT_CAPABILITIES_KEY}`);
|
|
63
|
+
}
|
|
64
|
+
validateClientCapabilities(clientCapabilities);
|
|
65
|
+
const clientInfo = meta[CLIENT_INFO_KEY];
|
|
66
|
+
if (clientInfo !== undefined && !isImplementation(clientInfo)) {
|
|
67
|
+
throw new McpProtocolError(-32602, `Invalid request metadata: ${CLIENT_INFO_KEY}`);
|
|
68
|
+
}
|
|
69
|
+
const progressToken = meta.progressToken;
|
|
70
|
+
if (progressToken !== undefined && !isProgressToken(progressToken)) {
|
|
71
|
+
throw new McpProtocolError(-32602, "Invalid request metadata: progressToken");
|
|
72
|
+
}
|
|
73
|
+
const logLevel = meta["io.modelcontextprotocol/logLevel"];
|
|
74
|
+
if (logLevel !== undefined && !LOG_LEVELS.has(logLevel)) {
|
|
75
|
+
throw new McpProtocolError(-32602, "Invalid request metadata: io.modelcontextprotocol/logLevel");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
version,
|
|
79
|
+
clientCapabilities,
|
|
80
|
+
clientInfo: clientInfo ?? null,
|
|
81
|
+
progressToken,
|
|
82
|
+
logLevel,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function serverImplementation({ name, version, title, description }) {
|
|
87
|
+
const implementation = { name: String(name), version: String(version) };
|
|
88
|
+
if (title) implementation.title = String(title);
|
|
89
|
+
if (description) implementation.description = String(description);
|
|
90
|
+
return implementation;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function modernCompleteResult(fields = {}, serverInfo) {
|
|
94
|
+
return modernResult(MCP_RESULT_COMPLETE, fields, serverInfo);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
export function modernCacheableResult(fields, { ttlMs, cacheScope, serverInfo }) {
|
|
99
|
+
const normalizedTtl = Number(ttlMs);
|
|
100
|
+
if (!Number.isFinite(normalizedTtl) || normalizedTtl < 0) throw new Error("ttlMs must be a non-negative number");
|
|
101
|
+
if (cacheScope !== "public" && cacheScope !== "private") throw new Error("cacheScope must be public or private");
|
|
102
|
+
return modernCompleteResult({ ...fields, ttlMs: normalizedTtl, cacheScope }, serverInfo);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function modernDiscoverResult({ supportedVersions, capabilities, instructions, ttlMs = 0, serverInfo }) {
|
|
106
|
+
const fields = {
|
|
107
|
+
supportedVersions: [...supportedVersions],
|
|
108
|
+
capabilities: structuredClone(capabilities),
|
|
109
|
+
};
|
|
110
|
+
if (instructions) fields.instructions = String(instructions);
|
|
111
|
+
return modernCacheableResult(fields, { ttlMs, cacheScope: "public", serverInfo });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
export function resultForProtocol(version, fields, { serverInfo, ttlMs, cacheScope } = {}) {
|
|
116
|
+
if (version !== MCP_MODERN_PROTOCOL_VERSION) return structuredClone(fields);
|
|
117
|
+
if (ttlMs !== undefined || cacheScope !== undefined) {
|
|
118
|
+
return modernCacheableResult(fields, {
|
|
119
|
+
ttlMs: ttlMs ?? 0,
|
|
120
|
+
cacheScope: cacheScope ?? "private",
|
|
121
|
+
serverInfo,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return modernCompleteResult(fields, serverInfo);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function modernServerInfoMeta(serverInfo) {
|
|
128
|
+
return serverInfo ? { [SERVER_INFO_KEY]: structuredClone(serverInfo) } : undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function modernResult(resultType, fields, serverInfo) {
|
|
132
|
+
const result = { ...structuredClone(fields), resultType };
|
|
133
|
+
const meta = modernServerInfoMeta(serverInfo);
|
|
134
|
+
if (meta) result._meta = { ...asMcpObject(result._meta), ...meta };
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
export function assertBoundedMcpJsonStructure(value, label = "MCP value") {
|
|
140
|
+
const stack = [{ value, depth: 0 }];
|
|
141
|
+
let nodes = 0;
|
|
142
|
+
while (stack.length > 0) {
|
|
143
|
+
const current = stack.pop();
|
|
144
|
+
nodes += 1;
|
|
145
|
+
if (nodes > MAX_MCP_JSON_NODES || current.depth > MAX_MCP_JSON_DEPTH) {
|
|
146
|
+
throw new McpProtocolError(-32602, `${label} exceeds bounded structural limits`);
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(current.value)) {
|
|
149
|
+
if (current.value.length > MAX_MCP_JSON_NODES - nodes - stack.length) {
|
|
150
|
+
throw new McpProtocolError(-32602, `${label} exceeds bounded structural limits`);
|
|
151
|
+
}
|
|
152
|
+
for (let index = 0; index < current.value.length; index += 1) {
|
|
153
|
+
stack.push({ value: current.value[index], depth: current.depth + 1 });
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!current.value || typeof current.value !== "object") continue;
|
|
158
|
+
if (!isPlainObject(current.value)) throw new McpProtocolError(-32602, `${label} contains a non-JSON object`);
|
|
159
|
+
for (const key in current.value) {
|
|
160
|
+
if (!Object.hasOwn(current.value, key)) continue;
|
|
161
|
+
if (key.length > MAX_MCP_JSON_KEY_LENGTH || stack.length + nodes >= MAX_MCP_JSON_NODES) {
|
|
162
|
+
throw new McpProtocolError(-32602, `${label} exceeds bounded structural limits`);
|
|
163
|
+
}
|
|
164
|
+
stack.push({ value: current.value[key], depth: current.depth + 1 });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function validateMetaKeys(meta) {
|
|
170
|
+
for (const key in meta) {
|
|
171
|
+
if (!Object.hasOwn(meta, key)) continue;
|
|
172
|
+
if (key.length > MAX_MCP_JSON_KEY_LENGTH || !META_KEY.test(key)) {
|
|
173
|
+
throw new McpProtocolError(-32602, "Invalid request metadata key");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function validateClientCapabilities(value) {
|
|
179
|
+
validateCapabilityObject(value, "roots");
|
|
180
|
+
validateCapabilityMap(value, "experimental");
|
|
181
|
+
validateCapabilityFields(value, "sampling", ["context", "tools"]);
|
|
182
|
+
validateCapabilityFields(value, "elicitation", ["form", "url"]);
|
|
183
|
+
const extensions = value.extensions;
|
|
184
|
+
if (extensions === undefined) return;
|
|
185
|
+
if (!isPlainObject(extensions)) throw new McpProtocolError(-32602, "Invalid client capability: extensions");
|
|
186
|
+
for (const key in extensions) {
|
|
187
|
+
if (!Object.hasOwn(extensions, key)) continue;
|
|
188
|
+
const settings = extensions[key];
|
|
189
|
+
if (key.length > MAX_MCP_JSON_KEY_LENGTH || !key.includes("/") || !META_KEY.test(key) || !isPlainObject(settings)) {
|
|
190
|
+
throw new McpProtocolError(-32602, "Invalid client extension capability");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function validateCapabilityObject(value, key) {
|
|
196
|
+
if (value[key] !== undefined && !isPlainObject(value[key])) {
|
|
197
|
+
throw new McpProtocolError(-32602, `Invalid client capability: ${key}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function validateCapabilityMap(value, key) {
|
|
202
|
+
validateCapabilityObject(value, key);
|
|
203
|
+
if (value[key] === undefined) return;
|
|
204
|
+
for (const name in value[key]) {
|
|
205
|
+
if (!Object.hasOwn(value[key], name)) continue;
|
|
206
|
+
if (name.length > MAX_MCP_JSON_KEY_LENGTH || !isPlainObject(value[key][name])) {
|
|
207
|
+
throw new McpProtocolError(-32602, `Invalid client capability: ${key}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function validateCapabilityFields(value, key, fields) {
|
|
213
|
+
validateCapabilityObject(value, key);
|
|
214
|
+
if (value[key] === undefined) return;
|
|
215
|
+
for (const field of fields) {
|
|
216
|
+
if (value[key][field] !== undefined && !isPlainObject(value[key][field])) {
|
|
217
|
+
throw new McpProtocolError(-32602, `Invalid client capability: ${key}.${field}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function boundedProtocolVersion(value) {
|
|
223
|
+
return value.length <= 128 ? value : "<invalid-length>";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isProgressToken(value) {
|
|
227
|
+
return typeof value === "string" || (typeof value === "number" && Number.isFinite(value));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isImplementation(value) {
|
|
231
|
+
if (!isPlainObject(value) || typeof value.name !== "string" || typeof value.version !== "string") return false;
|
|
232
|
+
for (const field of ["title", "description"]) {
|
|
233
|
+
if (value[field] !== undefined && typeof value[field] !== "string") return false;
|
|
234
|
+
}
|
|
235
|
+
if (value.websiteUrl !== undefined && !isAbsoluteUri(value.websiteUrl)) return false;
|
|
236
|
+
if (value.icons !== undefined && (!Array.isArray(value.icons) || !value.icons.every(isIcon))) return false;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isIcon(value) {
|
|
241
|
+
if (!isPlainObject(value) || !isAbsoluteUri(value.src)) return false;
|
|
242
|
+
if (value.mimeType !== undefined && typeof value.mimeType !== "string") return false;
|
|
243
|
+
if (value.sizes !== undefined && (!Array.isArray(value.sizes) || !value.sizes.every((size) => typeof size === "string"))) return false;
|
|
244
|
+
return value.theme === undefined || value.theme === "light" || value.theme === "dark";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isAbsoluteUri(value) {
|
|
248
|
+
if (typeof value !== "string" || !value) return false;
|
|
249
|
+
try { return Boolean(new URL(value).protocol); } catch { return false; }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isPlainObject(value) {
|
|
253
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
254
|
+
const prototype = Object.getPrototypeOf(value);
|
|
255
|
+
return prototype === Object.prototype || prototype === null;
|
|
256
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function validateSubscriptionFilter(value: unknown): Record<string, unknown>;
|
|
2
|
+
export function acceptedSubscriptionFilter(requested: unknown, serverCapabilities: unknown): Record<string, unknown>;
|
|
3
|
+
export function subscriptionAcknowledgedNotification(subscriptionId: string | number, notifications: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
export function subscriptionCompleteResult(subscriptionId: string | number, serverInfo?: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
McpProtocolError, asMcpObject, assertBoundedMcpJsonStructure, modernCompleteResult,
|
|
3
|
+
} from "./mcp-protocol.mjs";
|
|
4
|
+
|
|
5
|
+
const SUBSCRIPTION_ID_KEY = "io.modelcontextprotocol/subscriptionId";
|
|
6
|
+
const MAX_RESOURCE_SUBSCRIPTIONS = 256;
|
|
7
|
+
const MAX_RESOURCE_SUBSCRIPTION_LENGTH = 8192;
|
|
8
|
+
|
|
9
|
+
export function validateSubscriptionFilter(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
throw new McpProtocolError(-32602, "subscriptions/listen requires a notifications object");
|
|
12
|
+
}
|
|
13
|
+
assertBoundedMcpJsonStructure(value, "subscriptions/listen notifications");
|
|
14
|
+
for (const key of ["toolsListChanged", "promptsListChanged", "resourcesListChanged"]) {
|
|
15
|
+
if (value[key] !== undefined && typeof value[key] !== "boolean") {
|
|
16
|
+
throw new McpProtocolError(-32602, `subscriptions/listen notifications.${key} must be boolean`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (value.resourceSubscriptions !== undefined) {
|
|
20
|
+
if (!Array.isArray(value.resourceSubscriptions)
|
|
21
|
+
|| value.resourceSubscriptions.length > MAX_RESOURCE_SUBSCRIPTIONS
|
|
22
|
+
|| !value.resourceSubscriptions.every((uri) => typeof uri === "string" && uri.length <= MAX_RESOURCE_SUBSCRIPTION_LENGTH)) {
|
|
23
|
+
throw new McpProtocolError(-32602, "subscriptions/listen notifications.resourceSubscriptions exceeds its bounded string-array contract");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function acceptedSubscriptionFilter(requested, serverCapabilities) {
|
|
30
|
+
const input = asMcpObject(requested);
|
|
31
|
+
const capabilities = asMcpObject(serverCapabilities);
|
|
32
|
+
const tools = asMcpObject(capabilities.tools);
|
|
33
|
+
const prompts = asMcpObject(capabilities.prompts);
|
|
34
|
+
const resources = asMcpObject(capabilities.resources);
|
|
35
|
+
const accepted = {};
|
|
36
|
+
if (input.toolsListChanged === true && tools.listChanged === true) accepted.toolsListChanged = true;
|
|
37
|
+
if (input.promptsListChanged === true && prompts.listChanged === true) accepted.promptsListChanged = true;
|
|
38
|
+
if (input.resourcesListChanged === true && resources.listChanged === true) accepted.resourcesListChanged = true;
|
|
39
|
+
if (resources.subscribe === true && Array.isArray(input.resourceSubscriptions)) {
|
|
40
|
+
const uris = input.resourceSubscriptions.filter((value) => typeof value === "string" && value.length > 0);
|
|
41
|
+
if (uris.length > 0) accepted.resourceSubscriptions = [...new Set(uris)];
|
|
42
|
+
}
|
|
43
|
+
return accepted;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function subscriptionAcknowledgedNotification(subscriptionId, notifications) {
|
|
47
|
+
return {
|
|
48
|
+
jsonrpc: "2.0",
|
|
49
|
+
method: "notifications/subscriptions/acknowledged",
|
|
50
|
+
params: {
|
|
51
|
+
_meta: { [SUBSCRIPTION_ID_KEY]: subscriptionId },
|
|
52
|
+
notifications: structuredClone(notifications),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function subscriptionCompleteResult(subscriptionId, serverInfo) {
|
|
58
|
+
return modernCompleteResult({ _meta: { [SUBSCRIPTION_ID_KEY]: subscriptionId } }, serverInfo);
|
|
59
|
+
}
|
|
@@ -2,5 +2,6 @@ export const MCP_TEXT_PROJECTION_KEY: "$mcpText";
|
|
|
2
2
|
export function mirroredResultText(value: unknown): string;
|
|
3
3
|
export function projectMcpResult(value: unknown): {
|
|
4
4
|
text: string;
|
|
5
|
-
|
|
5
|
+
hasStructuredContent: boolean;
|
|
6
|
+
structuredContent: unknown;
|
|
6
7
|
};
|
|
@@ -35,14 +35,25 @@ export function projectMcpResult(value) {
|
|
|
35
35
|
const structuredContent = { ...object };
|
|
36
36
|
const text = String(structuredContent[MCP_TEXT_PROJECTION_KEY]).slice(0, 4096);
|
|
37
37
|
delete structuredContent[MCP_TEXT_PROJECTION_KEY];
|
|
38
|
-
return { text, structuredContent };
|
|
38
|
+
return { text, hasStructuredContent: true, structuredContent };
|
|
39
39
|
}
|
|
40
40
|
return {
|
|
41
41
|
text: mirroredResultText(value),
|
|
42
|
-
|
|
42
|
+
hasStructuredContent: isJsonValue(value),
|
|
43
|
+
structuredContent: value,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** @param {unknown} value */
|
|
48
|
+
function isJsonValue(value) {
|
|
49
|
+
return value === null
|
|
50
|
+
|| typeof value === "string"
|
|
51
|
+
|| typeof value === "boolean"
|
|
52
|
+
|| (typeof value === "number" && Number.isFinite(value))
|
|
53
|
+
|| Array.isArray(value)
|
|
54
|
+
|| (value && typeof value === "object");
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
/** @param {unknown} value */
|
|
47
58
|
function asObject(value) {
|
|
48
59
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"protocolVersion": "
|
|
3
|
+
"protocolVersion": "2026-07-28",
|
|
4
4
|
"supportedProtocolVersions": [
|
|
5
|
+
"2026-07-28",
|
|
5
6
|
"2025-11-25"
|
|
6
7
|
],
|
|
7
8
|
"instructions": [
|
|
8
9
|
"You are connected to a local workspace through machine-bridge-mcp.",
|
|
9
|
-
"At the start of each substantive local task, call resolve_task_capabilities with the current user request and target path.
|
|
10
|
+
"At the start of each substantive local task, call resolve_task_capabilities with the current user request and target path. Its execution_routing is bounded, set-level, and advisory: direct Bash remains the general escape hatch whenever the effective policy allows it. Reuse refresh.fingerprint as known_refresh_fingerprint to avoid repeating unchanged static instructions. MCP transport connections are not conversation identity; refresh task-specific ranking explicitly rather than assuming prior requests established context.",
|
|
10
11
|
"Before any GitHub read or mutation, apply the effective project control-plane instructions. When a project requires local git, gh, and gh api through Machine Bridge, never use a hosted GitHub connector or ChatGPT GitHub plugin; if the local control plane is unavailable, stop rather than substitute another GitHub tool.",
|
|
11
|
-
"
|
|
12
|
+
"MCP 2026-07-28 clients use server/discover and per-request metadata. The discovery result includes conservative built-in working agreements; session_bootstrap remains an explicit refresh tool for project facts, local instructions, and capability routing. Legacy 2025-11-25 initialize clients receive the same guidance through the compatibility adapter.",
|
|
12
13
|
"For existing-browser work, use the paired Machine Bridge browser extension. It operates the user current Chromium profile and tabs, can inspect DOM/source, fill complex forms, and inject sensitive text from registered local resources without returning those values.",
|
|
13
14
|
"For local application work, use structured application discovery and Accessibility actions. Arbitrary caller-supplied AppleScript or JavaScript is intentionally not accepted.",
|
|
14
15
|
"Remote mode uses a Cloudflare relay; stdio mode runs on the local machine. File and command operations execute on the user's local runtime, not in the Worker.",
|
|
@@ -24,8 +25,14 @@
|
|
|
24
25
|
"Managed-job resource redaction is defense in depth, not a guarantee against transformed or partial secret output. Use capture_output=discard for steps that may echo credentials.",
|
|
25
26
|
"Do not use shell encoding, renaming, or alternate tools to bypass host or platform safety policy.",
|
|
26
27
|
"run_process avoids shell parsing but is not an OS sandbox. exec_command is exposed only in shell mode and has the local user's authority.",
|
|
27
|
-
"
|
|
28
|
+
"Remote foreground run_process, run_local_command, exec_command, browser, and application calls are limited to 85 seconds and reject larger requested timeouts before local side effects begin. Keep mutations separate from validation. For longer work, use start_process/read_process or start_job/read_job. If the host omits those tools, start a non-interactive command that writes bounded output and an exit-status file under ignored project-local state, then poll those files with short calls.",
|
|
28
29
|
"Per-tool success, failure, cancellation, and timing events are debug-only; default logs contain lifecycle and infrastructure events, not a tool-call transcript.",
|
|
29
30
|
"Inspect before editing, prefer read_file line ranges, edit_file, or apply_patch over whole-file replacement, and report commands that were run."
|
|
31
|
+
],
|
|
32
|
+
"modernProtocolVersions": [
|
|
33
|
+
"2026-07-28"
|
|
34
|
+
],
|
|
35
|
+
"legacyProtocolVersions": [
|
|
36
|
+
"2025-11-25"
|
|
30
37
|
]
|
|
31
38
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const JSON_SCHEMA_2020_12: "https://json-schema.org/draft/2020-12/schema";
|
|
2
|
+
export type ToolValidationIssue = Readonly<{ instancePath: string; keyword: string; message: string }>;
|
|
3
|
+
export class ToolSchemaContractError extends Error {}
|
|
4
|
+
export class ToolArgumentValidationError extends Error {
|
|
5
|
+
code: "invalid_request";
|
|
6
|
+
retryable: false;
|
|
7
|
+
details: Readonly<{ tool: string; validation_issues: readonly ToolValidationIssue[] }>;
|
|
8
|
+
}
|
|
9
|
+
export function compileToolArgumentValidators(
|
|
10
|
+
tools: readonly Array<{ name: string; inputSchema?: unknown }>,
|
|
11
|
+
options?: { maximumDepth?: number; maximumNodes?: number; maximumIssues?: number; maximumPatternLength?: number; maximumValidationSteps?: number },
|
|
12
|
+
): Readonly<{
|
|
13
|
+
names: readonly string[];
|
|
14
|
+
has(tool: unknown): boolean;
|
|
15
|
+
validate(tool: unknown, value: unknown): Readonly<{ known: boolean; valid: boolean; issues: readonly ToolValidationIssue[] }>;
|
|
16
|
+
assert<T>(tool: unknown, value: T): T;
|
|
17
|
+
}>;
|