@nudge-ui/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/LICENSE +21 -0
- package/dist/bridge.d.ts +79 -0
- package/dist/bridge.js +731 -0
- package/dist/cli-entry.d.ts +1 -0
- package/dist/cli-entry.js +5 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.js +173 -0
- package/dist/cli.mjs +22663 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/protocol.d.ts +27 -0
- package/dist/protocol.js +18 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +243 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
export interface CliOptions {
|
|
3
|
+
readonly projectId: string;
|
|
4
|
+
readonly origin?: string;
|
|
5
|
+
readonly workspaceRoot: string;
|
|
6
|
+
readonly host: "127.0.0.1" | "::1" | "localhost";
|
|
7
|
+
readonly port: number;
|
|
8
|
+
readonly commandTimeoutMs: number;
|
|
9
|
+
readonly help: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CliEnvironment {
|
|
12
|
+
readonly CI?: string;
|
|
13
|
+
readonly INIT_CWD?: string;
|
|
14
|
+
readonly NUDGE_UI_PROJECT_ID?: string;
|
|
15
|
+
readonly NUDGE_UI_ORIGIN?: string;
|
|
16
|
+
readonly NUDGE_UI_WORKSPACE_ROOT?: string;
|
|
17
|
+
readonly NUDGE_UI_BRIDGE_HOST?: string;
|
|
18
|
+
readonly NUDGE_UI_BRIDGE_PORT?: string;
|
|
19
|
+
readonly NUDGE_UI_COMMAND_TIMEOUT_MS?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PageLaunchDependencies {
|
|
22
|
+
readonly fetch: typeof fetch;
|
|
23
|
+
readonly platform: NodeJS.Platform;
|
|
24
|
+
readonly spawn: (command: string, args: readonly string[], options: {
|
|
25
|
+
detached: true;
|
|
26
|
+
stdio: "ignore";
|
|
27
|
+
}) => Pick<ChildProcess, "unref">;
|
|
28
|
+
readonly warn: (message: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/** Opens a reachable paired project page using the platform URL handler. */
|
|
31
|
+
export declare function openPairedPage(pageUrl: string, dependencies?: PageLaunchDependencies): Promise<boolean>;
|
|
32
|
+
/** Parses only project-scoped, host-neutral CLI options. */
|
|
33
|
+
export declare function parseCliArguments(argv?: readonly string[], environment?: CliEnvironment): CliOptions;
|
|
34
|
+
export declare const CLI_USAGE = "Usage: nudge-mcp [options]\n\nStarts a project-scoped standard MCP stdio server and loopback browser bridge.\n\nOptions:\n --project-id <id> Stable project pairing identity\n --origin <origin> Exact app origin required for browser pairing\n --workspace-root <path> Project root supplied to the agent\n --host <loopback> 127.0.0.1, ::1, or localhost\n --port <port> Bridge port; default is deterministic per project, 0 is ephemeral\n --command-timeout-ms <ms> Canvas acknowledgement timeout\n --help Show this message\n";
|
|
35
|
+
/** Runs the CLI until the stdio MCP connection closes or the process stops. */
|
|
36
|
+
export declare function runCli(argv?: readonly string[], environment?: CliEnvironment): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
import { defaultBridgePort } from "@nudge-ui/agent-protocol";
|
|
5
|
+
import { createAgentCompanion } from "./server.js";
|
|
6
|
+
const DEFAULT_PORT = 0;
|
|
7
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 5_000;
|
|
8
|
+
const PAGE_PROBE_TIMEOUT_MS = 1_500;
|
|
9
|
+
const PAGE_LAUNCH_DEPENDENCIES = {
|
|
10
|
+
fetch: globalThis.fetch,
|
|
11
|
+
platform: process.platform,
|
|
12
|
+
spawn: (command, args, options) => spawn(command, args, options),
|
|
13
|
+
warn: (message) => process.stderr.write(`${message}\n`),
|
|
14
|
+
};
|
|
15
|
+
/** Opens a reachable paired project page using the platform URL handler. */
|
|
16
|
+
export async function openPairedPage(pageUrl, dependencies = PAGE_LAUNCH_DEPENDENCIES) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await dependencies.fetch(pageUrl, {
|
|
19
|
+
method: "GET",
|
|
20
|
+
signal: AbortSignal.timeout(PAGE_PROBE_TIMEOUT_MS),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error(`page returned ${response.status}`);
|
|
24
|
+
const command = dependencies.platform === "darwin"
|
|
25
|
+
? { executable: "open", args: [pageUrl] }
|
|
26
|
+
: dependencies.platform === "win32"
|
|
27
|
+
? { executable: "cmd", args: ["/c", "start", "", pageUrl] }
|
|
28
|
+
: { executable: "xdg-open", args: [pageUrl] };
|
|
29
|
+
const child = dependencies.spawn(command.executable, command.args, {
|
|
30
|
+
detached: true,
|
|
31
|
+
stdio: "ignore",
|
|
32
|
+
});
|
|
33
|
+
child.unref();
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const detail = error instanceof Error ? ` (${error.message})` : "";
|
|
38
|
+
dependencies.warn(`[nudge-ui] paired page is unavailable${detail}; open ${pageUrl} manually`);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function projectIdFor(root) {
|
|
43
|
+
const name = basename(root);
|
|
44
|
+
return name.length > 0 ? name : `project-${createHash("sha256").update(root).digest("hex").slice(0, 24)}`;
|
|
45
|
+
}
|
|
46
|
+
function valueAfter(argv, index, flag) {
|
|
47
|
+
const value = argv[index + 1];
|
|
48
|
+
if (!value || value.startsWith("--"))
|
|
49
|
+
throw new TypeError(`${flag} requires a value`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function parseInteger(value, flag, maximum) {
|
|
53
|
+
if (!/^\d+$/.test(value))
|
|
54
|
+
throw new TypeError(`${flag} must be a non-negative integer`);
|
|
55
|
+
const parsed = Number(value);
|
|
56
|
+
if (!Number.isSafeInteger(parsed) || parsed > maximum)
|
|
57
|
+
throw new TypeError(`${flag} is out of range`);
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
/** Parses only project-scoped, host-neutral CLI options. */
|
|
61
|
+
export function parseCliArguments(argv = process.argv.slice(2), environment = process.env) {
|
|
62
|
+
const root = resolve(environment.NUDGE_UI_WORKSPACE_ROOT ?? environment.INIT_CWD ?? process.cwd());
|
|
63
|
+
let projectId = environment.NUDGE_UI_PROJECT_ID ?? projectIdFor(root);
|
|
64
|
+
let origin = environment.NUDGE_UI_ORIGIN;
|
|
65
|
+
let host = (environment.NUDGE_UI_BRIDGE_HOST ?? "127.0.0.1");
|
|
66
|
+
let port = environment.NUDGE_UI_BRIDGE_PORT === undefined
|
|
67
|
+
? DEFAULT_PORT
|
|
68
|
+
: parseInteger(environment.NUDGE_UI_BRIDGE_PORT, "NUDGE_UI_BRIDGE_PORT", 65_535);
|
|
69
|
+
let commandTimeoutMs = environment.NUDGE_UI_COMMAND_TIMEOUT_MS === undefined
|
|
70
|
+
? DEFAULT_COMMAND_TIMEOUT_MS
|
|
71
|
+
: parseInteger(environment.NUDGE_UI_COMMAND_TIMEOUT_MS, "NUDGE_UI_COMMAND_TIMEOUT_MS", 60_000);
|
|
72
|
+
let workspaceRoot = root;
|
|
73
|
+
let help = false;
|
|
74
|
+
let explicitPort = environment.NUDGE_UI_BRIDGE_PORT !== undefined;
|
|
75
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
76
|
+
const argument = argv[index];
|
|
77
|
+
switch (argument) {
|
|
78
|
+
case "--help":
|
|
79
|
+
case "-h":
|
|
80
|
+
help = true;
|
|
81
|
+
break;
|
|
82
|
+
case "--project-id":
|
|
83
|
+
projectId = valueAfter(argv, index, argument);
|
|
84
|
+
index += 1;
|
|
85
|
+
break;
|
|
86
|
+
case "--origin":
|
|
87
|
+
origin = valueAfter(argv, index, argument);
|
|
88
|
+
index += 1;
|
|
89
|
+
break;
|
|
90
|
+
case "--workspace-root":
|
|
91
|
+
workspaceRoot = resolve(valueAfter(argv, index, argument));
|
|
92
|
+
index += 1;
|
|
93
|
+
break;
|
|
94
|
+
case "--host": {
|
|
95
|
+
const candidate = valueAfter(argv, index, argument);
|
|
96
|
+
if (candidate !== "127.0.0.1" && candidate !== "::1" && candidate !== "localhost") {
|
|
97
|
+
throw new TypeError("--host must be 127.0.0.1, ::1, or localhost");
|
|
98
|
+
}
|
|
99
|
+
host = candidate;
|
|
100
|
+
index += 1;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case "--port":
|
|
104
|
+
port = parseInteger(valueAfter(argv, index, argument), argument, 65_535);
|
|
105
|
+
explicitPort = true;
|
|
106
|
+
index += 1;
|
|
107
|
+
break;
|
|
108
|
+
case "--command-timeout-ms":
|
|
109
|
+
commandTimeoutMs = parseInteger(valueAfter(argv, index, argument), argument, 60_000);
|
|
110
|
+
if (commandTimeoutMs === 0)
|
|
111
|
+
throw new TypeError("--command-timeout-ms must be greater than zero");
|
|
112
|
+
index += 1;
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
throw new TypeError(`Unknown option ${argument}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (projectId.length === 0 || projectId.length > 256)
|
|
119
|
+
throw new TypeError("--project-id must be a bounded non-empty string");
|
|
120
|
+
if (!explicitPort)
|
|
121
|
+
port = defaultBridgePort(projectId);
|
|
122
|
+
return { projectId, origin, workspaceRoot, host, port, commandTimeoutMs, help };
|
|
123
|
+
}
|
|
124
|
+
export const CLI_USAGE = `Usage: nudge-mcp [options]
|
|
125
|
+
|
|
126
|
+
Starts a project-scoped standard MCP stdio server and loopback browser bridge.
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
--project-id <id> Stable project pairing identity
|
|
130
|
+
--origin <origin> Exact app origin required for browser pairing
|
|
131
|
+
--workspace-root <path> Project root supplied to the agent
|
|
132
|
+
--host <loopback> 127.0.0.1, ::1, or localhost
|
|
133
|
+
--port <port> Bridge port; default is deterministic per project, 0 is ephemeral
|
|
134
|
+
--command-timeout-ms <ms> Canvas acknowledgement timeout
|
|
135
|
+
--help Show this message
|
|
136
|
+
`;
|
|
137
|
+
/** Runs the CLI until the stdio MCP connection closes or the process stops. */
|
|
138
|
+
export async function runCli(argv = process.argv.slice(2), environment = process.env) {
|
|
139
|
+
const options = parseCliArguments(argv, environment);
|
|
140
|
+
if (options.help) {
|
|
141
|
+
process.stdout.write(CLI_USAGE);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const companion = createAgentCompanion({
|
|
145
|
+
...options,
|
|
146
|
+
onControllerUnavailable: (pageUrl) => openPairedPage(pageUrl).then(() => undefined),
|
|
147
|
+
});
|
|
148
|
+
let stopping = false;
|
|
149
|
+
const stop = () => {
|
|
150
|
+
if (stopping)
|
|
151
|
+
return;
|
|
152
|
+
stopping = true;
|
|
153
|
+
void companion.close();
|
|
154
|
+
};
|
|
155
|
+
process.once("SIGINT", stop);
|
|
156
|
+
process.once("SIGTERM", stop);
|
|
157
|
+
try {
|
|
158
|
+
const started = await companion.start();
|
|
159
|
+
// stdout belongs to MCP stdio. Keep discovery information on stderr so it
|
|
160
|
+
// can be observed by a host without corrupting JSON-RPC framing.
|
|
161
|
+
process.stderr.write(`[nudge-ui] browser bridge listening at ${started.address.url}\n`);
|
|
162
|
+
await new Promise((resolveWait) => {
|
|
163
|
+
const close = () => resolveWait();
|
|
164
|
+
companion.bridge.httpServer.once("close", close);
|
|
165
|
+
process.once("exit", close);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
process.off("SIGINT", stop);
|
|
170
|
+
process.off("SIGTERM", stop);
|
|
171
|
+
await companion.close();
|
|
172
|
+
}
|
|
173
|
+
}
|