@wirecat/cli-core 0.462.0 → 0.463.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/cli/bot/mcp.js +11 -3
- package/dist/cli/guide.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +9 -4
- package/dist/cli/mcp-server-command.js +16 -15
- package/dist/cli/messenger/mcp-command.d.ts +3 -4
- package/dist/cli/messenger/mcp-command.js +42 -26
- package/dist/kit/mcp-stdio.d.ts +2 -2
- package/dist/kit/mcp-stdio.js +2 -2
- package/dist/mcp/http/activity.d.ts +1 -1
- package/dist/mcp/http/activity.js +1 -1
- package/dist/mcp/http/oauth.js +1 -1
- package/package.json +1 -1
- package/parity.json +30 -22
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.463.0 — 27.10.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **The MCP server is `mcp serve`; `mcp` alone only shows its commands**
|
|
15
|
+
([WC-CMD-3](https://github.com/WireCatLabs/cli-base/blob/main/docs/standards/commands.md#wc-cmd-3--give-a-group-without-a-leaf-only-its-help-never-a-hidden-default-action)).
|
|
16
|
+
`mcpCommand` gives the `mcp` group with `serve`, `revoke`, `config`, `add`, `check` and `server`; `--http`, `--port`,
|
|
17
|
+
`--public-url` and `--permission` move from `mcp` to `mcp serve`, and `mcp --revoke` is now its own command,
|
|
18
|
+
`mcp revoke`, with no flag left behind. An agent is set up with
|
|
19
|
+
`claude mcp add <cli> -- <cli> <profile> mcp serve`. `bot mcp` is a group too, and the bot's server is
|
|
20
|
+
`bot mcp serve`. `serverEntry`, so `mcp config`, `mcp add`, `mcp check` and `bot mcp config`, writes `mcp serve`,
|
|
21
|
+
the server of every profile starts `<cli> <profile> mcp serve` for each, and `mcp server start` runs
|
|
22
|
+
`mcp serve --http`. There is no alias: an old client entry that runs `<cli> <profile> mcp` gets the group's help and
|
|
23
|
+
exits 2 — print it again with `mcp config` or `mcp add`.
|
|
24
|
+
- **`mcpServerCommand` takes `--permission` only when asked.** `mcp server start` offered `--permission` to
|
|
25
|
+
every CLI and passed it to a server that might not take it; that server died before it listened and `start`
|
|
26
|
+
answered `provider_unavailable` with `retry.safe: true`, so an agent retried for ever. A CLI whose `mcp serve` takes
|
|
27
|
+
`--permission` passes `permission: true` (`mcpCommand` does); otherwise `start --permission` is refused as an unknown
|
|
28
|
+
option before anything starts. A `--permission` set on the CLI's root is still handed over. A server that stops
|
|
29
|
+
before it listens is now `retry.safe: false`. A CLI's own target names its server as `argv: [..., "mcp", "serve",
|
|
30
|
+
"--http"]` once its `mcp` is a group too.
|
|
31
|
+
|
|
10
32
|
## 0.462.0 — 27.10.2026
|
|
11
33
|
|
|
12
34
|
### Changed — may break callers
|
package/dist/cli/bot/mcp.js
CHANGED
|
@@ -12,8 +12,16 @@ import { botExamples } from "./examples.js";
|
|
|
12
12
|
/** `bot mcp`, mounted only for a messenger that hands in its `run` on `BotMessenger.mcp`. */
|
|
13
13
|
export const botMcpCommand = (bot) => {
|
|
14
14
|
const { app } = bot;
|
|
15
|
-
const command = annotate(new Command("mcp"), { effects:
|
|
16
|
-
.description(`serve this bot to an agent over MCP
|
|
15
|
+
const command = annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
|
|
16
|
+
.description(`serve this bot to an agent over MCP and set it up — \`claude mcp add sales-bot -- ${app.command} sales bot mcp serve\``)
|
|
17
|
+
.summary("serve this bot to an agent over MCP, and set that up");
|
|
18
|
+
command.addCommand(annotate(new Command("serve"), {
|
|
19
|
+
effects: REMOTE_WRITE,
|
|
20
|
+
lifetime: "persistent",
|
|
21
|
+
mcp: false,
|
|
22
|
+
examples: botExamples(bot, "mcp serve"),
|
|
23
|
+
})
|
|
24
|
+
.description(`serve this bot to an agent over MCP, on stdin and stdout — \`claude mcp add sales-bot -- ${app.command} sales bot mcp serve\``)
|
|
17
25
|
.summary("serve this bot to an agent over MCP, on stdin and stdout")
|
|
18
26
|
.action(async function () {
|
|
19
27
|
const context = botContext(this, bot);
|
|
@@ -35,7 +43,7 @@ export const botMcpCommand = (bot) => {
|
|
|
35
43
|
...(mcp.keyOf ? { keyOf: mcp.keyOf } : {}),
|
|
36
44
|
...(mcp.skill ? { skill: mcp.skill } : {}),
|
|
37
45
|
}, context.streams.diagnostic);
|
|
38
|
-
});
|
|
46
|
+
}));
|
|
39
47
|
command.addCommand(annotate(new Command("config"), { effects: LOCAL_READ, examples: botExamples(bot, "mcp config") })
|
|
40
48
|
.description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing")
|
|
41
49
|
.summary("print the mcpServers entry for an MCP client; writes nothing")
|
package/dist/cli/guide.js
CHANGED
|
@@ -291,7 +291,7 @@ export const checkStep = (program, run) => {
|
|
|
291
291
|
const beforeDashes = rest.slice(index, rest.includes("--") ? rest.indexOf("--") : undefined);
|
|
292
292
|
if (help && beforeDashes.some((word) => word === help.long || word === help.short))
|
|
293
293
|
return [];
|
|
294
|
-
// A group with an action of its own runs as typed
|
|
294
|
+
// A group with an action of its own runs as typed rather than asking for one of its children.
|
|
295
295
|
const runsItself = at._actionHandler != null;
|
|
296
296
|
if (at.commands.length > 0 && !runsItself)
|
|
297
297
|
return [`needs one of: ${at.commands.map((one) => one.name()).join(", ")}`];
|
|
@@ -10,7 +10,7 @@ export interface McpServerTarget {
|
|
|
10
10
|
envPrefix: string;
|
|
11
11
|
env: NodeJS.ProcessEnv;
|
|
12
12
|
tokenFile: string;
|
|
13
|
-
/** The words after the CLI's own entry that run `mcp --http` in the foreground, before `--port`. */
|
|
13
|
+
/** The words after the CLI's own entry that run `mcp serve --http` in the foreground, before `--port`. */
|
|
14
14
|
argv: string[];
|
|
15
15
|
/** Added to the server's environment: the profile, for a CLI that reads it from there. */
|
|
16
16
|
childEnv?: Record<string, string>;
|
|
@@ -19,17 +19,22 @@ export interface McpServerTarget {
|
|
|
19
19
|
export interface McpServerOptions {
|
|
20
20
|
/** How help shows the CLI before `mcp`: `tg personal`. */
|
|
21
21
|
example: string;
|
|
22
|
-
/** The CLI's own `mcp --http` default, when it is not 8765. */
|
|
22
|
+
/** The CLI's own `mcp serve --http` default, when it is not 8765. */
|
|
23
23
|
defaultPort?: number;
|
|
24
|
+
/**
|
|
25
|
+
* `start` takes `--permission` and hands it to the server: only for a CLI whose `mcp serve` takes it too,
|
|
26
|
+
* since a server that refuses the option dies before it listens.
|
|
27
|
+
*/
|
|
28
|
+
permission?: boolean;
|
|
24
29
|
target: (self: Command) => McpServerTarget;
|
|
25
30
|
/** The CLI's own answer: the item in `--json`, else the lines. */
|
|
26
31
|
answer: (self: Command, item: object, lines: string[]) => void;
|
|
27
32
|
system?: (self: Command) => ServerSystem | undefined;
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
|
-
* `mcp --http` in the background: `start`, `stop`, `status`, as `server` does for `serve`. The server
|
|
35
|
+
* `mcp serve --http` in the background: `start`, `stop`, `status`, as `server` does for `serve`. The server
|
|
31
36
|
* keeps a lock beside its token file (`state.ts`) with its port, address and login code, so status
|
|
32
37
|
* survives the shell that started it, and `stop` ends only a process that carries this CLI's marker.
|
|
33
38
|
* None of it is offered over MCP: status holds a live login code.
|
|
34
39
|
*/
|
|
35
|
-
export declare const mcpServerCommand: ({ example, defaultPort, target, answer, system, }: McpServerOptions) => Command;
|
|
40
|
+
export declare const mcpServerCommand: ({ example, defaultPort, permission, target, answer, system, }: McpServerOptions) => Command;
|
|
@@ -40,28 +40,29 @@ const clientLines = (clients) => clients.length === 0
|
|
|
40
40
|
.join(", ")}.`,
|
|
41
41
|
];
|
|
42
42
|
/**
|
|
43
|
-
* `mcp --http` in the background: `start`, `stop`, `status`, as `server` does for `serve`. The server
|
|
43
|
+
* `mcp serve --http` in the background: `start`, `stop`, `status`, as `server` does for `serve`. The server
|
|
44
44
|
* keeps a lock beside its token file (`state.ts`) with its port, address and login code, so status
|
|
45
45
|
* survives the shell that started it, and `stop` ends only a process that carries this CLI's marker.
|
|
46
46
|
* None of it is offered over MCP: status holds a live login code.
|
|
47
47
|
*/
|
|
48
|
-
export const mcpServerCommand = ({ example, defaultPort = MCP_HTTP_DEFAULT_PORT, target, answer, system, }) => {
|
|
48
|
+
export const mcpServerCommand = ({ example, defaultPort = MCP_HTTP_DEFAULT_PORT, permission = false, target, answer, system, }) => {
|
|
49
49
|
const command = annotate(new Command("server"), { effects: LOCAL_READ, mcp: false })
|
|
50
50
|
.summary("run the HTTP MCP server in the background")
|
|
51
|
-
.description("start, stop and check `mcp --http` in the background; status shows the login code for a browser app");
|
|
51
|
+
.description("start, stop and check `mcp serve --http` in the background; status shows the login code for a browser app");
|
|
52
52
|
const machine = (self) => system?.(self) ?? thisMachine();
|
|
53
|
-
destroysNothing(annotate(command.command("start"), {
|
|
53
|
+
const start = destroysNothing(annotate(command.command("start"), {
|
|
54
54
|
effects: LOCAL_WRITE,
|
|
55
55
|
mcp: false,
|
|
56
56
|
examples: [`${example} mcp server start --public-url https://laptop.example.ts.net`],
|
|
57
57
|
}))
|
|
58
58
|
.summary("start the HTTP MCP server in the background")
|
|
59
|
-
.description("start `mcp --http` apart from this shell and answer once it listens, with its login code")
|
|
59
|
+
.description("start `mcp serve --http` apart from this shell and answer once it listens, with its login code")
|
|
60
60
|
.option("--port <port>", `the local port (default ${defaultPort})`)
|
|
61
|
-
.option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg")
|
|
62
|
-
|
|
63
|
-
.
|
|
64
|
-
|
|
61
|
+
.option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg");
|
|
62
|
+
if (permission)
|
|
63
|
+
start.option("--permission <key=level>", "make a permission stricter for this server only; repeat for more keys", (value, previous = []) => [...previous, value]);
|
|
64
|
+
start.action(async function () {
|
|
65
|
+
// A CLI with a root-wide `--permission` hands it over too, so the server is never looser than asked.
|
|
65
66
|
const flags = this.optsWithGlobals();
|
|
66
67
|
const one = target(this);
|
|
67
68
|
const url = publicUrlOf(flags.publicUrl, {
|
|
@@ -102,15 +103,15 @@ export const mcpServerCommand = ({ example, defaultPort = MCP_HTTP_DEFAULT_PORT,
|
|
|
102
103
|
}
|
|
103
104
|
if (!alive(pid)) {
|
|
104
105
|
const last = readableLog(tail(log, 5)).trim();
|
|
105
|
-
throw new CliError("provider_unavailable", `mcp --http stopped before it listened${last ? `:\n${last}` : ""}
|
|
106
|
+
throw new CliError("provider_unavailable", `mcp serve --http stopped before it listened${last ? `:\n${last}` : ""}`, { retryable: false });
|
|
106
107
|
}
|
|
107
108
|
await sys.pause();
|
|
108
109
|
}
|
|
109
|
-
throw new CliError("timeout", `mcp --http is running but not listening yet — \`${one.invocation} mcp server status\`; its log: ${log}`);
|
|
110
|
+
throw new CliError("timeout", `mcp serve --http is running but not listening yet — \`${one.invocation} mcp server status\`; its log: ${log}`);
|
|
110
111
|
});
|
|
111
112
|
destroysNothing(annotate(command.command("stop"), { effects: LOCAL_WRITE, mcp: false, examples: [`${example} mcp server stop`] }))
|
|
112
113
|
.summary("stop the background HTTP MCP server")
|
|
113
|
-
.description("stop `mcp --http` — only one that `mcp server start` started")
|
|
114
|
+
.description("stop `mcp serve --http` — only one that `mcp server start` started")
|
|
114
115
|
.action(async function () {
|
|
115
116
|
const one = target(this);
|
|
116
117
|
const lock = readHttpLock(httpLockFile(one.tokenFile));
|
|
@@ -121,14 +122,14 @@ export const mcpServerCommand = ({ example, defaultPort = MCP_HTTP_DEFAULT_PORT,
|
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
if (byOf(one, lock.pid) !== "server") {
|
|
124
|
-
throw new CliError("validation_error", `mcp --http for ${one.invocation} (PID ${lock.pid}) was not started by \`${one.invocation} mcp server start\` — stop it where it runs`);
|
|
125
|
+
throw new CliError("validation_error", `mcp serve --http for ${one.invocation} (PID ${lock.pid}) was not started by \`${one.invocation} mcp server start\` — stop it where it runs`);
|
|
125
126
|
}
|
|
126
127
|
process.kill(lock.pid, "SIGTERM");
|
|
127
128
|
const sys = machine(this);
|
|
128
129
|
for (let look = 0; look < LOOKS && alive(lock.pid); look++)
|
|
129
130
|
await sys.pause();
|
|
130
131
|
if (alive(lock.pid))
|
|
131
|
-
throw new CliError("timeout", `mcp --http (PID ${lock.pid}) has not stopped yet`);
|
|
132
|
+
throw new CliError("timeout", `mcp serve --http (PID ${lock.pid}) has not stopped yet`);
|
|
132
133
|
answer(this, { profile: one.profile ?? null, stopped: true, pid: lock.pid }, [
|
|
133
134
|
`Stopped serving ${one.invocation} over HTTP (PID ${lock.pid}).`,
|
|
134
135
|
]);
|
|
@@ -139,7 +140,7 @@ export const mcpServerCommand = ({ example, defaultPort = MCP_HTTP_DEFAULT_PORT,
|
|
|
139
140
|
examples: [`${example} mcp server status --json`],
|
|
140
141
|
})
|
|
141
142
|
.summary("whether the HTTP MCP server runs, its login code and the apps logged in")
|
|
142
|
-
.description("whether `mcp --http` runs, on which port and address, the login code for a new browser app, and the apps logged in")
|
|
143
|
+
.description("whether `mcp serve --http` runs, on which port and address, the login code for a new browser app, and the apps logged in")
|
|
143
144
|
.action(function () {
|
|
144
145
|
const one = target(this);
|
|
145
146
|
const lock = readHttpLock(httpLockFile(one.tokenFile));
|
|
@@ -17,11 +17,10 @@ export interface McpFlags {
|
|
|
17
17
|
http?: boolean;
|
|
18
18
|
port?: string;
|
|
19
19
|
publicUrl?: string;
|
|
20
|
-
revoke?: boolean;
|
|
21
20
|
}
|
|
22
|
-
/** The token file of `<tool> mcp --http` for every profile: no profile name can start with `_`. */
|
|
21
|
+
/** The token file of `<tool> mcp serve --http` for every profile: no profile name can start with `_`. */
|
|
23
22
|
export declare const ALL_PROFILES = "_all";
|
|
24
|
-
/** Where `mcp --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
23
|
+
/** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
25
24
|
export declare const httpTokenFile: (app: AppIdentity, profile: string, env: NodeJS.ProcessEnv) => string;
|
|
26
25
|
export declare const mcpCommand: (messenger: Messenger) => Command;
|
|
27
26
|
/**
|
|
@@ -44,7 +43,7 @@ export declare const redactTail: (text: string, home: string | undefined) => str
|
|
|
44
43
|
* servers with a trimmed environment leaves it out (measured 2026-09-28). Credentials are never copied.
|
|
45
44
|
*/
|
|
46
45
|
export declare const serverEntry: (app: AppIdentity, { profile, flags, execPath, scriptPath, env, }: {
|
|
47
|
-
/** Left out, the entry is `<tool> mcp`, the server of every profile. */
|
|
46
|
+
/** Left out, the entry is `<tool> mcp serve`, the server of every profile. */
|
|
48
47
|
profile?: string;
|
|
49
48
|
flags?: McpFlags;
|
|
50
49
|
execPath: string;
|
|
@@ -19,31 +19,36 @@ import { nextStep } from "../steps.js";
|
|
|
19
19
|
import { recalledAccount } from "./accounts.js";
|
|
20
20
|
import { messengerContext } from "./context.js";
|
|
21
21
|
const DEFAULT_PORT = MCP_HTTP_DEFAULT_PORT;
|
|
22
|
-
/** The token file of `<tool> mcp --http` for every profile: no profile name can start with `_`. */
|
|
22
|
+
/** The token file of `<tool> mcp serve --http` for every profile: no profile name can start with `_`. */
|
|
23
23
|
export const ALL_PROFILES = "_all";
|
|
24
|
-
/** Where `mcp --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
24
|
+
/** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
25
25
|
export const httpTokenFile = (app, profile, env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "mcp-http", `${profile}.json`);
|
|
26
26
|
const withFlags = (command) => command.option("--permission <key=level>", "make a permission stricter for this server only, like messages.send=deny; it never loosens the profile; repeat for more keys", (value, previous = []) => [...previous, value]);
|
|
27
27
|
export const mcpCommand = (messenger) => {
|
|
28
28
|
const { app } = messenger;
|
|
29
|
-
const command =
|
|
29
|
+
const command = annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
|
|
30
|
+
.summary("serve this profile's commands to an AI agent, and set that up")
|
|
31
|
+
.description(`serve this profile to an agent over MCP and set it up — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp serve\``);
|
|
32
|
+
command.addCommand(withFlags(annotate(new Command("serve"), {
|
|
33
|
+
effects: REMOTE_WRITE,
|
|
34
|
+
lifetime: "persistent",
|
|
35
|
+
mcp: false,
|
|
36
|
+
examples: [
|
|
37
|
+
`${app.command} personal mcp serve`,
|
|
38
|
+
`${app.command} personal mcp serve --http --public-url https://laptop.example.ts.net`,
|
|
39
|
+
],
|
|
40
|
+
})
|
|
30
41
|
.summary("serve this profile's commands to an AI agent")
|
|
31
|
-
.description(`serve this profile to an agent over MCP, on stdin and stdout — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp\`; with no profile, \`${app.command} mcp\` serves every profile and each tool asks which`))
|
|
42
|
+
.description(`serve this profile to an agent over MCP, on stdin and stdout — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp serve\`; with no profile, \`${app.command} mcp serve\` serves every profile and each tool asks which`))
|
|
32
43
|
.option("--http", "serve over HTTP on 127.0.0.1 for ChatGPT and Claude in the browser, behind your tunnel; the profile's permissions decide")
|
|
33
44
|
.option("--port <port>", `the local port for --http (default ${DEFAULT_PORT})`)
|
|
34
45
|
.option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg")
|
|
35
|
-
.
|
|
36
|
-
command.action(async function () {
|
|
46
|
+
.action(async function () {
|
|
37
47
|
const flags = this.optsWithGlobals();
|
|
38
48
|
const context = messengerContext(this, messenger, { needsProfile: false });
|
|
39
|
-
|
|
49
|
+
const profile = namedProfile(context.settings);
|
|
50
|
+
if (profile === undefined)
|
|
40
51
|
return serveEveryProfile(this, context, messenger, flags);
|
|
41
|
-
if (flags.revoke) {
|
|
42
|
-
const { revokeAll } = await import("../../mcp/http/oauth.js");
|
|
43
|
-
revokeAll(httpTokenFile(app, context.settings.profile, context.env));
|
|
44
|
-
renderAnswer(this, context, itemOf({ revoked: true, profile: context.settings.profile }));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
52
|
if (flags.http) {
|
|
48
53
|
const publicUrl = publicUrlOf(flags.publicUrl, {
|
|
49
54
|
missing: "--http",
|
|
@@ -51,7 +56,7 @@ export const mcpCommand = (messenger) => {
|
|
|
51
56
|
path: true,
|
|
52
57
|
});
|
|
53
58
|
const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
|
|
54
|
-
const tokenFile = httpTokenFile(app,
|
|
59
|
+
const tokenFile = httpTokenFile(app, profile, context.env);
|
|
55
60
|
await serveOverHttpUntilStopped(this, context, messenger, {}, {
|
|
56
61
|
publicUrl,
|
|
57
62
|
port: portOf(flags.port, DEFAULT_PORT),
|
|
@@ -64,9 +69,24 @@ export const mcpCommand = (messenger) => {
|
|
|
64
69
|
// Loaded here, not at the top: every other command would otherwise pay for the SDK.
|
|
65
70
|
const { serveOverStdio } = await import("../../mcp/server.js");
|
|
66
71
|
await serveOverStdio(this, context, messenger, {});
|
|
67
|
-
});
|
|
72
|
+
}));
|
|
73
|
+
command.addCommand(annotate(new Command("revoke"), {
|
|
74
|
+
effects: LOCAL_WRITE,
|
|
75
|
+
mcp: false,
|
|
76
|
+
examples: [`${app.command} personal mcp revoke`],
|
|
77
|
+
})
|
|
78
|
+
.summary("forget every login given to a browser app")
|
|
79
|
+
.description(`forget every login \`mcp serve --http\` gave a browser app, of this profile or, with no profile, of the server of every profile; each app must log in again`)
|
|
80
|
+
.action(async function () {
|
|
81
|
+
const context = messengerContext(this, messenger, { needsProfile: false });
|
|
82
|
+
const profile = namedProfile(context.settings);
|
|
83
|
+
const { revokeAll } = await import("../../mcp/http/oauth.js");
|
|
84
|
+
revokeAll(httpTokenFile(app, profile ?? ALL_PROFILES, context.env));
|
|
85
|
+
renderAnswer(this, context, itemOf({ revoked: true, profile: profile ?? null }));
|
|
86
|
+
}));
|
|
68
87
|
command.addCommand(mcpServerCommand({
|
|
69
88
|
example: `${app.command} personal`,
|
|
89
|
+
permission: true,
|
|
70
90
|
target: (self) => {
|
|
71
91
|
const { settings, env } = messengerContext(self, messenger, { needsProfile: false });
|
|
72
92
|
if (namedProfile(settings) === undefined)
|
|
@@ -75,7 +95,7 @@ export const mcpCommand = (messenger) => {
|
|
|
75
95
|
envPrefix: app.envPrefix,
|
|
76
96
|
env,
|
|
77
97
|
tokenFile: httpTokenFile(app, ALL_PROFILES, env),
|
|
78
|
-
argv: ["mcp", "--http"],
|
|
98
|
+
argv: ["mcp", "serve", "--http"],
|
|
79
99
|
version: app.version,
|
|
80
100
|
};
|
|
81
101
|
return {
|
|
@@ -84,7 +104,7 @@ export const mcpCommand = (messenger) => {
|
|
|
84
104
|
envPrefix: app.envPrefix,
|
|
85
105
|
env,
|
|
86
106
|
tokenFile: httpTokenFile(app, settings.profile, env),
|
|
87
|
-
argv: ["mcp", "--http"],
|
|
107
|
+
argv: ["mcp", "serve", "--http"],
|
|
88
108
|
childEnv: { [`${app.envPrefix}_PROFILE`]: settings.profile },
|
|
89
109
|
version: app.version,
|
|
90
110
|
};
|
|
@@ -194,22 +214,17 @@ export const mcpCommand = (messenger) => {
|
|
|
194
214
|
return command;
|
|
195
215
|
};
|
|
196
216
|
/**
|
|
197
|
-
* `<tool> mcp` with no profile: every profile on this computer, each call in that profile's own
|
|
217
|
+
* `<tool> mcp serve` with no profile: every profile on this computer, each call in that profile's own
|
|
218
|
+
* `<tool> <profile> mcp serve`
|
|
198
219
|
* (`src/mcp/all-profiles.ts`). Nothing picks a profile: each tool takes one, and the agent is told to ask.
|
|
199
220
|
*/
|
|
200
221
|
const serveEveryProfile = async (self, context, messenger, flags) => {
|
|
201
222
|
const { app } = messenger;
|
|
202
223
|
const { env } = context;
|
|
203
224
|
const tokenFile = httpTokenFile(app, ALL_PROFILES, env);
|
|
204
|
-
if (flags.revoke) {
|
|
205
|
-
const { revokeAll } = await import("../../mcp/http/oauth.js");
|
|
206
|
-
revokeAll(tokenFile);
|
|
207
|
-
renderAnswer(self, context, itemOf({ revoked: true, profile: null }));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
225
|
const profiles = () => messenger.resolveSettings(self.optsWithGlobals(), { env, needsProfile: false, readOnly: true }).profilesHere;
|
|
211
226
|
if (profiles().length === 0)
|
|
212
|
-
throw new CliError("configuration_error", `no ${app.command} profile on this computer yet — set one up, then run \`${app.command} mcp\` again`);
|
|
227
|
+
throw new CliError("configuration_error", `no ${app.command} profile on this computer yet — set one up, then run \`${app.command} mcp serve\` again`);
|
|
213
228
|
const given = environmentOf(self).mcp ?? {};
|
|
214
229
|
const execPath = given.execPath ?? process.execPath;
|
|
215
230
|
const scriptPath = given.scriptPath ?? realpathSync(process.argv[1] ?? "");
|
|
@@ -220,7 +235,7 @@ const serveEveryProfile = async (self, context, messenger, flags) => {
|
|
|
220
235
|
version: app.version,
|
|
221
236
|
name: messenger.name ?? app.command,
|
|
222
237
|
profiles,
|
|
223
|
-
child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", ...tightened], env }),
|
|
238
|
+
child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", "serve", ...tightened], env }),
|
|
224
239
|
instructions: instructions({
|
|
225
240
|
command: app.command,
|
|
226
241
|
name: messenger.name ?? app.command,
|
|
@@ -337,6 +352,7 @@ export const serverEntry = (app, { profile, flags = {}, execPath, scriptPath, en
|
|
|
337
352
|
scriptPath,
|
|
338
353
|
...(profile === undefined ? [] : [profile]),
|
|
339
354
|
"mcp",
|
|
355
|
+
"serve",
|
|
340
356
|
...(flags.permission ?? []).flatMap((entry) => ["--permission", entry]),
|
|
341
357
|
],
|
|
342
358
|
...(Object.keys(directories).length > 0 ? { env: directories } : {}),
|
package/dist/kit/mcp-stdio.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export interface McpStdio {
|
|
|
19
19
|
close: () => Promise<number>;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* A CLI's `mcp` driven over its stdin and stdout, through its own `run()`, with no process and no SDK:
|
|
23
|
-
* `start` passes the two streams to `run(["<profile>", "mcp"], { ...yourOptions, ...streams })`. It has
|
|
22
|
+
* A CLI's `mcp serve` driven over its stdin and stdout, through its own `run()`, with no process and no SDK:
|
|
23
|
+
* `start` passes the two streams to `run(["<profile>", "mcp", "serve"], { ...yourOptions, ...streams })`. It has
|
|
24
24
|
* already answered `initialize` when this resolves.
|
|
25
25
|
*/
|
|
26
26
|
export declare const mcpOverStdio: (start: (streams: McpStdioStreams) => Promise<number>, { protocolVersion }?: {
|
package/dist/kit/mcp-stdio.js
CHANGED
|
@@ -8,8 +8,8 @@ const parsedOrText = (text) => {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
* A CLI's `mcp` driven over its stdin and stdout, through its own `run()`, with no process and no SDK:
|
|
12
|
-
* `start` passes the two streams to `run(["<profile>", "mcp"], { ...yourOptions, ...streams })`. It has
|
|
11
|
+
* A CLI's `mcp serve` driven over its stdin and stdout, through its own `run()`, with no process and no SDK:
|
|
12
|
+
* `start` passes the two streams to `run(["<profile>", "mcp", "serve"], { ...yourOptions, ...streams })`. It has
|
|
13
13
|
* already answered `initialize` when this resolves.
|
|
14
14
|
*/
|
|
15
15
|
export const mcpOverStdio = async (start, { protocolVersion = "2025-06-18" } = {}) => {
|
|
@@ -15,7 +15,7 @@ export declare const readActivity: (path: string) => Activity;
|
|
|
15
15
|
export declare const calledTools: (body: unknown) => string[];
|
|
16
16
|
/**
|
|
17
17
|
* Keeps the activity file of one running server. A call is written at once, so status shows it while the owner
|
|
18
|
-
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp
|
|
18
|
+
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp revoke` rewrites that
|
|
19
19
|
* from another process.
|
|
20
20
|
*/
|
|
21
21
|
export declare const activityRecorder: (path: string, now?: () => number) => {
|
|
@@ -30,7 +30,7 @@ export const calledTools = (body) => (Array.isArray(body) ? body : [body]).flatM
|
|
|
30
30
|
});
|
|
31
31
|
/**
|
|
32
32
|
* Keeps the activity file of one running server. A call is written at once, so status shows it while the owner
|
|
33
|
-
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp
|
|
33
|
+
* watches; other requests at most every two seconds. Its own file, not the token file: `mcp revoke` rewrites that
|
|
34
34
|
* from another process.
|
|
35
35
|
*/
|
|
36
36
|
export const activityRecorder = (path, now = Date.now) => {
|
package/dist/mcp/http/oauth.js
CHANGED
|
@@ -132,7 +132,7 @@ export const ownerLogin = ({ issuer, resource, file, appName, onCode, now = () =
|
|
|
132
132
|
while (stored.clients.length > MAX_CLIENTS) {
|
|
133
133
|
const idle = stored.clients.findIndex((one) => !holding.has(one.id));
|
|
134
134
|
if (idle === -1)
|
|
135
|
-
return oauthError("invalid_client_metadata", "too many logged-in apps — run mcp
|
|
135
|
+
return oauthError("invalid_client_metadata", "too many logged-in apps — run mcp revoke first");
|
|
136
136
|
stored.clients.splice(idle, 1);
|
|
137
137
|
}
|
|
138
138
|
store.write(stored);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.463.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
package/parity.json
CHANGED
|
@@ -789,7 +789,7 @@
|
|
|
789
789
|
},
|
|
790
790
|
"--port": {
|
|
791
791
|
"value": "<port>",
|
|
792
|
-
"meaning": "the local port behind the tunnel, for mcp --http or bot watch --public-url"
|
|
792
|
+
"meaning": "the local port behind the tunnel, for mcp serve --http or bot watch --public-url"
|
|
793
793
|
},
|
|
794
794
|
"--priority": {
|
|
795
795
|
"value": "<n>",
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
},
|
|
805
805
|
"--public-url": {
|
|
806
806
|
"value": "<url>",
|
|
807
|
-
"meaning": "the tunnel's https address: the browser apps reach mcp --http through it, a messenger delivers to bot watch"
|
|
807
|
+
"meaning": "the tunnel's https address: the browser apps reach mcp serve --http through it, a messenger delivers to bot watch"
|
|
808
808
|
},
|
|
809
809
|
"--question": {
|
|
810
810
|
"meaning": "match only questions"
|
|
@@ -861,9 +861,6 @@
|
|
|
861
861
|
"value": "<n>",
|
|
862
862
|
"meaning": "the revision you read before editing"
|
|
863
863
|
},
|
|
864
|
-
"--revoke": {
|
|
865
|
-
"meaning": "forget every login given to a browser app over --http"
|
|
866
|
-
},
|
|
867
864
|
"--revote": {
|
|
868
865
|
"meaning": "people may change their vote"
|
|
869
866
|
},
|
|
@@ -1732,7 +1729,6 @@
|
|
|
1732
1729
|
},
|
|
1733
1730
|
"bot mcp": {
|
|
1734
1731
|
"in": ["max", "tg"],
|
|
1735
|
-
"options": {},
|
|
1736
1732
|
"planned": {
|
|
1737
1733
|
"wa": "whatsapp-cli: wa bot API, plans/2026-10-22-wa-bot-api"
|
|
1738
1734
|
}
|
|
@@ -1744,6 +1740,13 @@
|
|
|
1744
1740
|
"wa": "whatsapp-cli: wa bot API, plans/2026-10-22-wa-bot-api"
|
|
1745
1741
|
}
|
|
1746
1742
|
},
|
|
1743
|
+
"bot mcp serve": {
|
|
1744
|
+
"in": ["max", "tg"],
|
|
1745
|
+
"options": {},
|
|
1746
|
+
"planned": {
|
|
1747
|
+
"wa": "whatsapp-cli: wa bot API, plans/2026-10-22-wa-bot-api"
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1747
1750
|
"bot me": {
|
|
1748
1751
|
"in": ["max", "tg"],
|
|
1749
1752
|
"planned": {
|
|
@@ -3138,22 +3141,7 @@
|
|
|
3138
3141
|
}
|
|
3139
3142
|
},
|
|
3140
3143
|
"mcp": {
|
|
3141
|
-
"in": "all"
|
|
3142
|
-
"options": {
|
|
3143
|
-
"--http": "all",
|
|
3144
|
-
"--permission": "all",
|
|
3145
|
-
"--port": "all",
|
|
3146
|
-
"--public-url": "all",
|
|
3147
|
-
"--revoke": "all",
|
|
3148
|
-
"--yes": {
|
|
3149
|
-
"in": [],
|
|
3150
|
-
"planned": {
|
|
3151
|
-
"max": "P7",
|
|
3152
|
-
"tg": "P7",
|
|
3153
|
-
"wa": "P7"
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3144
|
+
"in": "all"
|
|
3157
3145
|
},
|
|
3158
3146
|
"mcp add": {
|
|
3159
3147
|
"in": "all",
|
|
@@ -3182,6 +3170,26 @@
|
|
|
3182
3170
|
}
|
|
3183
3171
|
}
|
|
3184
3172
|
},
|
|
3173
|
+
"mcp revoke": {
|
|
3174
|
+
"in": "all"
|
|
3175
|
+
},
|
|
3176
|
+
"mcp serve": {
|
|
3177
|
+
"in": "all",
|
|
3178
|
+
"options": {
|
|
3179
|
+
"--http": "all",
|
|
3180
|
+
"--permission": "all",
|
|
3181
|
+
"--port": "all",
|
|
3182
|
+
"--public-url": "all",
|
|
3183
|
+
"--yes": {
|
|
3184
|
+
"in": [],
|
|
3185
|
+
"planned": {
|
|
3186
|
+
"max": "P7",
|
|
3187
|
+
"tg": "P7",
|
|
3188
|
+
"wa": "P7"
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
},
|
|
3185
3193
|
"mcp server": {
|
|
3186
3194
|
"in": "all"
|
|
3187
3195
|
},
|