@wirecat/cli-core 0.469.0 → 0.471.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.
@@ -14,50 +14,123 @@ import { grantsAuthority, LOCAL_READ, LOCAL_WRITE, REMOTE_WRITE } from "../effec
14
14
  import { itemOf, renderAnswer } from "../envelope.js";
15
15
  import { MCP_HTTP_DEFAULT_PORT, mcpServerCommand } from "../mcp-server-command.js";
16
16
  import { rootOf } from "../profile.js";
17
- import { namedProfile } from "../settings.js";
17
+ import { NoProfileHere, namedProfile } from "../settings.js";
18
18
  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 serve --http` for every profile: no profile name can start with `_`. */
22
+ /** The token file of `<tool> mcp serve --http --profiles all`: no profile name can start with `_`. */
23
23
  export const ALL_PROFILES = "_all";
24
+ /** `--profiles all`; a profile named `all` is served alone, as `<tool> all mcp serve`. */
25
+ export const EVERY_PROFILE = "all";
26
+ /**
27
+ * The profile or profiles an MCP server is for, always named (NEED-1881, WC-MCP-9): a leading profile, or
28
+ * `--profiles` with a list or `all`. Neither, or both, is refused with the profiles on this computer. `--profiles`
29
+ * takes the place of `<PREFIX>_PROFILE`; under `<PREFIX>_PROFILE_LOCK` it may name the locked profile only
30
+ * (WC-ACCT-5).
31
+ */
32
+ export const servedProfiles = (self, messenger, { settings, env }, given, words) => {
33
+ const { app } = messenger;
34
+ const here = settings.profilesHere;
35
+ const listed = `profiles here: ${here.length > 0 ? here.join(", ") : "none"}`;
36
+ const named = namedProfile(settings);
37
+ if (given === undefined && named !== undefined)
38
+ return { profile: named };
39
+ if (given !== undefined && settings.sources.profile === "first word")
40
+ throw new CliError("validation_error", `name one profile or --profiles, not both: \`${app.command} ${named} ${words}\` or \`${app.command} ${words} ` +
41
+ `--profiles ${given}\``, { profiles: here });
42
+ if (given === undefined) {
43
+ if (here.length === 0)
44
+ throw new NoProfileHere(app);
45
+ throw new CliError("validation_error", `name the profiles to serve: \`${app.command} <profile> ${words}\`, or \`${app.command} ${words} --profiles ` +
46
+ `<a,b>\` or \`--profiles all\` — ${listed}`, { profiles: here });
47
+ }
48
+ const names = [...new Set(given.split(",").map((one) => one.trim()))].filter(Boolean).sort();
49
+ const lock = env[envName(app, "PROFILE_LOCK")]?.trim();
50
+ if (lock && names.some((one) => one !== lock))
51
+ throw new CliError("permission_error", `this process is locked to profile ${lock} (${envName(app, "PROFILE_LOCK")}) — --profiles may name only ${lock}`);
52
+ if (lock)
53
+ return { set: { option: lock, profiles: () => [lock], key: `_${lock}` } };
54
+ if (names.length === 1 && names[0] === EVERY_PROFILE) {
55
+ const every = () => messenger.resolveSettings(self.optsWithGlobals(), { env, needsProfile: false, readOnly: true }).profilesHere;
56
+ return { set: { option: EVERY_PROFILE, profiles: every, key: ALL_PROFILES } };
57
+ }
58
+ if (names.length === 0 || names.includes(EVERY_PROFILE))
59
+ throw new CliError("validation_error", `--profiles takes profile names, comma-separated, or \`all\` alone for every profile — a profile named all ` +
60
+ `is served as \`${app.command} all ${words}\`; ${listed}`, { profiles: here });
61
+ const unknown = names.filter((one) => !here.includes(one));
62
+ if (unknown.length > 0)
63
+ throw new CliError("validation_error", `no profile named ${unknown.map((one) => `"${one}"`).join(", ")} — ${listed}`, { profiles: here });
64
+ return { set: { option: names.join(","), profiles: () => names, key: `_${names.join(",")}` } };
65
+ };
66
+ const withProfiles = (command) => command.option("--profiles <names|all>", "serve several profiles from one server instead of one: names, comma-separated, or all for every profile");
24
67
  /** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
25
68
  export const httpTokenFile = (app, profile, env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "mcp-http", `${profile}.json`);
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
- export const mcpCommand = (messenger) => {
28
- const { app } = messenger;
69
+ const withFlags = (command, example) => command.option("--permission <key=level>", `make a permission stricter for this server only, like ${example}; it never loosens the profile; repeat for more keys`, (value, previous = []) => [...previous, value]);
70
+ const messengerHost = (messenger) => ({
71
+ app: messenger.app,
72
+ name: messenger.name ?? messenger.app.command,
73
+ resolveSettings: messenger.resolveSettings,
74
+ ...(messenger.profileState ? { profileState: messenger.profileState } : {}),
75
+ context: (self, options) => messengerContext(self, messenger, options),
76
+ permissionExample: "messages.send=deny",
77
+ serveStdio: async (self, context) => {
78
+ // Loaded here, not at the top: every other command would otherwise pay for the SDK.
79
+ const { serveOverStdio } = await import("../../mcp/server.js");
80
+ await serveOverStdio(self, context, messenger, {});
81
+ },
82
+ serveHttp: async (self, context, http) => {
83
+ const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
84
+ await serveOverHttpUntilStopped(self, context, messenger, {}, http);
85
+ },
86
+ instructions: async (profiles) => {
87
+ const { instructions } = await import("../../mcp/instructions.js");
88
+ return instructions({
89
+ command: messenger.app.command,
90
+ name: messenger.name ?? messenger.app.command,
91
+ profile: profiles,
92
+ writes: [],
93
+ });
94
+ },
95
+ signedIn: (self, profile, env) => (messenger.accountOf?.(self) ?? recalledAccount(messenger.app, messenger.provider, profile, env)) !== undefined,
96
+ });
97
+ export const mcpCommand = (messenger) => mcpGroup(messengerHost(messenger));
98
+ export const mcpGroup = (host) => {
99
+ const { app } = host;
100
+ const flagged = (command) => withFlags(command, host.permissionExample);
29
101
  const command = annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
30
102
  .summary("serve this profile's commands to an AI agent, and set that up")
31
103
  .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"), {
104
+ command.addCommand(withProfiles(flagged(annotate(new Command("serve"), {
33
105
  effects: REMOTE_WRITE,
34
106
  lifetime: "persistent",
35
107
  mcp: false,
36
108
  examples: [
37
109
  `${app.command} personal mcp serve`,
110
+ `${app.command} mcp serve --profiles work,personal`,
38
111
  `${app.command} personal mcp serve --http --public-url https://laptop.example.ts.net`,
39
112
  ],
40
113
  })
41
114
  .summary("serve this profile's commands to an AI agent")
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`))
115
+ .description(`serve this profile to an agent over MCP, on stdin and stdout — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp serve\`; \`${app.command} mcp serve --profiles a,b\` or \`--profiles all\` serves those profiles from one server and each tool asks which`)))
43
116
  .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")
44
117
  .option("--port <port>", `the local port for --http (default ${DEFAULT_PORT})`)
45
118
  .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")
46
119
  .action(async function () {
47
120
  const flags = this.optsWithGlobals();
48
- const context = messengerContext(this, messenger, { needsProfile: false });
49
- const profile = namedProfile(context.settings);
50
- if (profile === undefined)
51
- return serveEveryProfile(this, context, messenger, flags);
121
+ const context = host.context(this, { needsProfile: false });
122
+ const served = servedProfiles(this, host, context, flags.profiles, "mcp serve");
123
+ if ("set" in served)
124
+ return serveProfiles(this, context, host, flags, served.set);
125
+ const { profile } = served;
52
126
  if (flags.http) {
53
127
  const publicUrl = publicUrlOf(flags.publicUrl, {
54
128
  missing: "--http",
55
129
  reaches: `the browser apps reach ${app.command}`,
56
130
  path: true,
57
131
  });
58
- const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
59
132
  const tokenFile = httpTokenFile(app, profile, context.env);
60
- await serveOverHttpUntilStopped(this, context, messenger, {}, {
133
+ await host.serveHttp(this, context, {
61
134
  publicUrl,
62
135
  port: portOf(flags.port, DEFAULT_PORT),
63
136
  tokenFile,
@@ -66,74 +139,84 @@ export const mcpCommand = (messenger) => {
66
139
  });
67
140
  return;
68
141
  }
69
- // Loaded here, not at the top: every other command would otherwise pay for the SDK.
70
- const { serveOverStdio } = await import("../../mcp/server.js");
71
- await serveOverStdio(this, context, messenger, {});
142
+ await host.serveStdio(this, context);
72
143
  }));
73
- command.addCommand(annotate(new Command("revoke"), {
144
+ command.addCommand(withProfiles(annotate(new Command("revoke"), {
74
145
  effects: LOCAL_WRITE,
75
146
  mcp: false,
76
- examples: [`${app.command} personal mcp revoke`],
147
+ examples: [`${app.command} personal mcp revoke`, `${app.command} mcp revoke --profiles all`],
77
148
  })
78
149
  .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);
150
+ .description("forget every login `mcp serve --http` gave a browser app, of this profile or of the server `--profiles` names; each app must log in again")).action(async function () {
151
+ const context = host.context(this, { needsProfile: false });
152
+ const served = servedProfiles(this, host, context, this.opts().profiles, "mcp revoke");
83
153
  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 }));
154
+ revokeAll(httpTokenFile(app, "set" in served ? served.set.key : served.profile, context.env));
155
+ renderAnswer(this, context, itemOf("set" in served
156
+ ? { revoked: true, profile: null, profiles: served.set.option }
157
+ : { revoked: true, profile: served.profile }));
86
158
  }));
87
159
  command.addCommand(mcpServerCommand({
88
160
  example: `${app.command} personal`,
89
161
  permission: true,
162
+ profiles: true,
90
163
  target: (self) => {
91
- const { settings, env } = messengerContext(self, messenger, { needsProfile: false });
92
- if (namedProfile(settings) === undefined)
164
+ const context = host.context(self, { needsProfile: false });
165
+ const { env } = context;
166
+ const served = servedProfiles(self, host, context, self.opts().profiles, `mcp server ${self.name()}`);
167
+ if ("set" in served) {
168
+ const { set } = served;
93
169
  return {
94
170
  invocation: app.command,
171
+ profiles: { option: set.option, key: set.key, label: profileSetLabel(set) },
95
172
  envPrefix: app.envPrefix,
96
173
  env,
97
- tokenFile: httpTokenFile(app, ALL_PROFILES, env),
98
- argv: ["mcp", "serve", "--http"],
174
+ tokenFile: httpTokenFile(app, set.key, env),
175
+ argv: ["mcp", "serve", "--http", "--profiles", set.option],
99
176
  version: app.version,
100
177
  };
178
+ }
179
+ const { profile } = served;
101
180
  return {
102
- invocation: `${app.command} ${settings.profile}`,
103
- profile: settings.profile,
181
+ invocation: `${app.command} ${profile}`,
182
+ profile,
104
183
  envPrefix: app.envPrefix,
105
184
  env,
106
- tokenFile: httpTokenFile(app, settings.profile, env),
185
+ tokenFile: httpTokenFile(app, profile, env),
107
186
  argv: ["mcp", "serve", "--http"],
108
- childEnv: { [`${app.envPrefix}_PROFILE`]: settings.profile },
187
+ childEnv: { [`${app.envPrefix}_PROFILE`]: profile },
109
188
  version: app.version,
110
189
  };
111
190
  },
112
- answer: (self, item, lines) => renderAnswer(self, messengerContext(self, messenger, { needsProfile: false }), itemOf(item), () => lines.join("\n")),
191
+ answer: (self, item, lines) => renderAnswer(self, host.context(self, { needsProfile: false }), itemOf(item), () => lines.join("\n")),
113
192
  system: (self) => environmentOf(self).system,
114
193
  }));
115
- command.addCommand(withFlags(annotate(new Command("config"), {
194
+ command.addCommand(withProfiles(flagged(annotate(new Command("config"), {
116
195
  effects: LOCAL_READ,
117
- examples: [`${app.command} personal mcp config`],
196
+ examples: [`${app.command} personal mcp config`, `${app.command} mcp config --profiles all`],
118
197
  })
119
- .summary("print the local MCP configuration, of this profile or of every profile")
120
- .description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing")).action(async function () {
198
+ .summary("print the local MCP configuration of this profile, or of those --profiles names")
199
+ .description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing"))).action(async function () {
121
200
  const flags = this.optsWithGlobals();
122
- const context = messengerContext(this, messenger, { needsProfile: false });
123
- const { settings, env } = context;
201
+ const context = host.context(this, { needsProfile: false });
202
+ const { env } = context;
124
203
  const given = environmentOf(this).mcp ?? {};
125
- const named = namedProfile(settings);
204
+ const served = servedProfiles(this, host, context, flags.profiles, "mcp config");
126
205
  const entry = serverEntry(app, {
127
- ...(named === undefined ? {} : { profile: named }),
206
+ ...("set" in served ? { profiles: served.set.option } : { profile: served.profile }),
128
207
  flags,
129
208
  execPath: given.execPath ?? process.execPath,
130
209
  scriptPath: given.scriptPath ?? realpathSync(process.argv[1] ?? ""),
131
210
  env,
132
211
  });
133
212
  // Pasted into a file, so a person gets the client's own JSON, indented; a script finds it under `config`.
134
- renderAnswer(this, context, itemOf({ config: entry.config }, { next: entry.nodeBound ? [nextStep("mcp.config", { args: {} })] : [] }), () => JSON.stringify(entry.config, null, 2));
213
+ renderAnswer(this, context, itemOf({ config: entry.config }, {
214
+ next: entry.nodeBound
215
+ ? [nextStep("mcp.config", { args: { profiles: "set" in served ? served.set.option : undefined } })]
216
+ : [],
217
+ }), () => JSON.stringify(entry.config, null, 2));
135
218
  }));
136
- command.addCommand(withFlags(grantsAuthority(annotate(new Command("add"), {
219
+ command.addCommand(flagged(grantsAuthority(annotate(new Command("add"), {
137
220
  effects: LOCAL_WRITE,
138
221
  examples: [`${app.command} personal mcp add codex --allow-writes`],
139
222
  }))
@@ -142,7 +225,7 @@ export const mcpCommand = (messenger) => {
142
225
  .option("--allow-writes", "acknowledge that this profile offers writing tools")).action(async function (client) {
143
226
  if (client !== "codex" && client !== "claude-code")
144
227
  throw new CliError("validation_error", "choose codex or claude-code");
145
- const context = messengerContext(this, messenger);
228
+ const context = host.context(this);
146
229
  const { settings, env } = context;
147
230
  const given = environmentOf(this).mcp ?? {};
148
231
  const configuration = serverEntry(app, {
@@ -167,11 +250,11 @@ export const mcpCommand = (messenger) => {
167
250
  throw new CliError("validation_error", error instanceof Error ? error.message : "MCP setup failed");
168
251
  }
169
252
  }));
170
- command.addCommand(withFlags(annotate(new Command("check"), {
253
+ command.addCommand(flagged(annotate(new Command("check"), {
171
254
  effects: LOCAL_READ,
172
255
  examples: [`${app.command} personal mcp check`],
173
256
  }).description("check this profile's local MCP handshake and tool list")).action(async function () {
174
- const context = messengerContext(this, messenger);
257
+ const context = host.context(this);
175
258
  const { settings, env } = context;
176
259
  const given = environmentOf(this).mcp ?? {};
177
260
  const configuration = serverEntry(app, {
@@ -190,8 +273,8 @@ export const mcpCommand = (messenger) => {
190
273
  const { tools, potentialWrites } = await probeStdio(entry, {
191
274
  start: stderr.start,
192
275
  });
193
- const account = messenger.accountOf?.(this) ?? recalledAccount(app, messenger.provider, settings.profile, env);
194
- if (!account) {
276
+ const signedIn = host.signedIn(this, settings.profile, env);
277
+ if (signedIn === false) {
195
278
  const setup = rootOf(this).commands.some((one) => one.name() === "setup")
196
279
  ? ` — log in with \`${app.command} ${settings.profile} setup\``
197
280
  : "";
@@ -199,9 +282,9 @@ export const mcpCommand = (messenger) => {
199
282
  `tools will fail${setup}`);
200
283
  }
201
284
  renderAnswer(this, context, itemOf({
202
- healthy: account !== undefined,
285
+ healthy: signedIn !== false,
203
286
  profile: settings.profile,
204
- signed_in: account !== undefined,
287
+ signed_in: signedIn ?? null,
205
288
  tools: tools.length,
206
289
  potential_writes: potentialWrites.length,
207
290
  }));
@@ -213,42 +296,44 @@ export const mcpCommand = (messenger) => {
213
296
  }));
214
297
  return command;
215
298
  };
299
+ /** How the startup and status lines name a set, so the owner sees which accounts an agent reaches. */
300
+ export const profileSetLabel = (set) => {
301
+ const names = set.profiles().join(", ");
302
+ if (set.option === EVERY_PROFILE)
303
+ return `every profile (${names || "none yet"})`;
304
+ return set.profiles().length === 1 ? `profile ${names}` : `profiles ${names}`;
305
+ };
216
306
  /**
217
- * `<tool> mcp serve` with no profile: every profile on this computer, each call in that profile's own
218
- * `<tool> <profile> mcp serve`
219
- * (`src/mcp/all-profiles.ts`). Nothing picks a profile: each tool takes one, and the agent is told to ask.
307
+ * `<tool> mcp serve --profiles <names|all>`: those profiles from one server, each call in that profile's own
308
+ * `<tool> <profile> mcp serve` (`src/mcp/all-profiles.ts`). Nothing picks a profile: each tool takes one, and the
309
+ * agent is told to ask.
220
310
  */
221
- const serveEveryProfile = async (self, context, messenger, flags) => {
222
- const { app } = messenger;
311
+ const serveProfiles = async (self, context, host, flags, set) => {
312
+ const { app } = host;
223
313
  const { env } = context;
224
- const tokenFile = httpTokenFile(app, ALL_PROFILES, env);
225
- const state = (profile) => messenger.profileState?.(profile, env) ?? "signed-in";
226
- // Read again for each session, so a profile set up while the server runs appears. A bot has its own server.
227
- const profiles = () => messenger
228
- .resolveSettings(self.optsWithGlobals(), { env, needsProfile: false, readOnly: true })
229
- .profilesHere.filter((profile) => state(profile) !== "bot");
314
+ const tokenFile = httpTokenFile(app, set.key, env);
315
+ const state = (profile) => host.profileState?.(profile, env) ?? "signed-in";
316
+ const bots = set.option === EVERY_PROFILE ? [] : set.profiles().filter((profile) => state(profile) === "bot");
317
+ if (bots.length > 0)
318
+ throw new CliError("validation_error", `${bots.join(", ")} ${bots.length === 1 ? "is a bot profile" : "are bot profiles"}, served by \`${app.command} <profile> bot mcp serve\``);
319
+ // `all` reads the profiles again for each session, so one set up while the server runs appears.
320
+ const profiles = () => set.profiles().filter((profile) => state(profile) !== "bot");
230
321
  if (profiles().length === 0)
231
- throw new CliError("configuration_error", `no ${app.command} profile on this computer yet — set one up, then run \`${app.command} mcp serve\` again`);
322
+ throw new CliError("configuration_error", `no ${app.command} profile on this computer yet — set one up, then run \`${app.command} mcp serve --profiles all\` again`);
232
323
  const given = environmentOf(self).mcp ?? {};
233
324
  const execPath = given.execPath ?? process.execPath;
234
325
  const scriptPath = given.scriptPath ?? realpathSync(process.argv[1] ?? "");
235
326
  const tightened = (flags.permission ?? []).flatMap((entry) => ["--permission", entry]);
236
- const { instructions } = await import("../../mcp/instructions.js");
237
327
  const options = {
238
328
  command: app.command,
239
329
  version: app.version,
240
- name: messenger.name ?? app.command,
330
+ name: host.name,
241
331
  profiles,
242
332
  signedIn: (profile) => state(profile) === "signed-in",
243
333
  signIn: (profile) => `profile ${profile} is not signed in — run \`${app.command} ${profile} setup\`, or sign in from WireCat Desktop, ` +
244
334
  "then try again. If the owner did not choose this profile, ask which one.",
245
335
  child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", "serve", ...tightened], env }),
246
- instructions: instructions({
247
- command: app.command,
248
- name: messenger.name ?? app.command,
249
- profile: profiles(),
250
- writes: [],
251
- }),
336
+ instructions: await host.instructions(profiles()),
252
337
  note: (line) => context.renderer.note(line),
253
338
  };
254
339
  if (flags.http) {
@@ -268,13 +353,13 @@ const serveEveryProfile = async (self, context, messenger, flags) => {
268
353
  version: app.version,
269
354
  onCode: (code, expires) => context.renderer.note(env[mcpHttpMarker(app.envPrefix)] === undefined
270
355
  ? `login code for a new browser app: ${code} (until ${expires.toTimeString().slice(0, 5)}; a new one after each login)`
271
- : `a new login code for a browser app: \`${app.command} mcp server status\``),
356
+ : `a new login code for a browser app: \`${app.command} mcp server status --profiles ${set.option}\``),
272
357
  }).catch((error) => {
273
358
  if (error.code === "EADDRINUSE")
274
359
  throw new CliError("configuration_error", `port ${port} is in use — pass another with --port`);
275
360
  throw error;
276
361
  });
277
- context.renderer.note(`serving every profile on ${listening.url.href} — point your tunnel at it; connectors use ${mcpUrlOf(publicUrl).href}`);
362
+ context.renderer.note(`serving ${profileSetLabel(set)} on ${listening.url.href} — point your tunnel at it; connectors use ${mcpUrlOf(publicUrl).href}`);
278
363
  await new Promise((resolve) => {
279
364
  process.once("SIGINT", resolve);
280
365
  process.once("SIGTERM", resolve);
@@ -283,6 +368,7 @@ const serveEveryProfile = async (self, context, messenger, flags) => {
283
368
  return;
284
369
  }
285
370
  const { serveAllProfilesOverStdio } = await import("../../mcp/all-profiles.js");
371
+ context.renderer.note(`serving ${profileSetLabel(set)} over MCP on stdin and stdout`);
286
372
  const { stdin, stdout, signal } = environmentOf(self);
287
373
  await serveAllProfilesOverStdio(options, {
288
374
  ...(stdin ? { stdin: stdin } : {}),
@@ -331,6 +417,14 @@ export const redactTail = (text, home) => {
331
417
  const lines = out.split("\n").filter((line) => line.trim() !== "");
332
418
  return lines.slice(-TAIL_LINES).join("\n").slice(-TAIL_BYTES);
333
419
  };
420
+ /** `tg-work`; `tg` for every profile; `tg-personal-work` for a list, a name every client accepts. */
421
+ const serverName = (app, profile, profiles) => {
422
+ if (profile !== undefined)
423
+ return `${app.command}-${profile}`;
424
+ if (profiles === EVERY_PROFILE)
425
+ return app.command;
426
+ return `${app.command}-${(profiles ?? "").split(",").join("-")}`;
427
+ };
334
428
  const VERSION_MANAGER = /[\\/](\.nvm|nvm|\.fnm|fnm|fnm_multishells|\.volta|volta|\.asdf|mise)[\\/]/i;
335
429
  /**
336
430
  * `node` and the script by full path, on every platform: a client started from the desktop does
@@ -340,10 +434,13 @@ const VERSION_MANAGER = /[\\/](\.nvm|nvm|\.fnm|fnm|fnm_multishells|\.volta|volta
340
434
  * store. So is `XDG_RUNTIME_DIR`: the keyring is reached through it, and an MCP client that starts
341
435
  * servers with a trimmed environment leaves it out (measured 2026-09-28). Credentials are never copied.
342
436
  */
343
- export const serverEntry = (app, { profile, flags = {}, execPath, scriptPath, env, }) => {
437
+ export const serverEntry = (app, { profile, profiles, flags = {}, execPath, scriptPath, env, }) => {
438
+ if ((profile === undefined) === (profiles === undefined))
439
+ throw new CliError("validation_error", "an MCP entry serves one profile or the profiles --profiles names");
440
+ const words = profile === undefined ? `mcp config --profiles ${profiles}` : `${profile} mcp config`;
344
441
  if (installerOf(scriptPath) === "npx") {
345
442
  throw new CliError("validation_error", `this ${app.command} runs from npx's cache, which gets cleared, and the path would stop working — ` +
346
- `install it globally, then run \`${app.command} ${profile === undefined ? "" : `${profile} `}mcp config\` again`);
443
+ `install it globally, then run \`${app.command} ${words}\` again`);
347
444
  }
348
445
  const names = [
349
446
  ...["CONFIG_DIR", "STATE_DIR", "CACHE_DIR"].map((name) => envName(app, name)),
@@ -360,12 +457,13 @@ export const serverEntry = (app, { profile, flags = {}, execPath, scriptPath, en
360
457
  ...(profile === undefined ? [] : [profile]),
361
458
  "mcp",
362
459
  "serve",
460
+ ...(profiles === undefined ? [] : ["--profiles", profiles]),
363
461
  ...(flags.permission ?? []).flatMap((entry) => ["--permission", entry]),
364
462
  ],
365
463
  ...(Object.keys(directories).length > 0 ? { env: directories } : {}),
366
464
  };
367
465
  return {
368
- config: { mcpServers: { [profile === undefined ? app.command : `${app.command}-${profile}`]: server } },
466
+ config: { mcpServers: { [serverName(app, profile, profiles)]: server } },
369
467
  /** The path belongs to one Node version of a version manager; switching Node breaks the entry. */
370
468
  nodeBound: VERSION_MANAGER.test(execPath),
371
469
  };