---
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.
