refactor: bundle MCP servers inside plugins for cache compatibility

Claude Code only caches the plugin directory when installed from a
marketplace, not parent directories. This broke the shared mcp-servers/
architecture because relative paths like ../../mcp-servers/ resolved
to non-existent locations in the cache.

Changes:
- Move gitea and wikijs MCP servers into plugins/projman/mcp-servers/
- Move netbox MCP server into plugins/cmdb-assistant/mcp-servers/
- Update .mcp.json files to use ${CLAUDE_PLUGIN_ROOT}/mcp-servers/
- Update setup.sh to handle new bundled structure
- Add netbox.env config template to setup.sh
- Update CLAUDE.md and CANONICAL-PATHS.md documentation

This ensures plugins work correctly when installed and cached.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-15 17:23:02 -05:00
parent c73e4c4794
commit d84425cbb0
50 changed files with 158 additions and 112 deletions

132
CLAUDE.md
View File

@@ -161,10 +161,11 @@ Both plugins use **two shared MCP servers** at repository root level (`mcp-serve
- `tag_lesson` - Add tags to lessons learned
**Key Architecture Points:**
- MCP servers are **shared** by both plugins at `mcp-servers/gitea` and `mcp-servers/wikijs`
- MCP servers are **bundled inside each plugin** at `plugins/{plugin}/mcp-servers/`
- This ensures plugins work when cached by Claude Code (only plugin directory is cached)
- Each MCP server detects its mode (project-scoped vs company-wide) based on environment variables
- Configuration uses hybrid approach (system-level + project-level)
- All plugins reference `../../mcp-servers/` in their `.mcp.json` files (from `plugins/*/`)
- All plugins reference `${CLAUDE_PLUGIN_ROOT}/mcp-servers/` in their `.mcp.json` files
## Branch-Aware Security Model
@@ -261,76 +262,73 @@ See [docs/reference-material/projman-implementation-plan.md](docs/reference-mate
bandit/support-claude-mktplace/
├── .claude-plugin/
│ └── marketplace.json
├── mcp-servers/ # ← SHARED BY ALL PLUGINS
│ ├── gitea/
│ │ ├── .venv/
│ │ ├── requirements.txt # Python dependencies
│ │ ├── mcp_server/
│ │ │ ├── __init__.py
│ │ │ ├── server.py
│ │ │ ├── config.py # Mode detection (project/company)
│ │ │ ├── gitea_client.py
│ │ │ └── tools/
│ │ │ ── issues.py
│ │ │ └── labels.py
│ │ └── tests/
└── wikijs/
├── .venv/
├── requirements.txt # Python + GraphQL dependencies
├── mcp_server/
│ ├── __init__.py
│ ├── server.py
│ ├── config.py # Mode detection (project/company)
│ ├── wikijs_client.py # GraphQL client
│ └── tools/
├── pages.py
├── lessons_learned.py
── documentation.py
└── tests/
└── plugins/ # ← ALL PLUGINS
├── projman/ # ← PROJECT PLUGIN
│ ├── .claude-plugin/
│ │ ── plugin.json
── .mcp.json # Points to ../../mcp-servers/
│ ├── commands/
│ │ ├── sprint-plan.md
│ │ ── sprint-start.md
│ │ ├── sprint-status.md
│ │ ├── sprint-close.md
│ │ ── labels-sync.md
── agents/
│ │ ── planner.md
│ │ ├── orchestrator.md
│ │ ── executor.md
── skills/
└── label-taxonomy/
│ └── labels-reference.md
── README.md
└── CONFIGURATION.md
├── projman-pmo/ # ← PMO PLUGIN
├── .claude-plugin/
│ │ ── plugin.json
── .mcp.json # Points to ../../mcp-servers/
── commands/
│ ├── pmo-status.md
├── pmo-priorities.md
│ │ ├── pmo-dependencies.md
│ └── pmo-schedule.md
│ ├── agents/
│ │ └── pmo-coordinator.md
│ └── README.md
└── project-hygiene/ # ← CLEANUP PLUGIN
└── ...
├── plugins/ # ← ALL PLUGINS (with bundled MCP servers)
│ ├── projman/ # ← PROJECT PLUGIN
│ │ ├── .claude-plugin/
│ │ │ └── plugin.json
│ │ ├── .mcp.json # Points to ${CLAUDE_PLUGIN_ROOT}/mcp-servers/
│ │ ├── mcp-servers/ # ← MCP servers BUNDLED IN plugin
│ │ │ ├── gitea/
│ │ │ │ ├── .venv/
│ │ │ │ ├── requirements.txt
│ │ │ │ ├── mcp_server/
│ │ │ ── tests/
│ │ │ └── wikijs/
│ │ │ ├── .venv/
│ │ ├── requirements.txt
│ │ ├── mcp_server/
│ └── tests/
├── commands/
│ ├── sprint-plan.md
│ ├── sprint-start.md
│ ├── sprint-status.md
│ ├── sprint-close.md
│ └── labels-sync.md
├── agents/
│ │ ├── planner.md
│ │ ── orchestrator.md
│ │ └── executor.md
├── skills/
│ │ └── label-taxonomy/
│ │ └── labels-reference.md
│ │ ── README.md
── CONFIGURATION.md
│ ├── projman-pmo/ # ← PMO PLUGIN
│ │ ├── .claude-plugin/
│ │ ── plugin.json
│ │ ├── .mcp.json
│ │ ├── commands/
│ │ ── agents/
│ │── pmo-coordinator.md
│ │ ── README.md
│ ├── cmdb-assistant/ # ← CMDB PLUGIN
│ │ ── .claude-plugin/
│ │── plugin.json
│ │ ├── .mcp.json # Points to ${CLAUDE_PLUGIN_ROOT}/mcp-servers/
├── mcp-servers/ # ← MCP servers BUNDLED IN plugin
│ │── netbox/
│ │ ├── .venv/
│ │ ├── requirements.txt
│ │ └── mcp_server/
│ │ ── commands/
── agents/
── project-hygiene/ # ← CLEANUP PLUGIN
└── ...
├── scripts/ # Setup and maintenance scripts
│ ├── setup.sh
│ └── post-update.sh
└── docs/
```
### Key Design Decisions
**Two MCP Servers (Shared Architecture):**
- **Gitea MCP**: Issues, labels, repository management
- **Wiki.js MCP**: Documentation, lessons learned, knowledge base
- Servers are **shared** between both plugins at repository root
**MCP Servers (Bundled in Plugins):**
- **Gitea MCP**: Issues, labels, repository management (bundled in projman)
- **Wiki.js MCP**: Documentation, lessons learned, knowledge base (bundled in projman)
- **NetBox MCP**: Infrastructure management (bundled in cmdb-assistant)
- Servers are **bundled inside each plugin** that needs them
- This ensures plugins work when cached by Claude Code
- Mode detection based on environment variables (project vs company-wide)
- Benefits: Single source of truth, fix bugs once, professional architecture
**Python Implementation:**
- Python chosen over Node.js for MCP servers

View File

@@ -2,7 +2,7 @@
**This file defines ALL valid paths in this repository. No exceptions. No inference. No assumptions.**
Last Updated: 2025-12-12
Last Updated: 2025-12-15
---
@@ -19,15 +19,23 @@ support-claude-mktplace/
│ ├── references/ # Reference specifications
│ └── workflows/ # Workflow documentation
├── hooks/ # Shared hooks (if any)
├── mcp-servers/ # Shared MCP servers (AT ROOT)
│ ├── gitea/
│ ├── wikijs/
│ └── netbox/
├── plugins/ # ALL plugins (INSIDE plugins/)
├── plugins/ # ALL plugins with bundled MCP servers
│ ├── projman/
│ │ ├── .claude-plugin/
│ │ ├── mcp-servers/ # MCP servers bundled IN plugin
│ │ │ ├── gitea/
│ │ │ └── wikijs/
│ │ ├── commands/
│ │ ├── agents/
│ │ └── skills/
│ ├── projman-pmo/
│ ├── project-hygiene/
│ └── cmdb-assistant/
│ ├── .claude-plugin/
│ ├── mcp-servers/ # MCP servers bundled IN plugin
│ │ └── netbox/
│ ├── commands/
│ └── agents/
├── scripts/ # Setup and maintenance scripts
├── CLAUDE.md
├── README.md
@@ -50,19 +58,21 @@ support-claude-mktplace/
| Plugin agents | `plugins/{plugin-name}/agents/` | `plugins/projman/agents/` |
| Plugin .mcp.json | `plugins/{plugin-name}/.mcp.json` | `plugins/projman/.mcp.json` |
### MCP Server Paths
### MCP Server Paths (Bundled in Plugins)
MCP servers are now **bundled inside each plugin** to ensure they work when plugins are cached.
| Context | Pattern | Example |
|---------|---------|---------|
| MCP server location | `mcp-servers/{server-name}/` | `mcp-servers/gitea/` |
| MCP server code | `mcp-servers/{server-name}/mcp_server/` | `mcp-servers/gitea/mcp_server/` |
| MCP venv | `mcp-servers/{server-name}/.venv/` | `mcp-servers/gitea/.venv/` |
| MCP server location | `plugins/{plugin}/mcp-servers/{server}/` | `plugins/projman/mcp-servers/gitea/` |
| MCP server code | `plugins/{plugin}/mcp-servers/{server}/mcp_server/` | `plugins/projman/mcp-servers/gitea/mcp_server/` |
| MCP venv | `plugins/{plugin}/mcp-servers/{server}/.venv/` | `plugins/projman/mcp-servers/gitea/.venv/` |
### Relative Path Patterns (CRITICAL)
| From | To | Pattern |
|------|----|---------|
| Plugin .mcp.json | MCP server | `${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/{server}` |
| Plugin .mcp.json | Bundled MCP server | `${CLAUDE_PLUGIN_ROOT}/mcp-servers/{server}` |
| marketplace.json | Plugin | `./plugins/{plugin-name}` |
### Documentation Paths
@@ -92,16 +102,13 @@ support-claude-mktplace/
### Relative Path Calculation
From `plugins/projman/.mcp.json` to `mcp-servers/gitea/`:
From `plugins/projman/.mcp.json` to bundled `mcp-servers/gitea/`:
```
plugins/projman/.mcp.json
↑ go up to plugins/projman/ (../)
↑ go up to plugins/ (../)
↑ go up to root/ (../)
→ go down to mcp-servers/gitea/ (mcp-servers/gitea/)
→ MCP servers are IN the plugin at mcp-servers/
Result: ../../mcp-servers/gitea/
With variable: ${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/gitea/
Result: mcp-servers/gitea/
With variable: ${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/
```
From `.claude-plugin/marketplace.json` to `plugins/projman/`:
@@ -120,9 +127,18 @@ Result: ./plugins/projman
| Wrong | Why | Correct |
|-------|-----|---------|
| `projman/` at root | Plugins go in `plugins/` | `plugins/projman/` |
| `../mcp-servers/` from plugin | Missing one level | `../../mcp-servers/` |
| `mcp-servers/` at root | MCP servers are bundled in plugins | `plugins/{plugin}/mcp-servers/` |
| `../../mcp-servers/` from plugin | Old pattern, doesn't work with caching | `${CLAUDE_PLUGIN_ROOT}/mcp-servers/` |
| `./../../../plugins/projman` in marketplace | Wrong (old nested structure) | `./plugins/projman` |
| Creating `docs/CORRECT-ARCHITECTURE.md` | This file replaces it | Use `docs/CANONICAL-PATHS.md` |
---
## Architecture Note
MCP servers are bundled inside each plugin (not shared at root) because:
- Claude Code caches only the plugin directory when installed
- Relative paths to parent directories break in the cache
- Each plugin must be self-contained to work properly
---
@@ -130,4 +146,5 @@ Result: ./plugins/projman
| Date | Change | By |
|------|--------|-----|
| 2025-12-15 | Restructured: MCP servers now bundled in plugins | Claude Code |
| 2025-12-12 | Initial creation | Claude Code |

View File

@@ -1,11 +1,11 @@
{
"mcpServers": {
"netbox": {
"command": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/netbox/.venv/bin/python",
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox/.venv/bin/python",
"args": ["-m", "mcp_server.server"],
"cwd": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/netbox",
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox",
"env": {
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/netbox"
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox"
}
}
}

View File

@@ -1,19 +1,19 @@
{
"mcpServers": {
"gitea": {
"command": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/gitea/.venv/bin/python",
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/.venv/bin/python",
"args": ["-m", "mcp_server.server"],
"cwd": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/gitea",
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea",
"env": {
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/gitea"
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea"
}
},
"wikijs": {
"command": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/wikijs/.venv/bin/python",
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/wikijs/.venv/bin/python",
"args": ["-m", "mcp_server.server"],
"cwd": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/wikijs",
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/wikijs",
"env": {
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../../mcp-servers/wikijs"
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/wikijs"
}
}
}

View File

@@ -5,12 +5,12 @@
# Usage: ./scripts/setup.sh
#
# This script:
# 1. Creates Python virtual environments for MCP servers
# 1. Creates Python virtual environments for MCP servers (inside each plugin)
# 2. Installs dependencies
# 3. Creates config file templates (if missing)
# 4. Validates existing configuration
# 5. Checks/creates Wiki.js directory structure
# 6. Syncs Gitea labels (creates/updates, respects custom labels)
# 6. Validates label reference file
# 7. Reports remaining manual steps
#
@@ -40,11 +40,13 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1"; FAILED+=("$1"); }
log_todo() { echo -e "${YELLOW}[TODO]${NC} $1"; MANUAL_TODO+=("$1"); }
# --- Section 1: Python Environments ---
setup_python_env() {
local server_name="$1"
local server_path="$REPO_ROOT/mcp-servers/$server_name"
# MCP servers are now inside each plugin
setup_plugin_mcp() {
local plugin_name="$1"
local server_name="$2"
local server_path="$REPO_ROOT/plugins/$plugin_name/mcp-servers/$server_name"
log_info "Setting up $server_name MCP server..."
log_info "Setting up $server_name MCP server (plugin: $plugin_name)..."
if [[ ! -d "$server_path" ]]; then
log_error "$server_name directory not found at $server_path"
@@ -55,10 +57,10 @@ setup_python_env() {
# Check if venv exists
if [[ -d ".venv" ]]; then
log_skip "$server_name venv already exists"
log_skip "$plugin_name/$server_name venv already exists"
else
python3 -m venv .venv
log_success "$server_name venv created"
log_success "$plugin_name/$server_name venv created"
fi
# Install/update dependencies
@@ -67,9 +69,9 @@ setup_python_env() {
pip install -q --upgrade pip
pip install -q -r requirements.txt
deactivate
log_success "$server_name dependencies installed"
log_success "$plugin_name/$server_name dependencies installed"
else
log_error "$server_name requirements.txt not found"
log_error "$plugin_name/$server_name requirements.txt not found"
fi
cd "$REPO_ROOT"
@@ -117,6 +119,22 @@ EOF
log_success "wikijs.env template created"
log_todo "Edit ~/.config/claude/wikijs.env with your Wiki.js credentials"
fi
# NetBox config
if [[ -f "$config_dir/netbox.env" ]]; then
log_skip "netbox.env already exists"
else
cat > "$config_dir/netbox.env" << 'EOF'
# NetBox API Configuration
# Update these values with your NetBox instance details
NETBOX_API_URL=https://netbox.example.com/api
NETBOX_API_TOKEN=your_netbox_token_here
EOF
chmod 600 "$config_dir/netbox.env"
log_success "netbox.env template created"
log_todo "Edit ~/.config/claude/netbox.env with your NetBox credentials"
fi
}
# --- Section 3: Validate Configuration ---
@@ -128,7 +146,7 @@ validate_config() {
# Check Gitea config has real values
if [[ -f "$config_dir/gitea.env" ]]; then
source "$config_dir/gitea.env"
if [[ "$GITEA_API_TOKEN" == "your_gitea_token_here" ]] || [[ -z "$GITEA_API_TOKEN" ]]; then
if [[ "${GITEA_API_TOKEN:-}" == "your_gitea_token_here" ]] || [[ -z "${GITEA_API_TOKEN:-}" ]]; then
log_todo "Update GITEA_API_TOKEN in ~/.config/claude/gitea.env"
else
log_success "Gitea configuration appears valid"
@@ -138,12 +156,22 @@ validate_config() {
# Check Wiki.js config has real values
if [[ -f "$config_dir/wikijs.env" ]]; then
source "$config_dir/wikijs.env"
if [[ "$WIKIJS_API_TOKEN" == "your_wikijs_jwt_token_here" ]] || [[ -z "$WIKIJS_API_TOKEN" ]]; then
if [[ "${WIKIJS_API_TOKEN:-}" == "your_wikijs_jwt_token_here" ]] || [[ -z "${WIKIJS_API_TOKEN:-}" ]]; then
log_todo "Update WIKIJS_API_TOKEN in ~/.config/claude/wikijs.env"
else
log_success "Wiki.js configuration appears valid"
fi
fi
# Check NetBox config has real values
if [[ -f "$config_dir/netbox.env" ]]; then
source "$config_dir/netbox.env"
if [[ "${NETBOX_API_TOKEN:-}" == "your_netbox_token_here" ]] || [[ -z "${NETBOX_API_TOKEN:-}" ]]; then
log_todo "Update NETBOX_API_TOKEN in ~/.config/claude/netbox.env"
else
log_success "NetBox configuration appears valid"
fi
fi
}
# --- Section 4: Wiki.js Directory Structure ---
@@ -156,7 +184,7 @@ setup_wikijs_structure() {
local config_dir="$HOME/.config/claude"
if [[ -f "$config_dir/wikijs.env" ]]; then
source "$config_dir/wikijs.env"
if [[ "$WIKIJS_API_TOKEN" != "your_wikijs_jwt_token_here" ]] && [[ -n "$WIKIJS_API_TOKEN" ]]; then
if [[ "${WIKIJS_API_TOKEN:-}" != "your_wikijs_jwt_token_here" ]] && [[ -n "${WIKIJS_API_TOKEN:-}" ]]; then
log_info "Wiki.js credentials found - directory structure can be verified after first use"
log_success "Wiki.js setup deferred to first use"
else
@@ -238,9 +266,12 @@ main() {
echo "=============================================="
echo ""
# Python environments
setup_python_env "gitea"
setup_python_env "wikijs"
# Python environments for projman plugin
setup_plugin_mcp "projman" "gitea"
setup_plugin_mcp "projman" "wikijs"
# Python environment for cmdb-assistant plugin
setup_plugin_mcp "cmdb-assistant" "netbox"
# Configuration
setup_config_templates