Quellcode durchsuchen

fix(mcp): decouple plugin vs project-scope entrypoint resolution

The 1.5.4 fix (${CLAUDE_PLUGIN_ROOT:-.}) made .mcp.json parse in a
project-scope clone but it still failed to *connect*: CLAUDE_PLUGIN_ROOT
is unset outside a plugin install, so the path fell back to ".", which
Claude Code resolves against the launching process's cwd, not the repo
root.

A single entrypoint string cannot serve both contexts — plugin installs
need ${CLAUDE_PLUGIN_ROOT} (CLAUDE_PROJECT_DIR points at the user's
project there, not the plugin dir), clones need ${CLAUDE_PROJECT_DIR:-.},
and Claude Code does not support nested defaults. Decouple them:

- .mcp.json -> ${CLAUDE_PROJECT_DIR:-.}/build/index.js (clone workflow)
- .claude-plugin/plugin.json now declares its own mcpServers using
  ${CLAUDE_PLUGIN_ROOT}/build/index.js (plugin install). Because the
  plugin manifest declares mcpServers, the plugin no longer auto-loads
  the root .mcp.json, so there is no double-registration.

Document the dual-context behavior and scope precedence in the README
(From Source + Troubleshooting). Bump 1.5.4 -> 1.5.5.

Refs #15
Robert Sweet vor 3 Wochen
Ursprung
Commit
31e3602e93
6 geänderte Dateien mit 40 neuen und 5 gelöschten Zeilen
  1. 9 1
      .claude-plugin/plugin.json
  2. 1 1
      .mcp.json
  3. 5 0
      CHANGELOG.md
  4. 22 0
      README.md
  5. 2 2
      package-lock.json
  6. 1 1
      package.json

+ 9 - 1
.claude-plugin/plugin.json

@@ -1,9 +1,17 @@
 {
   "name": "apple-mail",
-  "version": "1.5.4",
+  "version": "1.5.5",
   "description": "Manage Apple Mail through natural language - read, search, send, and organize emails (macOS only)",
   "author": {
     "name": "Rob Sweet",
     "email": "rob@superiortech.io"
+  },
+  "mcpServers": {
+    "apple-mail": {
+      "command": "node",
+      "args": [
+        "${CLAUDE_PLUGIN_ROOT}/build/index.js"
+      ]
+    }
   }
 }

+ 1 - 1
.mcp.json

@@ -2,7 +2,7 @@
   "mcpServers": {
     "apple-mail": {
       "command": "node",
-      "args": ["${CLAUDE_PLUGIN_ROOT:-.}/build/index.js"]
+      "args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
     }
   }
 }

+ 5 - 0
CHANGELOG.md

@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.5.5] - 2026-06-01
+
+### Fixed
+- **`.mcp.json` parsed but never *connected* as a project-scope config (incomplete 1.5.4 fix)** — 1.5.4 switched the entrypoint to `${CLAUDE_PLUGIN_ROOT:-.}/build/index.js`, which fixed the *parse* error but not the actual failure: in a project-scope clone `CLAUDE_PLUGIN_ROOT` is unset, so the path fell back to `.`, which Claude Code resolves against the launching process's working directory — **not** the repo root — so the server still failed to connect. A single entrypoint string cannot serve both contexts: plugin installs require `${CLAUDE_PLUGIN_ROOT}` (in a plugin, `CLAUDE_PROJECT_DIR` points at the *user's* project, not the plugin dir), clones require `${CLAUDE_PROJECT_DIR:-.}`, and Claude Code does not support nested defaults (`${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}` does not expand). The two distribution paths are now **decoupled**: the root `.mcp.json` uses `${CLAUDE_PROJECT_DIR:-.}/build/index.js` for the clone/contributor workflow, and the plugin carries its own MCP config in `.claude-plugin/plugin.json` using `${CLAUDE_PLUGIN_ROOT}/build/index.js`. Because `plugin.json` now declares `mcpServers`, the plugin no longer auto-loads the root `.mcp.json`, so there is no double-registration. See the README's [Running from a clone](README.md#running-from-a-clone-in-claude-code-project-scope-mcpjson) section. ([#15](https://github.com/sweetrb/apple-mail-mcp/issues/15))
+
 ## [1.5.4] - 2026-06-01
 
 ### Fixed

+ 22 - 0
README.md

@@ -700,6 +700,22 @@ If installed from source, use this configuration:
 }
 ```
 
+#### Running from a clone in Claude Code (project-scope `.mcp.json`)
+
+This repo ships a `.mcp.json` at its root so that, when you run `claude` from inside a clone, the server is registered automatically as a **project-scope** server — no manual config needed. After `npm run build`, just launch Claude Code from the repo directory and approve the server when prompted.
+
+The entrypoint is written as:
+
+```json
+"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
+```
+
+`CLAUDE_PROJECT_DIR` is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. **You must launch `claude` from inside the repo** for this to work — the bare `.` fallback is only a last resort and is *not* reliable, because it resolves against the launching process's working directory, not the repo.
+
+> **Why not `${CLAUDE_PLUGIN_ROOT}`?** `CLAUDE_PLUGIN_ROOT` is set **only** for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't use `CLAUDE_PROJECT_DIR` (in a plugin, that points at the *user's* project, not the plugin's own directory). Claude Code does **not** support nested defaults like `${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}`, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the **plugin** carries its own MCP config in `.claude-plugin/plugin.json` (using `${CLAUDE_PLUGIN_ROOT}`), while the root `.mcp.json` is dedicated to the **clone** workflow (using `${CLAUDE_PROJECT_DIR:-.}`). Because `plugin.json` declares its own `mcpServers`, the plugin does not also auto-load the root `.mcp.json`, so there is no double-registration.
+
+> **Heads-up on scope precedence:** project-scope (`.mcp.json`) outranks user-scope. If you *also* have an `apple-mail` entry registered at user scope (e.g. an absolute path in `~/.claude.json`), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope `.mcp.json` is the intended source. To pin a specific local build instead, register it at **local** scope (`claude mcp add apple-mail -s local -- node /abs/path/build/index.js`), which outranks project scope.
+
 ---
 
 ## Security and Privacy
@@ -788,6 +804,12 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
 - Verify Mail.app can send emails manually
 - Check if the account is configured correctly in Mail.app
 
+### `apple-mail` server fails to connect when run from a clone
+- The root `.mcp.json` resolves its entrypoint via `${CLAUDE_PROJECT_DIR:-.}/build/index.js`. **Launch `claude` from inside the repo directory** — `CLAUDE_PROJECT_DIR` only resolves to the repo root in that case; the bare `.` fallback uses the launching shell's working directory and will point at the wrong place otherwise.
+- Run `npm run build` first — the server is `build/index.js`, which doesn't exist until you build.
+- Run `claude mcp list` to check status. If you see a *conflicting scopes* warning for `apple-mail`, you have it registered at more than one scope; project-scope wins. See [Running from a clone](#running-from-a-clone-in-claude-code-project-scope-mcpjson) for how scope precedence resolves.
+- If `claude mcp get apple-mail` shows **⏸ Pending approval**, approve the project-scope server (Claude Code prompts on startup, or run it again after approving).
+
 ---
 
 ## Development

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "apple-mail-mcp",
-  "version": "1.5.4",
+  "version": "1.5.5",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "apple-mail-mcp",
-      "version": "1.5.4",
+      "version": "1.5.5",
       "license": "MIT",
       "os": [
         "darwin"

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "apple-mail-mcp",
-  "version": "1.5.4",
+  "version": "1.5.5",
   "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
   "type": "module",
   "main": "build/index.js",