@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +2 -2
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +140 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +286 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4151 -489
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
/**
|
|
3
|
+
* Pugi MCP server (β4 M2) — exposes Pugi's native tool surface to other
|
|
4
|
+
* agents (the upstream tool, OpenCode, Codex CLI, any client that speaks
|
|
5
|
+
* MCP).
|
|
6
|
+
*
|
|
7
|
+
* Transport-agnostic core. The stdio entry-point lives at the bottom of
|
|
8
|
+
* this module (`serveStdio`); the HTTP+SSE wrapper lives in
|
|
9
|
+
* `./http-server.ts` and feeds the same core via the synchronous
|
|
10
|
+
* `handleMessage` entry-point.
|
|
11
|
+
*
|
|
12
|
+
* Spec: https://modelcontextprotocol.io/specification (2024-11-05).
|
|
13
|
+
*
|
|
14
|
+
* Methods implemented:
|
|
15
|
+
* - `initialize` -> protocol handshake, server capabilities
|
|
16
|
+
* - `notifications/initialized` -> ack, no-op
|
|
17
|
+
* - `tools/list` -> Pugi tool schemas
|
|
18
|
+
* - `tools/call` -> dispatch to the underlying executor
|
|
19
|
+
* - `ping` -> liveness, returns `{}` (some MCP clients
|
|
20
|
+
* poll this on a HTTP transport)
|
|
21
|
+
*
|
|
22
|
+
* NOT yet implemented (deferred — these are not on the β4 acceptance
|
|
23
|
+
* surface):
|
|
24
|
+
* - `resources/*` — file resource browser
|
|
25
|
+
* - `prompts/*` — server-supplied prompts
|
|
26
|
+
* - `sampling/*` — agent sampling callbacks
|
|
27
|
+
* - `notifications/cancelled` — client-side cancellation (we honour
|
|
28
|
+
* the AbortSignal passed at construction
|
|
29
|
+
* instead)
|
|
30
|
+
*
|
|
31
|
+
* Why hand-rolled instead of `@modelcontextprotocol/sdk`:
|
|
32
|
+
* - The SDK ships every transport (stdio, HTTP, WebSocket, SSE) and
|
|
33
|
+
* drags in a 3 MB compressed dependency footprint. Pugi-CLI ships as
|
|
34
|
+
* `npm i -g pugi` and every transitive dep is supply-chain risk.
|
|
35
|
+
* - The core JSON-RPC envelope is <500 LOC and we already maintain the
|
|
36
|
+
* client-side variant in `./client.ts` — keeping the server matching
|
|
37
|
+
* hand-roll lets a code reviewer hold both sides of the wire in one
|
|
38
|
+
* head.
|
|
39
|
+
* - Pugi-specific extensions (permission FSM hooks on `tools/call`,
|
|
40
|
+
* bearer-auth attestation on HTTP, scope-limited tool filtering for
|
|
41
|
+
* paired-agent worktrees) drop in cleanly because we own the
|
|
42
|
+
* dispatch table.
|
|
43
|
+
*/
|
|
44
|
+
/* ---------- protocol types (mirror client.ts conventions) ----------------- */
|
|
45
|
+
export const PUGI_MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
46
|
+
export const PUGI_MCP_SERVER_NAME = 'pugi';
|
|
47
|
+
export const PUGI_MCP_SERVER_VERSION = '0.1.0';
|
|
48
|
+
/** JSON-RPC standard error codes used by the server. */
|
|
49
|
+
export const MCP_ERROR_CODES = Object.freeze({
|
|
50
|
+
PARSE_ERROR: -32700,
|
|
51
|
+
INVALID_REQUEST: -32600,
|
|
52
|
+
METHOD_NOT_FOUND: -32601,
|
|
53
|
+
INVALID_PARAMS: -32602,
|
|
54
|
+
INTERNAL_ERROR: -32603,
|
|
55
|
+
/**
|
|
56
|
+
* Pugi-specific: operator-side permission gate refused this tool call.
|
|
57
|
+
* Distinct from generic INTERNAL_ERROR so MCP clients can surface a
|
|
58
|
+
* "permission denied" status to their user.
|
|
59
|
+
*/
|
|
60
|
+
PERMISSION_REFUSED: -32001,
|
|
61
|
+
/**
|
|
62
|
+
* Pugi-specific: HTTP transport saw an invalid or missing bearer
|
|
63
|
+
* token. Stdio transport never emits this code (no auth).
|
|
64
|
+
*/
|
|
65
|
+
AUTH_REQUIRED: -32002,
|
|
66
|
+
});
|
|
67
|
+
export class McpServerToolError extends Error {
|
|
68
|
+
code;
|
|
69
|
+
constructor(message, code = MCP_ERROR_CODES.INTERNAL_ERROR) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = 'McpServerToolError';
|
|
72
|
+
this.code = code;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a Pugi MCP server bound to a fixed tool surface. Stateless
|
|
77
|
+
* between requests — the same instance can drive many concurrent stdio
|
|
78
|
+
* connections (one per child process) without cross-talk.
|
|
79
|
+
*/
|
|
80
|
+
export function createPugiMcpServer(options) {
|
|
81
|
+
const events = new EventEmitter();
|
|
82
|
+
// Build a lookup by name once at construction. Duplicate names are a
|
|
83
|
+
// programmer error and we throw eagerly so the bug surfaces in the
|
|
84
|
+
// boot path, not at the first `tools/call`.
|
|
85
|
+
const byName = new Map();
|
|
86
|
+
for (const tool of options.tools) {
|
|
87
|
+
// Reject names that would re-encode as MCP `mcp__<server>__<tool>`
|
|
88
|
+
// false-positively on the consumer side — a server name containing
|
|
89
|
+
// `__` makes parseMcpToolName misalign the split. We surface the
|
|
90
|
+
// bug here, not at first dispatch. β4 r1 P2.
|
|
91
|
+
if (tool.name.includes('__')) {
|
|
92
|
+
throw new Error(`pugi mcp server: tool name "${tool.name}" must not contain "__" (collides with mcp__<server>__<tool> naming)`);
|
|
93
|
+
}
|
|
94
|
+
if (byName.has(tool.name)) {
|
|
95
|
+
throw new Error(`pugi mcp server: duplicate tool name "${tool.name}" — every tool in the surface must be unique`);
|
|
96
|
+
}
|
|
97
|
+
byName.set(tool.name, tool);
|
|
98
|
+
}
|
|
99
|
+
const tools = Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
+
const log = options.log ?? (() => { });
|
|
101
|
+
if (typeof options.permissionGate !== 'function') {
|
|
102
|
+
throw new Error('pugi mcp server: options.permissionGate is required (β4 r1 P1 #2 — no implicit allow-all default)');
|
|
103
|
+
}
|
|
104
|
+
const permissionGate = options.permissionGate;
|
|
105
|
+
const requireInitialized = options.requireInitialized !== false;
|
|
106
|
+
let initialized = false;
|
|
107
|
+
async function dispatch(method, params, clientId) {
|
|
108
|
+
switch (method) {
|
|
109
|
+
case 'initialize': {
|
|
110
|
+
// The MCP spec permits multiple `initialize` calls before
|
|
111
|
+
// `notifications/initialized` settles the handshake — we just
|
|
112
|
+
// re-affirm. After init, repeated `initialize` is harmless on
|
|
113
|
+
// our side and lets the client recover from a state desync.
|
|
114
|
+
return {
|
|
115
|
+
protocolVersion: PUGI_MCP_PROTOCOL_VERSION,
|
|
116
|
+
capabilities: {
|
|
117
|
+
tools: { listChanged: false },
|
|
118
|
+
},
|
|
119
|
+
serverInfo: {
|
|
120
|
+
name: PUGI_MCP_SERVER_NAME,
|
|
121
|
+
version: PUGI_MCP_SERVER_VERSION,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
case 'ping': {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
case 'tools/list': {
|
|
129
|
+
return {
|
|
130
|
+
tools: tools.map((tool) => ({
|
|
131
|
+
name: tool.name,
|
|
132
|
+
description: tool.description,
|
|
133
|
+
inputSchema: tool.inputSchema,
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
case 'tools/call': {
|
|
138
|
+
if (options.signal?.aborted) {
|
|
139
|
+
throw new McpServerToolError('server shutting down', MCP_ERROR_CODES.INTERNAL_ERROR);
|
|
140
|
+
}
|
|
141
|
+
// β4 r1 P2 — require `initialize` + `notifications/initialized`
|
|
142
|
+
// handshake first. Without this, an attacker with the bearer
|
|
143
|
+
// token can dispatch a `tools/call` without ever advertising
|
|
144
|
+
// client capabilities; the MCP spec mandates the handshake.
|
|
145
|
+
if (requireInitialized && !initialized) {
|
|
146
|
+
throw new McpServerToolError('tools/call: MCP handshake not complete — send `initialize` + `notifications/initialized` first', MCP_ERROR_CODES.INVALID_REQUEST);
|
|
147
|
+
}
|
|
148
|
+
const p = (params ?? {});
|
|
149
|
+
const rawName = p['name'];
|
|
150
|
+
if (typeof rawName !== 'string') {
|
|
151
|
+
throw new McpServerToolError('tools/call: params.name (string) is required', MCP_ERROR_CODES.INVALID_PARAMS);
|
|
152
|
+
}
|
|
153
|
+
// Trim whitespace-only names so the lookup fails fast with
|
|
154
|
+
// METHOD_NOT_FOUND instead of silently matching an entry that
|
|
155
|
+
// happens to share leading/trailing whitespace. β4 r1 P2.
|
|
156
|
+
const toolName = rawName.trim();
|
|
157
|
+
if (toolName.length === 0) {
|
|
158
|
+
throw new McpServerToolError('tools/call: params.name (string) is required', MCP_ERROR_CODES.INVALID_PARAMS);
|
|
159
|
+
}
|
|
160
|
+
const tool = byName.get(toolName);
|
|
161
|
+
if (!tool) {
|
|
162
|
+
throw new McpServerToolError(`tools/call: tool "${toolName}" is not registered`, MCP_ERROR_CODES.METHOD_NOT_FOUND);
|
|
163
|
+
}
|
|
164
|
+
const rawArgs = p['arguments'];
|
|
165
|
+
let args;
|
|
166
|
+
if (rawArgs === undefined || rawArgs === null) {
|
|
167
|
+
args = {};
|
|
168
|
+
}
|
|
169
|
+
else if (typeof rawArgs === 'object' && !Array.isArray(rawArgs)) {
|
|
170
|
+
args = rawArgs;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new McpServerToolError('tools/call: params.arguments must be a JSON object', MCP_ERROR_CODES.INVALID_PARAMS);
|
|
174
|
+
}
|
|
175
|
+
const allowed = await permissionGate({
|
|
176
|
+
tool,
|
|
177
|
+
arguments: args,
|
|
178
|
+
...(clientId ? { clientId } : {}),
|
|
179
|
+
});
|
|
180
|
+
if (!allowed) {
|
|
181
|
+
log('warn', `permission refused: ${tool.name}`);
|
|
182
|
+
throw new McpServerToolError(`permission refused by operator: ${tool.name}`, MCP_ERROR_CODES.PERMISSION_REFUSED);
|
|
183
|
+
}
|
|
184
|
+
// Stamp clientId on emitted events so the HTTP transport can
|
|
185
|
+
// route them to the originating SSE listener instead of
|
|
186
|
+
// broadcasting to every bearer-holder (β4 r1 P1 #5).
|
|
187
|
+
events.emit('tool_call', {
|
|
188
|
+
name: tool.name,
|
|
189
|
+
args,
|
|
190
|
+
...(clientId ? { clientId } : {}),
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
const text = await tool.execute(args);
|
|
194
|
+
events.emit('tool_result', {
|
|
195
|
+
name: tool.name,
|
|
196
|
+
ok: true,
|
|
197
|
+
summary: text.slice(0, 200),
|
|
198
|
+
...(clientId ? { clientId } : {}),
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: 'text', text }],
|
|
202
|
+
isError: false,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
207
|
+
events.emit('tool_result', {
|
|
208
|
+
name: tool.name,
|
|
209
|
+
ok: false,
|
|
210
|
+
summary: message.slice(0, 200),
|
|
211
|
+
...(clientId ? { clientId } : {}),
|
|
212
|
+
});
|
|
213
|
+
// Tool-level failures surface as MCP `isError: true` content —
|
|
214
|
+
// the client knows the call ran but failed, distinct from a
|
|
215
|
+
// protocol-level error (bad tool name, malformed params).
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: 'text', text: message }],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
case 'notifications/initialized': {
|
|
223
|
+
initialized = true;
|
|
224
|
+
events.emit('initialized');
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
default: {
|
|
228
|
+
if (method.startsWith('notifications/')) {
|
|
229
|
+
// Silently accept unknown notifications — the spec requires
|
|
230
|
+
// they be ignored, not errored.
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
throw new McpServerToolError(`method not found: ${method}`, MCP_ERROR_CODES.METHOD_NOT_FOUND);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function handleMessage(request) {
|
|
238
|
+
const clientId = request.meta?.clientId;
|
|
239
|
+
// Notifications never get a response.
|
|
240
|
+
const isNotification = request.id === undefined || request.id === null;
|
|
241
|
+
if (isNotification) {
|
|
242
|
+
try {
|
|
243
|
+
await dispatch(request.method, request.params, clientId);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
log('error', `notification handler threw: ${error.message}`);
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const id = request.id;
|
|
251
|
+
try {
|
|
252
|
+
const result = await dispatch(request.method, request.params, clientId);
|
|
253
|
+
// dispatch may return null for void responses (notifications) —
|
|
254
|
+
// but we already filtered those above. Treat null here as `{}`.
|
|
255
|
+
return {
|
|
256
|
+
jsonrpc: '2.0',
|
|
257
|
+
id,
|
|
258
|
+
result: result ?? {},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const isToolError = error instanceof McpServerToolError;
|
|
263
|
+
const code = isToolError ? error.code : MCP_ERROR_CODES.INTERNAL_ERROR;
|
|
264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
265
|
+
log('error', `dispatch ${request.method} (id=${id}) failed: ${message}`);
|
|
266
|
+
return {
|
|
267
|
+
jsonrpc: '2.0',
|
|
268
|
+
id,
|
|
269
|
+
error: { code, message },
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
handleMessage,
|
|
275
|
+
events,
|
|
276
|
+
listToolsSync() {
|
|
277
|
+
return tools.slice();
|
|
278
|
+
},
|
|
279
|
+
// Internal: exposed to tests via type assertion when they want to
|
|
280
|
+
// verify the initialized flag advanced. Not part of the public
|
|
281
|
+
// interface intentionally — production callers should listen on
|
|
282
|
+
// `events` instead.
|
|
283
|
+
// @ts-expect-error — debug accessor
|
|
284
|
+
_isInitialized() {
|
|
285
|
+
return initialized;
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Run the server on stdio. Reads one JSON-RPC line per request from
|
|
291
|
+
* stdin, writes the response (or nothing for notifications) as one line
|
|
292
|
+
* to stdout. Lines are `\n`-terminated UTF-8 JSON.
|
|
293
|
+
*
|
|
294
|
+
* Returns a promise that resolves when stdin closes. The caller can race
|
|
295
|
+
* it against `signal` to force shutdown — the stdio reader is shielded
|
|
296
|
+
* by the same signal, so an abort terminates the line loop cleanly.
|
|
297
|
+
*/
|
|
298
|
+
export async function serveStdio(options) {
|
|
299
|
+
const stdin = options.stdin ?? process.stdin;
|
|
300
|
+
const stdout = options.stdout ?? process.stdout;
|
|
301
|
+
const { server, signal } = options;
|
|
302
|
+
return new Promise((resolve) => {
|
|
303
|
+
let buffer = '';
|
|
304
|
+
let closed = false;
|
|
305
|
+
const finish = () => {
|
|
306
|
+
if (closed)
|
|
307
|
+
return;
|
|
308
|
+
closed = true;
|
|
309
|
+
stdin.off('data', onData);
|
|
310
|
+
stdin.off('end', finish);
|
|
311
|
+
stdin.off('close', finish);
|
|
312
|
+
if (signal) {
|
|
313
|
+
signal.removeEventListener('abort', finish);
|
|
314
|
+
}
|
|
315
|
+
resolve();
|
|
316
|
+
};
|
|
317
|
+
const writeFrame = (response) => {
|
|
318
|
+
stdout.write(`${JSON.stringify(response)}\n`);
|
|
319
|
+
};
|
|
320
|
+
const writeParseError = (id, message) => {
|
|
321
|
+
writeFrame({
|
|
322
|
+
jsonrpc: '2.0',
|
|
323
|
+
id,
|
|
324
|
+
error: { code: MCP_ERROR_CODES.PARSE_ERROR, message },
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
const processLine = async (line) => {
|
|
328
|
+
const trimmed = line.trim();
|
|
329
|
+
if (trimmed.length === 0)
|
|
330
|
+
return;
|
|
331
|
+
let parsed;
|
|
332
|
+
try {
|
|
333
|
+
parsed = JSON.parse(trimmed);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
writeParseError(null, `invalid JSON: ${error.message}`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
340
|
+
writeParseError(null, 'request must be a JSON object');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const candidate = parsed;
|
|
344
|
+
if (candidate.jsonrpc !== '2.0' || typeof candidate.method !== 'string') {
|
|
345
|
+
writeParseError(typeof candidate.id === 'number' || typeof candidate.id === 'string'
|
|
346
|
+
? candidate.id
|
|
347
|
+
: null, 'invalid JSON-RPC envelope: jsonrpc=2.0 + string method required');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// Stdio is a single-tenant transport — the parent process owns
|
|
351
|
+
// both ends — so we intentionally drop any inbound `meta.clientId`.
|
|
352
|
+
// Per-connection scoping only makes sense for HTTP+SSE.
|
|
353
|
+
const request = {
|
|
354
|
+
jsonrpc: '2.0',
|
|
355
|
+
method: candidate.method,
|
|
356
|
+
...(candidate.id !== undefined ? { id: candidate.id } : {}),
|
|
357
|
+
...(candidate.params !== undefined ? { params: candidate.params } : {}),
|
|
358
|
+
};
|
|
359
|
+
const response = await server.handleMessage(request);
|
|
360
|
+
if (response)
|
|
361
|
+
writeFrame(response);
|
|
362
|
+
};
|
|
363
|
+
const onData = (chunk) => {
|
|
364
|
+
buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
365
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
366
|
+
while (newlineIndex !== -1) {
|
|
367
|
+
const line = buffer.slice(0, newlineIndex);
|
|
368
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
369
|
+
// Fire-and-forget the line handler; ordering of responses
|
|
370
|
+
// matches request ordering at the protocol level because the
|
|
371
|
+
// dispatch is microtask-serialized via the await above for
|
|
372
|
+
// each line — Node iterates `data` callbacks synchronously,
|
|
373
|
+
// and processLine awaits the dispatcher before returning so
|
|
374
|
+
// the next iteration of the loop sees the prior one settled.
|
|
375
|
+
void processLine(line);
|
|
376
|
+
newlineIndex = buffer.indexOf('\n');
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
if (signal) {
|
|
380
|
+
if (signal.aborted) {
|
|
381
|
+
finish();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
signal.addEventListener('abort', finish, { once: true });
|
|
385
|
+
}
|
|
386
|
+
if (!stdin.readable) {
|
|
387
|
+
// Stream already closed (e.g. parent piped EOF immediately).
|
|
388
|
+
finish();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
stdin.setEncoding('utf8');
|
|
392
|
+
stdin.on('data', onData);
|
|
393
|
+
stdin.on('end', finish);
|
|
394
|
+
stdin.on('close', finish);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=server.js.map
|
package/dist/core/mcp/trust.js
CHANGED
|
@@ -9,18 +9,18 @@ import { z } from 'zod';
|
|
|
9
9
|
* used in `.pugi/mcp.json` / `~/.pugi/mcp.json` `servers` map).
|
|
10
10
|
*
|
|
11
11
|
* Trust states:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* - `pending` — declared but not yet approved. Connection refused,
|
|
13
|
+
* tools NOT surfaced to the agent.
|
|
14
|
+
* - `trusted` — operator approved this server. Tools surface as
|
|
15
|
+
* `mcp.<server>.<tool>` and the registry will spawn the child.
|
|
16
|
+
* - `denied` — operator blocked this server. Connection refused.
|
|
17
17
|
*
|
|
18
18
|
* Why a separate ledger instead of mutating `.pugi/mcp.json` in place:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* - `.pugi/mcp.json` is committed in some teams' workspaces. The trust
|
|
20
|
+
* state is per-operator and must not bleed across users via git.
|
|
21
|
+
* - `~/.pugi/trust-mcp.json` is the user-level source of truth and
|
|
22
|
+
* overrides whatever trust value is declared in the workspace
|
|
23
|
+
* `mcp.json`. A repo cannot opt itself in.
|
|
24
24
|
*
|
|
25
25
|
* The PUGI_HOME env var redirects the ledger path for tests.
|
|
26
26
|
*/
|