@nudge-ui/mcp 0.2.5 → 0.2.6
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/dist/adapterRouting.d.ts +0 -1
- package/dist/adapterRouting.js +0 -1
- package/dist/bridge.d.ts +2 -7
- package/dist/bridge.js +1 -4
- package/dist/bridgeSession.d.ts +6 -15
- package/dist/bridgeSession.js +4 -4
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +2 -3
- package/dist/discovery.d.ts +1 -1
- package/dist/discovery.js +1 -1
- package/dist/internal/agent-protocol.d.ts +20 -42
- package/dist/internal/agent-protocol.js +18 -35
- package/dist/project.d.ts +1 -6
- package/dist/project.js +3 -8
- package/dist/projectSessionClient.d.ts +0 -3
- package/dist/projectSessionClient.js +0 -3
- package/dist/protocol.d.ts +2 -19
- package/dist/protocol.js +1 -17
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -1
- package/dist/sessionRegistry.d.ts +0 -2
- package/dist/sessionRegistry.js +0 -2
- package/package.json +1 -1
package/dist/adapterRouting.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export interface ProjectTarget {
|
|
|
3
3
|
readonly workspaceRoot?: string;
|
|
4
4
|
readonly sessionId?: string;
|
|
5
5
|
}
|
|
6
|
-
/** Keeps each explicit project selection independent for the lifetime of an MCP connection. */
|
|
7
6
|
export declare class AdapterRouting {
|
|
8
7
|
readonly registryRoot: string;
|
|
9
8
|
private closing;
|
package/dist/adapterRouting.js
CHANGED
|
@@ -4,7 +4,6 @@ import { DiscoveredProjectRouter } from "./discovery.js";
|
|
|
4
4
|
import { defaultSessionRegistryRoot, resolveWorkspaceRoot } from "./project.js";
|
|
5
5
|
import { readLiveProjectSessions } from "./sessionRegistry.js";
|
|
6
6
|
import { selectProjectSession } from "./sessionSelection.js";
|
|
7
|
-
/** Keeps each explicit project selection independent for the lifetime of an MCP connection. */
|
|
8
7
|
export class AdapterRouting {
|
|
9
8
|
registryRoot;
|
|
10
9
|
closing = false;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type BridgeClock, type BridgeSessionControls } from "./bridgeSession.js";
|
|
2
2
|
export interface BrowserBridgeOptions {
|
|
3
|
-
/** Stable project identity used to scope every browser message. */
|
|
4
3
|
readonly projectId: string;
|
|
5
4
|
/** Canonical app origin that pairing and Canvas routes must use. */
|
|
6
5
|
readonly origin: string;
|
|
7
|
-
/** Other explicit browser origins
|
|
6
|
+
/** Other explicit browser origins, such as loopback aliases on the same port. */
|
|
8
7
|
readonly allowedOrigins?: readonly string[];
|
|
9
8
|
readonly host?: "127.0.0.1" | "::1" | "localhost";
|
|
10
9
|
/** Use port 0 in tests or when several projects run concurrently. */
|
|
@@ -23,15 +22,11 @@ export interface BridgeAddress {
|
|
|
23
22
|
readonly port: number;
|
|
24
23
|
readonly url: string;
|
|
25
24
|
}
|
|
26
|
-
/** A loopback HTTP server exposing one project session to its browser and MCP adapter. */
|
|
27
25
|
export interface BrowserBridge extends BridgeSessionControls {
|
|
28
26
|
readonly projectId: string;
|
|
29
27
|
readonly address: BridgeAddress | null;
|
|
30
28
|
start(): Promise<BridgeAddress>;
|
|
31
29
|
close(): Promise<void>;
|
|
32
30
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Creates the local browser bridge. It binds only to loopback and keeps every
|
|
35
|
-
* pairing, prompt, command, and acknowledgement in process memory.
|
|
36
|
-
*/
|
|
31
|
+
/** Binds only to loopback and keeps every pairing, prompt, command, and acknowledgement in process memory. */
|
|
37
32
|
export declare function createLoopbackBridge(options: BrowserBridgeOptions): BrowserBridge;
|
package/dist/bridge.js
CHANGED
|
@@ -187,10 +187,7 @@ function writeSse(response, envelope) {
|
|
|
187
187
|
return false;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Creates the local browser bridge. It binds only to loopback and keeps every
|
|
192
|
-
* pairing, prompt, command, and acknowledgement in process memory.
|
|
193
|
-
*/
|
|
190
|
+
/** Binds only to loopback and keeps every pairing, prompt, command, and acknowledgement in process memory. */
|
|
194
191
|
export function createLoopbackBridge(options) {
|
|
195
192
|
const host = options.host ?? "127.0.0.1";
|
|
196
193
|
if (!LOOPBACK_HOSTS.has(host))
|
package/dist/bridgeSession.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { AgentActivity, AgentDeliveredPrompt, AgentRequestOutcome, AgentSketchAttachment, AgentStatusSnapshot, AgentStatusUpdate, BridgeEnvelope, CanvasCommand, CanvasCommandResult, FitCanvasCommand, FocusCanvasGroupCommand, PairingResponse, PresentRoutesCommand, PromptDispatchResponse, ReadCanvasStateCommand, RemoveCanvasGroupCommand } from "./internal/agent-protocol.js";
|
|
2
2
|
export declare const AGENT_CLAIM_TIMEOUT_MS = 30000;
|
|
3
3
|
export declare const AGENT_ACKNOWLEDGMENT_TIMEOUT_MS = 60000;
|
|
4
|
+
/** Timer handle: Node returns Timeout objects and test clocks use numeric IDs; only the creating clock interprets it. */
|
|
5
|
+
export type BridgeTimeoutHandle = NodeJS.Timeout | number;
|
|
4
6
|
/** A clock port keeps command timeout and claim expiry tests deterministic. */
|
|
5
7
|
export interface BridgeClock {
|
|
6
8
|
now(): number;
|
|
7
|
-
setTimeout(callback: () => void, delayMs: number):
|
|
8
|
-
clearTimeout(handle:
|
|
9
|
+
setTimeout(callback: () => void, delayMs: number): BridgeTimeoutHandle;
|
|
10
|
+
clearTimeout(handle: BridgeTimeoutHandle): void;
|
|
9
11
|
}
|
|
10
12
|
type WithOptionalCommandId<T extends CanvasCommand> = Omit<T, "commandId"> & {
|
|
11
13
|
readonly commandId?: string;
|
|
@@ -22,7 +24,6 @@ export interface BrowserStream {
|
|
|
22
24
|
send(envelope: BridgeEnvelope): boolean;
|
|
23
25
|
end(): void;
|
|
24
26
|
}
|
|
25
|
-
/** Proof that a request came from the paired browser. */
|
|
26
27
|
export interface BrowserGrant {
|
|
27
28
|
readonly origin: string;
|
|
28
29
|
readonly sessionToken: string;
|
|
@@ -35,31 +36,23 @@ export interface PromptDispatchInput {
|
|
|
35
36
|
}
|
|
36
37
|
export interface ListenOptions {
|
|
37
38
|
readonly signal?: AbortSignal;
|
|
38
|
-
/**
|
|
39
|
+
/** Shown in the browser while this listener is active. */
|
|
39
40
|
readonly agentClientName?: string;
|
|
40
41
|
}
|
|
41
|
-
/** In-process controls a host or an embedded agent uses on one project session. */
|
|
42
42
|
export interface BridgeSessionControls {
|
|
43
43
|
getStatus(): AgentStatusSnapshot;
|
|
44
|
-
/** Host-side approval of a browser origin from the allow-list. */
|
|
45
44
|
pairBrowser(origin: string, pageUrl?: string): PairingResponse;
|
|
46
45
|
/** Revokes the pairing and interrupts any working request. */
|
|
47
46
|
disconnectBrowser(sessionToken: string): void;
|
|
48
|
-
/** Blocks until the paired browser dispatches a prompt. */
|
|
49
47
|
waitForPrompt(options?: ListenOptions): Promise<AgentDeliveredPrompt>;
|
|
50
48
|
/** Retrieves an unacknowledged request, including its sketch attachments. */
|
|
51
49
|
getRequest(requestId: string): AgentDeliveredPrompt;
|
|
52
|
-
/** Reports the status of the request currently in flight. */
|
|
53
50
|
updateRequestStatus(update: AgentStatusUpdate | AgentRequestOutcome): void;
|
|
54
|
-
/** Emits temporary source attribution for the working request. */
|
|
55
51
|
reportActivity(activity: AgentActivity): void;
|
|
56
52
|
/** Sends a Canvas command to the browser and waits for one bounded acknowledgement. */
|
|
57
53
|
dispatchCanvasCommand(input: CanvasCommandInput): Promise<CanvasCommandResult>;
|
|
58
54
|
}
|
|
59
|
-
/**
|
|
60
|
-
* The transport-free state of one project bridge. Browser-facing transitions
|
|
61
|
-
* trust their caller to have authorized the request first.
|
|
62
|
-
*/
|
|
55
|
+
/** Browser-facing transitions trust their caller to have authorized the request first. */
|
|
63
56
|
export interface BridgeSession extends BridgeSessionControls {
|
|
64
57
|
readonly projectId: string;
|
|
65
58
|
readonly agentClaimed: boolean;
|
|
@@ -83,9 +76,7 @@ export interface BridgeSession extends BridgeSessionControls {
|
|
|
83
76
|
}
|
|
84
77
|
export interface BridgeSessionOptions {
|
|
85
78
|
readonly projectId: string;
|
|
86
|
-
/** Canonical app origin that pairing and Canvas routes must use. */
|
|
87
79
|
readonly origin: string;
|
|
88
|
-
/** Other explicit browser origins, such as loopback aliases on the same port. */
|
|
89
80
|
readonly allowedOrigins?: readonly string[];
|
|
90
81
|
readonly commandTimeoutMs?: number;
|
|
91
82
|
readonly clock?: BridgeClock;
|
package/dist/bridgeSession.js
CHANGED
|
@@ -20,10 +20,11 @@ function canonicalOrigins(options) {
|
|
|
20
20
|
const origins = [options.origin, ...(options.allowedOrigins ?? [])].map((candidate) => canonicalOrigin(candidate));
|
|
21
21
|
if (origins[0] === null)
|
|
22
22
|
throw new TypeError("origin must be a canonical HTTP(S) origin");
|
|
23
|
-
|
|
23
|
+
const canonical = origins.filter((candidate) => candidate !== null);
|
|
24
|
+
if (canonical.length !== origins.length) {
|
|
24
25
|
throw new TypeError("allowedOrigins must contain canonical HTTP(S) origins");
|
|
25
26
|
}
|
|
26
|
-
return
|
|
27
|
+
return canonical;
|
|
27
28
|
}
|
|
28
29
|
export function createBridgeSession(options) {
|
|
29
30
|
const { projectId } = options;
|
|
@@ -79,8 +80,7 @@ export function createBridgeSession(options) {
|
|
|
79
80
|
};
|
|
80
81
|
const publishStatus = () => publish({ type: "status", status: getStatus() });
|
|
81
82
|
const authorizeOrigin = (candidate) => {
|
|
82
|
-
//
|
|
83
|
-
// stays closed rather than trusting the first caller.
|
|
83
|
+
// An empty allow-list stays closed rather than trusting the first caller.
|
|
84
84
|
if (candidate && (candidate === pairing?.origin || isAllowedOrigin(candidate, allowedOrigins)))
|
|
85
85
|
return candidate;
|
|
86
86
|
throw new BridgeSessionError("origin_not_allowed", "The browser origin is not allowed for this project.");
|
package/dist/cli.d.ts
CHANGED
|
@@ -7,5 +7,4 @@ export type CliEnvironment = Readonly<Record<string, string | undefined>>;
|
|
|
7
7
|
/** Serving takes no options; `doctor` accepts a diagnostic scope. */
|
|
8
8
|
export declare function parseCliArguments(argv?: readonly string[], environment?: CliEnvironment): CliOptions;
|
|
9
9
|
export declare const CLI_USAGE = "Usage: nudge-mcp [doctor] [options]\n\nStarts a standard MCP stdio adapter that discovers the bridges owned by running\nprojects. Every tool call names the checkout or application it targets.\n\nOptions:\n --workspace-root <path> doctor only: limit diagnostics to one checkout or application\n --help Show this message\n";
|
|
10
|
-
/** Runs the CLI until the stdio MCP connection closes or the process stops. */
|
|
11
10
|
export declare function runCli(argv?: readonly string[], environment?: CliEnvironment): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -57,7 +57,6 @@ Options:
|
|
|
57
57
|
--workspace-root <path> doctor only: limit diagnostics to one checkout or application
|
|
58
58
|
--help Show this message
|
|
59
59
|
`;
|
|
60
|
-
/** Runs the CLI until the stdio MCP connection closes or the process stops. */
|
|
61
60
|
export async function runCli(argv = process.argv.slice(2), environment = process.env) {
|
|
62
61
|
const options = parseCliArguments(argv, environment);
|
|
63
62
|
if (options.help) {
|
|
@@ -106,8 +105,8 @@ export async function runCli(argv = process.argv.slice(2), environment = process
|
|
|
106
105
|
await adapter.start();
|
|
107
106
|
await new Promise((resolveAdapter) => {
|
|
108
107
|
const finish = () => resolveAdapter();
|
|
109
|
-
//
|
|
110
|
-
//
|
|
108
|
+
// Installing a signal handler suppresses Node's default exit, so the
|
|
109
|
+
// handler needs this direct resolver to terminate.
|
|
111
110
|
resolveAdapterWait = finish;
|
|
112
111
|
process.stdin.once("end", finish);
|
|
113
112
|
process.once("exit", finish);
|
package/dist/discovery.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface DiscoveredProjectSession extends ProjectSession {
|
|
|
9
9
|
readonly status: Pick<AgentStatusSnapshot, "connection" | "listenerActive" | "paired">;
|
|
10
10
|
}
|
|
11
11
|
export declare function discoverProjectSessions(applicationRoot: string | undefined, registryRoot?: string): Promise<DiscoveredProjectSession[]>;
|
|
12
|
-
/**
|
|
12
|
+
/** Follows one selected live project across bridge restarts. */
|
|
13
13
|
export declare class DiscoveredProjectRouter {
|
|
14
14
|
readonly workspaceRoot: string;
|
|
15
15
|
readonly applicationRoot: string;
|
package/dist/discovery.js
CHANGED
|
@@ -33,7 +33,7 @@ export async function discoverProjectSessions(applicationRoot, registryRoot = de
|
|
|
33
33
|
};
|
|
34
34
|
}).sort((left, right) => right.startedAt.localeCompare(left.startedAt));
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/** Follows one selected live project across bridge restarts. */
|
|
37
37
|
export class DiscoveredProjectRouter {
|
|
38
38
|
workspaceRoot;
|
|
39
39
|
applicationRoot;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public, host-neutral contracts shared by the Nudge browser inspector and
|
|
3
|
-
* its local MCP companion.
|
|
4
|
-
*
|
|
5
|
-
* This module deliberately has no Node, browser, MCP SDK, or framework
|
|
6
|
-
* imports. Hosts can use the same validation and message shapes without
|
|
7
|
-
* pulling the companion into a browser bundle.
|
|
8
|
-
*/
|
|
1
|
+
/** Host-neutral contracts shared by the browser inspector and the local MCP companion; no Node, browser, MCP SDK, or framework imports. */
|
|
9
2
|
export declare const AGENT_PROTOCOL_VERSION: 3;
|
|
10
3
|
export declare const AGENT_PROTOCOL_LIMITS: {
|
|
11
4
|
readonly projectId: 256;
|
|
@@ -38,6 +31,18 @@ export declare const AGENT_PROTOCOL_LIMITS: {
|
|
|
38
31
|
readonly agentClientName: 120;
|
|
39
32
|
};
|
|
40
33
|
export type AgentProtocolVersion = typeof AGENT_PROTOCOL_VERSION;
|
|
34
|
+
/** Stable loopback endpoint names shared by the browser client and the MCP bridge. */
|
|
35
|
+
export declare const BRIDGE_ENDPOINTS: {
|
|
36
|
+
readonly health: "/health";
|
|
37
|
+
readonly pair: "/pair";
|
|
38
|
+
readonly takeover: "/takeover";
|
|
39
|
+
readonly events: "/events";
|
|
40
|
+
readonly prompt: "/prompt";
|
|
41
|
+
readonly status: "/status";
|
|
42
|
+
readonly canvasAcknowledgement: "/canvas/ack";
|
|
43
|
+
readonly disconnect: "/disconnect";
|
|
44
|
+
};
|
|
45
|
+
export type BridgeEndpoint = (typeof BRIDGE_ENDPOINTS)[keyof typeof BRIDGE_ENDPOINTS];
|
|
41
46
|
/** A project identity is the pairing boundary for one companion process. */
|
|
42
47
|
export interface AgentProjectIdentity {
|
|
43
48
|
readonly projectId: string;
|
|
@@ -54,14 +59,12 @@ export interface AgentRoute {
|
|
|
54
59
|
}
|
|
55
60
|
/** The owner marker lets the browser remove only groups created by the agent. */
|
|
56
61
|
export type CanvasGroupOwner = "agent" | "user";
|
|
57
|
-
/** A browser-reported comparison group. */
|
|
58
62
|
export interface CanvasGroup {
|
|
59
63
|
readonly id: string;
|
|
60
64
|
readonly label: string;
|
|
61
65
|
readonly owner: CanvasGroupOwner;
|
|
62
66
|
readonly routes: readonly AgentRoute[];
|
|
63
67
|
}
|
|
64
|
-
/** A compact, serialisable Canvas snapshot returned by the browser. */
|
|
65
68
|
export interface CanvasState {
|
|
66
69
|
readonly mode: "inspect" | "canvas";
|
|
67
70
|
readonly groups: readonly CanvasGroup[];
|
|
@@ -287,32 +290,16 @@ export interface ProjectBridgeHandle {
|
|
|
287
290
|
};
|
|
288
291
|
close(): Promise<void>;
|
|
289
292
|
}
|
|
290
|
-
/**
|
|
291
|
-
* The `startProjectBridge` export of `@nudge-ui/mcp/project`. Hosts load it
|
|
292
|
-
* dynamically from the application's own dependencies, so this type is the
|
|
293
|
-
* only compile-time link between the two packages.
|
|
294
|
-
*/
|
|
293
|
+
/** The `@nudge-ui/mcp/project` export; hosts load it dynamically, so this is the only compile-time link between the packages. */
|
|
295
294
|
export type StartProjectBridge = (options: ProjectBridgeStartOptions) => Promise<ProjectBridgeHandle>;
|
|
296
|
-
/**
|
|
297
|
-
* Returns a canonical origin or null. Credentials, paths, and fragments are
|
|
298
|
-
* rejected so the browser cannot broaden the pairing boundary accidentally.
|
|
299
|
-
*/
|
|
295
|
+
/** Canonical origin or null; credentials, paths, and fragments are rejected so pairing cannot broaden accidentally. */
|
|
300
296
|
export declare function canonicalOrigin(value: unknown): string | null;
|
|
301
|
-
/** Returns true only for exact, canonical HTTP(S) origins. */
|
|
302
297
|
export declare function isCanonicalOrigin(value: unknown): value is string;
|
|
303
|
-
/**
|
|
304
|
-
* Checks an origin against an explicit allow-list. An empty allow-list does
|
|
305
|
-
* not mean wildcard access; callers must choose an origin before pairing.
|
|
306
|
-
*/
|
|
298
|
+
/** An empty allow-list is not wildcard access; callers must choose an origin before pairing. */
|
|
307
299
|
export declare function isAllowedOrigin(value: unknown, allowedOrigins: readonly string[]): value is string;
|
|
308
|
-
/**
|
|
309
|
-
* Validates an agent route and ensures it belongs to the paired origin.
|
|
310
|
-
* Fragments are retained because they are useful initial Canvas locations.
|
|
311
|
-
*/
|
|
300
|
+
/** Fragments are retained because they are useful initial Canvas locations. */
|
|
312
301
|
export declare function isSameOriginRoute(value: unknown, origin: string): value is AgentRoute;
|
|
313
|
-
/** Validates and normalises route values for a presentation command. */
|
|
314
302
|
export declare function validateRoutes(value: unknown, origin: string): AgentRoute[];
|
|
315
|
-
/** Validates the fields shared by every companion message. */
|
|
316
303
|
export declare function isProtocolVersion(value: unknown): value is AgentProtocolVersion;
|
|
317
304
|
export declare function isProjectIdentity(value: unknown): value is AgentProjectIdentity;
|
|
318
305
|
export declare function isAgentSketchCaptureMetadata(value: unknown): value is AgentSketchCaptureMetadata;
|
|
@@ -326,21 +313,12 @@ export declare function isCanvasState(value: unknown): value is CanvasState;
|
|
|
326
313
|
export declare function isCanvasGroup(value: unknown): value is CanvasGroup;
|
|
327
314
|
export declare function isCanvasCommand(value: unknown): value is CanvasCommand;
|
|
328
315
|
export declare function isAgentDeliveredPrompt(value: unknown): value is AgentDeliveredPrompt;
|
|
329
|
-
/** A
|
|
316
|
+
/** A successful result never carries an error. */
|
|
330
317
|
export declare function isCanvasCommandResult(value: unknown): value is CanvasCommandResult;
|
|
331
|
-
/** The connection state shown for a pairing, a listener, and the current request. */
|
|
332
318
|
export declare function deriveConnectionState(status: Pick<AgentStatusSnapshot, "listenerActive" | "paired" | "request">): AgentConnectionState;
|
|
333
|
-
/**
|
|
334
|
-
* Decodes a status snapshot from any peer. Unknown fields are dropped and the
|
|
335
|
-
* connection state is recomputed, so a snapshot can never claim a state its
|
|
336
|
-
* own fields contradict.
|
|
337
|
-
*/
|
|
319
|
+
/** Unknown fields are dropped and the connection state is recomputed, so a snapshot cannot claim a state its own fields contradict. */
|
|
338
320
|
export declare function decodeAgentStatusSnapshot(value: unknown): AgentStatusSnapshot | null;
|
|
339
|
-
/**
|
|
340
|
-
* Decodes the event inside a bridge envelope. A Canvas command is passed
|
|
341
|
-
* through as received: the Canvas controller validates it so a malformed
|
|
342
|
-
* command still receives an error acknowledgement instead of a timeout.
|
|
343
|
-
*/
|
|
321
|
+
/** A Canvas command is passed through as received: the Canvas controller validates it so a malformed command still receives an error acknowledgement. */
|
|
344
322
|
export declare function decodeBridgeEvent(value: unknown): BridgeEvent | null;
|
|
345
323
|
/** Decodes a pairing or takeover response for the exact project and origin requested. */
|
|
346
324
|
export declare function decodePairingResponse(value: unknown, expected: Pick<PairingRequest, "projectId" | "origin">): PairingResponse | null;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public, host-neutral contracts shared by the Nudge browser inspector and
|
|
3
|
-
* its local MCP companion.
|
|
4
|
-
*
|
|
5
|
-
* This module deliberately has no Node, browser, MCP SDK, or framework
|
|
6
|
-
* imports. Hosts can use the same validation and message shapes without
|
|
7
|
-
* pulling the companion into a browser bundle.
|
|
8
|
-
*/
|
|
1
|
+
/** Host-neutral contracts shared by the browser inspector and the local MCP companion; no Node, browser, MCP SDK, or framework imports. */
|
|
9
2
|
export const AGENT_PROTOCOL_VERSION = 3;
|
|
10
3
|
export const AGENT_PROTOCOL_LIMITS = {
|
|
11
4
|
projectId: 256,
|
|
@@ -37,6 +30,17 @@ export const AGENT_PROTOCOL_LIMITS = {
|
|
|
37
30
|
canvasCardCount: 128,
|
|
38
31
|
agentClientName: 120,
|
|
39
32
|
};
|
|
33
|
+
/** Stable loopback endpoint names shared by the browser client and the MCP bridge. */
|
|
34
|
+
export const BRIDGE_ENDPOINTS = {
|
|
35
|
+
health: "/health",
|
|
36
|
+
pair: "/pair",
|
|
37
|
+
takeover: "/takeover",
|
|
38
|
+
events: "/events",
|
|
39
|
+
prompt: "/prompt",
|
|
40
|
+
status: "/status",
|
|
41
|
+
canvasAcknowledgement: "/canvas/ack",
|
|
42
|
+
disconnect: "/disconnect",
|
|
43
|
+
};
|
|
40
44
|
export const CANVAS_COMMAND_TYPES = [
|
|
41
45
|
"read-state",
|
|
42
46
|
"present-routes",
|
|
@@ -93,10 +97,7 @@ function encodedByteLength(value) {
|
|
|
93
97
|
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
94
98
|
return Math.max(0, Math.floor(value.length * 3 / 4) - padding);
|
|
95
99
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Returns a canonical origin or null. Credentials, paths, and fragments are
|
|
98
|
-
* rejected so the browser cannot broaden the pairing boundary accidentally.
|
|
99
|
-
*/
|
|
100
|
+
/** Canonical origin or null; credentials, paths, and fragments are rejected so pairing cannot broaden accidentally. */
|
|
100
101
|
export function canonicalOrigin(value) {
|
|
101
102
|
if (!nonEmptyString(value, AGENT_PROTOCOL_LIMITS.origin))
|
|
102
103
|
return null;
|
|
@@ -113,24 +114,17 @@ export function canonicalOrigin(value) {
|
|
|
113
114
|
return null;
|
|
114
115
|
return parsed.origin;
|
|
115
116
|
}
|
|
116
|
-
/** Returns true only for exact, canonical HTTP(S) origins. */
|
|
117
117
|
export function isCanonicalOrigin(value) {
|
|
118
118
|
return canonicalOrigin(value) === value;
|
|
119
119
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Checks an origin against an explicit allow-list. An empty allow-list does
|
|
122
|
-
* not mean wildcard access; callers must choose an origin before pairing.
|
|
123
|
-
*/
|
|
120
|
+
/** An empty allow-list is not wildcard access; callers must choose an origin before pairing. */
|
|
124
121
|
export function isAllowedOrigin(value, allowedOrigins) {
|
|
125
122
|
const origin = canonicalOrigin(value);
|
|
126
123
|
if (!origin || allowedOrigins.length === 0)
|
|
127
124
|
return false;
|
|
128
125
|
return allowedOrigins.some((candidate) => canonicalOrigin(candidate) === origin);
|
|
129
126
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Validates an agent route and ensures it belongs to the paired origin.
|
|
132
|
-
* Fragments are retained because they are useful initial Canvas locations.
|
|
133
|
-
*/
|
|
127
|
+
/** Fragments are retained because they are useful initial Canvas locations. */
|
|
134
128
|
export function isSameOriginRoute(value, origin) {
|
|
135
129
|
if (!isRecord(value) || !hasOnlyKeys(value, ["url", "title", "label"]))
|
|
136
130
|
return false;
|
|
@@ -154,7 +148,6 @@ export function isSameOriginRoute(value, origin) {
|
|
|
154
148
|
return false;
|
|
155
149
|
return true;
|
|
156
150
|
}
|
|
157
|
-
/** Validates and normalises route values for a presentation command. */
|
|
158
151
|
export function validateRoutes(value, origin) {
|
|
159
152
|
if (!Array.isArray(value) || value.length === 0 || value.length > AGENT_PROTOCOL_LIMITS.routeCount) {
|
|
160
153
|
throw new TypeError(`routes must contain between 1 and ${AGENT_PROTOCOL_LIMITS.routeCount} items`);
|
|
@@ -175,7 +168,6 @@ export function validateRoutes(value, origin) {
|
|
|
175
168
|
};
|
|
176
169
|
});
|
|
177
170
|
}
|
|
178
|
-
/** Validates the fields shared by every companion message. */
|
|
179
171
|
export function isProtocolVersion(value) {
|
|
180
172
|
return value === AGENT_PROTOCOL_VERSION;
|
|
181
173
|
}
|
|
@@ -418,7 +410,7 @@ function isCanvasCommandError(value) {
|
|
|
418
410
|
&& nonEmptyString(value.code, 128)
|
|
419
411
|
&& typeof value.message === "string" && value.message.length <= AGENT_PROTOCOL_LIMITS.prompt;
|
|
420
412
|
}
|
|
421
|
-
/** A
|
|
413
|
+
/** A successful result never carries an error. */
|
|
422
414
|
export function isCanvasCommandResult(value) {
|
|
423
415
|
if (!isRecord(value) || !hasOnlyKeys(value, ["commandId", "ok", "state", "group", "error"]))
|
|
424
416
|
return false;
|
|
@@ -428,7 +420,6 @@ export function isCanvasCommandResult(value) {
|
|
|
428
420
|
&& (value.group === undefined || isCanvasGroup(value.group))
|
|
429
421
|
&& (value.error === undefined || (!value.ok && isCanvasCommandError(value.error)));
|
|
430
422
|
}
|
|
431
|
-
/** The connection state shown for a pairing, a listener, and the current request. */
|
|
432
423
|
export function deriveConnectionState(status) {
|
|
433
424
|
if (status.request?.status === "delivered")
|
|
434
425
|
return "delivered";
|
|
@@ -460,11 +451,7 @@ function decodeStatusRequest(value) {
|
|
|
460
451
|
...(error === undefined ? {} : { error }),
|
|
461
452
|
};
|
|
462
453
|
}
|
|
463
|
-
/**
|
|
464
|
-
* Decodes a status snapshot from any peer. Unknown fields are dropped and the
|
|
465
|
-
* connection state is recomputed, so a snapshot can never claim a state its
|
|
466
|
-
* own fields contradict.
|
|
467
|
-
*/
|
|
454
|
+
/** Unknown fields are dropped and the connection state is recomputed, so a snapshot cannot claim a state its own fields contradict. */
|
|
468
455
|
export function decodeAgentStatusSnapshot(value) {
|
|
469
456
|
if (!isRecord(value)
|
|
470
457
|
|| value.protocolVersion !== AGENT_PROTOCOL_VERSION
|
|
@@ -491,11 +478,7 @@ export function decodeAgentStatusSnapshot(value) {
|
|
|
491
478
|
: {}),
|
|
492
479
|
};
|
|
493
480
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Decodes the event inside a bridge envelope. A Canvas command is passed
|
|
496
|
-
* through as received: the Canvas controller validates it so a malformed
|
|
497
|
-
* command still receives an error acknowledgement instead of a timeout.
|
|
498
|
-
*/
|
|
481
|
+
/** A Canvas command is passed through as received: the Canvas controller validates it so a malformed command still receives an error acknowledgement. */
|
|
499
482
|
export function decodeBridgeEvent(value) {
|
|
500
483
|
if (!isRecord(value))
|
|
501
484
|
return null;
|
package/dist/project.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ export interface ProjectBridgeOptions {
|
|
|
19
19
|
readonly workspaceRoot?: string;
|
|
20
20
|
readonly projectId?: string;
|
|
21
21
|
readonly origin: string;
|
|
22
|
-
/** Other explicit browser origins, such as loopback aliases on the same port. */
|
|
23
22
|
readonly allowedOrigins?: readonly string[];
|
|
24
23
|
readonly host?: "127.0.0.1" | "::1" | "localhost";
|
|
25
24
|
readonly port?: number;
|
|
@@ -45,10 +44,6 @@ export interface StoredProjectSession extends ProjectSession {
|
|
|
45
44
|
export declare function defaultSessionRegistryRoot(): string;
|
|
46
45
|
/** Resolves a checkout or worktree without conflating related worktrees. */
|
|
47
46
|
export declare function resolveWorkspaceRoot(input: string): Promise<string>;
|
|
48
|
-
/**
|
|
49
|
-
* Starts the project-owned browser bridge and registers it for local MCP
|
|
50
|
-
* adapters. The descriptor contains a private control credential and is never
|
|
51
|
-
* suitable for browser bundles or user-facing tool results.
|
|
52
|
-
*/
|
|
47
|
+
/** The descriptor carries a private control credential; never expose it to browser bundles or tool results. */
|
|
53
48
|
export declare const startProjectBridge: (options: ProjectBridgeOptions) => Promise<ProjectBridgeRuntime>;
|
|
54
49
|
export {};
|
package/dist/project.js
CHANGED
|
@@ -9,9 +9,8 @@ import { createLoopbackBridge } from "./bridge.js";
|
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
const REGISTRY_SCHEMA_VERSION = 1;
|
|
11
11
|
export function defaultSessionRegistryRoot() {
|
|
12
|
-
// MCP hosts
|
|
13
|
-
//
|
|
14
|
-
// and agent adapter, so both processes discover the same private registry.
|
|
12
|
+
// MCP hosts sanitize TMPDIR and XDG_RUNTIME_DIR for stdio child processes;
|
|
13
|
+
// the OS account home is stable across the development server and adapter.
|
|
15
14
|
return join(userInfo().homedir, ".cache", "nudge-ui", "sessions");
|
|
16
15
|
}
|
|
17
16
|
async function gitValue(cwd, args) {
|
|
@@ -92,11 +91,7 @@ async function closeProjectBridge(descriptorPath, bridge) {
|
|
|
92
91
|
await bridge.close();
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Starts the project-owned browser bridge and registers it for local MCP
|
|
97
|
-
* adapters. The descriptor contains a private control credential and is never
|
|
98
|
-
* suitable for browser bundles or user-facing tool results.
|
|
99
|
-
*/
|
|
94
|
+
/** The descriptor carries a private control credential; never expose it to browser bundles or tool results. */
|
|
100
95
|
export const startProjectBridge = (async (options) => {
|
|
101
96
|
const origin = canonicalOrigin(options.origin);
|
|
102
97
|
if (!origin)
|
|
@@ -7,14 +7,11 @@ export interface SessionHealth {
|
|
|
7
7
|
readonly claimed: boolean;
|
|
8
8
|
readonly status: AgentStatusSnapshot;
|
|
9
9
|
}
|
|
10
|
-
/** Represents an HTTP response from a project bridge that could be classified. */
|
|
11
10
|
export declare class ProjectSessionResponseError extends Error {
|
|
12
11
|
readonly status: number;
|
|
13
12
|
constructor(status: number, message: string);
|
|
14
13
|
}
|
|
15
|
-
/** Reads and validates a bridge's health; an invalid body is a classified response error. */
|
|
16
14
|
export declare function readSessionHealth(session: StoredProjectSession, timeoutMs?: number): Promise<SessionHealth>;
|
|
17
|
-
/** Owns one project's claim, heartbeat, and authenticated control requests. */
|
|
18
15
|
export declare class ProjectSessionClient {
|
|
19
16
|
readonly descriptor: StoredProjectSession;
|
|
20
17
|
readonly agentId: string;
|
|
@@ -3,7 +3,6 @@ import { decodeAgentStatusSnapshot, isAgentDeliveredPrompt, isCanvasCommandResul
|
|
|
3
3
|
import { AGENT_CONTROL_ENDPOINTS } from "./protocol.js";
|
|
4
4
|
const CONTROL_TIMEOUT_MS = 5_000;
|
|
5
5
|
const CANVAS_TIMEOUT_MS = 65_000;
|
|
6
|
-
/** Represents an HTTP response from a project bridge that could be classified. */
|
|
7
6
|
export class ProjectSessionResponseError extends Error {
|
|
8
7
|
status;
|
|
9
8
|
constructor(status, message) {
|
|
@@ -48,11 +47,9 @@ async function requestJson(session, path, decode, init = {}, { signal, timeoutMs
|
|
|
48
47
|
}
|
|
49
48
|
return decoded;
|
|
50
49
|
}
|
|
51
|
-
/** Reads and validates a bridge's health; an invalid body is a classified response error. */
|
|
52
50
|
export async function readSessionHealth(session, timeoutMs = CONTROL_TIMEOUT_MS) {
|
|
53
51
|
return await requestJson(session, AGENT_CONTROL_ENDPOINTS.health, decodeSessionHealth, {}, { timeoutMs });
|
|
54
52
|
}
|
|
55
|
-
/** Owns one project's claim, heartbeat, and authenticated control requests. */
|
|
56
53
|
export class ProjectSessionClient {
|
|
57
54
|
descriptor;
|
|
58
55
|
agentId;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Companion-facing aliases and transport envelopes.
|
|
3
|
-
*
|
|
4
|
-
* The domain contracts live in the private shared protocol module. This public
|
|
5
|
-
* subpath keeps transport names discoverable for Node consumers while
|
|
6
|
-
* preserving one protocol vocabulary for browser and agent adapters.
|
|
7
|
-
*/
|
|
8
1
|
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "./internal/agent-protocol.js";
|
|
9
2
|
export type { AgentActivity, BridgeActivityEvent, AgentConnectionState, AgentProjectIdentity, AgentPromptRequest, AgentDeliveredPrompt, AgentSketchAttachment, AgentSketchCaptureMetadata, AgentSketchMetadata, AgentRequestOutcome, AgentRequestStatus, AgentRoute, AgentStatusSnapshot, AgentStatusUpdate, BridgeCanvasCommandEvent, BridgeConnectedEvent, BridgeDisconnectedEvent, BridgeEnvelope, BridgeEvent, BridgeEventType, BridgeStatusEvent, CanvasCommand, CanvasCommandAcknowledgement, CanvasCommandError, CanvasCommandResult, CanvasGroup, CanvasGroupOwner, CanvasState, FitCanvasCommand, FocusCanvasGroupCommand, PairingRequest, PairingResponse, PresentRoutesCommand, PromptDispatchRequest, PromptDispatchResponse, ReadCanvasStateCommand, RemoveCanvasGroupCommand, StatusEventRequest, } from "./internal/agent-protocol.js";
|
|
10
3
|
/** Stable endpoint names used by the loopback browser bridge. */
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
readonly pair: "/pair";
|
|
14
|
-
readonly takeover: "/takeover";
|
|
15
|
-
readonly events: "/events";
|
|
16
|
-
readonly prompt: "/prompt";
|
|
17
|
-
readonly status: "/status";
|
|
18
|
-
readonly canvasAcknowledgement: "/canvas/ack";
|
|
19
|
-
readonly disconnect: "/disconnect";
|
|
20
|
-
};
|
|
4
|
+
export { BRIDGE_ENDPOINTS } from "./internal/agent-protocol.js";
|
|
5
|
+
export type { BridgeEndpoint } from "./internal/agent-protocol.js";
|
|
21
6
|
/** Private, loopback-only endpoints used by an MCP adapter process. */
|
|
22
7
|
export declare const AGENT_CONTROL_ENDPOINTS: {
|
|
23
8
|
readonly health: "/__nudge/agent/health";
|
|
@@ -31,8 +16,6 @@ export declare const AGENT_CONTROL_ENDPOINTS: {
|
|
|
31
16
|
readonly activity: "/__nudge/agent/activity";
|
|
32
17
|
readonly canvas: "/__nudge/agent/canvas";
|
|
33
18
|
};
|
|
34
|
-
export type BridgeEndpoint = (typeof BRIDGE_ENDPOINTS)[keyof typeof BRIDGE_ENDPOINTS];
|
|
35
|
-
/** Error body returned by every rejected bridge request. */
|
|
36
19
|
export interface BridgeHttpError {
|
|
37
20
|
readonly error: {
|
|
38
21
|
readonly code: string;
|
package/dist/protocol.js
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Companion-facing aliases and transport envelopes.
|
|
3
|
-
*
|
|
4
|
-
* The domain contracts live in the private shared protocol module. This public
|
|
5
|
-
* subpath keeps transport names discoverable for Node consumers while
|
|
6
|
-
* preserving one protocol vocabulary for browser and agent adapters.
|
|
7
|
-
*/
|
|
8
1
|
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "./internal/agent-protocol.js";
|
|
9
2
|
/** Stable endpoint names used by the loopback browser bridge. */
|
|
10
|
-
export
|
|
11
|
-
health: "/health",
|
|
12
|
-
pair: "/pair",
|
|
13
|
-
takeover: "/takeover",
|
|
14
|
-
events: "/events",
|
|
15
|
-
prompt: "/prompt",
|
|
16
|
-
status: "/status",
|
|
17
|
-
canvasAcknowledgement: "/canvas/ack",
|
|
18
|
-
disconnect: "/disconnect",
|
|
19
|
-
};
|
|
3
|
+
export { BRIDGE_ENDPOINTS } from "./internal/agent-protocol.js";
|
|
20
4
|
/** Private, loopback-only endpoints used by an MCP adapter process. */
|
|
21
5
|
export const AGENT_CONTROL_ENDPOINTS = {
|
|
22
6
|
health: "/__nudge/agent/health",
|
package/dist/server.d.ts
CHANGED
|
@@ -11,7 +11,6 @@ export interface DiscoveredAgentAdapter {
|
|
|
11
11
|
start(transport?: Transport): Promise<void>;
|
|
12
12
|
close(): Promise<void>;
|
|
13
13
|
}
|
|
14
|
-
/** Creates a stdio MCP adapter that discovers project-owned bridges. */
|
|
15
14
|
export declare function createDiscoveredAgentAdapter(options?: {
|
|
16
15
|
readonly registryRoot?: string;
|
|
17
16
|
readonly serverName?: string;
|
package/dist/server.js
CHANGED
|
@@ -278,7 +278,6 @@ function installTools(mcpServer, routing) {
|
|
|
278
278
|
}
|
|
279
279
|
});
|
|
280
280
|
}
|
|
281
|
-
/** Creates a stdio MCP adapter that discovers project-owned bridges. */
|
|
282
281
|
export function createDiscoveredAgentAdapter(options = {}) {
|
|
283
282
|
const router = new AdapterRouting(options.registryRoot);
|
|
284
283
|
const mcpServer = new McpServer({
|
|
@@ -11,7 +11,5 @@ export interface SessionInspection {
|
|
|
11
11
|
readonly unreachable: readonly Pick<StoredProjectSession, "workspaceRoot" | "appRoot" | "sessionId">[];
|
|
12
12
|
readonly live: LiveProjectSession[];
|
|
13
13
|
}
|
|
14
|
-
/** Separates absent, invalid, incompatible, and unreachable registrations. */
|
|
15
14
|
export declare function inspectProjectSessions(registryRoot: string): Promise<SessionInspection>;
|
|
16
|
-
/** Keeps only compatible bridges that answer authenticated probes. */
|
|
17
15
|
export declare function readLiveProjectSessions(registryRoot: string): Promise<LiveProjectSession[]>;
|
package/dist/sessionRegistry.js
CHANGED
|
@@ -4,7 +4,6 @@ import { AGENT_PROTOCOL_VERSION } from "./internal/agent-protocol.js";
|
|
|
4
4
|
import { ProjectSessionResponseError, readSessionHealth } from "./projectSessionClient.js";
|
|
5
5
|
const PROBE_TIMEOUT_MS = 750;
|
|
6
6
|
const MAX_DESCRIPTOR_BYTES = 32 * 1024;
|
|
7
|
-
/** Separates absent, invalid, incompatible, and unreachable registrations. */
|
|
8
7
|
export async function inspectProjectSessions(registryRoot) {
|
|
9
8
|
const names = (await readRegistryDirectory(registryRoot)).filter((name) => name.endsWith(".json"));
|
|
10
9
|
const records = await Promise.all(names.map((name) => readDescriptor(resolve(registryRoot, name))));
|
|
@@ -20,7 +19,6 @@ export async function inspectProjectSessions(registryRoot) {
|
|
|
20
19
|
live: probes.flatMap((probe) => probe.kind === "live" ? [probe.session] : []),
|
|
21
20
|
};
|
|
22
21
|
}
|
|
23
|
-
/** Keeps only compatible bridges that answer authenticated probes. */
|
|
24
22
|
export async function readLiveProjectSessions(registryRoot) {
|
|
25
23
|
return (await inspectProjectSessions(registryRoot)).live;
|
|
26
24
|
}
|