@piagent/platform 1.1.9 → 1.2.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +2 -2
  3. package/docs/command-reference-vietnamese.md +13 -4
  4. package/docs/distribution-standard.md +7 -7
  5. package/docs/mcp-and-tools.md +108 -2
  6. package/docs/model-options.md +10 -8
  7. package/docs/operator-manual-vietnamese.md +12 -12
  8. package/docs/publishing-for-teams.md +1 -0
  9. package/docs/quickstart-vietnamese.md +2 -2
  10. package/docs/release-install-policy.md +6 -6
  11. package/docs/security-threat-model.md +1 -1
  12. package/docs/team-onboarding.md +4 -4
  13. package/docs/vercel-docs-site.md +34 -4
  14. package/docs-site/architecture.html +152 -0
  15. package/docs-site/assets/docs.css +1001 -0
  16. package/docs-site/assets/docs.js +84 -0
  17. package/docs-site/commands.html +203 -0
  18. package/docs-site/content/architecture.html +29 -0
  19. package/docs-site/content/commands.html +80 -0
  20. package/docs-site/content/index.html +80 -0
  21. package/docs-site/content/mcp-approval.html +81 -0
  22. package/docs-site/content/mcp-auth.html +76 -0
  23. package/docs-site/content/mcp-servers.html +111 -0
  24. package/docs-site/content/mcp.html +120 -0
  25. package/docs-site/content/permissions.html +54 -0
  26. package/docs-site/content/profiles.html +68 -0
  27. package/docs-site/content/quickstart.html +144 -0
  28. package/docs-site/content/runtime.html +45 -0
  29. package/docs-site/content/scope.html +71 -0
  30. package/docs-site/content/security.html +51 -0
  31. package/docs-site/content/team.html +23 -0
  32. package/docs-site/content/workflows.html +47 -0
  33. package/docs-site/index.html +131 -1534
  34. package/docs-site/mcp-approval.html +204 -0
  35. package/docs-site/mcp-auth.html +199 -0
  36. package/docs-site/mcp-servers.html +234 -0
  37. package/docs-site/mcp.html +243 -0
  38. package/docs-site/permissions.html +177 -0
  39. package/docs-site/profiles.html +191 -0
  40. package/docs-site/quickstart.html +267 -0
  41. package/docs-site/runtime.html +168 -0
  42. package/docs-site/scope.html +194 -0
  43. package/docs-site/security.html +174 -0
  44. package/docs-site/team.html +142 -0
  45. package/docs-site/workflows.html +170 -0
  46. package/package.json +4 -2
  47. package/packages/piagent-core/capabilities/capability-core.js +17 -1
  48. package/packages/piagent-core/extensions/document-intake.ts +5 -5
  49. package/packages/piagent-core/extensions/piagent-guard.ts +316 -0
  50. package/packages/piagent-core/mcp/mcp-approval-store.js +175 -0
  51. package/packages/piagent-core/mcp/mcp-auth-readiness.js +197 -0
  52. package/packages/piagent-core/mcp/mcp-command-actions.js +291 -0
  53. package/packages/piagent-core/mcp/mcp-config-layers.js +133 -0
  54. package/packages/piagent-core/mcp/mcp-server-catalog.js +150 -0
  55. package/packages/piagent-core/mcp/mcp-server-entry.js +242 -0
  56. package/packages/piagent-core/mcp/mcp-session-view.js +185 -0
  57. package/packages/piagent-core/package.json +7 -3
  58. package/packages/piagent-core/prompts/model-options.md +8 -8
  59. package/scripts/build-docs-site.mjs +421 -0
  60. package/scripts/configure-model-scope.sh +2 -2
  61. package/scripts/configure-subagents.sh +1 -1
  62. package/scripts/install-global.sh +5 -5
  63. package/scripts/mcp-manage.mjs +567 -0
  64. package/scripts/piagent-cli.mjs +1 -1
  65. package/scripts/preview-docs-site.mjs +88 -0
  66. package/scripts/setup.sh +2 -2
  67. package/scripts/team-doctor.sh +18 -2
  68. package/scripts/verify-local.sh +46 -19
  69. package/scripts/verify-release-identity.mjs +8 -2
  70. package/scripts/configure-mcp.sh +0 -349
@@ -137,7 +137,7 @@ function runtimeMatrixStatus() {
137
137
  surface: `${platform}/${arch}`,
138
138
  status: "outside-release-matrix",
139
139
  teamRolloutReady: false,
140
- note: "This runtime is outside the documented v1.1.9 support matrix."
140
+ note: "This runtime is outside the documented v1.2.0 support matrix."
141
141
  };
142
142
  }
143
143
 
@@ -169,7 +169,7 @@ for (const rel of [
169
169
  "templates/project/.pi/memory/memory_summary.md",
170
170
  "templates/project/.pi/memory/MEMORY.md",
171
171
  "scripts/setup.sh",
172
- "scripts/configure-mcp.sh",
172
+ "scripts/mcp-manage.mjs",
173
173
  "scripts/configure-subagents.sh"
174
174
  ]) {
175
175
  if (!exists(rel)) errors.push(`missing platform file: ${rel}`);
@@ -247,6 +247,22 @@ if (piHasMcpAdapter && totalMcpServers === 0) {
247
247
  warnings.push("Pi MCP adapter is installed but no MCP servers are configured; run `piagent-mcp --preset core --scope global` or `/mcp setup`");
248
248
  }
249
249
 
250
+ // Counting servers says nothing about whether any of them can answer. A server
251
+ // whose token is not exported, or which this repository defines and nobody
252
+ // approved, is configured and unusable, and finding that out when a call fails
253
+ // is the thing this check exists to prevent.
254
+ try {
255
+ const { collectServers } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "mcp", "mcp-config-layers.js")).href);
256
+ const { evaluateServerReadiness, BLOCKING_STATES } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "mcp", "mcp-auth-readiness.js")).href);
257
+ for (const server of collectServers({ projectPath })) {
258
+ const readiness = evaluateServerReadiness(server, { projectPath });
259
+ if (!BLOCKING_STATES.has(readiness.state)) continue;
260
+ warnings.push(`MCP server ${server.name} (${server.scope}) is configured but not usable: ${readiness.detail}${readiness.remedy ? `; ${readiness.remedy}` : ""}`);
261
+ }
262
+ } catch (error) {
263
+ warnings.push(`MCP readiness check could not run: ${error instanceof Error ? error.message : String(error)}`);
264
+ }
265
+
250
266
  const subagentConfigPath = path.join(process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".pi", "agent"), "extensions", "subagent", "config.json");
251
267
  const subagentConfig = readJsonIfPresent(subagentConfigPath);
252
268
  if (piHasSubagents && !subagentConfig) {
@@ -126,9 +126,16 @@ required_files=(
126
126
  "$ROOT/docs/capability-packs.md"
127
127
  "$ROOT/docs/runtime-policy-design.md"
128
128
  "$ROOT/docs-site/index.html"
129
+ "$ROOT/docs-site/mcp.html"
129
130
  "$ROOT/docs-site/favicon.svg"
130
131
  "$ROOT/docs-site/assets/piagent-logo.svg"
132
+ "$ROOT/docs-site/assets/docs.css"
133
+ "$ROOT/docs-site/assets/docs.js"
134
+ "$ROOT/docs-site/content/index.html"
135
+ "$ROOT/docs-site/content/mcp.html"
131
136
  "$ROOT/docs-site/vercel.json"
137
+ "$ROOT/scripts/build-docs-site.mjs"
138
+ "$ROOT/scripts/preview-docs-site.mjs"
132
139
  "$ROOT/schemas/project-profile.schema.json"
133
140
  "$ROOT/schemas/task-contract.schema.json"
134
141
  "$ROOT/schemas/capability-pack.schema.json"
@@ -160,7 +167,7 @@ required_files=(
160
167
  "$ROOT/scripts/pi-auto.sh"
161
168
  "$ROOT/scripts/pi-model-catalog.sh"
162
169
  "$ROOT/scripts/configure-model-scope.sh"
163
- "$ROOT/scripts/configure-mcp.sh"
170
+ "$ROOT/scripts/mcp-manage.mjs"
164
171
  "$ROOT/scripts/configure-subagents.sh"
165
172
  "$ROOT/scripts/capability-catalog.mjs"
166
173
  "$ROOT/scripts/migrate-project-state.mjs"
@@ -182,6 +189,9 @@ required_files=(
182
189
  "$ROOT/tests/document-intake.test.mjs"
183
190
  "$ROOT/tests/runtime-advisories.test.mjs"
184
191
  "$ROOT/scripts/check-runtime-advisories.mjs"
192
+ "$ROOT/packages/piagent-core/mcp/mcp-session-view.js"
193
+ "$ROOT/packages/piagent-core/mcp/mcp-command-actions.js"
194
+ "$ROOT/tests/mcp-session-command.test.mjs"
185
195
  "$ROOT/tests/migrate-project-state.test.mjs"
186
196
  "$ROOT/tests/uninstall-global.test.mjs"
187
197
  "$ROOT/tests/import-agent-instructions.test.mjs"
@@ -413,33 +423,33 @@ require_documented "gpt-5.6" "$ROOT/README.md" "$ROOT/docs/model-options.md" "$R
413
423
  require_documented "claude-fable-5" "$ROOT/README.md" "$ROOT/docs/model-options.md" "$ROOT/packages/piagent-core/prompts/model-options.md"
414
424
  require_documented "piagent-models" "$ROOT/README.md" "$ROOT/docs/model-options.md"
415
425
  require_documented "enabledModels" "$ROOT/templates/global/settings.json" "$ROOT/docs/model-options.md" "$ROOT/scripts/configure-model-scope.sh"
416
- require_documented "piagent-mcp" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/configure-mcp.sh"
426
+ require_documented "piagent-mcp" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/mcp-manage.mjs"
417
427
  require_documented "pi-mcp-adapter" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/install-global.sh"
418
428
  require_documented "piagent-subagents" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/scripts/configure-subagents.sh"
419
429
  require_documented "subagents-fleet" "$ROOT/docs/command-reference-vietnamese.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/README.md"
420
430
  require_documented "health check" "$ROOT/docs/command-reference-vietnamese.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/README.md"
421
431
  require_documented "pi-subagents" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/scripts/install-global.sh" "$ROOT/scripts/setup.sh"
422
432
  require_documented "piagent-scout" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/packages/piagent-core/subagents"
423
- grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/scripts/configure-mcp.sh" >/dev/null
433
+ grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
424
434
  grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/docs/mcp-and-tools.md" >/dev/null
425
- grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/scripts/configure-mcp.sh" >/dev/null
435
+ grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
426
436
  grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/docs/mcp-and-tools.md" >/dev/null
427
- grep -F "@playwright/mcp@0.0.78" "$ROOT/scripts/configure-mcp.sh" >/dev/null
437
+ grep -F "@playwright/mcp@0.0.78" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
428
438
  grep -F "@playwright/mcp@0.0.78" "$ROOT/docs/mcp-and-tools.md" >/dev/null
429
- require_documented "https://mcp.figma.com/mcp" "$ROOT/scripts/configure-mcp.sh" "$ROOT/docs/mcp-and-tools.md"
430
- grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/scripts/configure-mcp.sh" >/dev/null
439
+ require_documented "https://mcp.figma.com/mcp" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" "$ROOT/docs/mcp-and-tools.md"
440
+ grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
431
441
  grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/docs/mcp-and-tools.md" >/dev/null
432
- grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/scripts/configure-mcp.sh" >/dev/null
442
+ grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
433
443
  grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
434
- grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/scripts/configure-mcp.sh" >/dev/null
444
+ grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
435
445
  grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
436
- grep -F 'CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: "1"' "$ROOT/scripts/configure-mcp.sh" >/dev/null
446
+ grep -F 'CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: "1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
437
447
  grep -F '"CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS": "1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
438
- grep -F 'CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: "1"' "$ROOT/scripts/configure-mcp.sh" >/dev/null
448
+ grep -F 'CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: "1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
439
449
  grep -F '"CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS": "1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
440
- grep -F 'configure-mcp.sh" --scope global --preset "$MCP_PRESET" --replace' "$ROOT/scripts/install-global.sh" >/dev/null
441
- grep -F 'piagent-mcp --preset core --scope global --replace' "$ROOT/scripts/configure-mcp.sh" >/dev/null
442
- if grep -E '@latest|"@upstash/context7-mcp"|"chrome-devtools-mcp"|"@playwright/mcp"|"ghcr\.io/github/github-mcp-server"' "$ROOT/scripts/configure-mcp.sh" >/dev/null; then
450
+ grep -F 'mcp-manage.mjs" --scope global --preset "$MCP_PRESET" --replace' "$ROOT/scripts/install-global.sh" >/dev/null
451
+ grep -F 'piagent-mcp --preset core --scope global --replace' "$ROOT/scripts/mcp-manage.mjs" >/dev/null
452
+ if grep -E '@latest|"@upstash/context7-mcp"|"chrome-devtools-mcp"|"@playwright/mcp"|"ghcr\.io/github/github-mcp-server"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null; then
443
453
  echo "MCP production presets contain a mutable dependency source"
444
454
  exit 1
445
455
  fi
@@ -471,6 +481,9 @@ const terms = [
471
481
  ["mit", "suhiko"].join(""),
472
482
  ["Cod", "ex CLI"].join(""),
473
483
  ["Claude", " CLI"].join(""),
484
+ ["Claude", " Code"].join(""),
485
+ ["claude", " mcp"].join(""),
486
+ ["codex", " mcp"].join(""),
474
487
  ["Cod", "ex-inspired"].join(""),
475
488
  ["Cod", "ex-grade"].join(""),
476
489
  ["Pi vs ", "Codex"].join(""),
@@ -485,12 +498,21 @@ console.log(terms.map(escapeRegex).join("|"));
485
498
  NODE
486
499
  )"
487
500
 
501
+ # Every surface an outsider reads: the published docs, the site, and the strings
502
+ # the runtime prints back at an operator. The site is checked at its source,
503
+ # since `build-docs-site.mjs --check` separately proves the committed pages match
504
+ # it; the builder itself is listed too, because navigation labels and page titles
505
+ # live there rather than in a content fragment.
488
506
  if grep -R -E -i \
489
507
  "$public_wording_pattern" \
490
508
  "$ROOT/README.md" \
491
509
  "$ROOT/docs" \
510
+ "$ROOT/docs-site/content" \
511
+ "$ROOT/scripts/build-docs-site.mjs" \
492
512
  "$ROOT/packages/piagent-core/README.md" \
493
513
  "$ROOT/packages/piagent-core/prompts" \
514
+ "$ROOT/packages/piagent-core/mcp" \
515
+ "$ROOT/packages/piagent-core/extensions" \
494
516
  "$ROOT/templates/project/AGENTS.md" >/dev/null; then
495
517
  echo "Public docs contain non-neutral platform wording"
496
518
  exit 1
@@ -509,6 +531,8 @@ node --check "$ROOT/scripts/migrate-project-state.mjs" >/dev/null
509
531
  node --check "$ROOT/scripts/import-agent-instructions.mjs" >/dev/null
510
532
  node --check "$ROOT/scripts/check-runtime-advisories.mjs" >/dev/null
511
533
  node --check "$ROOT/scripts/check-published-site.mjs" >/dev/null
534
+ node --check "$ROOT/scripts/build-docs-site.mjs" >/dev/null
535
+ node --check "$ROOT/scripts/preview-docs-site.mjs" >/dev/null
512
536
  run_quietly "npm test" npm test
513
537
  if [[ -x "$ROOT/node_modules/.bin/tsc" ]]; then
514
538
  run_quietly "npm run typecheck" npm run typecheck
@@ -519,7 +543,9 @@ bash -n "$ROOT/scripts/pi-session-stats.sh"
519
543
  bash -n "$ROOT/scripts/pi-auto.sh"
520
544
  bash -n "$ROOT/scripts/pi-model-catalog.sh"
521
545
  bash -n "$ROOT/scripts/configure-model-scope.sh"
522
- bash -n "$ROOT/scripts/configure-mcp.sh"
546
+ node --check "$ROOT/scripts/mcp-manage.mjs"
547
+ node --check "$ROOT/packages/piagent-core/mcp/mcp-session-view.js"
548
+ node --check "$ROOT/packages/piagent-core/mcp/mcp-command-actions.js"
523
549
  bash -n "$ROOT/scripts/configure-subagents.sh"
524
550
  bash -n "$ROOT/scripts/install-global.sh"
525
551
  bash -n "$ROOT/scripts/init-project.sh"
@@ -531,8 +557,8 @@ else
531
557
  bash "$ROOT/scripts/pi-model-catalog.sh" --json >/dev/null
532
558
  fi
533
559
  bash "$ROOT/scripts/configure-model-scope.sh" --dry-run --preset full --default-model openai-codex/gpt-5.5:xhigh >/dev/null
534
- bash "$ROOT/scripts/configure-mcp.sh" --list >/dev/null
535
- bash "$ROOT/scripts/configure-mcp.sh" --dry-run --preset popular --scope project --project "$ROOT" >/dev/null
560
+ node "$ROOT/scripts/mcp-manage.mjs" --list >/dev/null
561
+ node "$ROOT/scripts/mcp-manage.mjs" --dry-run --preset popular --scope project --project "$ROOT" >/dev/null
536
562
  node --input-type=module - "$ROOT" <<'NODE'
537
563
  import assert from "node:assert/strict";
538
564
  import { execFileSync } from "node:child_process";
@@ -553,8 +579,8 @@ try {
553
579
  internal: { url: "https://mcp.example.invalid/api" }
554
580
  }
555
581
  }, null, 2)}\n`);
556
- execFileSync("bash", [
557
- path.join(root, "scripts", "configure-mcp.sh"),
582
+ execFileSync(process.execPath, [
583
+ path.join(root, "scripts", "mcp-manage.mjs"),
558
584
  "--config", configPath,
559
585
  "--preset", "popular",
560
586
  "--replace"
@@ -577,6 +603,7 @@ bash "$ROOT/scripts/configure-subagents.sh" --list >/dev/null
577
603
  bash "$ROOT/scripts/configure-subagents.sh" --dry-run --preset safe >/dev/null
578
604
  bash "$ROOT/scripts/runtime-policy-smoke.sh" >/dev/null
579
605
 
606
+ node "$ROOT/scripts/build-docs-site.mjs" --check >/dev/null
580
607
  node "$ROOT/scripts/capability-catalog.mjs" catalog --check >/dev/null
581
608
  node "$ROOT/scripts/capability-catalog.mjs" doctor --profile "$ROOT/.pi/piagent-profile.json" --lock "$ROOT/.pi/piagent-profile.lock.json" >/dev/null
582
609
  node "$ROOT/scripts/capability-catalog.mjs" doctor --profile "$ROOT/adapters/generic/profile.json" >/dev/null
@@ -71,8 +71,14 @@ if (capabilityLock.core?.packageVersion !== version) fail("capability lock packa
71
71
  const changelog = fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8");
72
72
  if (!changelog.includes(`## ${expectedTag} -`)) fail(`CHANGELOG.md has no ${expectedTag} release section`);
73
73
 
74
- const docsSite = fs.readFileSync(path.join(root, "docs-site/index.html"), "utf8");
75
- if (!docsSite.includes(`${expectedTag} docs`)) fail(`docs-site version badge does not identify ${expectedTag}`);
74
+ // The badge is generated onto every page, so a stale build shows up as one page
75
+ // still carrying the previous version rather than as a single missed edit.
76
+ const docsPages = fs.readdirSync(path.join(root, "docs-site")).filter((name) => name.endsWith(".html"));
77
+ if (docsPages.length === 0) fail("docs-site has no generated pages");
78
+ for (const page of docsPages) {
79
+ const html = fs.readFileSync(path.join(root, "docs-site", page), "utf8");
80
+ if (!html.includes(`${expectedTag} docs`)) fail(`docs-site/${page} version badge does not identify ${expectedTag}`);
81
+ }
76
82
 
77
83
  if (options.tag) {
78
84
  if (options.tag !== expectedTag) fail(`release tag ${options.tag} does not match package version ${expectedTag}`);
@@ -1,349 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- usage() {
5
- cat <<'USAGE'
6
- Usage:
7
- scripts/configure-mcp.sh [options]
8
-
9
- Purpose:
10
- Merge a safe MCP baseline into a standard MCP config file for Pi and other MCP clients.
11
- Secrets are never written directly; configs reference environment variables.
12
-
13
- Options:
14
- --scope <global|pi-global|project|pi-project>
15
- Config target (default: global)
16
- --project <path> Project path for project/pi-project scopes (default: current directory)
17
- --config <path> Explicit config path; overrides --scope/--project
18
- --preset <minimal|docs|browser|github|design|design-local|web|core|popular|all>
19
- MCP preset to merge (default: core)
20
- --replace Replace existing definitions for servers in the preset
21
- --dry-run Print the merged JSON without writing
22
- --list Print available presets/servers
23
- -h, --help
24
-
25
- Recommended presets:
26
- core Context7 docs + Chrome DevTools + GitHub
27
- popular core + Playwright + Figma remote MCP
28
- all popular + Figma desktop/local MCP
29
-
30
- Examples:
31
- piagent-mcp --preset core --scope global --replace
32
- piagent-mcp --preset popular --scope project --project /path/to/repo
33
- piagent-mcp --preset design-local --scope project
34
-
35
- After writing config:
36
- pi
37
- /mcp
38
- /mcp setup
39
- /mcp reconnect
40
- USAGE
41
- }
42
-
43
- PLATFORM_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
44
- SCOPE="global"
45
- PROJECT_PATH="$PWD"
46
- CONFIG_PATH=""
47
- PRESET="core"
48
- REPLACE=false
49
- DRY_RUN=false
50
- LIST=false
51
- # Answers "is this preset name real?" and writes nothing, so a caller can reject
52
- # a bad preset before it installs anything.
53
- VALIDATE_PRESET=false
54
-
55
- while [[ $# -gt 0 ]]; do
56
- case "$1" in
57
- --scope)
58
- SCOPE="${2:-}"
59
- shift 2
60
- ;;
61
- --project)
62
- PROJECT_PATH="${2:-}"
63
- shift 2
64
- ;;
65
- --config)
66
- CONFIG_PATH="${2:-}"
67
- shift 2
68
- ;;
69
- --preset)
70
- PRESET="${2:-}"
71
- shift 2
72
- ;;
73
- --replace)
74
- REPLACE=true
75
- shift
76
- ;;
77
- --dry-run)
78
- DRY_RUN=true
79
- shift
80
- ;;
81
- --list)
82
- LIST=true
83
- shift
84
- ;;
85
- --validate-preset)
86
- VALIDATE_PRESET=true
87
- shift
88
- ;;
89
- -h|--help)
90
- usage
91
- exit 0
92
- ;;
93
- *)
94
- echo "Unknown option: $1" >&2
95
- usage >&2
96
- exit 2
97
- ;;
98
- esac
99
- done
100
-
101
- if [[ -z "$PRESET" ]]; then
102
- echo "FAIL: --preset cannot be empty" >&2
103
- exit 2
104
- fi
105
-
106
- if [[ -z "$CONFIG_PATH" ]]; then
107
- case "$SCOPE" in
108
- global)
109
- CONFIG_PATH="${XDG_CONFIG_HOME:-"${HOME}/.config"}/mcp/mcp.json"
110
- ;;
111
- pi-global)
112
- CONFIG_PATH="${PI_CODING_AGENT_DIR:-"${HOME}/.pi/agent"}/mcp.json"
113
- ;;
114
- project)
115
- CONFIG_PATH="$PROJECT_PATH/.mcp.json"
116
- ;;
117
- pi-project)
118
- CONFIG_PATH="$PROJECT_PATH/.pi/mcp.json"
119
- ;;
120
- *)
121
- echo "FAIL: unsupported scope: $SCOPE" >&2
122
- usage >&2
123
- exit 2
124
- ;;
125
- esac
126
- fi
127
-
128
- node --input-type=module - "$CONFIG_PATH" "$PRESET" "$REPLACE" "$DRY_RUN" "$LIST" "$VALIDATE_PRESET" <<'NODE'
129
- import fs from "node:fs";
130
- import path from "node:path";
131
-
132
- const [configPath, presetName, replaceRaw, dryRunRaw, listRaw, validateRaw] = process.argv.slice(2);
133
- const replace = replaceRaw === "true";
134
- const dryRun = dryRunRaw === "true";
135
- const listOnly = listRaw === "true";
136
- const validateOnly = validateRaw === "true";
137
-
138
- const baselineSettings = {
139
- toolPrefix: "server",
140
- directTools: false,
141
- idleTimeout: 10,
142
- outputGuard: true
143
- };
144
-
145
- const servers = {
146
- context7: {
147
- description: "Official Context7 MCP server for up-to-date library/framework docs.",
148
- config: {
149
- command: "npx",
150
- args: ["-y", "@upstash/context7-mcp@3.2.4"],
151
- env: {
152
- CONTEXT7_API_KEY: "${CONTEXT7_API_KEY}"
153
- },
154
- lifecycle: "lazy",
155
- directTools: false
156
- }
157
- },
158
- "chrome-devtools": {
159
- description: "Chrome DevTools MCP for runtime browser inspection, console logs, screenshots, and performance checks.",
160
- config: {
161
- command: "npx",
162
- args: ["-y", "chrome-devtools-mcp@1.6.0", "--no-performance-crux"],
163
- env: {
164
- CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: "1",
165
- CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: "1"
166
- },
167
- lifecycle: "lazy",
168
- directTools: false
169
- }
170
- },
171
- playwright: {
172
- description: "Playwright MCP for browser automation and UI verification workflows.",
173
- config: {
174
- command: "npx",
175
- args: ["-y", "@playwright/mcp@0.0.78"],
176
- lifecycle: "lazy",
177
- directTools: false
178
- }
179
- },
180
- github: {
181
- description: "Official GitHub MCP server via Docker. Requires Docker and GITHUB_PERSONAL_ACCESS_TOKEN when used.",
182
- config: {
183
- command: "docker",
184
- args: [
185
- "run",
186
- "-i",
187
- "--rm",
188
- "-e",
189
- "GITHUB_PERSONAL_ACCESS_TOKEN",
190
- "-e",
191
- "GITHUB_READ_ONLY=1",
192
- "-e",
193
- "GITHUB_LOCKDOWN_MODE=1",
194
- "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"
195
- ],
196
- env: {
197
- GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}"
198
- },
199
- lifecycle: "lazy",
200
- directTools: false
201
- }
202
- },
203
- figma: {
204
- description: "Figma remote MCP server. Requires Figma OAuth from the MCP panel when first used.",
205
- config: {
206
- url: "https://mcp.figma.com/mcp",
207
- auth: "oauth",
208
- lifecycle: "lazy",
209
- directTools: false
210
- }
211
- },
212
- "figma-desktop": {
213
- description: "Figma desktop/local MCP server. Requires Figma desktop Dev Mode MCP enabled.",
214
- config: {
215
- url: "http://127.0.0.1:3845/mcp",
216
- lifecycle: "lazy",
217
- directTools: false
218
- }
219
- }
220
- };
221
-
222
- // Writing a server definition does not make it usable. What each one still
223
- // needs from the operator is stated here so it can be printed at the moment the
224
- // definition is written, rather than living only in a description string that
225
- // shows up under --list.
226
- const prerequisites = {
227
- context7: ["optional: export CONTEXT7_API_KEY for a higher documentation quota"],
228
- "chrome-devtools": ["a local Chrome installation"],
229
- github: [
230
- "Docker running",
231
- "export GITHUB_PERSONAL_ACCESS_TOKEN with the scopes you intend to use"
232
- ],
233
- playwright: ["browsers installed on first use (npx playwright install)"],
234
- figma: ["Figma OAuth, completed from the Pi MCP panel on first use"],
235
- "figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"]
236
- };
237
-
238
- const presets = {
239
- minimal: [],
240
- docs: ["context7"],
241
- browser: ["chrome-devtools", "playwright"],
242
- github: ["github"],
243
- design: ["figma"],
244
- "design-local": ["figma-desktop"],
245
- web: ["context7", "chrome-devtools", "playwright"],
246
- core: ["context7", "chrome-devtools", "github"],
247
- popular: ["context7", "chrome-devtools", "playwright", "github", "figma"],
248
- all: ["context7", "chrome-devtools", "playwright", "github", "figma", "figma-desktop"]
249
- };
250
-
251
- if (listOnly) {
252
- console.log(JSON.stringify({
253
- presets,
254
- servers: Object.fromEntries(Object.entries(servers).map(([name, value]) => [
255
- name,
256
- { description: value.description, requires: prerequisites[name] ?? [] }
257
- ]))
258
- }, null, 2));
259
- process.exit(0);
260
- }
261
-
262
- if (!Object.hasOwn(presets, presetName)) {
263
- console.error(`FAIL: unknown preset: ${presetName}`);
264
- console.error(`Available presets: ${Object.keys(presets).join(", ")}`);
265
- process.exit(2);
266
- }
267
-
268
- // Answering the name question and stopping, so a caller can ask before it
269
- // installs rather than discovering the typo after two packages have landed.
270
- if (validateOnly) {
271
- process.stdout.write(`${presetName}\n`);
272
- process.exit(0);
273
- }
274
-
275
- let current = {};
276
- if (fs.existsSync(configPath)) {
277
- try {
278
- current = JSON.parse(fs.readFileSync(configPath, "utf8"));
279
- } catch (error) {
280
- console.error(`FAIL: cannot parse existing MCP config: ${configPath}`);
281
- console.error(error instanceof Error ? error.message : String(error));
282
- process.exit(1);
283
- }
284
- }
285
-
286
- if (!current || typeof current !== "object" || Array.isArray(current)) current = {};
287
- if (!current.settings || typeof current.settings !== "object" || Array.isArray(current.settings)) current.settings = {};
288
- if (!current.mcpServers || typeof current.mcpServers !== "object" || Array.isArray(current.mcpServers)) current.mcpServers = {};
289
-
290
- current.settings = {
291
- ...baselineSettings,
292
- ...current.settings
293
- };
294
-
295
- const added = [];
296
- const kept = [];
297
- const replaced = [];
298
- for (const name of presets[presetName]) {
299
- if (!Object.hasOwn(servers, name)) {
300
- throw new Error(`Preset ${presetName} references missing server ${name}`);
301
- }
302
- if (Object.hasOwn(current.mcpServers, name)) {
303
- if (replace) {
304
- current.mcpServers[name] = servers[name].config;
305
- replaced.push(name);
306
- } else {
307
- kept.push(name);
308
- }
309
- } else {
310
- current.mcpServers[name] = servers[name].config;
311
- added.push(name);
312
- }
313
- }
314
-
315
- const output = `${JSON.stringify(current, null, 2)}\n`;
316
- const report = {
317
- configPath,
318
- preset: presetName,
319
- dryRun,
320
- replace,
321
- added,
322
- kept,
323
- replaced,
324
- serverCount: Object.keys(current.mcpServers).length
325
- };
326
-
327
- // stdout stays machine-readable; the operator note goes to stderr.
328
- function reportPrerequisites() {
329
- const lines = [];
330
- for (const name of presets[presetName]) {
331
- for (const item of prerequisites[name] ?? []) lines.push(` ${name}: ${item}`);
332
- }
333
- if (lines.length === 0) return;
334
- console.error("");
335
- console.error("Server definitions are written. Each still needs this before it can connect:");
336
- for (const line of lines) console.error(line);
337
- console.error("Servers connect lazily, so nothing above is checked until the first call.");
338
- }
339
-
340
- if (dryRun) {
341
- console.log(JSON.stringify(report, null, 2));
342
- console.log(output);
343
- } else {
344
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
345
- fs.writeFileSync(configPath, output);
346
- console.log(JSON.stringify(report, null, 2));
347
- reportPrerequisites();
348
- }
349
- NODE