conversion du mcp de local vers host web

This commit is contained in:
2026-05-31 19:54:39 +02:00
parent 23eb3f3c84
commit 684224d217
3808 changed files with 1145 additions and 411051 deletions
+57 -307
View File
@@ -1,323 +1,73 @@
# CLAUDE.md — MCP Wiki Server for EasyWMS
# CLAUDE.md
## Project Overview
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build an MCP (Model Context Protocol) server that serves a compiled wiki about EasyWMS (Mecalux WMS) to Claude. The wiki contains ~65-70 dense, cross-referenced Markdown pages covering every aspect of the WMS: core entities, operational flows, modules, architecture, troubleshooting.
## What this is
This MCP server is **read-only** — it serves knowledge. It is designed to work alongside a separate MCP API server (already built) that queries live WMS data. Together:
- **MCP Wiki** (this project) = "how the WMS is supposed to work" (theory, logic, configuration)
- **MCP API** (separate project) = "what the WMS actually contains right now" (live data, logs, workflows)
Read-only MCP server (Streamable HTTP transport) that serves a compiled EasyWMS / Mecalux WMS wiki. Designed to be paired in a Claude client with a separate `wms-api` MCP that queries live WMS data:
## Target Environment
- **wms-wiki** (this repo) — *how the WMS is supposed to work* (concepts, modules, configuration). 5 tools + 2 resources, all read-only.
- **wms-api** (separate repo) — *what the WMS currently contains* (live data, logs, workflows).
- **Runtime:** Node.js (v18+)
- **OS:** Windows Server (same VM as the WMS) or any machine with Node.js
- **Transport:** stdio (launched by Claude Desktop or Claude Code)
- **Dependencies:** `@modelcontextprotocol/sdk`, `dotenv`, and minimal others — keep it lightweight
- **No network required:** all data is local (wiki/ folder)
The wiki itself is ~135 markdown pages under `wiki/`, with YAML frontmatter (title/type/tags/related/sources). It's regenerated upstream by a separate `mcp-wiki` compilation project and dropped in.
## Project Structure
```
mcp-wiki-server/
├── CLAUDE.md ← This file
├── package.json
├── .env ← Wiki path configuration
├── wiki/ ← Compiled wiki pages (copied from mcp-wiki project)
│ ├── _index.md
│ ├── _log.md
│ ├── _lint_report.md
│ ├── glossary.md
│ ├── concepts/ ← ~30 concept pages
│ ├── modules/ ← ~23 module pages
│ ├── architecture/ ← ~4 pages
│ └── operations/ ← ~2 pages
└── src/
├── index.js ← Main MCP server entry point
├── config/
│ └── constants.js ← Paths, cache settings
├── services/
│ ├── wiki-index.js ← Loads and parses _index.md + frontmatter from all pages
│ ├── search.js ← Full-text + tag search across wiki pages
│ └── page.js ← Read individual wiki pages
├── tools/
│ ├── search-tool.js ← search_wiki tool
│ ├── page-tool.js ← get_wiki_page tool
│ ├── browse-tool.js ← list_wiki_sections tool
│ ├── glossary-tool.js← get_glossary_term tool
│ └── related-tool.js ← get_related_pages tool
└── resources/
├── overview.js ← wiki://overview resource
└── entities-map.js ← wiki://entities-map resource
```
## MCP Tools to Implement (5 tools)
### 1. `search_wiki`
**Purpose:** Search across all wiki pages by keyword, tag, or entity name.
**Parameters:**
- `query` (string, required) — Search terms
- `tags` (string[], optional) — Filter by tags from frontmatter
- `type` (string, optional) — Filter by page type: "concept", "module", "architecture", "operation"
- `max_results` (number, optional, default: 5) — Max pages returned
**Behavior:**
1. Search in page titles (highest weight)
2. Search in frontmatter tags and `related` entries (medium weight)
3. Search in page body text (lower weight)
4. Return results sorted by relevance score
5. Each result includes: title, path, type, summary (first paragraph), relevance score
**Implementation notes:**
- Build an in-memory inverted index at startup from all wiki pages
- Parse YAML frontmatter to extract title, type, tags, related, sources
- Index words from title, tags, headings (h2/h3), and body text with different weights
- Support partial matches and case-insensitive search
- The search index should be rebuilt if wiki files change (check mtime on page load, or just rebuild at startup since pages are static)
### 2. `get_wiki_page`
**Purpose:** Load the full content of a specific wiki page.
**Parameters:**
- `path` (string, required) — Page path relative to wiki/ (e.g., "concepts/container.md")
**Behavior:**
1. Read the file from wiki/{path}
2. Return full markdown content including frontmatter
3. If page not found, return a helpful error listing similar page names (fuzzy match)
**Implementation notes:**
- Simple file read — no transformation needed
- Add a "Related pages" footer with clickable links if the frontmatter has `related` entries
- Cache file contents in memory (pages don't change at runtime)
### 3. `list_wiki_sections`
**Purpose:** Return the complete wiki structure for navigation.
**Parameters:**
- `section` (string, optional) — Filter by section: "concepts", "modules", "architecture", "operations", or omit for all
**Behavior:**
1. Return a structured list of all pages grouped by section
2. Each entry includes: title, path, type, one-line summary
3. If section filter is provided, return only that section
**Implementation notes:**
- Built from the in-memory index (same data as search_wiki)
- This is the first tool Claude should call when it needs to orient itself
### 4. `get_glossary_term`
**Purpose:** Quick definition lookup for WMS-specific terms.
**Parameters:**
- `term` (string, required) — Term to look up (case-insensitive)
**Behavior:**
1. Parse glossary.md into a dictionary of term → definition
2. Search for exact match first, then partial match
3. Return the definition + link to the glossary page
4. If not found, suggest similar terms
**Implementation notes:**
- Parse glossary.md at startup into a Map<string, string>
- Support abbreviations (e.g., "PDL" → "Picking Dedicated Location")
- Fuzzy matching: Levenshtein distance or simple startsWith/includes
### 5. `get_related_pages`
**Purpose:** Find all pages related to a given entity, command, or concept.
**Parameters:**
- `name` (string, required) — Entity name, command name, or concept (e.g., "Container", "putaway", "ASN")
**Behavior:**
1. Search the `related` frontmatter field of all pages for the given name
2. Also search page body for mentions of the name
3. Return list of pages that reference this concept, with context (the sentence/heading where it's mentioned)
**Implementation notes:**
- Uses the same inverted index as search_wiki
- Additionally parses `related` frontmatter arrays
- Useful when Claude wants to understand all the places a concept appears
## MCP Resources to Implement (2 resources)
### `wiki://overview`
**Content:** A condensed overview of the EasyWMS wiki — what sections exist, how many pages, key concepts. This is automatically loaded into Claude's context when the MCP connects.
**Build from:** `wiki/_index.md` content + page counts per section
### `wiki://entities-map`
**Content:** The entities relationship map showing how WMS entities relate to each other.
**Build from:** `wiki/architecture/entities-map.md` content
## Implementation Guidelines
### Startup Sequence
1. Read `WIKI_PATH` from `.env` (default: `./wiki`)
2. Scan all `.md` files in wiki/
3. Parse frontmatter (YAML between `---` markers) for each file
4. Build in-memory index:
- `pages[]` — array of {path, title, type, tags, related, summary, body, headings}
- `invertedIndex{}` — word → [{page, weight, context}]
- `glossary{}` — term → definition
5. Register MCP tools and resources
6. Start stdio transport
### Frontmatter Parsing
Every wiki page has this structure:
```markdown
---
title: "Page Title"
type: concept | module | architecture | operation
sources:
- path/to/raw/source.md
related:
- concepts/other-page.md
- modules/some-module.md
last_compiled: "2026-04-10"
---
# Page Title
Page body content...
```
Parse the YAML between the `---` markers. Use a simple regex-based parser or `yaml` npm package.
### Search Relevance Scoring
```
title exact match: 100
title partial match: 50
tag exact match: 40
heading (h2/h3) match: 30
related entry match: 25
body text match: 10
```
### Error Handling
- All tools must return structured errors, never crash the server
- Use `console.error()` for logging (stdout reserved for MCP protocol)
- If wiki/ folder is missing or empty, return a clear error message
### Performance
- Wiki is small (~70 files, ~1-2MB total) — load everything into memory at startup
- No database needed
- No pagination needed for search results (max 10-20 results)
- File watching is optional (pages are static between syncs)
## .env Configuration
```env
# Path to the wiki folder (absolute or relative to project root)
WIKI_PATH=./wiki
# Optional: enable debug logging
DEBUG=false
```
## Claude Desktop Integration
After building, configure Claude Desktop to use both MCP servers:
```json
{
"mcpServers": {
"wms-wiki": {
"command": "node",
"args": ["C:\\path\\to\\mcp-wiki-server\\src\\index.js"]
},
"wms-api": {
"command": "node",
"args": ["C:\\path\\to\\wms-mcp-server\\src\\index.js"]
}
}
}
```
## Custom Instructions for Claude Desktop
When both MCPs are configured, add this to Claude Desktop's custom instructions:
```
You have access to two MCP servers for EasyWMS:
1. wms-wiki — Documentation and knowledge base. Use it to understand HOW the WMS works:
- search_wiki: find relevant documentation pages
- get_wiki_page: read full page content
- list_wiki_sections: browse available documentation
- get_glossary_term: look up WMS-specific terms
- get_related_pages: find all pages mentioning a concept
2. wms-api — Live WMS data. Use it to see WHAT the WMS currently contains:
- query_wms_entities: query live data via LINQ
- search_workflows: find workflow definitions
- search_ad_elements: search application dictionary
- read_recent_logs: check recent log entries
WORKFLOW for diagnosing issues:
1. FIRST use wms-wiki to understand the feature/concept involved
2. THEN use wms-api to check the actual state of the WMS
3. Compare theory (wiki) vs reality (live data) to identify the problem
4. Use wiki error/troubleshooting sections for known solutions
WORKFLOW for answering "how does X work":
1. search_wiki or list_wiki_sections to find relevant pages
2. get_wiki_page to read the full documentation
3. If needed, get_glossary_term for specific terminology
Always consult the wiki BEFORE querying live data — it gives you the context to interpret what the API returns.
```
## Testing
After implementation, test these scenarios in Claude Desktop:
1. **"What is a container in EasyWMS?"**
→ Should call search_wiki("container") → get_wiki_page("concepts/container.md")
2. **"How does putaway work?"**
→ Should call get_wiki_page("concepts/putaway.md") → comprehensive answer
3. **"What's a PDL?"**
→ Should call get_glossary_term("PDL") → "Picking Dedicated Location" + link to replenishment.md
4. **"What ERP messages are involved in reception?"**
→ Should call search_wiki("reception ERP") → get_wiki_page("concepts/reception.md") → extract ERP section
5. **"Why are my putaway tasks failing?"** (with wms-api also connected)
→ Should call search_wiki("putaway errors") → read troubleshooting → then query live WMS data for comparison
6. **"List all available modules"**
→ Should call list_wiki_sections("modules") → return all module pages
## Development Commands
## Commands
```bash
# Install dependencies
npm install
# Local dev — MCP_ALLOWED_HOSTS is required, the transport rejects all hosts otherwise.
MCP_ALLOWED_HOSTS="127.0.0.1:3000,localhost:3000" HOST=127.0.0.1 PORT=3000 \
npm start
# Run locally for testing
node src/index.js
# Smoke test (in another terminal): healthz + initialize + tools/list
npm run smoke-test
# Override target:
MCP_URL=https://mcp-wms.arthur-ria.fr HOST_HEADER=mcp-wms.arthur-ria.fr npm run smoke-test
# Test with MCP inspector (if available)
npx @modelcontextprotocol/inspector node src/index.js
# MCP Inspector for interactive debugging (still uses stdio under the hood)
npm run inspect
# Docker
docker build -t mcp-wms-wiki:dev .
docker run --rm -p 3000:3000 -e MCP_ALLOWED_HOSTS=127.0.0.1:3000 mcp-wms-wiki:dev
# No test suite. Verification is the smoke-test script + tools/call against a live server.
```
## Dependencies
## Required env vars
```json
{
"dependencies": {
"@modelcontextprotocol/sdk": "latest",
"dotenv": "^16.0.0"
}
}
```
| Var | Default | Notes |
|---|---|---|
| `WIKI_PATH` | `./wiki` | Absolute or relative to project root |
| `PORT` | `3000` | |
| `HOST` | `0.0.0.0` | `127.0.0.1` for local dev to avoid LAN exposure |
| `MCP_ALLOWED_HOSTS` | *(empty)* | **Required**. Comma-separated Host headers. Empty → DNS-rebind guard rejects everything |
Keep dependencies minimal. No YAML parser needed if you use regex for frontmatter. No search library needed — the wiki is small enough for a custom in-memory index.
`.env.example` documents local-dev values. Production injects vars via the Docker / TrueNAS app definition — `.env` is for local dev only and is git-ignored.
## Key Technical Constraints
## Architecture — the non-obvious parts
1. **stdout is reserved for MCP protocol** — use `console.error()` for all logging
2. **Windows paths** — use `path.join()` everywhere, never hardcode `/` or `\\`
3. **Encoding** — all wiki files are UTF-8
4. **No network** — everything is local file reads
5. **Startup speed** — loading ~70 MD files into memory should take < 1 second
6. **Memory** — wiki is ~1-2MB, index adds maybe 5MB — trivial for Node.js
**Startup (once)**: [src/index.js](src/index.js) loads `.env`, builds `ctx = { pages, invertedIndex, glossary, wikiPath }` from the wiki folder via [src/services/wiki-index.js](src/services/wiki-index.js), freezes it. Everything reads from this in-memory snapshot — no file watching, no rebuild at runtime. Restart to pick up wiki changes.
**Per-request** (HTTP): each `/mcp` hit instantiates a *new* `StreamableHTTPServerTransport` AND a *new* `McpServer` via `createServer(ctx)`. The factory re-registers all 5 tools + 2 resources, capturing `ctx` by reference (cheap — no copies). This is required for safe stateless concurrency: the SDK pairs one `McpServer` to one transport, so sharing across concurrent requests would crosstalk responses. `res.on('close')` tears both down.
**Transport mode**: stateless (`sessionIdGenerator: undefined`) + `enableJsonResponse: true` — JSON one-shot responses, no SSE long-polling. This is the simplification that makes the reverse-proxy story (NPM + Cloudflare) trivial. If a future tool needs streaming or server-initiated notifications, this assumption breaks and you'll need stateful mode.
**DNS-rebind protection** is on by default and depends entirely on `MCP_ALLOWED_HOSTS`. Forgetting to set it = HTTP 403 on every request. The error is silent at startup.
**Auth (V1 = passthrough)**: `authMiddleware` is mounted only on `/mcp`, never on `/healthz`. V2 OAuth hook point is documented inline in `src/index.js` — wire in `@modelcontextprotocol/sdk/server/auth/*` there, don't sprinkle auth elsewhere.
**Tools and resources** live under [src/tools/](src/tools/) and [src/resources/](src/resources/). Each tool exports `<name>ToolDefinition` (name + description + JSON-schema for docs) and `handle<Name>(args, ctx)`. The McpServer wiring in `createServer()` uses Zod schemas separately because the SDK's `server.tool()` takes Zod, not JSON-schema. Keep both in sync.
**Path-traversal guard** is centralized in [src/services/page.js](src/services/page.js) `safeResolveWikiPath()`. The `get_wiki_page` tool layer is intentionally dumb — it forwards the raw client input. Do *not* add a second sanitization upstream: it gives false confidence and the previous attempt (a `.replace(/\.\.\//g, '')`) was bypassable. Any new file-reading code path must go through `safeResolveWikiPath`.
## Conventions / things not to do
- **Don't add per-tool state.** `ctx` is the only mutable-looking surface (it's frozen). Tools take `(args, ctx)` and return `{ content: [{ type: 'text', text }] }`. Stick to this shape.
- **Don't change the 5 tool names or input shapes** without coordinating with clients. They are the published contract: `search_wiki`, `get_wiki_page`, `list_wiki_sections`, `get_glossary_term`, `get_related_pages`.
- **Don't write to disk at runtime.** This server is read-only by design. No caches that survive a restart, no logs to files (stderr only — Docker collects it).
- **`console.error` for all logging.** stdout was reserved by the old stdio transport; the convention stays because the MCP Inspector flow (`npm run inspect`) still uses stdio.
- **YAML frontmatter parsing is regex-based**, not a real YAML parser ([src/services/wiki-index.js](src/services/wiki-index.js)). It handles only the subset present in the wiki: scalar `key: value` and `key:` + ` - item` arrays. Don't introduce frontmatter shapes it can't parse without upgrading the parser.
## Deploy target
TrueNAS Scale custom app via [compose.yaml](compose.yaml), behind Nginx Proxy Manager + Cloudflare on `mcp-wms.arthur-ria.fr`. The wiki is **baked into the image** (not a volume) — rebuild + redeploy on wiki updates. NPM needs `proxy_buffering off; proxy_read_timeout 3600;` even though we're on JSON one-shot, for safety. Cloudflare: DNS-only (gray cloud) recommended initially.