Files
my-pi/pi-permission-system/docs/plans/archive/0019-drop-legacy-settings-json-mcp-fallback.md
T

126 lines
8.7 KiB
Markdown

---
issue: 19
issue_title: "Drop legacy ~/.pi/agent/settings.json fallback for MCP server names"
---
# Drop legacy `~/.pi/agent/settings.json` fallback for MCP server names
## Problem Statement
`PermissionManager.getConfiguredMcpServerNames()` reads MCP server names from two paths: `mcp.json` and the legacy `settings.json` (Pi's own settings file).
The `settings.json` fallback reaches into another package's config file whose structure Pi can change at any time.
The supported MCP server config source is `mcp.json`, which the manager already reads.
Keeping the fallback creates a fragile coupling and a confusing second source of truth for MCP server name derivation.
## Goals
- Remove `defaultLegacyGlobalSettingsPath()` and all references to `legacyGlobalSettingsPath` from `src/permission-manager.ts`.
- Remove `legacyGlobalSettingsPath` from the `PermissionManager` constructor options.
- Keep `mcp.json` as the sole file-based source for derived MCP server names (the `mcpServerNames` override remains).
- Add a test confirming that server names in a `settings.json`-style file are **not** picked up.
- Verify no README or docs reference `settings.json` as a source for MCP server names (none found).
## Non-Goals
- Changing the MCP target derivation logic (`pushMcpToolPermissionTargets`, `addDerivedMcpServerTargets`, `createMcpPermissionTargets`).
- Changing how users configure MCP servers in Pi itself.
- Adding any new MCP config sources.
## Background
### Relevant modules
| File | Role |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/permission-manager.ts` | Contains `defaultLegacyGlobalSettingsPath()`, the `legacyGlobalSettingsPath` field, and uses it in `getConfiguredMcpServerNames()`. This is the only file that references the legacy path. |
| `src/index.ts` | Constructs `PermissionManager` — does not pass `legacyGlobalSettingsPath`, so it gets the default. No changes needed. |
| `tests/permission-system.test.ts` | Tests use the `mcpServerNames` override, not the file-based lookup. No existing tests exercise the legacy path. |
### Permission surface
**MCP** — specifically the server-name derivation used to expand bare tool names into `server:tool` permission targets.
The change does not affect any permission decision logic; it only narrows the set of files consulted for server name discovery.
### Existing code path
```typescript
// In getConfiguredMcpServerNames():
const paths = [this.globalMcpConfigPath, this.legacyGlobalSettingsPath];
```
After this change, the array becomes `[this.globalMcpConfigPath]` — or the method simplifies to read only `mcp.json`.
## Design Overview
This is a pure removal — no new types, no new config fields, no merge-precedence changes.
### What's removed
1. `defaultLegacyGlobalSettingsPath()` — the free function returning `join(getAgentDir(), "settings.json")`.
2. `legacyGlobalSettingsPath` — the private field on `PermissionManager`.
3. `legacyGlobalSettingsPath` — the optional constructor parameter.
4. The second element in the `paths` array inside `getConfiguredMcpServerNames()`.
### What stays
- `globalMcpConfigPath` and `defaultGlobalMcpConfigPath()` — unchanged.
- `mcpServerNames` constructor override — unchanged.
- `getConfiguredMcpServerNamesFromPaths()` and `readConfiguredMcpServerNamesFromConfigPath()` — unchanged (still used for `mcp.json`).
### Edge cases
- A user who only had MCP servers defined in `settings.json` (not `mcp.json`) would silently lose server-name derivation.
This is intentional: `settings.json` was never documented as a permission-system config source, and any servers there are still usable in Pi — they just won't influence permission target expansion.
The worst case is that a bare tool name `foo_myserver` stops matching the `myserver:foo_myserver` expansion, falling through to the default MCP policy (which defaults to `ask`, not `allow`).
This cannot silently weaken a permission — it can only make a permission stricter.
## Module-Level Changes
### `src/permission-manager.ts` — changed
- Delete `defaultLegacyGlobalSettingsPath()`.
- Remove `legacyGlobalSettingsPath` from the private fields.
- Remove `legacyGlobalSettingsPath` from the constructor options interface and the constructor body.
- In `getConfiguredMcpServerNames()`, change the `paths` array to `[this.globalMcpConfigPath]`.
### `tests/permission-system.test.ts` — changed
- Add a test constructing a `PermissionManager` with a temp `settings.json` containing `mcpServers` and confirm `getConfiguredMcpServerNames()` (via `checkPermission` on an MCP tool) does **not** derive targets from those names.
Since `getConfiguredMcpServerNames()` is private, the test will use `checkPermission("mcp", ...)` with a bare tool name and assert the server-derived targets are absent.
- Alternatively, add a focused unit test for `getConfiguredMcpServerNamesFromPaths()` (the module-level function) to confirm only the `mcp.json` path is consulted.
### No schema, config, or README changes required
`settings.json` is not referenced in `schemas/permissions.schema.json`, `config/config.example.json`, or `README.md`.
## TDD Order
1. **Red: test that `settings.json` server names are not used.**
Write a test that creates a temp `settings.json` with `{ "mcpServers": { "legacy-server": {} } }` and a `mcp.json` without that server.
Construct a `PermissionManager` with those paths.
Call `checkPermission("mcp", { tool: "some_tool_legacy-server" })` and assert the result does **not** produce a `legacy-server:some_tool_legacy-server` target match.
This test should pass even before the removal (since the derivation path exists but only affects ordering), so frame the assertion as: the manager must produce identical results whether or not `settings.json` exists.
Commit: `test: verify MCP server names come only from mcp.json (#19)`
2. **Green: remove legacy settings.json fallback.**
Delete `defaultLegacyGlobalSettingsPath()`, the `legacyGlobalSettingsPath` field, the constructor option, and the array entry in `getConfiguredMcpServerNames()`.
All existing tests must still pass.
Commit: `feat: drop legacy settings.json fallback for MCP server names (#19)`
3. **Verify: run full test suite.**
Confirm `npm test` and `npm run build` pass cleanly.
Commit (if any fixups needed): `fix: adjust tests after legacy path removal (#19)`
## Risks and Mitigations
| Risk | Mitigation |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Could this silently weaken a permission?** | No. Removing a server-name source can only make derivation *less* permissive — a bare tool name that previously matched a server-qualified allow rule would now fall through to the default MCP policy (`ask`). This is stricter, not weaker. |
| **Users relying on `settings.json` for MCP server name derivation.** | This was never documented. Users who configure MCP servers in `settings.json` can add the same entries to `mcp.json` or use explicit `server:tool` patterns in their permission policy. |
| **On-disk identity change.** | None. No config directory, log filename, slash command, or event channel name is affected. |
| **Breaking change?** | Non-breaking. The constructor option `legacyGlobalSettingsPath` was internal and not part of any public API contract. No policy file format changes. |
## Open Questions
None — the scope is narrow and unambiguous.