conversion du mcp de local vers host web
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user