pi-mcp-client 0.7.0 → 0.8.1

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 CHANGED
@@ -1,9 +1,7 @@
1
1
  # 🔌 Pi MCP Client
2
2
 
3
- Connect Pi to MCP servers. The assistant discovers tools and resources on demand,
4
- reads resources as context, and calls tools natively through the official
5
- TypeScript SDK. You can also select, preview, and use server-provided prompts.
6
- No bridge process or invocation proxy.
3
+ Connect Pi to MCP servers so the model can use their tools and read their
4
+ resources. You can also browse, preview, and use server-provided prompts.
7
5
 
8
6
  ## 🚀 Installation
9
7
 
@@ -23,15 +21,15 @@ This server doesn't require credentials. Ask Pi:
23
21
 
24
22
  > Search Cloudflare's documentation for how to deploy a Worker.
25
23
 
26
- You configure servers and manage authentication. The assistant discovers
27
- capabilities, reads resources, and activates tools as needed. You don't need to
28
- type tool calls or select tools before asking a question.
24
+ You configure servers and sign in when needed. The model finds and uses
25
+ relevant tools and resources—you don't need to select tools before asking a
26
+ question.
29
27
 
30
28
  Use these commands in Pi to manage your connections:
31
29
 
32
30
  | Command | Purpose |
33
31
  | --- | --- |
34
- | `/mcp` | Inspect server status and loaded-tool counts. Idle connections are normal; servers connect on demand. |
32
+ | `/mcp` | Check connection status, available-tool counts, and tools loaded for the model when troubleshooting or checking your setup. |
35
33
  | `/mcp login <server>` | Sign in to an HTTP server. Only this command opens the login browser. |
36
34
  | `/mcp prompt <server> [name] [argument=value ...]` | Browse prompts or open one by name, then review a preview before using it. |
37
35
  | `/mcp reload` | Apply changes after editing your MCP configuration files. |
@@ -43,10 +41,12 @@ permissions. Tool activation isn't a per-call approval prompt. See
43
41
  ## ⚙️ Configuration
44
42
 
45
43
  Store server definitions in `~/.pi/agent/mcp.json` or a trusted project's
46
- `.mcp.json`. Project definitions replace same-named global definitions in full.
44
+ `.mcp.json`. Project servers load only after an explicit
45
+ [trust decision](docs/behavior.md#project-trust). Project definitions replace
46
+ same-named global definitions in full.
47
47
  You can edit these files or use `/mcp add` and `/mcp remove`. To reuse a
48
48
  Claude/Cursor JSON or Codex TOML file, run `/mcp import --scope global <path>` and
49
- review the selected connections before saving. No conversion file is needed.
49
+ review the selected connections before saving.
50
50
 
51
51
  Detailed guides:
52
52
 
@@ -57,14 +57,15 @@ Detailed guides:
57
57
  - [Commands](docs/commands.md): Server management, tool browsing, prompt selection,
58
58
  and resource watches. These are commands **you** run in Pi.
59
59
  - [Tool reference](docs/tool-reference.md): Discovery, activation, resource reads,
60
- and argument completions. This is the **assistant's** interface, not a user API.
61
- - [Behavior](docs/behavior.md): Sessions, caching, result display, and permissions.
60
+ and argument completions. This is the **model's** interface, not a user API.
61
+ - [Behavior](docs/behavior.md): When servers connect, when tools become available,
62
+ and what permissions they have.
62
63
  - [Troubleshooting](docs/troubleshooting.md): Error codes, recovery, and large
63
64
  results.
64
65
 
65
66
  ## 🧰 Requirements
66
67
 
67
- - Pi 0.85.1 or later, with additive dynamic tool loading.
68
+ - Pi 0.85.1 or later.
68
69
  - Node.js 22 or later.
69
70
  - The server executable for stdio connections.
70
71
  - An available OS credential store for OAuth. Linux requires a working Secret
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import { existsSync } from "node:fs";
2
3
  import { join as join4 } from "node:path";
3
4
  import {
4
5
  BorderedLoader as BorderedLoader2,
@@ -2329,7 +2330,7 @@ async function inspectConfigScopes(agentDir, cwd, trusted) {
2329
2330
  async function updateServerConfig(agentDir, cwd, trusted, mutation, validate) {
2330
2331
  const scoped = mutation.action !== "toggle";
2331
2332
  if (scoped && mutation.scope === "project" && !trusted)
2332
- throw new ConfigMutationError("Project configuration requires a trusted project. Use global scope or trust the project first.");
2333
+ throw new ConfigMutationError("Project configuration requires a trusted project. Use global scope, or run /trust to trust this folder first.");
2333
2334
  const paths = [join(agentDir, "mcp.json"), ...trusted ? [join(cwd, ".mcp.json")] : []];
2334
2335
  const targets = await Promise.all(paths.map(configTarget));
2335
2336
  if (scoped && targets.length === 2 && targets[0] === targets[1])
@@ -3973,16 +3974,16 @@ function importAction(name, scope, scopes) {
3973
3974
  if (scope === "project" && Object.hasOwn(scopes.global, name)) return "Override global definition";
3974
3975
  return `Add ${scope} definition`;
3975
3976
  }
3976
- async function runImportCommand(input, ctx, agentDir, signal, assertCurrent, save) {
3977
+ async function runImportCommand(input, ctx, agentDir, projectTrusted, signal, assertCurrent, save) {
3977
3978
  if (!ctx.hasUI) throw new ConfigMutationError("Configuration imports require an interactive session (TUI or RPC).");
3978
3979
  const { scope, path } = parseImportCommand(input);
3979
- const trusted = ctx.isProjectTrusted();
3980
+ const trusted = projectTrusted();
3980
3981
  if (scope === "project" && !trusted)
3981
- throw new ConfigMutationError("Project configuration requires a trusted project. Use global scope or trust the project first.");
3982
+ throw new ConfigMutationError("Project configuration requires a trusted project. Use global scope, or run /trust to trust this folder first.");
3982
3983
  const guard = () => {
3983
3984
  signal.throwIfAborted();
3984
3985
  assertCurrent();
3985
- if (ctx.isProjectTrusted() !== trusted)
3986
+ if (projectTrusted() !== trusted)
3986
3987
  throw new ConfigMutationError("Project trust changed during the preview. Run /mcp import again; nothing was saved.");
3987
3988
  };
3988
3989
  const select = async (title, choices) => {
@@ -4541,33 +4542,61 @@ async function authenticate(identity, open2, signal, store, options = {}) {
4541
4542
  }
4542
4543
 
4543
4544
  // src/management.ts
4544
- import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
4545
+ import { truncateToWidth as truncateToWidth2, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
4545
4546
  var serverStates = {
4546
- disconnected: { glyph: "\u25CB", label: "idle" },
4547
- connected: { glyph: "\u25CF", label: "connected" },
4548
- connecting: { glyph: "\u25B6\uFE0E", label: "connecting" },
4549
- failed: { glyph: "\u2718\uFE0E", label: "error" },
4550
- disabled: { glyph: "\u25CB", label: "disabled" }
4547
+ disconnected: { glyph: "\u25CB", label: "idle", color: "muted" },
4548
+ connected: { glyph: "\u25CF", label: "connected", color: "success" },
4549
+ connecting: { glyph: "\u25B6\uFE0E", label: "connecting", color: "warning" },
4550
+ failed: { glyph: "\u2718\uFE0E", label: "error", color: "error" },
4551
+ disabled: { glyph: "\u25CB", label: "disabled", color: "dim" }
4551
4552
  };
4552
- function serverMatrix(servers, loaded, width = 80) {
4553
+ function serverMatrix(servers, loaded, width = 80, theme) {
4553
4554
  if (width <= 0) return "";
4554
- const fit = (text) => plain(truncateToWidth2(text, width));
4555
- if (!servers.length) return fit("No MCP servers configured.");
4556
- const nameWidth = Math.min(40, Math.max(6, ...servers.map(({ name }) => name.length)));
4557
- const heading = ` ${"Server".padEnd(nameWidth)} ${"State".padEnd(10)} ${"Tools".padStart(5)} ${"Loaded".padStart(6)}`;
4558
- const rows = servers.map((server) => {
4555
+ const fg = (color, text) => theme ? theme.fg(color, text) : text;
4556
+ const bold = (text) => theme ? theme.bold(text) : text;
4557
+ const fit = (text) => {
4558
+ const fitted = truncateToWidth2(text, width, "\u2026");
4559
+ return theme ? fitted : plain(fitted);
4560
+ };
4561
+ const wrap = (text) => wrapTextWithAnsi(text, width).map(fit);
4562
+ const rows = [];
4563
+ if (!servers.length) return wrap(fg("muted", "No MCP servers configured.")).join("\n");
4564
+ const toolsWidth = Math.max(5, ...servers.map((server) => String(server.catalogSize ?? "\u2014").length));
4565
+ const loadedWidth = Math.max(6, ...servers.map((server) => String(loaded.get(server.name) ?? 0).length));
4566
+ const fixedWidth = 18 + toolsWidth + loadedWidth;
4567
+ const compact = width < fixedWidth + 12;
4568
+ const nameWidth = Math.max(0, Math.min(
4569
+ 40,
4570
+ width - fixedWidth,
4571
+ Math.max(6, ...servers.map(({ name }) => visibleWidth(line(name))))
4572
+ ));
4573
+ const padName = (name) => {
4574
+ const text = plain(truncateToWidth2(line(name), nameWidth, "\u2026"));
4575
+ return text + " ".repeat(Math.max(0, nameWidth - visibleWidth(text)));
4576
+ };
4577
+ if (!compact) rows.push(fg("muted", bold(
4578
+ ` ${"Server".padEnd(nameWidth)} ${"State".padEnd(10)} ${"Tools".padStart(toolsWidth)} ${"Loaded".padStart(loadedWidth)}`
4579
+ )));
4580
+ for (const server of servers) {
4559
4581
  const state = serverStates[server.state];
4560
- const name = plain(truncateToWidth2(line(server.name), nameWidth)).padEnd(nameWidth);
4561
- return `${state.glyph} ${name} ${state.label.padEnd(10)} ${String(server.catalogSize ?? "\u2014").padStart(5)} ${String(loaded.get(server.name) ?? 0).padStart(6)}`;
4562
- });
4563
- const errors = servers.filter((server) => server.state === "failed" && server.error).map((server) => `\u2718\uFE0E ${line(server.name)}: [${server.error.code}] ${line(server.error.message)}`);
4564
- return [
4565
- heading,
4566
- ...rows,
4567
- "",
4568
- "Connections open on demand. \u2014 = catalog not fetched.",
4569
- ...errors
4570
- ].map(fit).join("\n");
4582
+ const count = loaded.get(server.name) ?? 0;
4583
+ const tools = String(server.catalogSize ?? "\u2014");
4584
+ const name = fg(server.state === "disabled" ? "dim" : "text", bold(compact ? line(server.name) : padName(server.name)));
4585
+ const glyph = fg(state.color, state.glyph);
4586
+ if (compact) {
4587
+ rows.push(...wrap(`${glyph} ${name}`));
4588
+ rows.push(...wrap(` ${fg(state.color, state.label)} \xB7 ${fg("muted", `${tools} tools \xB7 ${count} loaded`)}`));
4589
+ } else {
4590
+ rows.push(`${glyph} ${name} ${fg(state.color, state.label.padEnd(10))} ` + fg(server.catalogSize === void 0 ? "dim" : "text", tools.padStart(toolsWidth)) + " " + fg(count ? "accent" : "dim", String(count).padStart(loadedWidth)));
4591
+ }
4592
+ }
4593
+ rows.push("", ...wrap(fg("dim", "Connections open on demand. \u2014 = catalog not fetched.")));
4594
+ const errors = servers.filter((server) => server.state === "failed" && server.error);
4595
+ if (errors.length) rows.push("");
4596
+ for (const server of errors) {
4597
+ rows.push(...wrap(fg("error", `\u2718\uFE0E ${line(server.name)}: [${line(server.error.code)}]`) + fg("muted", ` ${line(server.error.message)}`)));
4598
+ }
4599
+ return rows.map(fit).join("\n");
4571
4600
  }
4572
4601
  function toolPickerLabel(tool, index, columns = 80) {
4573
4602
  const width = Math.max(0, columns - 4);
@@ -5913,6 +5942,58 @@ function renderResult(result, options, theme, isError) {
5913
5942
  };
5914
5943
  }
5915
5944
 
5945
+ // src/status-panel.ts
5946
+ var STATUS_ENTRY = "mcp-status";
5947
+ function statusPanel(snapshot, theme) {
5948
+ const loaded = new Map(snapshot.loaded);
5949
+ return {
5950
+ render(width) {
5951
+ if (width <= 0) return [];
5952
+ const padding = width > 2 ? 1 : 0;
5953
+ const prefix = " ".repeat(padding);
5954
+ return serverMatrix(snapshot.servers, loaded, width - padding * 2, theme).split("\n").map((row) => prefix + row);
5955
+ },
5956
+ // Styling and layout are recomputed on every render, including theme changes.
5957
+ invalidate() {
5958
+ }
5959
+ };
5960
+ }
5961
+
5962
+ // src/trust.ts
5963
+ import {
5964
+ hasTrustRequiringProjectResources,
5965
+ ProjectTrustStore,
5966
+ SettingsManager
5967
+ } from "@earendil-works/pi-coding-agent";
5968
+ var CHOICES = /* @__PURE__ */ new Map([
5969
+ ["Trust", { trusted: true, save: true }],
5970
+ ["Trust (this session only)", { trusted: true, save: false }],
5971
+ ["Do not trust", { trusted: false, save: true }],
5972
+ ["Do not trust (this session only)", { trusted: false, save: false }]
5973
+ ]);
5974
+ function projectTrustDecision(agentDir, cwd, piTrusted) {
5975
+ if (!piTrusted) return false;
5976
+ if (hasTrustRequiringProjectResources(cwd)) return true;
5977
+ const saved = new ProjectTrustStore(agentDir).get(cwd);
5978
+ if (saved !== null) return saved;
5979
+ const policy = SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getDefaultProjectTrust();
5980
+ return policy === "ask" ? void 0 : policy === "always";
5981
+ }
5982
+ async function askProjectTrust(agentDir, cwd, select, signal) {
5983
+ const answer = await select(
5984
+ `Trust project folder?
5985
+ ${cwd}
5986
+
5987
+ Its .mcp.json defines MCP servers, which can run local commands with your permissions. Saved decisions use Pi's project trust and also apply to Pi project resources.`,
5988
+ [...CHOICES.keys()],
5989
+ { signal }
5990
+ );
5991
+ const choice = answer === void 0 ? void 0 : CHOICES.get(answer);
5992
+ if (!choice || signal.aborted) return void 0;
5993
+ if (choice.save) new ProjectTrustStore(agentDir).set(cwd, choice.trusted);
5994
+ return choice.trusted;
5995
+ }
5996
+
5916
5997
  // src/index.ts
5917
5998
  var CommandUsageError = class extends Error {
5918
5999
  };
@@ -5941,6 +6022,12 @@ function mcpClient(pi, options = {}) {
5941
6022
  let loginController;
5942
6023
  let promptController;
5943
6024
  let importController;
6025
+ let trustController;
6026
+ let sessionTrust;
6027
+ pi.registerEntryRenderer(
6028
+ STATUS_ENTRY,
6029
+ (entry, _options, theme) => statusPanel(entry.data ?? { servers: [], loaded: [] }, theme)
6030
+ );
5944
6031
  pi.registerMessageRenderer("mcp-prompt", (message, { expanded, outputPad }, theme) => {
5945
6032
  const details = object(message.details) ? message.details : {};
5946
6033
  const count = Number(details.count ?? 0);
@@ -6004,6 +6091,35 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
6004
6091
  }
6005
6092
  });
6006
6093
  }
6094
+ const trustDecision = (ctx) => projectTrustDecision(agentDir, ctx.cwd, ctx.isProjectTrusted()) ?? (sessionTrust?.cwd === ctx.cwd ? sessionTrust.trusted : void 0);
6095
+ const projectTrusted = (ctx) => trustDecision(ctx) ?? false;
6096
+ async function resolveProjectTrust(ctx) {
6097
+ let trusted = trustDecision(ctx);
6098
+ if (!existsSync(join4(ctx.cwd, ".mcp.json"))) return trusted ?? false;
6099
+ let answered = false;
6100
+ if (trusted === void 0 && ctx.hasUI) {
6101
+ const controller = new AbortController();
6102
+ trustController = controller;
6103
+ try {
6104
+ trusted = await askProjectTrust(
6105
+ agentDir,
6106
+ ctx.cwd,
6107
+ ctx.ui.select.bind(ctx.ui),
6108
+ AbortSignal.any([controller.signal, ...ctx.signal ? [ctx.signal] : []])
6109
+ );
6110
+ } finally {
6111
+ if (trustController === controller) trustController = void 0;
6112
+ }
6113
+ if (controller.signal.aborted) return false;
6114
+ if (trusted !== void 0) {
6115
+ sessionTrust = { cwd: ctx.cwd, trusted };
6116
+ answered = true;
6117
+ }
6118
+ }
6119
+ if (!trusted && !answered && ctx.hasUI && ctx.isProjectTrusted())
6120
+ ctx.ui.notify("Project .mcp.json ignored: this folder isn't trusted for MCP servers. Run /trust to save a decision, then /mcp reload.", "warning");
6121
+ return trusted ?? false;
6122
+ }
6007
6123
  const restore = (ctx) => {
6008
6124
  const tools = restoredTools(ctx.sessionManager.getBranch()).filter((tool) => {
6009
6125
  try {
@@ -6038,14 +6154,15 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
6038
6154
  }
6039
6155
  };
6040
6156
  ctx.signal?.throwIfAborted();
6157
+ const trusted = mutation ? projectTrusted(ctx) : await resolveProjectTrust(ctx);
6041
6158
  const update = mutation && await updateServerConfig(
6042
6159
  agentDir,
6043
6160
  ctx.cwd,
6044
- ctx.isProjectTrusted(),
6161
+ trusted,
6045
6162
  mutation,
6046
6163
  validate
6047
6164
  );
6048
- const nextConfig = update ? update.config : await loadConfig(agentDir, ctx.cwd, ctx.isProjectTrusted());
6165
+ const nextConfig = update ? update.config : await loadConfig(agentDir, ctx.cwd, trusted);
6049
6166
  validate(nextConfig);
6050
6167
  const next = new McpRuntime(
6051
6168
  nextConfig,
@@ -6073,15 +6190,18 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
6073
6190
  return update?.scope;
6074
6191
  }
6075
6192
  pi.on("session_start", async (_event, ctx) => {
6076
- sessionGeneration++;
6193
+ const generation = ++sessionGeneration;
6077
6194
  promptController?.abort();
6078
6195
  importController?.abort();
6196
+ trustController?.abort();
6079
6197
  await runtime?.close();
6080
6198
  runtime = void 0;
6081
6199
  config = {};
6082
6200
  configError = void 0;
6083
6201
  try {
6084
- config = await loadConfig(agentDir, ctx.cwd, ctx.isProjectTrusted());
6202
+ const trusted = await resolveProjectTrust(ctx);
6203
+ if (generation !== sessionGeneration) return;
6204
+ config = await loadConfig(agentDir, ctx.cwd, trusted);
6085
6205
  runtime = new McpRuntime(config, ctx.cwd, join4(agentDir, "cache", "pi-mcp-client"), createSdkConnector(storeFactory));
6086
6206
  resourceNotifications(runtime, ctx);
6087
6207
  } catch (error) {
@@ -6101,6 +6221,7 @@ Text output is limited to 2000 lines or 50 KiB; larger results are saved to a pr
6101
6221
  loginController?.abort();
6102
6222
  promptController?.abort();
6103
6223
  importController?.abort();
6224
+ trustController?.abort();
6104
6225
  const old = runtime;
6105
6226
  runtime = void 0;
6106
6227
  await old?.close();
@@ -6331,7 +6452,7 @@ Variables: ${(candidate.variables ?? []).join(", ") || "none"}. Supply known val
6331
6452
  }
6332
6453
  });
6333
6454
  pi.registerCommand("mcp", {
6334
- description: "Manage MCP servers: add|remove|import --scope global|project, list, status, reload, enable|disable|get|tools|login|logout|reconnect|refresh <server>; prompt <server> [name] [argument=value ...]; subscriptions; subscribe|unsubscribe <server> <uri>",
6455
+ description: "Show MCP server status; manage servers: add|remove|import --scope global|project, reload, enable|disable|get|tools|login|logout|reconnect|refresh <server>; prompt <server> [name] [argument=value ...]; subscriptions; subscribe|unsubscribe <server> <uri>",
6335
6456
  getArgumentCompletions(prefix) {
6336
6457
  const serverActions = ["enable", "disable", "get", "tools", "prompt", "login", "logout", "reconnect", "refresh", "subscribe", "unsubscribe"];
6337
6458
  const input = prefix.trimStart();
@@ -6339,7 +6460,7 @@ Variables: ${(candidate.variables ?? []).join(", ") || "none"}. Supply known val
6339
6460
  if (configuration !== void 0) return configuration;
6340
6461
  const match = /^(\S+)\s+(.*)$/s.exec(input);
6341
6462
  if (!match) {
6342
- return ["list", "status", "reload", "subscriptions", "add", "remove", "import", ...serverActions].filter((action2) => action2.startsWith(input)).map((action2) => ({ value: action2, label: action2 }));
6463
+ return ["reload", "subscriptions", "add", "remove", "import", ...serverActions].filter((action2) => action2.startsWith(input)).map((action2) => ({ value: action2, label: action2 }));
6343
6464
  }
6344
6465
  const [, action, partialServer] = match;
6345
6466
  if (action === "login") {
@@ -6355,7 +6476,7 @@ Variables: ${(candidate.variables ?? []).join(", ") || "none"}. Supply known val
6355
6476
  async handler(args, ctx) {
6356
6477
  const generation = sessionGeneration;
6357
6478
  await ctx.waitForIdle();
6358
- let [action = "status", server, ...extra] = args.trim().split(/\s+/).filter(Boolean);
6479
+ let [action = "", server, ...extra] = args.trim().split(/\s+/).filter(Boolean);
6359
6480
  try {
6360
6481
  if (generation !== sessionGeneration)
6361
6482
  throw new CommandUsageError("The Pi session changed while waiting for idle.");
@@ -6370,6 +6491,7 @@ Variables: ${(candidate.variables ?? []).join(", ") || "none"}. Supply known val
6370
6491
  args,
6371
6492
  ctx,
6372
6493
  agentDir,
6494
+ () => projectTrusted(ctx),
6373
6495
  AbortSignal.any([controller.signal, ...ctx.signal ? [ctx.signal] : []]),
6374
6496
  () => {
6375
6497
  if (sessionGeneration !== generation || runtime !== activeRuntime)
@@ -6429,7 +6551,7 @@ ${snapshot.body}`,
6429
6551
  server = mutation.server;
6430
6552
  await reloadConfiguration(ctx, mutation);
6431
6553
  const remaining = Object.hasOwn(config, server);
6432
- const message = mutation.action === "add" ? `\u2714\uFE0E ${server}: saved in ${mutation.scope} configuration. Connections, authentication, and tool discovery remain on demand.` + (mutation.scope === "global" && ctx.isProjectTrusted() ? " Trusted project definitions take precedence over global definitions." : "") : `\u2714\uFE0E ${server}: removed from ${mutation.scope} configuration. Credentials were retained.` + (remaining ? " A definition from the other scope remains effective; connections reopen on demand." : " Its connection is closed and its tools are no longer active.");
6554
+ const message = mutation.action === "add" ? `\u2714\uFE0E ${server}: saved in ${mutation.scope} configuration. Connections, authentication, and tool discovery remain on demand.` + (mutation.scope === "global" && projectTrusted(ctx) ? " Trusted project definitions take precedence over global definitions." : "") : `\u2714\uFE0E ${server}: removed from ${mutation.scope} configuration. Credentials were retained.` + (remaining ? " A definition from the other scope remains effective; connections reopen on demand." : " Its connection is closed and its tools are no longer active.");
6433
6555
  if (ctx.hasUI) ctx.ui.notify(message, "info");
6434
6556
  return;
6435
6557
  }
@@ -6495,19 +6617,23 @@ ${snapshot.body}`,
6495
6617
  );
6496
6618
  return;
6497
6619
  }
6498
- if ((action === "status" || action === "list") && !server) {
6620
+ if (!action) {
6499
6621
  const statuses = current().serverStatuses();
6500
6622
  const loaded = /* @__PURE__ */ new Map();
6501
6623
  for (const name of pi.getActiveTools()) {
6502
6624
  const tool = exposure.definitions.get(name);
6503
6625
  if (tool) loaded.set(tool.server, (loaded.get(tool.server) ?? 0) + 1);
6504
6626
  }
6505
- if (ctx.hasUI) ctx.ui.notify(serverMatrix(statuses, loaded), "info");
6627
+ if (ctx.mode === "tui") {
6628
+ pi.appendEntry(STATUS_ENTRY, { servers: statuses, loaded: [...loaded] });
6629
+ } else if (ctx.hasUI) {
6630
+ ctx.ui.notify(serverMatrix(statuses, loaded), "info");
6631
+ }
6506
6632
  return;
6507
6633
  }
6508
6634
  if (!server || extra.length > 0 && !(action === "login" && extra.length === 1 && extra[0] === "--no-browser") || !Object.hasOwn(config, server) || config[server].disabled)
6509
6635
  throw new CommandUsageError(
6510
- "Usage: /mcp list|status|reload, /mcp enable|disable|get|tools|login|logout|reconnect|refresh <server>, or /mcp add|remove|import --scope global|project ... . Disabled servers accept enable, disable, get, logout, and scoped removal."
6636
+ "Usage: /mcp, /mcp reload, /mcp enable|disable|get|tools|login|logout|reconnect|refresh <server>, or /mcp add|remove|import --scope global|project ... . Disabled servers accept enable, disable, get, logout, and scoped removal."
6511
6637
  );
6512
6638
  if (action === "tools") {
6513
6639
  if (!ctx.hasUI)
@@ -6618,7 +6744,7 @@ Opening browser. Esc to cancel.`
6618
6744
  await current().promptCatalog(server, ctx.signal, true);
6619
6745
  } else
6620
6746
  throw new CommandUsageError(
6621
- "Unknown MCP command. Use /mcp list|status|reload or /mcp enable|disable|get|tools|login|logout|reconnect|refresh <server>, or /mcp add|remove|import --scope global|project ... ."
6747
+ "Unknown MCP command. Use /mcp, /mcp reload, or /mcp enable|disable|get|tools|login|logout|reconnect|refresh <server>, or /mcp add|remove|import --scope global|project ... ."
6622
6748
  );
6623
6749
  if (ctx.hasUI)
6624
6750
  ctx.ui.notify(
@@ -2,7 +2,7 @@
2
2
 
3
3
  [Back to the README](../README.md)
4
4
 
5
- You manage authentication through `/mcp login` and `/mcp logout`. The assistant
5
+ You manage authentication through `/mcp login` and `/mcp logout`. The model
6
6
  can't initiate an OAuth login: only your explicit login command opens the browser.
7
7
  For externally managed bearer tokens, use
8
8
  [headers and secret commands](configuration.md#secret-commands) instead.
package/docs/behavior.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [Back to the README](../README.md)
4
4
 
5
5
  This page explains how the extension manages state and displays results. For the
6
- assistant's tool-call interface, see the [tool reference](tool-reference.md).
6
+ model's tool-call interface, see the [tool reference](tool-reference.md).
7
7
 
8
8
  ## Sessions
9
9
 
@@ -15,7 +15,7 @@ assistant's tool-call interface, see the [tool reference](tool-reference.md).
15
15
  Other providers receive the expanded tool list normally.
16
16
  - Discovery respects server filters; activation also respects Pi's tool exclusions.
17
17
  An explicit tool allowlist must include both `mcp_tools` and the native tools
18
- the assistant needs to load.
18
+ the model needs to load.
19
19
 
20
20
  ## Resource snapshots
21
21
 
@@ -88,7 +88,7 @@ reconnection or configuration reload.
88
88
  When a connected server reports a tool-list change, the extension invalidates its
89
89
  memory and disk catalogs. The next discovery or activation fetches the current
90
90
  list, including new or removed tools. Notifications don't replace active tool
91
- definitions: the assistant must activate changed schemas again before use. Calls
91
+ definitions: the model must activate changed schemas again before use. Calls
92
92
  validate the live catalog before execution and refuse removed or changed tools.
93
93
  Disconnected, cache-only searches can't receive notifications and still use the
94
94
  24-hour disk-cache expiry.
@@ -115,7 +115,7 @@ indentation and syntax highlighting. Explicit JSON resource MIME types (includin
115
115
  `application/*+json`) and structured content identify JSON without guessing.
116
116
  Other explicit MIME types stay plain text; unlabeled text is checked for JSON.
117
117
 
118
- Formatting changes only the display, not the response sent to the assistant.
118
+ Formatting changes only the display, not the response sent to the model.
119
119
  Invalid or truncated JSON stays plain text. Results that would exceed formatting
120
120
  limits also stay plain text. Resource-link MIME types describe the linked content,
121
121
  not the displayed link label. Supported images use the existing result display;
@@ -125,7 +125,8 @@ see [large results](troubleshooting.md#large-results) for size limits.
125
125
 
126
126
  Only load configuration you trust. Server executables and secret commands run
127
127
  with your user permissions; trusted project configuration can replace global
128
- connections and settings.
128
+ connections and settings. See [project trust](#project-trust) for when a
129
+ project's `.mcp.json` loads.
129
130
 
130
131
  Configuration imports require an explicit file, scope, selection, and final
131
132
  confirmation. Import previews hide connection values; review the source file
@@ -148,3 +149,28 @@ Pi's tool restrictions to prevent discovery and resource operations. Already act
148
149
  native tools have their own tool restrictions. Per-resource permission policies
149
150
  aren't implemented. Prompt selection uses explicit user commands, not the
150
151
  model-facing tool allowlist; disable the server to prevent prompt access.
152
+
153
+ ## Project trust
154
+
155
+ A project's `.mcp.json` can start local commands, so it loads only after an
156
+ explicit trust decision for the folder. Pi asks for trust only in folders with Pi
157
+ project resources, such as `.pi/settings.json` or `.agents/skills`, and trusts
158
+ other folders implicitly. The extension doesn't rely on that implicit trust:
159
+
160
+ - **Folders with Pi project resources:** Pi's decision applies, including
161
+ `--approve` and session-only trust.
162
+ - **Other folders:** a saved decision for the folder or a parent folder applies.
163
+ Without one, Pi's `defaultProjectTrust` setting applies: `always` loads project
164
+ servers and `never` ignores them. With the default `ask`, interactive sessions
165
+ ask when a `.mcp.json` exists, and headless sessions ignore the file.
166
+ - **Untrusted folders:** `--no-approve` or a refusal in Pi always ignores project
167
+ servers.
168
+
169
+ Saved answers go to Pi's project trust store, so they also apply to Pi project
170
+ resources. Use Pi's `/trust` command to save or change a decision, then run
171
+ `/mcp reload`; MCP servers don't require a restart. In folders without Pi project
172
+ resources, `--approve` doesn't trust `.mcp.json`; save a decision instead.
173
+
174
+ Only session start and `/mcp reload` ask. Other configuration commands, such as
175
+ `/mcp add` and `/mcp import`, use the current decision. Untrusted project files
176
+ are neither read nor changed.
package/docs/commands.md CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  [Back to the README](../README.md)
4
4
 
5
- These are commands **you run in Pi**, not tools the assistant calls. Use them to
6
- manage servers, inspect capabilities, use prompts, and watch resource changes. The assistant's
7
- separate interface is documented in the [tool reference](tool-reference.md).
5
+ These are commands **you run in Pi**, not tools the model calls. Use them to
6
+ manage servers, inspect capabilities, use prompts, and watch resource changes.
7
+ The model's separate interface is documented in the [tool reference](tool-reference.md).
8
8
 
9
9
  ## Command reference
10
10
 
11
11
  | Command | Purpose |
12
12
  | --- | --- |
13
- | `/mcp`, `/mcp list`, `/mcp status` | Show a server status matrix with catalog and loaded-tool counts. |
13
+ | `/mcp` | Check server status, catalog tool counts, and tools loaded for the model. |
14
14
  | `/mcp add --scope <scope> [options] <server> <url>` | Save an HTTP server without connecting. For stdio, use `<server> -- <command> [args...]`. |
15
15
  | `/mcp remove --scope <scope> <server>` | Remove a definition from the selected scope, retaining credentials. |
16
16
  | `/mcp import --scope <scope> <path>` | Preview and select servers from Claude/Cursor JSON or Codex TOML, then confirm a scoped import. |
@@ -33,11 +33,16 @@ See [Authentication](authentication.md) for login and logout procedures, and
33
33
 
34
34
  ## Inspect servers and tools
35
35
 
36
- The `/mcp` status matrix distinguishes idle (`○`), connected (`●`), connecting
37
- (`▶︎`), disabled (`○`), and failed (`✘︎`) servers. Idle is normal: connections open
38
- on demand. A dash (`—`) means the catalog hasn't been fetched, not that the server
39
- has no tools. The **Loaded** column counts tools currently active for the
40
- assistant.
36
+ Use `/mcp` to check your setup or investigate a connection problem. It reports
37
+ whether each server is idle, connected, connecting, disabled, or in an error
38
+ state, along with catalog tool counts and tools loaded for the model.
39
+
40
+ Idle is normal: connections open on demand. A dash (`—`) means the catalog hasn't
41
+ been fetched, not that the server has no tools. **Loaded** counts tools currently
42
+ active for the model.
43
+
44
+ Checking status doesn't open connections or send information to the model.
45
+ The result stays in your transcript; run the command again for updated status.
41
46
 
42
47
  Use `/mcp get <server>` to check the effective transport, protocol, filters,
43
48
  and connection status without connecting or running secret commands. Connection
@@ -48,15 +53,14 @@ unavailable credential store is reported separately from missing tokens. Header
48
53
  and stdio credentials are identified as externally managed; inspection never
49
54
  executes them.
50
55
 
51
- Use `/mcp tools <server>` to fetch the current catalog and browse a scrollable
52
- list. Rows show tool names and descriptions, trimmed to the terminal width with
53
- an ellipsis. Select a tool to see a multiline signature and parameter details,
54
- with each parameter in a separate paragraph. Browsing respects your include and
55
- exclude filters and doesn't activate tools or add their schemas to the assistant's
56
- context. This command requires an interactive UI.
56
+ Use `/mcp tools <server>` to find out what a server can do. It fetches the current
57
+ catalog so you can inspect tool descriptions, parameters, and required inputs.
58
+ Browsing respects your include and exclude filters and doesn't activate tools
59
+ or add their schemas to the model's context. This command requires an
60
+ interactive UI.
57
61
 
58
62
  Refreshing a catalog doesn't replace active tool definitions. After a schema
59
- change, ask the assistant to activate the exact tool again. See
63
+ change, ask the model to activate the exact tool again. See
60
64
  [tool changes and caching](behavior.md#discovery-and-caching). Failed tool calls
61
65
  aren't retried automatically; verify whether an interrupted operation completed
62
66
  before trying again.
@@ -108,7 +112,7 @@ Both commands wait for active agent work to finish, then
108
112
  [apply the configuration](configuration.md#apply-changes).
109
113
 
110
114
  Disabling removes the server from discovery and deactivates its tools. Enabling
111
- doesn't connect, authenticate, or load tools; ask the assistant to discover the
115
+ doesn't connect, authenticate, or load tools; ask the model to discover the
112
116
  capabilities you need.
113
117
 
114
118
  ## Add and remove servers
@@ -116,8 +120,9 @@ capabilities you need.
116
120
  Both commands require an explicit `--scope global` or `--scope project`:
117
121
 
118
122
  - **Global:** `~/.pi/agent/mcp.json`.
119
- - **Project:** `.mcp.json` in the current trusted project. Untrusted project files
120
- are neither read nor changed.
123
+ - **Project:** `.mcp.json` in the current
124
+ [trusted project](behavior.md#project-trust). Untrusted project files are
125
+ neither read nor changed.
121
126
 
122
127
  Add an HTTP server by URL, or a stdio server after `--`:
123
128
 
@@ -190,7 +195,8 @@ automatically; no conversion file is needed:
190
195
  ```
191
196
 
192
197
  The command requires an interactive TUI or RPC session and an explicit
193
- `--scope global` or `--scope project`. Project scope requires a trusted project.
198
+ `--scope global` or `--scope project`. Project scope requires a
199
+ [trusted project](behavior.md#project-trust).
194
200
  Relative source paths resolve against Pi's current directory; `~/` is supported.
195
201
  The path isn't evaluated by a shell, and no application settings are scanned.
196
202
 
@@ -282,7 +288,7 @@ streams. It never opens the URI as a file or generic URL.
282
288
  An update marks the watch as changed (`↻`) and shows a UI notification. Repeated
283
289
  updates coalesce into that marker until you unsubscribe. No content is fetched,
284
290
  no model turn starts, and existing resource results remain unchanged. Ask the
285
- assistant to read the resource for a new snapshot; unsubscribe and subscribe again
291
+ model to read the resource for a new snapshot; unsubscribe and subscribe again
286
292
  to reset the change marker.
287
293
 
288
294
  Watches are memory-only, limited to 50 per server connection, and require an
@@ -2,10 +2,11 @@
2
2
 
3
3
  [Back to the README](../README.md)
4
4
 
5
- You configure which servers the assistant can access. Add connections to
6
- `~/.pi/agent/mcp.json`, or `.mcp.json` in a trusted project. For command-based
7
- setup, see [Add and remove servers](commands.md#add-and-remove-servers). To reuse
8
- an existing Claude/Cursor JSON or Codex TOML file, see
5
+ You configure which servers the model can access. Add connections to
6
+ `~/.pi/agent/mcp.json`, or `.mcp.json` in a
7
+ [trusted project](behavior.md#project-trust). For command-based setup, see
8
+ [Add and remove servers](commands.md#add-and-remove-servers). To reuse an
9
+ existing Claude/Cursor JSON or Codex TOML file, see
9
10
  [Import server definitions](commands.md#import-server-definitions).
10
11
 
11
12
  ## Files and transports
@@ -16,8 +17,10 @@ MCP configuration standard. Live configuration must use JSON, not VS Code's
16
17
  this format. `PI_CODING_AGENT_DIR` overrides the global Pi directory.
17
18
  Project definitions replace same-named global definitions in full; fields and
18
19
  filters aren't merged. Untrusted project definitions aren't loaded or edited.
19
- An explicitly named import source is read as data for review; saving into project
20
- scope still requires project trust.
20
+ Project servers need an explicit trust decision even in folders that Pi trusts
21
+ implicitly; see [project trust](behavior.md#project-trust). An explicitly named
22
+ import source is read as data for review; saving into project scope still
23
+ requires project trust.
21
24
 
22
25
  ```json
23
26
  {
@@ -44,13 +47,24 @@ scope still requires project trust.
44
47
  | `type` | Optional `stdio` or `http`. If omitted, inferred from `command` or `url`. A conflicting type is rejected. |
45
48
  | `command`, `args` | Executable and arguments for a stdio server. No shell is used. |
46
49
  | `cwd` | Working directory for stdio; defaults to Pi's current directory. Relative paths resolve there. |
47
- | `env` | Additional environment variables for stdio. |
50
+ | `env` | Environment variables for stdio, in addition to a minimal inherited set. |
48
51
  | `url` | Streamable HTTP endpoint; mutually exclusive with `command`. |
49
52
  | `headers` | HTTP request headers, including optional bearer authentication. |
50
53
 
51
54
  Strings in `command`, `args`, `cwd`, `env`, `url`, and `headers` support `${VAR}`
52
55
  interpolation. Missing variables prevent that server from connecting.
53
56
 
57
+ Stdio servers don't inherit Pi's environment. They receive only the MCP SDK's
58
+ default set (`HOME`, `LOGNAME`, `PATH`, `SHELL`, `TERM`, and `USER` on Unix, and
59
+ similar system variables on Windows) plus `env`. Pass other variables a server
60
+ needs explicitly, for example a custom browser location:
61
+
62
+ ```json
63
+ "env": {
64
+ "PLAYWRIGHT_BROWSERS_PATH": "${PLAYWRIGHT_BROWSERS_PATH}"
65
+ }
66
+ ```
67
+
54
68
  Only stdio and Streamable HTTP are supported. The extension rejects `type: "sse"`
55
69
  and unsupported connection fields rather than silently changing their meaning.
56
70
 
@@ -119,7 +133,7 @@ server definition:
119
133
 
120
134
  | Field | Purpose |
121
135
  | --- | --- |
122
- | `description` | Short capability description for the assistant's server directory. |
136
+ | `description` | Short capability description for the model's server directory. |
123
137
  | `oauthClientId` | Optional pre-registered public client ID. Supports `${ENV_VAR}` interpolation, not secret commands. |
124
138
  | `oauthScopes` | Optional array of 1–100 unique OAuth scope tokens to request at login. Omitted scopes use SDK/server defaults. Values are literal, without interpolation. |
125
139
  | `oauthCallbackPort` | Optional loopback callback port, from 1 to 65535. Defaults to `19847`. |
@@ -1,22 +1,22 @@
1
- # Assistant tool reference
1
+ # Model tool reference
2
2
 
3
3
  [Back to the README](../README.md)
4
4
 
5
- This page documents **the assistant's interface**. The examples illustrate tool
6
- calls the assistant makes; they aren't slash commands or JavaScript for you to
5
+ This page documents **the model's interface**. The examples illustrate tool
6
+ calls the model makes; they aren't slash commands or JavaScript for you to
7
7
  run. Describe your task in natural language. For operations you control directly,
8
8
  see [Commands](commands.md).
9
9
 
10
10
  The `mcp_tools` tool supports four mutually exclusive operations:
11
11
 
12
- | Operation | What the assistant can do | What it doesn't do |
12
+ | Operation | What the model can do | What it doesn't do |
13
13
  | --- | --- | --- |
14
14
  | `query` | Discover tool, resource, and prompt metadata. | Read content or activate tools. |
15
15
  | `activate` | Load full schemas for exact tool identifiers. | Invoke tools. |
16
16
  | `read` | Fetch one resource as conversation context. | Activate tools or follow links automatically. |
17
17
  | `complete` | Request server suggestions for a template variable. | Read resources, activate tools, or select a value. |
18
18
 
19
- The assistant passes exactly one of `query`, `activate`, `read`, or `complete`.
19
+ The model passes exactly one of `query`, `activate`, `read`, or `complete`.
20
20
  The optional `kind`, `server`, and `limit` fields are query-only; reads and
21
21
  completions carry their server inside their respective objects.
22
22
 
@@ -40,7 +40,7 @@ content type when supplied. Concrete resources and tools include exact next-call
40
40
  arguments; templates include a read-call shape and variable names.
41
41
 
42
42
  Prompt candidates show the owning server, name, description, argument metadata,
43
- and a user-only command such as `/mcp prompt docs explain`. The assistant can
43
+ and a user-only command such as `/mcp prompt docs explain`. The model can
44
44
  recommend this command but can't retrieve or run prompts through `mcp_tools`.
45
45
  Only you can [select, preview, and use a prompt](commands.md#use-server-prompts).
46
46
 
@@ -62,7 +62,7 @@ mcp_tools({ activate: ["linear.list_teams", "linear.get_team"] })
62
62
 
63
63
  Activation accepts 1–50 exact `server.tool` or `mcp__server__tool` identifiers,
64
64
  ignores duplicates, and works without a prior search. Typos never activate fuzzy
65
- matches: failures list nearby catalog names when available so the assistant can
65
+ matches: failures list nearby catalog names when available so the model can
66
66
  retry with an exact identifier. Each identifier reports `loaded`, `already loaded`,
67
67
  or `not loaded` with a reason. Partial success keeps the tools that loaded.
68
68
 
@@ -74,7 +74,7 @@ tool. There is no invocation proxy. Previously loaded tools remain available;
74
74
  ## Read resources as context
75
75
 
76
76
  For a request such as “Use the authentication guide to explain this API,” the
77
- assistant can discover and read relevant context:
77
+ model can discover and read relevant context:
78
78
 
79
79
  ```js
80
80
  mcp_tools({ query: "authentication guide", kind: "resources" })
@@ -87,7 +87,7 @@ request, even for `file:` or `https:` URIs. There is no fallback when the server
87
87
  can't read the URI. The server still controls which data it returns.
88
88
 
89
89
  Tool-returned resource links include an exact `mcp_tools({read: ...})` call. The
90
- assistant can read such links directly, without prior discovery or activation;
90
+ model can read such links directly, without prior discovery or activation;
91
91
  linked resources don't have to appear in the catalog.
92
92
 
93
93
  Reading attaches content as the tool result itself, not as a second message. The
@@ -100,7 +100,7 @@ limits and private spill files.
100
100
 
101
101
  Discovery also lists URI templates, such as `schema://tables/{table}`, without
102
102
  enumerating every possible table. Templates have a `[template]` label, variable
103
- names, and a read-call shape. The assistant supplies known argument values:
103
+ names, and a read-call shape. The model supplies known argument values:
104
104
 
105
105
  ```js
106
106
  mcp_tools({ query: "table schema", server: "warehouse", kind: "resources" })
@@ -117,7 +117,7 @@ The `read` object accepts either `uri` or `template` plus `arguments`, never bot
117
117
  The selected server must advertise the exact template. The official SDK expands
118
118
  strings or string arrays into a concrete URI, then reads it through that same
119
119
  server. Template variables aren't an input schema: no required fields or allowed
120
- values are inferred. The assistant should use values from your request or prior
120
+ values are inferred. The model should use values from your request or prior
121
121
  results and ask you when a needed value is unknown rather than inventing an
122
122
  identifier.
123
123
 
@@ -128,7 +128,7 @@ content; see [result display](behavior.md#result-display).
128
128
 
129
129
  ## Complete resource arguments
130
130
 
131
- The assistant can ask a server for suggested values for one advertised template
131
+ The model can ask a server for suggested values for one advertised template
132
132
  variable:
133
133
 
134
134
  ```js
@@ -142,7 +142,7 @@ mcp_tools({
142
142
  ```
143
143
 
144
144
  `value` is the current prefix and can be empty. For dependent suggestions, the
145
- assistant can add `arguments: { knownVariable: "value" }` inside `complete`.
145
+ model can add `arguments: { knownVariable: "value" }` inside `complete`.
146
146
  These context values must be strings, not arrays. The server must advertise
147
147
  completion support and the exact template; the variable must occur in that
148
148
  template.
@@ -27,16 +27,16 @@ unavailable server isn't an empty catalog.
27
27
  | `connection_failed` | Server executable, working directory, endpoint, network, and TLS configuration. |
28
28
  | `timeout` | Server responsiveness and the applicable request, secret-command, or OAuth time limit. |
29
29
  | `protocol_error` | Server compatibility and the `protocol` setting. |
30
- | `tool_changed` | Check server filters and ask the assistant to activate the exact tool again for its current schema. Run `/mcp reload` if connection configuration changed. |
30
+ | `tool_changed` | Check server filters and ask the model to activate the exact tool again for its current schema. Run `/mcp reload` if connection configuration changed. |
31
31
  | `tool_error` | The server's tool result and inputs; verify the outcome before retrying. |
32
- | `resource_invalid` | The assistant needs an exact absolute resource URI from discovery or a tool-returned link. |
32
+ | `resource_invalid` | The model needs an exact absolute resource URI from discovery or a tool-returned link. |
33
33
  | `resource_not_found` | Refresh resource metadata or obtain a new link. |
34
- | `resources_unsupported` | Ask the assistant to use the server's tools instead, or choose a resource-capable server. |
35
- | `completions_unsupported` | Supply known template values; the assistant should ask you if a value is missing. |
36
- | `completion_invalid` | The assistant needs an advertised template variable and a string prefix. |
37
- | `subscriptions_unsupported` | Choose a server with subscription support, or ask the assistant to read when needed. |
34
+ | `resources_unsupported` | Ask the model to use the server's tools instead, or choose a resource-capable server. |
35
+ | `completions_unsupported` | Supply known template values; the model should ask you if a value is missing. |
36
+ | `completion_invalid` | The model needs an advertised template variable and a string prefix. |
37
+ | `subscriptions_unsupported` | Choose a server with subscription support, or ask the model to read when needed. |
38
38
  | `subscription_limit` | Remove a watch before adding another; the limit is 50 per connection. |
39
- | `catalog_changed` | Ask the assistant to retry discovery after the server catalog settles. |
39
+ | `catalog_changed` | Ask the model to retry discovery after the server catalog settles. |
40
40
  | `oauth_failed` | An unclassified OAuth failure. Check the service's requirements and `/mcp get <server>` for the client type, scopes, and callback URL. Only public/PKCE clients are supported, not clients requiring a secret. |
41
41
  | `oauth_client_required` | The server doesn't support dynamic registration. Configure `oauthClientId` for a registered public/PKCE client and register the exact callback URL. Reload, then log in again. |
42
42
  | `oauth_registration_rejected` | The server rejected dynamic registration. Check public/native client eligibility and the callback URL, or configure an approved public client ID. |
@@ -69,6 +69,21 @@ remain visible as content, even when the tool reports an error; they aren't
69
69
  sanitized transport diagnostics. Tool-call failures aren't replayed automatically;
70
70
  verify the outcome before retrying.
71
71
 
72
+ ## Missing project servers
73
+
74
+ A project's `.mcp.json` loads only after an explicit trust decision, even in
75
+ folders that Pi otherwise trusts implicitly. Headless sessions ignore it when no
76
+ decision exists. Run Pi's `/trust` command to save a decision, then run
77
+ `/mcp reload`. See [project trust](behavior.md#project-trust).
78
+
79
+ ## Missing server dependencies
80
+
81
+ If a stdio server reports a missing browser, SDK, credential, or configuration
82
+ file that works in your shell, it probably reads an environment variable that
83
+ it doesn't receive. Servers don't inherit Pi's environment; pass the variable
84
+ through `env`, for example `PLAYWRIGHT_BROWSERS_PATH`. See
85
+ [files and transports](configuration.md#files-and-transports).
86
+
72
87
  ## Large results
73
88
 
74
89
  Text output is limited to 2,000 lines or 50 KiB, including resource reads and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-client",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "MCP tools for Pi, discovered on demand and called natively through the official SDK.",
5
5
  "type": "module",
6
6
  "license": "MIT",