feat(mcp): add 4 new tools — compare_versions, get_threat, list_threats, search_examples
New tools (8 → 12 total): - compare_versions(from, to): diff Claude Code releases between two versions, aggregating highlights and breaking changes across the range - get_threat(id): look up any CVE or attack technique (T-code) with full details, severity, mitigation, and source references - list_threats(category?): browse the threat database — summary table or detailed view by section (cves, authors, skills, techniques, mitigations, sources) - search_examples(query, limit?): semantic search across 199 templates with token-aware scoring and get_example() hints Infrastructure: - content.ts: add loadThreatDb() with memory cache and dual-mode loading (GUIDE_ROOT filesystem in dev, GitHub fetch in production) - Threat DB interface with correct Record<string, string> type for minimum_safe_versions Docs: - mcp-server/README.md: document all 12 tools with usage examples - mcp-server/IDEAS.md: future ideas (quiz, methodology, workflow, diff resource) - CHANGELOG.md: [Unreleased] entry for all 4 tools - README.md: promote MCP section to standalone ## after Quick Start (was ### inside Quick Start) - guide/architecture.md: add MCP server to Extended Tool Ecosystem Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e62af76767
commit
7236362c1e
33 changed files with 7686 additions and 22 deletions
|
|
@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Added
|
||||
|
||||
- **MCP Server: 3 new tools** — compare_versions, get_threat/list_threats, search_examples
|
||||
- `compare_versions(from, to?)` — diff entre deux versions Claude Code CLI : toutes les releases dans la plage, highlights agrégés, breaking changes agrégés
|
||||
- `get_threat(id)` — lookup CVE (ex: `CVE-2025-53109`) ou technique d'attaque (ex: `T001`) depuis la threat database v2.4.0
|
||||
- `list_threats(category?)` — browse la threat-db : résumé global avec counts (sans catégorie) ou liste détaillée par section (`cves`, `authors`, `skills`, `techniques`, `mitigations`, `sources`)
|
||||
- `search_examples(query, limit?)` — recherche sémantique dans les 175 templates par intention (ex: `"hook lint"`, `"agent code review"`) — complémentaire à `get_example` (nom exact) et `list_examples` (catégorie)
|
||||
- `mcp-server/IDEAS.md` — futures idées documentées : `get_quiz`, `get_methodology`, `get_workflow`, resource `diff`, prompt `security-review`
|
||||
- Total : 8 tools → 12 tools (+ 3 resources + 1 prompt)
|
||||
|
||||
- **Terminal Personalization Settings** — documentation `spinnerVerbs` + `spinnerTipsOverride` dans `guide/ultimate-guide.md` §3.3 Settings & Permissions
|
||||
- Nouvelle section "Terminal Personalization Settings" (ligne 4978) : exemples JSON pour `spinnerVerbs` (mode replace/add) et `spinnerTipsOverride` (avec `excludeDefault: true`)
|
||||
- `settings.json` available keys enrichi : ajout `spinnerVerbs`, `spinnerTipsOverride`, `plansDirectory`, `enableAllProjectMcpServers`
|
||||
|
|
|
|||
44
README.md
44
README.md
|
|
@ -10,6 +10,7 @@
|
|||
<a href="./quiz/"><img src="https://img.shields.io/badge/Quiz-274_questions-orange?style=for-the-badge" alt="Quiz"/></a>
|
||||
<a href="./examples/"><img src="https://img.shields.io/badge/Templates-175-green?style=for-the-badge" alt="Templates"/></a>
|
||||
<a href="./guide/security-hardening.md"><img src="https://img.shields.io/badge/🛡️_Threat_DB-24_CVEs_·_655_malicious_skills-red?style=for-the-badge" alt="Threat Database"/></a>
|
||||
<a href="./mcp-server/"><img src="https://img.shields.io/badge/MCP_Server-npx_ready-blueviolet?style=for-the-badge" alt="MCP Server"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -93,40 +94,39 @@ Both guides serve different needs. Choose based on your priority.
|
|||
|
||||
**Quickest path**: [Cheat Sheet](./guide/cheatsheet.md) — 1 printable page with daily essentials
|
||||
|
||||
**Interactive onboarding** (no clone needed):
|
||||
**Interactive onboarding** (no setup needed):
|
||||
```bash
|
||||
claude "Fetch and follow the onboarding instructions from: https://raw.githubusercontent.com/FlorianBruniaux/claude-code-ultimate-guide/main/tools/onboarding-prompt.md"
|
||||
```
|
||||
|
||||
**Browse directly**: [Full Guide](./guide/ultimate-guide.md) | [Visual Diagrams](./guide/diagrams/) | [Examples](./examples/) | [Quiz](./quiz/)
|
||||
|
||||
<details>
|
||||
<summary><strong>Prerequisites & Minimal CLAUDE.md Template</strong></summary>
|
||||
---
|
||||
|
||||
**Prerequisites**: Node.js 18+ | [Anthropic API key](https://console.anthropic.com/)
|
||||
## 🔌 MCP Server — Use the guide from any Claude Code session
|
||||
|
||||
```markdown
|
||||
# Project: [NAME]
|
||||
No cloning needed. Add to `~/.claude.json` and ask questions directly from any session:
|
||||
|
||||
## Tech Stack
|
||||
- Language: [e.g., TypeScript]
|
||||
- Framework: [e.g., Next.js 14]
|
||||
- Testing: [e.g., Vitest]
|
||||
|
||||
## Commands
|
||||
- Build: `npm run build`
|
||||
- Test: `npm test`
|
||||
- Lint: `npm run lint`
|
||||
|
||||
## Rules
|
||||
- Run tests before marking tasks complete
|
||||
- Follow existing code patterns
|
||||
- Keep commits atomic and conventional
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"claude-code-guide": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["-y", "claude-code-ultimate-guide-mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save as `CLAUDE.md` in your project root. Claude reads it automatically.
|
||||
12 tools: `search_guide`, `read_section`, `get_cheatsheet`, `get_digest`, `get_example`, `list_examples`, `get_release`, `get_changelog`, `list_topics`, `compare_versions`, `get_threat`, `list_threats`, `search_examples` — plus 8 slash commands `/ccguide:*` and a Haiku agent.
|
||||
|
||||
</details>
|
||||
**Onboarding one-liner** (once MCP is configured):
|
||||
```bash
|
||||
claude "Use the claude-code-guide MCP server. Activate the claude-code-expert prompt, then run a personalized onboarding: ask me 3 questions about my goal, experience level, and preferred tone — then build a custom learning path using search_guide and read_section to navigate the guide with live source links."
|
||||
```
|
||||
|
||||
→ [MCP Server README](./mcp-server/README.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -277,6 +277,7 @@ Beyond the 8 core tools, Claude Code can leverage:
|
|||
- **Context7**: Official library documentation lookup
|
||||
- **Sequential**: Structured multi-step reasoning
|
||||
- **Playwright**: Browser automation and E2E testing
|
||||
- **claude-code-ultimate-guide**: 12 tools — guide search, release tracking, `compare_versions`, security threat lookup (`get_threat`, `list_threats` with 28 CVEs + 655 malicious skills), template search (`search_examples`) — `npx -y claude-code-ultimate-guide-mcp`
|
||||
|
||||
**Community Plugins**:
|
||||
- **ast-grep**: AST-based structural code search (explicit invocation)
|
||||
|
|
|
|||
78
mcp-server/IDEAS.md
Normal file
78
mcp-server/IDEAS.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# MCP Server — Future Ideas
|
||||
|
||||
Tracked ideas that didn't make it into the current release. Implementation complexity varies; all are technically feasible with existing data.
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
### `get_quiz(topic?, count?)`
|
||||
|
||||
Interactive quiz from `machine-readable/questions.json` (274 questions, already used by the landing site).
|
||||
|
||||
- `topic` (optional string): filter by topic (e.g. "hooks", "agents", "mcp")
|
||||
- `count` (optional number, default 5, max 20): number of questions to return
|
||||
- Returns questions with options, correct answer, and explanation
|
||||
- Useful for learning validation, onboarding, and teaching workflows
|
||||
|
||||
**Data**: `machine-readable/questions.json` — not currently bundled in the package; would need bundling or GitHub fetch.
|
||||
|
||||
---
|
||||
|
||||
### `get_methodology(name)`
|
||||
|
||||
Step-by-step workflows for TDD, SDD, BDD from `guide/methodologies.md`.
|
||||
|
||||
- `name` (string): `tdd | sdd | bdd | all`
|
||||
- Returns the workflow steps, when to use it, and example commands
|
||||
- Useful for agents doing test-driven development or spec-driven design
|
||||
|
||||
**Data**: `guide/methodologies.md` — fetched on demand (already in section-reader infrastructure).
|
||||
|
||||
---
|
||||
|
||||
### `get_workflow(name)`
|
||||
|
||||
Step-by-step workflows from `guide/workflows/` directory.
|
||||
|
||||
- `name` (string): partial name match (e.g. "code-review", "refactor", "debug")
|
||||
- Returns the workflow with steps, triggers, and example prompts
|
||||
- Could list available workflows when no name provided
|
||||
|
||||
**Data**: `guide/workflows/*.md` — fetched on demand.
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
### `claude-code-guide://diff`
|
||||
|
||||
Shows what changed between the bundled YAML index version and the live GitHub version.
|
||||
|
||||
- Fetch live `machine-readable/reference.yaml` from GitHub
|
||||
- Diff against bundled version (entry count, new keys, changed values)
|
||||
- Helps users know when the package is stale vs. the guide
|
||||
|
||||
**Complexity**: Medium — requires async resource handler + structured diffing.
|
||||
|
||||
---
|
||||
|
||||
## Prompts
|
||||
|
||||
### `security-review`
|
||||
|
||||
Dedicated security audit workflow prompt using the threat database.
|
||||
|
||||
- Guides the model through: check CVEs → check authors → check skills → check techniques
|
||||
- Returns a structured security posture report
|
||||
- Reuses `list_threats` and `get_threat` tools internally
|
||||
|
||||
**Dependency**: Requires `threats.ts` tools (already implemented in v1.1.0).
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All ideas use data already in the repo — no new data sources needed
|
||||
- `get_quiz` requires bundling `questions.json` (currently not in npm package)
|
||||
- `get_methodology` and `get_workflow` are low-effort since section-reader already handles arbitrary file fetching
|
||||
230
mcp-server/README.md
Normal file
230
mcp-server/README.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# claude-code-ultimate-guide-mcp
|
||||
|
||||
MCP server for the [Claude Code Ultimate Guide](https://github.com/FlorianBruniaux/claude-code-ultimate-guide) — search, read, and explore 20,000+ lines of documentation directly from Claude Code or any MCP-compatible client.
|
||||
|
||||
No need to clone the repo. The guide's structured index is bundled in the package (~130KB compressed), and file content is fetched from GitHub on demand with 24h local cache.
|
||||
|
||||
## Installation
|
||||
|
||||
### Quick start (npx)
|
||||
|
||||
Add to `~/.claude.json` (user-level, all projects):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"claude-code-guide": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["-y", "claude-code-ultimate-guide-mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Global install
|
||||
|
||||
```bash
|
||||
npm install -g claude-code-ultimate-guide-mcp
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"claude-code-guide": {
|
||||
"type": "stdio",
|
||||
"command": "claude-code-guide-mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Per-project
|
||||
|
||||
Add to `.claude/settings.json` at your repo root.
|
||||
|
||||
## Tools
|
||||
|
||||
### Search & Navigation
|
||||
|
||||
| Tool | Signature | Description |
|
||||
|------|-----------|-------------|
|
||||
| `search_guide` | `(query, limit?)` | Search by keyword or question across 882 indexed entries. Returns ranked results with GitHub links. |
|
||||
| `read_section` | `(path, offset?, limit?)` | Read a file section with pagination (500 lines max per call). Returns GitHub + guide site links. |
|
||||
| `list_topics` | `()` | Browse all 25 topic categories in the guide with entry counts. |
|
||||
|
||||
### Templates & Examples
|
||||
|
||||
| Tool | Signature | Description |
|
||||
|------|-----------|-------------|
|
||||
| `get_example` | `(name)` | Fetch a production-ready template by exact name (agents, hooks, commands, skills). |
|
||||
| `list_examples` | `(category?)` | List all templates by category with GitHub links. Categories: `agents`, `commands`, `hooks`, `skills`, `scripts`. |
|
||||
| `search_examples` | `(query, limit?)` | Semantic search across all templates by intent (e.g. `"hook lint"`, `"agent code review"`). |
|
||||
|
||||
### What's New
|
||||
|
||||
| Tool | Signature | Description |
|
||||
|------|-----------|-------------|
|
||||
| `get_changelog` | `(count?)` | Last N entries from the guide CHANGELOG (default 5, max 20). |
|
||||
| `get_digest` | `(period)` | Combined digest of guide changes + Claude Code CLI releases. Period: `day`, `week`, `month`. |
|
||||
| `get_release` | `(version?, count?)` | Claude Code CLI release details. Pass a version (e.g. `"2.1.59"`) or omit for latest + recent N. |
|
||||
| `compare_versions` | `(from, to?)` | Diff between two Claude Code versions: aggregated highlights and breaking changes for all releases in range. |
|
||||
|
||||
### Security Intelligence
|
||||
|
||||
| Tool | Signature | Description |
|
||||
|------|-----------|-------------|
|
||||
| `get_threat` | `(id)` | Look up a CVE (e.g. `"CVE-2025-53109"`) or attack technique (e.g. `"T001"`) from the threat database. |
|
||||
| `list_threats` | `(category?)` | Browse the threat database. Without category: global summary with counts. With category: full section list. Categories: `cves`, `authors`, `skills`, `techniques`, `mitigations`, `sources`. |
|
||||
|
||||
### Quick Reference
|
||||
|
||||
| Tool | Signature | Description |
|
||||
|------|-----------|-------------|
|
||||
| `get_cheatsheet` | `(section?)` | Full cheatsheet or filtered to a specific section (e.g. `"hooks"`, `"agents"`, `"mcp"`). |
|
||||
|
||||
## Resources
|
||||
|
||||
| URI | Description |
|
||||
|-----|-------------|
|
||||
| `claude-code-guide://reference` | Full structured index (94KB YAML, ~900 entries) — use as fallback when search isn't enough |
|
||||
| `claude-code-guide://releases` | Claude Code official releases history (YAML) |
|
||||
| `claude-code-guide://llms` | Guide identity/navigation file (llms.txt) |
|
||||
|
||||
## Prompts
|
||||
|
||||
| Prompt | Args | Description |
|
||||
|--------|------|-------------|
|
||||
| `claude-code-expert` | `question?` | Activates expert mode with optimal workflow: search → read → example → YAML fallback |
|
||||
|
||||
## Onboarding (first run)
|
||||
|
||||
After installing the MCP server, run this in any Claude Code session for a personalized guided tour:
|
||||
|
||||
```bash
|
||||
claude "Use the claude-code-guide MCP server. Activate the claude-code-expert prompt, then run a personalized onboarding: ask me 3 questions about my goal, experience level, and preferred tone — then build a custom learning path using search_guide and read_section to navigate the guide with live source links."
|
||||
```
|
||||
|
||||
This replaces the static URL-fetch approach with live search across 900+ indexed entries, always up to date, with GitHub + guide site links on every result.
|
||||
|
||||
## Usage examples
|
||||
|
||||
```
|
||||
# Search
|
||||
search_guide("hooks")
|
||||
search_guide("cost optimization")
|
||||
search_guide("custom agents")
|
||||
|
||||
# Read content
|
||||
read_section("guide/ultimate-guide.md", 8077)
|
||||
read_section("guide/cheatsheet.md")
|
||||
|
||||
# Templates
|
||||
get_example("code-reviewer")
|
||||
get_example("pre-commit hook")
|
||||
list_examples("agents")
|
||||
list_examples("hooks")
|
||||
search_examples("hook lint")
|
||||
search_examples("agent code review")
|
||||
|
||||
# What's new
|
||||
get_digest("week")
|
||||
get_digest("month")
|
||||
get_changelog(10)
|
||||
get_release()
|
||||
get_release("2.1.59")
|
||||
compare_versions("2.1.50", "2.1.59")
|
||||
compare_versions("2.0.0")
|
||||
|
||||
# Security
|
||||
get_threat("CVE-2025-53109")
|
||||
get_threat("T001")
|
||||
list_threats()
|
||||
list_threats("cves")
|
||||
list_threats("techniques")
|
||||
|
||||
# Quick reference
|
||||
get_cheatsheet()
|
||||
get_cheatsheet("hooks")
|
||||
list_topics()
|
||||
```
|
||||
|
||||
## Slash command shortcuts
|
||||
|
||||
Install the companion slash commands for one-keystroke access in Claude Code. They live in `.claude/commands/ccguide/` of the guide repo — copy or symlink to `~/.claude/commands/ccguide/` for global availability.
|
||||
|
||||
```bash
|
||||
# From the guide repo root
|
||||
cp -r .claude/commands/ccguide ~/.claude/commands/ccguide
|
||||
```
|
||||
|
||||
Once installed, these commands are available in any Claude Code session:
|
||||
|
||||
| Command | Example | What it does |
|
||||
|---------|---------|--------------|
|
||||
| `/ccguide:search <query>` | `/ccguide:search hooks` | Search + auto-read top results |
|
||||
| `/ccguide:cheatsheet [section]` | `/ccguide:cheatsheet hooks` | Full cheatsheet or filtered |
|
||||
| `/ccguide:digest <period>` | `/ccguide:digest week` | Guide + CC releases digest |
|
||||
| `/ccguide:example <name>` | `/ccguide:example code-reviewer` | Fetch a template |
|
||||
| `/ccguide:examples [category]` | `/ccguide:examples agents` | List templates by category |
|
||||
| `/ccguide:release [version]` | `/ccguide:release 2.1.59` | CC CLI release details |
|
||||
| `/ccguide:changelog [count]` | `/ccguide:changelog 10` | Recent guide CHANGELOG |
|
||||
| `/ccguide:topics` | `/ccguide:topics` | Browse all 25 categories |
|
||||
|
||||
## Custom agent
|
||||
|
||||
A `claude-code-guide` agent is included in `.claude/agents/claude-code-guide.md`. It uses Haiku (fast, cheap) and searches the guide automatically before answering Claude Code questions.
|
||||
|
||||
Copy to your `~/.claude/agents/` to use it globally:
|
||||
|
||||
```bash
|
||||
cp .claude/agents/claude-code-guide.md ~/.claude/agents/claude-code-guide.md
|
||||
```
|
||||
|
||||
Then invoke with: `use claude-code-guide agent to answer: how do I configure hooks?`
|
||||
|
||||
## Dev mode (local repo)
|
||||
|
||||
If you've cloned the guide repo, set `GUIDE_ROOT` to read files locally instead of fetching from GitHub:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"claude-code-guide": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["/path/to/claude-code-ultimate-guide/mcp-server/dist/index.js"],
|
||||
"env": {
|
||||
"GUIDE_ROOT": "/path/to/claude-code-ultimate-guide"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With `GUIDE_ROOT` set:
|
||||
- YAML indexes loaded from the local repo (stays in sync with local changes)
|
||||
- File content read directly from disk (no GitHub fetch, no cache)
|
||||
|
||||
## Bundled content
|
||||
|
||||
The npm package includes (~130KB compressed total):
|
||||
- `content/reference.yaml` — 94KB structured index (~900 entries, ~882 indexed)
|
||||
- `content/claude-code-releases.yaml` — 27KB releases history (76 releases)
|
||||
- `content/llms.txt` — 8KB identity file
|
||||
|
||||
Guide markdown files (3.5MB) are **not** bundled — they're fetched from GitHub on demand and cached at `~/.cache/claude-code-guide/{version}/`.
|
||||
|
||||
## Cache
|
||||
|
||||
File content is cached at `~/.cache/claude-code-guide/{package-version}/` with 24h TTL. If offline, stale cache is served as fallback. If no cache exists and offline, tools return inline summaries from the YAML index instead.
|
||||
|
||||
## MCP Inspector
|
||||
|
||||
Test locally with the official MCP Inspector:
|
||||
|
||||
```bash
|
||||
cd mcp-server
|
||||
npm run build
|
||||
GUIDE_ROOT=.. npx @modelcontextprotocol/inspector node dist/index.js
|
||||
```
|
||||
701
mcp-server/content/claude-code-releases.yaml
Normal file
701
mcp-server/content/claude-code-releases.yaml
Normal file
|
|
@ -0,0 +1,701 @@
|
|||
# Claude Code Official Releases (condensed)
|
||||
# Source: github.com/anthropics/claude-code/CHANGELOG.md
|
||||
# Purpose: Track Claude Code product releases for documentation sync
|
||||
# Maintained: Manual updates when new releases are announced
|
||||
|
||||
latest: "2.1.59"
|
||||
updated: "2026-02-26"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# RELEASES (newest first, condensed highlights only)
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
releases:
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 2.1.x Series (January-February 2026)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
- version: "2.1.59"
|
||||
date: "2026-02-26"
|
||||
highlights:
|
||||
- "⭐ Auto-memory: Claude automatically saves useful context; manage with /memory"
|
||||
- "⭐ /copy command: interactive picker for selecting individual code blocks or full response"
|
||||
- "Smarter 'always allow' prefix suggestions for compound bash commands (per-subcommand prefixes)"
|
||||
- "Fixed MCP OAuth token refresh race condition with multiple simultaneous instances"
|
||||
- "Fixed config file corruption wiping authentication when multiple instances ran simultaneously"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.58"
|
||||
date: "2026-02-26"
|
||||
highlights:
|
||||
- "Remote Control expanded to more users"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.56"
|
||||
date: "2026-02-25"
|
||||
highlights:
|
||||
- "VSCode: Fixed another cause of 'command claude-vscode.editor.openLast not found' crashes"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.55"
|
||||
date: "2026-02-25"
|
||||
highlights:
|
||||
- "Fixed BashTool failing on Windows with EINVAL error"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.53"
|
||||
date: "2026-02-25"
|
||||
highlights:
|
||||
- "Stability fixes: panic on Windows, crashes on process spawn (Windows), WebAssembly crashes (Linux x64/Windows x64/ARM64)"
|
||||
- "Fixed graceful shutdown leaving stale sessions with Remote Control; `--worktree` flag sometimes ignored on first launch"
|
||||
- "Fixed UI flicker where user input disappeared briefly after submission; bulk agent kill (ctrl+f) single notification"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.52"
|
||||
date: "2026-02-24"
|
||||
highlights:
|
||||
- "VSCode: Fixed extension crash on Windows ('command claude-vscode.editor.openLast not found')"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.51"
|
||||
date: "2026-02-24"
|
||||
highlights:
|
||||
- "⭐ `claude remote-control` subcommand for external builds enabling local environment serving"
|
||||
- "BashTool skips login shell by default when shell snapshot available (was `CLAUDE_BASH_NO_LOGIN=true`); tool results persist to disk at 50K chars (was 100K)"
|
||||
- "SDK: `CLAUDE_CODE_ACCOUNT_UUID`, `CLAUDE_CODE_USER_EMAIL`, `CLAUDE_CODE_ORGANIZATION_UUID` env vars for account metadata"
|
||||
- "/model picker shows human-readable labels (e.g., 'Sonnet 4.5') for pinned versions; custom npm registries for plugin installs"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.50"
|
||||
date: "2026-02-21"
|
||||
highlights:
|
||||
- "⭐ `WorktreeCreate` and `WorktreeRemove` hook events for custom VCS setup/teardown with agent worktree isolation"
|
||||
- "`isolation: worktree` in agent definitions for declarative worktree isolation; `claude agents` CLI command to list configured agents"
|
||||
- "Opus 4.6 (fast mode) now has full 1M context window; `CLAUDE_CODE_DISABLE_1M_CONTEXT` env var to disable it"
|
||||
- "Major memory leak fixes + startup performance improvements for headless mode; `CLAUDE_CODE_SIMPLE` now fully minimal"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.49"
|
||||
date: "2026-02-20"
|
||||
highlights:
|
||||
- "⭐ `--worktree` / `-w` flag to start Claude in isolated git worktree; subagents support `isolation: 'worktree'`"
|
||||
- "Agent definitions support `background: true` to always run as background task"
|
||||
- "`ConfigChange` hook event for enterprise security auditing (fires when config files change during session)"
|
||||
- "Simple mode now includes file edit tool; many bug fixes for background agents, permissions, sessions"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.47"
|
||||
date: "2026-02-19"
|
||||
highlights:
|
||||
- "VS Code plan preview auto-updates as Claude iterates, commenting enabled when plan is ready"
|
||||
- "⭐ `ctrl+f` to kill all background agents (replaces double-ESC); background agents continue when ESC pressed"
|
||||
- "Added `last_assistant_message` field to Stop/SubagentStop hook inputs"
|
||||
- "70+ bug fixes: PDF compaction, Unicode curly quotes, parallel file edits, OSC 8 hyperlinks, Windows rendering, session persistence"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.46"
|
||||
date: "2026-02-19"
|
||||
highlights:
|
||||
- "Fixed orphaned Claude Code processes after terminal disconnect on macOS"
|
||||
- "Added support for using claude.ai MCP connectors in Claude Code"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.45"
|
||||
date: "2026-02-17"
|
||||
highlights:
|
||||
- "⭐ Claude Sonnet 4.6 model support"
|
||||
- "`spinnerTipsOverride` setting for customizable spinner tips (with `excludeDefault` option)"
|
||||
- "SDK: `SDKRateLimitInfo` and `SDKRateLimitEvent` types for rate limit tracking"
|
||||
- "Fixed Agent Teams on Bedrock/Vertex/Foundry; improved memory for large shell outputs"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.44"
|
||||
date: "2026-02-17"
|
||||
highlights:
|
||||
- "Fixed auth refresh errors"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.43"
|
||||
date: "2026-02-17"
|
||||
highlights:
|
||||
- "Fixed AWS auth refresh hanging indefinitely (3-minute timeout added)"
|
||||
- "Fixed structured-outputs beta header sent unconditionally on Vertex/Bedrock"
|
||||
- "Fixed spurious warnings for non-agent markdown files in `.claude/agents/`"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.42"
|
||||
date: "2026-02-14"
|
||||
highlights:
|
||||
- "Optimized startup via deferred Zod schema construction"
|
||||
- "Improved prompt cache hit rate by moving date outside system prompt"
|
||||
- "Opus 4.6 effort callout for eligible users"
|
||||
- "Better error messaging for image dimension limits (suggests `/compact`)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.41"
|
||||
date: "2026-02-13"
|
||||
highlights:
|
||||
- "Guard against launching Claude Code inside another Claude Code session"
|
||||
- "`claude auth login/status/logout` CLI subcommands"
|
||||
- "Windows ARM64 (win32-arm64) native binary support"
|
||||
- "`speed` attribute in OTel events for fast mode visibility"
|
||||
- "`/rename` auto-generates session name from conversation context"
|
||||
- "Fixed Agent Teams wrong model for Bedrock/Vertex/Foundry"
|
||||
- "Multiple stability fixes (FileReadTool FIFOs, MCP images, background tasks, stale permissions)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.39"
|
||||
date: "2026-02-10"
|
||||
highlights:
|
||||
- "Improved terminal rendering performance"
|
||||
- "Fixed fatal errors being swallowed instead of displayed"
|
||||
- "Fixed process hanging after session close"
|
||||
- "Fixed character loss at terminal screen boundary"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.38"
|
||||
date: "2026-02-10"
|
||||
highlights:
|
||||
- "Fixed VS Code terminal scroll-to-top regression"
|
||||
- "Fixed Tab key queueing slash commands instead of autocompleting"
|
||||
- "Security: Heredoc delimiter parsing to prevent command smuggling"
|
||||
- "Security: Blocked writes to `.claude/skills` in sandbox mode"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.37"
|
||||
date: "2026-02-08"
|
||||
highlights:
|
||||
- "Fixed /fast not immediately available after enabling /extra-usage"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.36"
|
||||
date: "2026-02-08"
|
||||
highlights:
|
||||
- "⭐ Fast mode now available for Opus 4.6"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.34"
|
||||
date: "2026-02-07"
|
||||
highlights:
|
||||
- "Fixed crash when agent teams setting changed between renders"
|
||||
- "Security fix: Sandbox-excluded commands could bypass Bash ask permission"
|
||||
breaking:
|
||||
- "Security fix: sandbox.excludedCommands / dangerouslyDisableSandbox bypass with autoAllowBashIfSandboxed"
|
||||
|
||||
- version: "2.1.33"
|
||||
date: "2026-02-06"
|
||||
highlights:
|
||||
- "Agent teams fixes: tmux sessions, availability warnings"
|
||||
- "`TeammateIdle` and `TaskCompleted` hook events for multi-agent workflows"
|
||||
- "Agent frontmatter: `memory` field (user/project/local), `Task(agent_type)` sub-agent restriction"
|
||||
- "VSCode: Remote sessions, branch/message count in session picker"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.32"
|
||||
date: "2026-02-05"
|
||||
highlights:
|
||||
- "⭐ Opus 4.6 is now available"
|
||||
- "⭐ Agent teams preview (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)"
|
||||
- "⭐ Automatic memory recording and recall"
|
||||
- "'Summarize from here' for partial conversation summarization"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.31"
|
||||
date: "2026-02-03"
|
||||
highlights:
|
||||
- "Session resume hint on exit showing how to continue"
|
||||
- "Fixed PDF errors locking sessions & bash sandbox errors"
|
||||
- "LSP compatibility improvements & terminal rendering fixes"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.30"
|
||||
date: "2026-02-02"
|
||||
highlights:
|
||||
- "`pages` parameter for Read tool PDFs (e.g., `pages: '1-5'`)"
|
||||
- "Pre-configured OAuth for MCP servers (Slack support)"
|
||||
- "⭐ `/debug` command for session troubleshooting"
|
||||
- "git log/show read-only flags support"
|
||||
- "Task tool metrics (tokens, duration, tool uses)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.29"
|
||||
date: "2026-01-31"
|
||||
highlights:
|
||||
- "Fixed startup performance with saved hook context"
|
||||
- "Improved session recovery speed for long-duration sessions"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.27"
|
||||
date: "2026-01-29"
|
||||
highlights:
|
||||
- "`--from-pr` flag to resume sessions linked to GitHub PR number/URL"
|
||||
- "Sessions auto-linked to PRs when created via `gh pr create`"
|
||||
- "Windows: Fixed bash execution and console flashing issues"
|
||||
- "VSCode: Fixed OAuth token expiration (401 errors)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.25"
|
||||
date: "2026-01-30"
|
||||
highlights:
|
||||
- "Fixed beta header validation for Bedrock/Vertex gateway users"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.23"
|
||||
date: "2026-01-29"
|
||||
highlights:
|
||||
- "Customizable spinner verbs setting (spinnerVerbs)"
|
||||
- "mTLS and corporate proxy connectivity fixes"
|
||||
- "Improved terminal rendering performance"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.22"
|
||||
date: "2026-01-28"
|
||||
highlights:
|
||||
- "Improved task UI performance with virtualization"
|
||||
- "Vim selection and deletion fixes"
|
||||
- "LSP improvements: Kotlin support, UTF-16 ranges"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.21"
|
||||
date: "2026-01-28"
|
||||
highlights:
|
||||
- "Skills/commands can specify required/recommended Claude Code version"
|
||||
- "New TaskCreate fields: category, checklist, parentId"
|
||||
- "Automatic Claude Code update checking at session start"
|
||||
- "Tasks appear in /context with 'Disable tasks' shortcut"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.20"
|
||||
date: "2026-01-27"
|
||||
highlights:
|
||||
- "TaskUpdate: status='deleted' for task removal"
|
||||
- "PR review status indicator (colored dot + link)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.19"
|
||||
date: "2026-01-25"
|
||||
highlights:
|
||||
- "New: CLAUDE_CODE_ENABLE_TASKS env var (set false for old task system)"
|
||||
- "New: Shorthand $0, $1 for arguments in custom commands"
|
||||
- "[VSCode] Session forking and rewind enabled for all users"
|
||||
- "Fixed: Multiple session resuming/naming issues with git worktrees"
|
||||
breaking:
|
||||
- "Indexed argument syntax changed: $ARGUMENTS.0 → $ARGUMENTS[0] (bracket syntax)"
|
||||
|
||||
- version: "2.1.18"
|
||||
date: "2026-01-24"
|
||||
highlights:
|
||||
- "⭐ Customizable keyboard shortcuts with /keybindings command"
|
||||
- "Per-context keybindings, chord sequences, workflow personalization"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.17"
|
||||
date: "2026-01-23"
|
||||
highlights:
|
||||
- "Fix: Crashes on processors without AVX instruction support"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.16"
|
||||
date: "2026-01-22"
|
||||
highlights:
|
||||
- "⭐ New task management system with dependency tracking"
|
||||
- "[VSCode] Native plugin management support"
|
||||
- "[VSCode] OAuth users can browse/resume remote sessions"
|
||||
- "Fixed: OOM crashes when resuming sessions with heavy subagent usage"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.15"
|
||||
date: "2026-01-22"
|
||||
highlights:
|
||||
- "⚠️ Deprecation notice for npm installations (use `claude install`)"
|
||||
- "UI rendering performance improved with React Compiler"
|
||||
- "Fixed: MCP stdio server timeout not killing child process"
|
||||
breaking:
|
||||
- "npm installations deprecated - migrate to native installer"
|
||||
|
||||
- version: "2.1.14"
|
||||
date: "2026-01-21"
|
||||
highlights:
|
||||
- "History-based autocomplete in bash mode (! + Tab)"
|
||||
- "Search in installed plugins list"
|
||||
- "Git commit SHA pinning for plugins"
|
||||
- "Multiple fixes: context window, memory, autocomplete"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.12"
|
||||
date: "2026-01-18"
|
||||
highlights:
|
||||
- "Bug fix: Message rendering"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.11"
|
||||
date: "2026-01-17"
|
||||
highlights:
|
||||
- "Fix: Excessive MCP connection requests for HTTP/SSE transports"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.10"
|
||||
date: "2026-01-17"
|
||||
highlights:
|
||||
- "New `Setup` hook event (--init, --init-only, --maintenance flags)"
|
||||
- "Keyboard shortcut 'c' to copy OAuth URL"
|
||||
- "File suggestions show as removable attachments"
|
||||
- "[VSCode] Plugin install count + trust warnings"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.9"
|
||||
date: "2026-01-16"
|
||||
highlights:
|
||||
- "`auto:N` syntax for MCP tool search threshold"
|
||||
- "`plansDirectory` setting for custom plan file locations"
|
||||
- "Session URL attribution to commits/PRs from web sessions"
|
||||
- "PreToolUse hooks can return `additionalContext`"
|
||||
- "${CLAUDE_SESSION_ID} string substitution for skills"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.7"
|
||||
date: "2026-01-15"
|
||||
highlights:
|
||||
- "`showTurnDuration` setting to hide turn duration messages"
|
||||
- "MCP tool search auto mode enabled by default"
|
||||
- "Inline display of agent final response in task notifications"
|
||||
breaking:
|
||||
- "OAuth/API Console URLs: console.anthropic.com → platform.claude.com"
|
||||
- "Security fix: Wildcard permission rules could match compound commands"
|
||||
|
||||
- version: "2.1.6"
|
||||
date: "2026-01-14"
|
||||
highlights:
|
||||
- "Search functionality in /config command"
|
||||
- "Date range filtering in /stats (r to cycle)"
|
||||
- "Auto-discovery of skills from nested .claude/skills directories"
|
||||
- "Updates section in /doctor showing auto-update channel"
|
||||
breaking:
|
||||
- "Security fix: Permission bypass via shell line continuation"
|
||||
|
||||
- version: "2.1.5"
|
||||
date: "2026-01-13"
|
||||
highlights:
|
||||
- "`CLAUDE_CODE_TMPDIR` env var for custom temp directory"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.4"
|
||||
date: "2026-01-12"
|
||||
highlights:
|
||||
- "`CLAUDE_CODE_DISABLE_BACKGROUND_TASKS` env var"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.3"
|
||||
date: "2026-01-11"
|
||||
highlights:
|
||||
- "Merged slash commands and skills (simplified mental model)"
|
||||
- "Release channel toggle (stable/latest) in /config"
|
||||
- "/doctor warnings for unreachable permission rules"
|
||||
breaking: []
|
||||
|
||||
- version: "2.1.2"
|
||||
date: "2026-01-10"
|
||||
highlights:
|
||||
- "Windows Package Manager (winget) support"
|
||||
- "Clickable hyperlinks for file paths (OSC 8 terminals)"
|
||||
- "Shift+Tab shortcut in plan mode for auto-accept edits"
|
||||
- "Large bash outputs saved to disk instead of truncated"
|
||||
breaking:
|
||||
- "Security fix: Command injection in bash command processing"
|
||||
- "Deprecated: C:\\ProgramData\\ClaudeCode managed settings path"
|
||||
|
||||
- version: "2.1.0"
|
||||
date: "2026-01-08"
|
||||
highlights:
|
||||
- "⭐ MAJOR: Automatic skill hot-reload"
|
||||
- "⭐ MAJOR: Shift+Enter works OOTB (iTerm2, WezTerm, Ghostty, Kitty)"
|
||||
- "⭐ MAJOR: New Vim motions (;, y, p, text objects, >>, <<, J)"
|
||||
- "Unified Ctrl+B for backgrounding all tasks"
|
||||
- "/plan command shortcut"
|
||||
- "Slash command autocomplete anywhere in input"
|
||||
- "`language` setting for response language"
|
||||
- "Skills context: fork support"
|
||||
- "Hooks support in agent/skill frontmatter"
|
||||
- "MCP list_changed notifications support"
|
||||
- "/teleport and /remote-env commands"
|
||||
- "Tab support for disabling specific agents"
|
||||
- "--tools flag in interactive mode"
|
||||
- "YAML-style lists in frontmatter allowed-tools"
|
||||
breaking:
|
||||
- "OAuth URLs: console.anthropic.com → platform.claude.com"
|
||||
- "Removed permission prompt for entering plan mode"
|
||||
- "[SDK] Minimum zod peer dependency: ^4.0.0"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 2.0.x Series (December 2025 - January 2026)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
- version: "2.0.76"
|
||||
date: "2026-01-05"
|
||||
highlights:
|
||||
- "Fix: macOS code-sign warning with Claude in Chrome"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.74"
|
||||
date: "2026-01-04"
|
||||
highlights:
|
||||
- "⭐ LSP (Language Server Protocol) tool for code intelligence"
|
||||
- "/terminal-setup for Kitty, Alacritty, Zed, Warp"
|
||||
- "Ctrl+T in /theme to toggle syntax highlighting"
|
||||
- "Grouped skills/agents by source in /context"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.73"
|
||||
date: "2026-01-03"
|
||||
highlights:
|
||||
- "Clickable [Image #N] links"
|
||||
- "Alt-Y yank-pop to cycle kill ring history"
|
||||
- "Search filtering in plugin discover screen"
|
||||
- "[VSCode] Tab icon badges (permissions, unread)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.72"
|
||||
date: "2026-01-02"
|
||||
highlights:
|
||||
- "⭐ Claude in Chrome (Beta) - browser control from Claude Code"
|
||||
- "Reduced terminal flickering"
|
||||
- "QR code for mobile app"
|
||||
- "Thinking toggle: Tab → Alt+T"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.71"
|
||||
date: "2026-01-01"
|
||||
highlights:
|
||||
- "/config toggle for prompt suggestions"
|
||||
- "/settings alias for /config"
|
||||
- "New Rust-based syntax highlighting engine"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.70"
|
||||
date: "2025-12-30"
|
||||
highlights:
|
||||
- "Enter key accepts/submits prompt suggestions immediately"
|
||||
- "Wildcard syntax mcp__server__* for MCP tool permissions"
|
||||
- "Auto-update toggle for plugin marketplaces"
|
||||
- "3x memory usage improvement for large conversations"
|
||||
breaking:
|
||||
- "Removed # shortcut for quick memory entry"
|
||||
|
||||
- version: "2.0.68"
|
||||
date: "2025-12-28"
|
||||
highlights:
|
||||
- "IME support for Chinese, Japanese, Korean"
|
||||
- "Enterprise managed settings support"
|
||||
- "Improved plan mode exit UX"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.67"
|
||||
date: "2025-12-26"
|
||||
highlights:
|
||||
- "⭐ Thinking mode enabled by default for Opus 4.5"
|
||||
- "Thinking config moved to /config"
|
||||
- "Search in /permissions with / shortcut"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.65"
|
||||
date: "2025-12-24"
|
||||
highlights:
|
||||
- "Alt+P to switch models while writing prompt"
|
||||
- "Context window info in status line"
|
||||
- "`fileSuggestion` setting for custom @ search"
|
||||
- "CLAUDE_CODE_SHELL env var"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.64"
|
||||
date: "2025-12-22"
|
||||
highlights:
|
||||
- "⭐ Instant auto-compacting"
|
||||
- "⭐ Async agents and bash commands with wake-up messages"
|
||||
- "/stats with usage graphs, streaks, favorite model"
|
||||
- "Named sessions: /rename, /resume <name>"
|
||||
- "Support for .claude/rules/ directory"
|
||||
- "Image dimension metadata for coordinate mappings"
|
||||
breaking:
|
||||
- "Unshipped AgentOutputTool/BashOutputTool → TaskOutputTool"
|
||||
|
||||
- version: "2.0.62"
|
||||
date: "2025-12-20"
|
||||
highlights:
|
||||
- "`attribution` setting for commit/PR bylines"
|
||||
- "(Recommended) indicator in multiple-choice questions"
|
||||
breaking:
|
||||
- "Deprecated: includeCoAuthoredBy (use attribution)"
|
||||
|
||||
- version: "2.0.60"
|
||||
date: "2025-12-18"
|
||||
highlights:
|
||||
- "⭐ Background agents (work while you work)"
|
||||
- "--disable-slash-commands CLI flag"
|
||||
- "Model name in Co-Authored-By commits"
|
||||
- "/mcp enable|disable [server-name]"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.58"
|
||||
date: "2025-12-16"
|
||||
highlights:
|
||||
- "⭐ Opus 4.5 available for Pro users"
|
||||
breaking:
|
||||
- "[Windows] Managed settings prefer C:\\Program Files\\ClaudeCode"
|
||||
|
||||
- version: "2.0.57"
|
||||
date: "2025-12-15"
|
||||
highlights:
|
||||
- "Feedback input when rejecting plans"
|
||||
- "[VSCode] Streaming message support"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.55"
|
||||
date: "2025-12-13"
|
||||
highlights:
|
||||
- "Improved AskUserQuestion auto-submit"
|
||||
- "Better fuzzy matching for @ file suggestions"
|
||||
breaking:
|
||||
- "Proxy DNS opt-in: CLAUDE_CODE_PROXY_RESOLVES_HOSTS=true"
|
||||
|
||||
- version: "2.0.51"
|
||||
date: "2025-12-10"
|
||||
highlights:
|
||||
- "⭐ MAJOR: Opus 4.5 released"
|
||||
- "⭐ MAJOR: Claude Code for Desktop"
|
||||
- "Updated usage limits for Opus 4.5"
|
||||
- "Plan Mode builds more precise plans"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.45"
|
||||
date: "2025-12-05"
|
||||
highlights:
|
||||
- "⭐ Microsoft Foundry support"
|
||||
- "PermissionRequest hook for auto-approve/deny"
|
||||
- "& prefix for background tasks to web"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.43"
|
||||
date: "2025-12-03"
|
||||
highlights:
|
||||
- "`permissionMode` field for custom agents"
|
||||
- "`skills` frontmatter for auto-loading subagent skills"
|
||||
- "SubagentStart hook event"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.41"
|
||||
date: "2025-12-01"
|
||||
highlights:
|
||||
- "Model parameter for prompt-based stop hooks"
|
||||
- "Plugins: sharing and installing output styles"
|
||||
- "Allow more safe git commands without approval"
|
||||
- "Teleporting from web auto-sets upstream branch"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.37"
|
||||
date: "2025-11-28"
|
||||
highlights:
|
||||
- "Matcher values for Notification hook events"
|
||||
- "Output Styles: keep-coding-instructions option"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.35"
|
||||
date: "2025-11-26"
|
||||
highlights:
|
||||
- "Improved fuzzy search for commands"
|
||||
- "CLAUDE_CODE_EXIT_AFTER_STOP_DELAY env var"
|
||||
breaking:
|
||||
- "Migrated ignorePatterns to deny permissions"
|
||||
|
||||
- version: "2.0.32"
|
||||
date: "2025-11-23"
|
||||
highlights:
|
||||
- "Output styles un-deprecated (community feedback)"
|
||||
- "`companyAnnouncements` setting"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.30"
|
||||
date: "2025-11-20"
|
||||
highlights:
|
||||
- "`allowUnsandboxedCommands` sandbox setting"
|
||||
- "`disallowedTools` for custom agent definitions"
|
||||
- "Prompt-based stop hooks"
|
||||
- "SSE MCP servers on native build"
|
||||
breaking:
|
||||
- "Deprecated: Output styles (later un-deprecated in 2.0.32)"
|
||||
- "Removed: Custom ripgrep configuration"
|
||||
|
||||
- version: "2.0.28"
|
||||
date: "2025-11-18"
|
||||
highlights:
|
||||
- "⭐ Plan mode: introduced Plan subagent"
|
||||
- "Subagents: resume capability"
|
||||
- "Subagents: dynamic model selection"
|
||||
- "--max-budget-usd flag (SDK)"
|
||||
- "Git-based plugins branch/tag support (#branch)"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.27"
|
||||
date: "2025-11-15"
|
||||
highlights:
|
||||
- "New UI for permission prompts"
|
||||
- "Branch filtering and search in session resume"
|
||||
breaking: []
|
||||
|
||||
- version: "2.0.25"
|
||||
date: "2025-11-12"
|
||||
highlights: []
|
||||
breaking:
|
||||
- "Removed legacy SDK entrypoint → @anthropic-ai/claude-agent-sdk"
|
||||
|
||||
- version: "2.0.24"
|
||||
date: "2025-11-10"
|
||||
highlights:
|
||||
- "Claude Code Web: Web → CLI teleport"
|
||||
- "Sandbox mode for BashTool (Linux & Mac)"
|
||||
- "Bedrock: awsAuthRefresh output display"
|
||||
breaking: []
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# SUMMARY - Key Breaking Changes by Area
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
breaking_summary:
|
||||
urls:
|
||||
- "OAuth/API Console: console.anthropic.com → platform.claude.com (v2.1.0, v2.1.7)"
|
||||
windows:
|
||||
- "Managed settings: C:\\ProgramData\\ClaudeCode → C:\\Program Files\\ClaudeCode (v2.0.58, deprecated v2.1.2)"
|
||||
sdk:
|
||||
- "Removed legacy SDK entrypoint → @anthropic-ai/claude-agent-sdk (v2.0.25)"
|
||||
- "Minimum zod peer dependency: ^4.0.0 (v2.1.0)"
|
||||
shortcuts:
|
||||
- "Removed # shortcut for quick memory (v2.0.70)"
|
||||
security:
|
||||
- "Command injection fix in bash processing (v2.1.2)"
|
||||
- "Wildcard permission rules compound commands fix (v2.1.7)"
|
||||
- "Shell line continuation permission bypass fix (v2.1.6)"
|
||||
- "Sandbox-excluded commands bypass with autoAllowBashIfSandboxed (v2.1.34)"
|
||||
- "Heredoc delimiter command smuggling prevention (v2.1.38)"
|
||||
installation:
|
||||
- "npm installations deprecated - use native installer (v2.1.15)"
|
||||
behavior:
|
||||
- "ultrathink/think keywords now cosmetic only — thinking default with Opus 4.5 (v2.0.67)"
|
||||
syntax:
|
||||
- "Indexed argument syntax changed: $ARGUMENTS.0 → $ARGUMENTS[0] (v2.1.19)"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# MILESTONE FEATURES (quick reference)
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
milestones:
|
||||
"2.1.36": "Fast mode for Opus 4.6"
|
||||
"2.1.32": "Opus 4.6, Agent teams preview, Automatic memory"
|
||||
"2.1.18": "Customizable keyboard shortcuts with /keybindings"
|
||||
"2.1.16": "New task management system with dependency tracking"
|
||||
"2.1.0": "Skill hot-reload, Shift+Enter OOTB, Vim motions, /plan command"
|
||||
"2.0.74": "LSP tool for code intelligence"
|
||||
"2.0.72": "Claude in Chrome (browser control)"
|
||||
"2.0.67": "Thinking mode default for Opus 4.5"
|
||||
"2.0.64": "Instant auto-compact, async agents, named sessions"
|
||||
"2.0.60": "Background agents"
|
||||
"2.0.51": "Opus 4.5, Claude Code for Desktop"
|
||||
"2.0.45": "Microsoft Foundry, PermissionRequest hook"
|
||||
"2.0.28": "Plan subagent, subagent resume/model selection"
|
||||
"2.0.24": "Web teleport, Sandbox mode"
|
||||
166
mcp-server/content/llms.txt
Normal file
166
mcp-server/content/llms.txt
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# The Ultimate Claude Code Guide
|
||||
|
||||
> A comprehensive, self-contained guide to mastering Claude Code - Anthropic's official CLI for AI-assisted development.
|
||||
|
||||
## What This Repository Contains
|
||||
|
||||
This repository provides everything needed to go from Claude Code beginner to power user:
|
||||
|
||||
1. **Complete Guide** (`guide/ultimate-guide.md`) - 9,600+ lines covering all aspects of Claude Code
|
||||
2. **Cheatsheet** (`guide/cheatsheet.md`) - 1-page printable daily reference
|
||||
3. **Architecture Internals** (`guide/architecture.md`) - How Claude Code works under the hood (master loop, tools, context)
|
||||
4. **Audit Prompt** (`tools/audit-prompt.md`) - Self-contained prompt to analyze your Claude Code setup against best practices
|
||||
|
||||
## Target Audience
|
||||
|
||||
- **Beginners**: Installation, first workflow, essential commands (15 min to productivity)
|
||||
- **Intermediate**: Memory files, agents, skills, hooks configuration
|
||||
- **Power Users**: MCP servers, Trinity pattern, CI/CD integration, autonomous workflows
|
||||
|
||||
## Key Topics Covered
|
||||
|
||||
### Core Concepts
|
||||
- Context Management (the most critical concept - context windows, compaction, zones)
|
||||
- Plan Mode (safe read-only exploration before making changes)
|
||||
- Memory Files (CLAUDE.md for persistent context across sessions)
|
||||
- Rewind (undo mechanism for file changes)
|
||||
|
||||
### Customization
|
||||
- **Agents**: Custom AI personas with specific tools and instructions
|
||||
- **Skills**: Reusable knowledge modules for complex domains
|
||||
- **Commands**: Custom slash commands for frequent workflows
|
||||
- **Hooks**: Event-driven automation (PreToolUse, PostToolUse, UserPromptSubmit)
|
||||
|
||||
### Advanced Features
|
||||
- **MCP Servers**: Model Context Protocol for extended capabilities
|
||||
- Serena (codebase indexation + session memory)
|
||||
- Context7 (library documentation lookup)
|
||||
- Sequential (structured multi-step reasoning)
|
||||
- Playwright (browser automation)
|
||||
- **Trinity Pattern**: Combining Plan Mode + Extended Thinking + MCP for complex tasks
|
||||
- **CI/CD Integration**: Headless mode, GitHub Actions, Verify Gate pattern
|
||||
|
||||
### Best Practices
|
||||
- Single Source of Truth pattern for conventions
|
||||
- Shell Scripts vs AI Agents decision framework
|
||||
- Tight feedback loops for rapid iteration
|
||||
- Continuous improvement mindset
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
claude-code-ultimate-guide/
|
||||
├── README.md # Overview and quick start
|
||||
├── CONTRIBUTING.md # Contribution guidelines
|
||||
├── CHANGELOG.md # Version history
|
||||
├── LICENSE # CC BY-SA 4.0
|
||||
├── guide/ # Core documentation
|
||||
│ ├── ultimate-guide.md # Complete guide (main content)
|
||||
│ ├── cheatsheet.md # 1-page reference
|
||||
│ ├── architecture.md # How Claude Code works internally
|
||||
│ └── adoption-approaches.md # Implementation strategy
|
||||
├── tools/ # Interactive utilities
|
||||
│ ├── audit-prompt.md # Setup audit tool
|
||||
│ ├── onboarding-prompt.md # Personalized onboarding
|
||||
│ └── mobile-access.md # Mobile access setup
|
||||
├── machine-readable/ # LLM/AI consumption
|
||||
│ ├── reference.yaml # Machine-optimized index
|
||||
│ └── llms.txt # This file (for AI indexation)
|
||||
├── exports/ # Generated outputs
|
||||
│ ├── notebooklm.pdf # Visual overview
|
||||
│ └── kimi.pdf # Full text export
|
||||
├── examples/ # Ready-to-use templates
|
||||
│ ├── agents/ # Custom AI personas
|
||||
│ ├── skills/ # Knowledge modules
|
||||
│ ├── commands/ # Slash commands
|
||||
│ ├── hooks/ # Event automation (bash + PowerShell)
|
||||
│ ├── config/ # Configuration files
|
||||
│ └── memory/ # CLAUDE.md templates
|
||||
└── quiz/ # Interactive knowledge quiz
|
||||
```
|
||||
|
||||
## Guide Structure (10 Sections + Architecture)
|
||||
|
||||
**Architecture Deep Dive** (`guide/architecture.md`):
|
||||
- Master Loop: `while(tool_call)` - no DAGs, no classifiers, no RAG
|
||||
- 8 Core Tools: Bash, Read, Edit, Write, Grep, Glob, Task, TodoWrite
|
||||
- Context: ~200K tokens, auto-compact at 75-92%
|
||||
- Sub-agents: isolated context, max depth=1
|
||||
- Philosophy: "less scaffolding, more model"
|
||||
|
||||
**Main Guide Sections** (`guide/ultimate-guide.md`):
|
||||
1. **Quick Start** - Installation, first workflow, essential commands
|
||||
2. **Core Concepts** - Context management, Plan Mode, Rewind, Mental Model
|
||||
3. **Memory & Settings** - CLAUDE.md files, .claude/ folder, precedence rules
|
||||
4. **Agents** - Custom AI personas, Tool SEO, orchestration patterns
|
||||
5. **Skills** - Reusable knowledge modules
|
||||
6. **Commands** - Custom slash commands, variable interpolation
|
||||
7. **Hooks** - Event-driven automation (security, formatting, logging)
|
||||
8. **MCP Servers** - Serena, Context7, Sequential, Playwright, Postgres
|
||||
9. **Advanced Patterns** - Trinity, CI/CD, feedback loops, vibe coding
|
||||
10. **Reference** - Commands, shortcuts, troubleshooting, checklists
|
||||
|
||||
## Key Commands Reference
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/help` | Show all available commands |
|
||||
| `/status` | Check context usage and session state |
|
||||
| `/compact` | Compress context (use when >70%) |
|
||||
| `/clear` | Fresh start (reset conversation) |
|
||||
| `/plan` | Enter safe read-only planning mode |
|
||||
| `/rewind` | Undo recent changes |
|
||||
|
||||
## Context Management Rules
|
||||
|
||||
- **Green Zone (0-50%)**: Work freely
|
||||
- **Yellow Zone (50-70%)**: Be selective with context loading
|
||||
- **Red Zone (70-90%)**: Use `/compact` immediately
|
||||
- **Critical (90%+)**: Use `/clear` to reset
|
||||
|
||||
## Platform Support
|
||||
|
||||
- **macOS/Linux**: Full support with bash/zsh examples
|
||||
- **Windows**: PowerShell and batch file alternatives provided (note: Windows commands are AI-generated and not tested by the author)
|
||||
|
||||
## Related Resources
|
||||
|
||||
- Official: https://docs.anthropic.com/en/docs/claude-code
|
||||
- Official llms.txt (index): https://code.claude.com/docs/llms.txt
|
||||
- Official llms-full.txt (complete): https://code.claude.com/docs/llms-full.txt
|
||||
- DeepWiki: https://deepwiki.com/FlorianBruniaux/claude-code-ultimate-guide
|
||||
- Inspiration: https://claudelog.com/
|
||||
- Whitepapers (FR + EN): https://www.florian.bruniaux.com/guides — 9 focused whitepapers on Claude Code (foundations, prompting, customization, security, architecture, team, privacy, reference, agent teams)
|
||||
|
||||
## Author
|
||||
|
||||
Florian BRUNIAUX - Founding Engineer at Méthode Aristote
|
||||
- GitHub: https://github.com/FlorianBruniaux
|
||||
- LinkedIn: https://www.linkedin.com/in/florian-bruniaux-43408b83/
|
||||
|
||||
## License
|
||||
|
||||
CC BY-SA 4.0 - Free to share and adapt with attribution.
|
||||
|
||||
## How to Use This Guide
|
||||
|
||||
1. **New to Claude Code?** Start with README.md Quick Start section
|
||||
2. **Choose your path** See Learning Paths in README for audience-specific guides
|
||||
3. **Want comprehensive learning?** Read guide/ultimate-guide.md
|
||||
4. **Need daily reference?** Print guide/cheatsheet.md
|
||||
5. **Want to audit your setup?** Use tools/audit-prompt.md
|
||||
6. **Need templates?** Browse examples/ folder for ready-to-use configs
|
||||
|
||||
## Machine-Optimized Reference
|
||||
|
||||
For fast LLM parsing, see `machine-readable/reference.yaml` (~2K tokens) - structured YAML with:
|
||||
- Decision tree for task routing
|
||||
- Prompting formula (WHAT/WHERE/HOW/VERIFY)
|
||||
- Commands, shortcuts, CLI flags
|
||||
- Context management zones and symptoms
|
||||
- MCP servers, extended thinking, cost optimization
|
||||
- Anti-patterns and troubleshooting
|
||||
|
||||
## Keywords
|
||||
|
||||
Claude Code, Anthropic, CLI, AI-assisted development, coding assistant, context management, MCP servers, agents, skills, hooks, commands, Plan Mode, CLAUDE.md, memory files, CI/CD integration, autonomous workflows, developer productivity, AI coding tools
|
||||
1625
mcp-server/content/reference.yaml
Normal file
1625
mcp-server/content/reference.yaml
Normal file
File diff suppressed because it is too large
Load diff
2604
mcp-server/package-lock.json
generated
Normal file
2604
mcp-server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
42
mcp-server/package.json
Normal file
42
mcp-server/package.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "claude-code-ultimate-guide-mcp",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP server for the Claude Code Ultimate Guide — search, read, and explore 20K+ lines of documentation directly from Claude Code",
|
||||
"keywords": ["mcp", "claude-code", "anthropic", "documentation", "guide"],
|
||||
"author": "Florian Bruniaux",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/FlorianBruniaux/claude-code-ultimate-guide",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FlorianBruniaux/claude-code-ultimate-guide.git",
|
||||
"directory": "mcp-server"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"bin": {
|
||||
"claude-code-guide-mcp": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.js",
|
||||
"dist/**/*.d.ts",
|
||||
"content"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "GUIDE_ROOT=.. node --watch dist/index.js",
|
||||
"start": "node dist/index.js",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.6.0",
|
||||
"yaml": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
13
mcp-server/src/index.ts
Normal file
13
mcp-server/src/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { createServer } from './server.js';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const server = createServer();
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
process.stderr.write(`[claude-code-guide] Fatal error: ${err}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
36
mcp-server/src/lib/changelog-parser.ts
Normal file
36
mcp-server/src/lib/changelog-parser.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export interface ChangelogEntry {
|
||||
version: string;
|
||||
date: string;
|
||||
dateObj: Date;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function parseChangelog(raw: string): ChangelogEntry[] {
|
||||
const entries: ChangelogEntry[] = [];
|
||||
// Match headers like: ## [3.27.0] - 2026-02-20 or ## [Unreleased]
|
||||
const headerRe = /^## \[([^\]]+)\](?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/m;
|
||||
const blocks = raw.split(/^(?=## \[)/m).filter((b) => b.trim());
|
||||
|
||||
for (const block of blocks) {
|
||||
const match = block.match(headerRe);
|
||||
if (!match) continue;
|
||||
const version = match[1];
|
||||
const dateStr = match[2] ?? '';
|
||||
const dateObj = dateStr ? new Date(dateStr) : new Date(0);
|
||||
entries.push({ version, date: dateStr, dateObj, content: block.trim() });
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function filterByPeriod(
|
||||
entries: ChangelogEntry[],
|
||||
period: 'day' | 'week' | 'month',
|
||||
): ChangelogEntry[] {
|
||||
const MS = { day: 86_400_000, week: 7 * 86_400_000, month: 30 * 86_400_000 };
|
||||
const cutoff = Date.now() - MS[period];
|
||||
return entries.filter(
|
||||
// Include [Unreleased] (treat as today) + dated entries within the window
|
||||
(e) => e.version === 'Unreleased' || e.dateObj.getTime() >= cutoff,
|
||||
);
|
||||
}
|
||||
278
mcp-server/src/lib/content.ts
Normal file
278
mcp-server/src/lib/content.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { resolve, join, sep } from 'path';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { fetchFile } from './fetcher.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = resolve(__filename, '..');
|
||||
|
||||
// Dual mode: GUIDE_ROOT env var = local dev, else bundled content
|
||||
const GUIDE_ROOT = process.env.GUIDE_ROOT
|
||||
? resolve(process.env.GUIDE_ROOT)
|
||||
: null;
|
||||
|
||||
const CONTENT_DIR = GUIDE_ROOT
|
||||
? resolve(GUIDE_ROOT, 'machine-readable')
|
||||
: resolve(__dirname, '../../content');
|
||||
|
||||
const ALLOWED_EXTENSIONS = new Set([
|
||||
'.md', '.yaml', '.yml', '.sh', '.ts', '.js', '.json', '.py', '.txt',
|
||||
]);
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export type DeepDiveTarget =
|
||||
| { type: 'line'; file: 'guide/ultimate-guide.md'; line: number }
|
||||
| { type: 'file'; path: string; line?: number }
|
||||
| { type: 'url'; url: string }
|
||||
| { type: 'inline'; text: string }
|
||||
| { type: 'structured'; data: unknown };
|
||||
|
||||
export interface IndexEntry {
|
||||
key: string;
|
||||
section: string;
|
||||
value: unknown;
|
||||
searchableText: string;
|
||||
target?: DeepDiveTarget;
|
||||
}
|
||||
|
||||
export interface ReferenceData {
|
||||
version: string;
|
||||
entries: IndexEntry[];
|
||||
raw: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ReleasesData {
|
||||
latest: string;
|
||||
updated: string;
|
||||
releases: unknown[];
|
||||
raw: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ThreatDbData {
|
||||
version: string;
|
||||
updated: string;
|
||||
sources: unknown[];
|
||||
malicious_authors: unknown[];
|
||||
malicious_skills: unknown[];
|
||||
cve_database: unknown[];
|
||||
attack_techniques: unknown[];
|
||||
minimum_safe_versions: Record<string, string>;
|
||||
raw: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ─── Path resolver ────────────────────────────────────────────────────────────
|
||||
|
||||
export function resolveContentPath(relativePath: string): string | null {
|
||||
const base = GUIDE_ROOT ?? resolve(__dirname, '../../..');
|
||||
|
||||
// Layer 1: resolve and check starts with base
|
||||
const resolved = resolve(base, relativePath);
|
||||
if (!resolved.startsWith(base + sep)) return null;
|
||||
|
||||
// Layer 2: extension whitelist
|
||||
const ext = relativePath.slice(relativePath.lastIndexOf('.'));
|
||||
if (!ALLOWED_EXTENSIONS.has(ext)) return null;
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export function isDevMode(): boolean {
|
||||
return GUIDE_ROOT !== null;
|
||||
}
|
||||
|
||||
export function getGuideRoot(): string {
|
||||
return GUIDE_ROOT ?? resolve(__dirname, '../../..');
|
||||
}
|
||||
|
||||
// ─── YAML cache ───────────────────────────────────────────────────────────────
|
||||
|
||||
let referenceCache: ReferenceData | null = null;
|
||||
let releasesCache: ReleasesData | null = null;
|
||||
let threatDbCache: ThreatDbData | null = null;
|
||||
|
||||
export function loadReference(): ReferenceData {
|
||||
if (referenceCache) return referenceCache;
|
||||
|
||||
const filePath = join(CONTENT_DIR, 'reference.yaml');
|
||||
const raw = parseYaml(readFileSync(filePath, 'utf8')) as Record<string, unknown>;
|
||||
|
||||
const entries: IndexEntry[] = [];
|
||||
flattenReference(raw, '', entries);
|
||||
|
||||
referenceCache = {
|
||||
version: (raw.version as string) ?? 'unknown',
|
||||
entries,
|
||||
raw,
|
||||
};
|
||||
return referenceCache;
|
||||
}
|
||||
|
||||
export function loadReleases(): ReleasesData {
|
||||
if (releasesCache) return releasesCache;
|
||||
|
||||
const filePath = join(CONTENT_DIR, 'claude-code-releases.yaml');
|
||||
const raw = parseYaml(readFileSync(filePath, 'utf8')) as Record<string, unknown>;
|
||||
|
||||
releasesCache = {
|
||||
latest: (raw.latest as string) ?? 'unknown',
|
||||
updated: (raw.updated as string) ?? 'unknown',
|
||||
releases: (raw.releases as unknown[]) ?? [],
|
||||
raw,
|
||||
};
|
||||
return releasesCache;
|
||||
}
|
||||
|
||||
export async function loadThreatDb(): Promise<ThreatDbData> {
|
||||
if (threatDbCache) return threatDbCache;
|
||||
|
||||
const THREAT_DB_PATH = 'examples/commands/resources/threat-db.yaml';
|
||||
let content: string;
|
||||
|
||||
if (GUIDE_ROOT) {
|
||||
content = readFileSync(join(GUIDE_ROOT, THREAT_DB_PATH), 'utf8');
|
||||
} else {
|
||||
const fetched = await fetchFile(THREAT_DB_PATH);
|
||||
if (!fetched) throw new Error('Failed to load threat-db.yaml');
|
||||
content = fetched;
|
||||
}
|
||||
|
||||
const raw = parseYaml(content) as Record<string, unknown>;
|
||||
|
||||
threatDbCache = {
|
||||
version: (raw.version as string) ?? 'unknown',
|
||||
updated: (raw.updated as string) ?? 'unknown',
|
||||
sources: (raw.sources as unknown[]) ?? [],
|
||||
malicious_authors: (raw.malicious_authors as unknown[]) ?? [],
|
||||
malicious_skills: (raw.malicious_skills as unknown[]) ?? [],
|
||||
cve_database: (raw.cve_database as unknown[]) ?? [],
|
||||
attack_techniques: (raw.attack_techniques as unknown[]) ?? [],
|
||||
minimum_safe_versions: (raw.minimum_safe_versions as Record<string, string>) ?? {},
|
||||
raw,
|
||||
};
|
||||
return threatDbCache;
|
||||
}
|
||||
|
||||
export function loadLlmsTxt(): string {
|
||||
const filePath = join(CONTENT_DIR, 'llms.txt');
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
export function getReferenceYamlRaw(): string {
|
||||
const filePath = join(CONTENT_DIR, 'reference.yaml');
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
export function getReleasesYamlRaw(): string {
|
||||
const filePath = join(CONTENT_DIR, 'claude-code-releases.yaml');
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
// ─── Deep dive resolver ───────────────────────────────────────────────────────
|
||||
|
||||
export function resolveDeepDive(value: unknown): DeepDiveTarget | undefined {
|
||||
if (value === null || value === undefined) return undefined;
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return { type: 'line', file: 'guide/ultimate-guide.md', line: value };
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (value.startsWith('http://') || value.startsWith('https://')) {
|
||||
return { type: 'url', url: value };
|
||||
}
|
||||
// File path with optional :line suffix
|
||||
const filePathMatch = value.match(/^(guide\/|examples\/|whitepapers\/|machine-readable\/)(.+?)(?::(\d+))?$/);
|
||||
if (filePathMatch) {
|
||||
return {
|
||||
type: 'file',
|
||||
path: filePathMatch[1] + filePathMatch[2],
|
||||
line: filePathMatch[3] ? parseInt(filePathMatch[3], 10) : undefined,
|
||||
};
|
||||
}
|
||||
return { type: 'inline', text: value };
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return { type: 'structured', data: value };
|
||||
}
|
||||
|
||||
return { type: 'inline', text: String(value) };
|
||||
}
|
||||
|
||||
// ─── Reference flattener ──────────────────────────────────────────────────────
|
||||
|
||||
function flattenReference(
|
||||
obj: Record<string, unknown>,
|
||||
prefix: string,
|
||||
entries: IndexEntry[],
|
||||
): void {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}_${key}` : key;
|
||||
|
||||
if (key === 'version' || key === 'generated' || key === 'description' || key === 'note') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) continue;
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
const obj2 = value as Record<string, unknown>;
|
||||
// Check if it's a leaf-like object (has deep_dive or simple scalar values)
|
||||
const hasDeepDive = 'deep_dive' in obj2;
|
||||
const hasNestedObjects = Object.values(obj2).some(
|
||||
(v) => typeof v === 'object' && v !== null && !Array.isArray(v) && !('deep_dive' in (v as Record<string, unknown>)),
|
||||
);
|
||||
|
||||
if (hasDeepDive || !hasNestedObjects) {
|
||||
// Treat as leaf entry
|
||||
const searchableText = buildSearchableText(fullKey, value);
|
||||
const target = hasDeepDive
|
||||
? resolveDeepDive((obj2 as Record<string, unknown>).deep_dive)
|
||||
: resolveDeepDive(value);
|
||||
|
||||
entries.push({
|
||||
key: fullKey,
|
||||
section: prefix.split('_')[0] ?? fullKey,
|
||||
value,
|
||||
searchableText,
|
||||
target,
|
||||
});
|
||||
} else {
|
||||
flattenReference(obj2, fullKey, entries);
|
||||
}
|
||||
} else {
|
||||
const searchableText = buildSearchableText(fullKey, value);
|
||||
const target = resolveDeepDive(value);
|
||||
entries.push({
|
||||
key: fullKey,
|
||||
section: prefix.split('_')[0] ?? fullKey,
|
||||
value,
|
||||
searchableText,
|
||||
target,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildSearchableText(key: string, value: unknown): string {
|
||||
const parts: string[] = [key.replace(/_/g, ' ')];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
parts.push(value);
|
||||
} else if (typeof value === 'number') {
|
||||
parts.push(String(value));
|
||||
} else if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
if (typeof item === 'string') parts.push(item);
|
||||
else if (typeof item === 'object' && item !== null) {
|
||||
parts.push(JSON.stringify(item));
|
||||
}
|
||||
}
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
parts.push(JSON.stringify(value));
|
||||
}
|
||||
|
||||
return parts.join(' ').toLowerCase();
|
||||
}
|
||||
69
mcp-server/src/lib/fetcher.ts
Normal file
69
mcp-server/src/lib/fetcher.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
const PACKAGE_VERSION = '1.0.0';
|
||||
const GITHUB_RAW_BASE =
|
||||
'https://raw.githubusercontent.com/FlorianBruniaux/claude-code-ultimate-guide/main';
|
||||
const CACHE_DIR = resolve(homedir(), '.cache', 'claude-code-guide', PACKAGE_VERSION);
|
||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
||||
|
||||
function getCachePath(filePath: string): string {
|
||||
// Use hash to avoid path issues, but keep extension for readability
|
||||
const hash = createHash('md5').update(filePath).digest('hex').slice(0, 8);
|
||||
const safe = filePath.replace(/[^a-zA-Z0-9._-]/g, '_');
|
||||
return resolve(CACHE_DIR, `${hash}_${safe}`);
|
||||
}
|
||||
|
||||
function isCacheValid(cachePath: string): boolean {
|
||||
if (!existsSync(cachePath)) return false;
|
||||
const stat = statSync(cachePath);
|
||||
return Date.now() - stat.mtimeMs < CACHE_TTL_MS;
|
||||
}
|
||||
|
||||
export async function fetchFile(filePath: string): Promise<string | null> {
|
||||
// Normalize path separators
|
||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||
const cachePath = getCachePath(normalizedPath);
|
||||
|
||||
// Return cache if valid
|
||||
if (isCacheValid(cachePath)) {
|
||||
return readFileSync(cachePath, 'utf8');
|
||||
}
|
||||
|
||||
// Fetch from GitHub
|
||||
const url = `${GITHUB_RAW_BASE}/${normalizedPath}`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: { 'User-Agent': 'claude-code-ultimate-guide-mcp/1.0.0' },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Return stale cache if available (offline fallback)
|
||||
if (existsSync(cachePath)) {
|
||||
return readFileSync(cachePath, 'utf8');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
|
||||
// Write to cache
|
||||
mkdirSync(dirname(cachePath), { recursive: true });
|
||||
writeFileSync(cachePath, content, 'utf8');
|
||||
|
||||
return content;
|
||||
} catch {
|
||||
// Offline fallback: return stale cache
|
||||
if (existsSync(cachePath)) {
|
||||
return readFileSync(cachePath, 'utf8');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCacheDir(): string {
|
||||
return CACHE_DIR;
|
||||
}
|
||||
184
mcp-server/src/lib/search.ts
Normal file
184
mcp-server/src/lib/search.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { loadReference, type IndexEntry } from './content.js';
|
||||
|
||||
// ─── Stop words ───────────────────────────────────────────────────────────────
|
||||
|
||||
const STOP_WORDS = new Set([
|
||||
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
|
||||
'do', 'does', 'did', 'have', 'has', 'had', 'will', 'would',
|
||||
'can', 'could', 'should', 'may', 'might', 'shall',
|
||||
'i', 'you', 'he', 'she', 'it', 'we', 'they', 'my', 'your',
|
||||
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from',
|
||||
'how', 'what', 'when', 'where', 'which', 'who', 'why',
|
||||
'not', 'no', 'up', 'out', 'if', 'about', 'into', 'that', 'this',
|
||||
'and', 'or', 'but', 'so', 'yet', 'nor', 'use', 'using', 'used',
|
||||
'get', 'set', 'add', 'run', 'make', 'see', 'all', 'new',
|
||||
]);
|
||||
|
||||
// ─── Synonymes domaine ────────────────────────────────────────────────────────
|
||||
|
||||
const SYNONYMS: Record<string, string[]> = {
|
||||
env: ['environment', 'env_var', 'variable', 'envvar'],
|
||||
config: ['configuration', 'settings', 'configure', 'setup'],
|
||||
cmd: ['command', 'commands', 'slash'],
|
||||
auth: ['authentication', 'permission', 'permissions', 'authorize'],
|
||||
hook: ['hooks', 'event', 'pre_tool', 'post_tool', 'events'],
|
||||
agent: ['agents', 'subagent', 'teammate', 'subagents'],
|
||||
mcp: ['model_context_protocol', 'mcp_server', 'mcp_servers', 'protocol'],
|
||||
skill: ['skills', 'skill_module', 'modules'],
|
||||
debug: ['debugging', 'troubleshoot', 'troubleshooting', 'diagnose'],
|
||||
cost: ['token', 'tokens', 'pricing', 'budget', 'optimization', 'cost'],
|
||||
security: ['secure', 'hardening', 'vulnerability', 'cve', 'threat'],
|
||||
sandbox: ['isolation', 'container', 'docker', 'isolated'],
|
||||
slash: ['command', 'commands', 'custom_command'],
|
||||
install: ['installation', 'setup', 'installing'],
|
||||
memory: ['persistence', 'serena', 'context'],
|
||||
test: ['testing', 'tdd', 'bdd', 'spec', 'specs'],
|
||||
workflow: ['workflows', 'process', 'flow'],
|
||||
template: ['templates', 'example', 'examples', 'boilerplate'],
|
||||
model: ['claude', 'opus', 'sonnet', 'haiku', 'llm'],
|
||||
key: ['keyboard', 'shortcut', 'keybinding', 'keybindings'],
|
||||
};
|
||||
|
||||
// ─── Levenshtein distance (inline, ~20 lines) ─────────────────────────────────
|
||||
|
||||
function levenshtein(a: string, b: string): number {
|
||||
if (Math.abs(a.length - b.length) > 3) return 99; // fast bail
|
||||
const m = a.length, n = b.length;
|
||||
const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>
|
||||
Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
|
||||
);
|
||||
for (let i = 1; i <= m; i++) {
|
||||
for (let j = 1; j <= n; j++) {
|
||||
dp[i][j] =
|
||||
a[i - 1] === b[j - 1]
|
||||
? dp[i - 1][j - 1]
|
||||
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
||||
}
|
||||
}
|
||||
return dp[m][n];
|
||||
}
|
||||
|
||||
// ─── Query processing ─────────────────────────────────────────────────────────
|
||||
|
||||
export function tokenizeQuery(query: string): string[] {
|
||||
const tokens = query
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_\s-]/g, ' ')
|
||||
.split(/\s+/)
|
||||
.filter((t) => t.length > 1 && !STOP_WORDS.has(t));
|
||||
|
||||
// Expand synonyms
|
||||
const expanded = new Set(tokens);
|
||||
for (const token of tokens) {
|
||||
const syns = SYNONYMS[token];
|
||||
if (syns) {
|
||||
for (const syn of syns) expanded.add(syn);
|
||||
}
|
||||
// Also check partial matches on synonym keys
|
||||
for (const [key, syns2] of Object.entries(SYNONYMS)) {
|
||||
if (key.startsWith(token) || token.startsWith(key)) {
|
||||
expanded.add(key);
|
||||
for (const s of syns2) expanded.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(expanded);
|
||||
}
|
||||
|
||||
// ─── Scoring ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SearchResult {
|
||||
key: string;
|
||||
section: string;
|
||||
score: number;
|
||||
value: unknown;
|
||||
target: ReturnType<typeof import('./content.js').resolveDeepDive>;
|
||||
hint: string;
|
||||
}
|
||||
|
||||
function scoreEntry(entry: IndexEntry, tokens: string[], originalTokens: string[]): number {
|
||||
let score = 0;
|
||||
const keyLower = entry.key.toLowerCase();
|
||||
const keySegments = keyLower.split('_');
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.length < 2) continue;
|
||||
|
||||
if (keyLower === token) {
|
||||
score += 20;
|
||||
} else if (keyLower.includes(token)) {
|
||||
score += 10;
|
||||
} else if (keySegments.some((seg) => seg.startsWith(token))) {
|
||||
score += 7;
|
||||
} else if (entry.searchableText.includes(token)) {
|
||||
score += 5;
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzy only if no exact matches and token is long enough
|
||||
if (score === 0) {
|
||||
for (const token of originalTokens) {
|
||||
if (token.length <= 4) continue;
|
||||
for (const seg of keySegments) {
|
||||
if (seg.length > 4 && levenshtein(token, seg) <= 2) {
|
||||
score += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
function buildHint(entry: IndexEntry): string {
|
||||
const { target } = entry;
|
||||
if (!target) return `Key: ${entry.key}`;
|
||||
|
||||
switch (target.type) {
|
||||
case 'line':
|
||||
return `guide/ultimate-guide.md:${target.line}`;
|
||||
case 'file':
|
||||
return target.line ? `${target.path}:${target.line}` : target.path;
|
||||
case 'url':
|
||||
return target.url;
|
||||
case 'inline':
|
||||
return target.text.length > 100 ? target.text.slice(0, 100) + '…' : target.text;
|
||||
case 'structured':
|
||||
return `[structured data] — ${entry.key}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Main search function ─────────────────────────────────────────────────────
|
||||
|
||||
export function searchGuide(query: string, limit = 10): SearchResult[] {
|
||||
const ref = loadReference();
|
||||
const tokens = tokenizeQuery(query);
|
||||
const originalTokens = query
|
||||
.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter((t) => t.length > 1 && !STOP_WORDS.has(t));
|
||||
|
||||
if (tokens.length === 0) return [];
|
||||
|
||||
const results: SearchResult[] = [];
|
||||
|
||||
for (const entry of ref.entries) {
|
||||
const score = scoreEntry(entry, tokens, originalTokens);
|
||||
if (score > 0) {
|
||||
results.push({
|
||||
key: entry.key,
|
||||
section: entry.section,
|
||||
score,
|
||||
value: entry.value,
|
||||
target: entry.target,
|
||||
hint: buildHint(entry),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, Math.min(limit, 20));
|
||||
}
|
||||
103
mcp-server/src/lib/section-reader.ts
Normal file
103
mcp-server/src/lib/section-reader.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolveContentPath, isDevMode } from './content.js';
|
||||
import { fetchFile } from './fetcher.js';
|
||||
|
||||
export interface SectionResult {
|
||||
content: string;
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
totalLines: number;
|
||||
hasMore: boolean;
|
||||
nextOffset: number | null;
|
||||
}
|
||||
|
||||
const MAX_LINES = 500;
|
||||
|
||||
// ─── Heading level detector ───────────────────────────────────────────────────
|
||||
|
||||
function getHeadingLevel(line: string): number | null {
|
||||
const match = line.match(/^(#{1,6})\s/);
|
||||
return match ? match[1].length : null;
|
||||
}
|
||||
|
||||
// ─── Section extraction ───────────────────────────────────────────────────────
|
||||
|
||||
export function extractSection(
|
||||
lines: string[],
|
||||
offset: number,
|
||||
limit: number,
|
||||
): SectionResult {
|
||||
const totalLines = lines.length;
|
||||
const startLine = Math.max(1, Math.min(offset, totalLines));
|
||||
const effectiveLimit = Math.min(limit, MAX_LINES);
|
||||
|
||||
// Find heading level of starting section (for boundary detection)
|
||||
let sectionLevel: number | null = null;
|
||||
for (let i = startLine - 1; i < Math.min(startLine + 5, totalLines); i++) {
|
||||
const level = getHeadingLevel(lines[i]);
|
||||
if (level !== null) {
|
||||
sectionLevel = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let inCodeFence = false;
|
||||
let endLine = startLine - 1;
|
||||
const collected: string[] = [];
|
||||
|
||||
for (let i = startLine - 1; i < totalLines && collected.length < effectiveLimit; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Track code fences
|
||||
if (line.trimStart().startsWith('```')) {
|
||||
inCodeFence = !inCodeFence;
|
||||
}
|
||||
|
||||
// Check heading boundary (only outside code fences, only same/higher level)
|
||||
if (!inCodeFence && i > startLine - 1 && sectionLevel !== null) {
|
||||
const level = getHeadingLevel(line);
|
||||
if (level !== null && level <= sectionLevel) {
|
||||
break; // Stop at same or higher heading
|
||||
}
|
||||
}
|
||||
|
||||
collected.push(line);
|
||||
endLine = i + 1;
|
||||
}
|
||||
|
||||
const hasMore = endLine < totalLines && collected.length >= effectiveLimit;
|
||||
|
||||
return {
|
||||
content: collected.join('\n'),
|
||||
startLine,
|
||||
endLine,
|
||||
totalLines,
|
||||
hasMore,
|
||||
nextOffset: hasMore ? endLine + 1 : null,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────────────────────
|
||||
|
||||
export async function readSection(
|
||||
filePath: string,
|
||||
offset = 1,
|
||||
limit = MAX_LINES,
|
||||
): Promise<SectionResult | null> {
|
||||
let content: string | null = null;
|
||||
|
||||
if (isDevMode()) {
|
||||
// Dev mode: read from local filesystem
|
||||
const resolved = resolveContentPath(filePath);
|
||||
if (!resolved || !existsSync(resolved)) return null;
|
||||
content = readFileSync(resolved, 'utf8');
|
||||
} else {
|
||||
// Production: fetch from GitHub (with cache)
|
||||
content = await fetchFile(filePath);
|
||||
}
|
||||
|
||||
if (content === null) return null;
|
||||
|
||||
const lines = content.split('\n');
|
||||
return extractSection(lines, offset, limit);
|
||||
}
|
||||
23
mcp-server/src/lib/urls.ts
Normal file
23
mcp-server/src/lib/urls.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const GITHUB_BASE = 'https://github.com/FlorianBruniaux/claude-code-ultimate-guide/blob/main';
|
||||
const GUIDE_SITE_BASE = 'https://cc.bruniaux.com/guide';
|
||||
|
||||
export function githubUrl(filePath: string, line?: number): string {
|
||||
const base = `${GITHUB_BASE}/${filePath}`;
|
||||
return line ? `${base}#L${line}` : base;
|
||||
}
|
||||
|
||||
// Only ultimate-guide.md is rendered on the guide site
|
||||
export function guideSiteUrl(filePath: string, line?: number): string | null {
|
||||
if (filePath !== 'guide/ultimate-guide.md') return null;
|
||||
// Line numbers don't map to anchors directly — link to the reader root
|
||||
// If a line is provided, append a hint as a hash comment (not a real anchor)
|
||||
return line ? `${GUIDE_SITE_BASE}/#L${line}` : GUIDE_SITE_BASE;
|
||||
}
|
||||
|
||||
export function formatLinks(filePath: string, line?: number): string {
|
||||
const gh = githubUrl(filePath, line);
|
||||
const site = guideSiteUrl(filePath, line);
|
||||
const parts = [`GitHub: ${gh}`];
|
||||
if (site) parts.push(`Guide: ${site}`);
|
||||
return parts.join(' | ');
|
||||
}
|
||||
70
mcp-server/src/prompts/index.ts
Normal file
70
mcp-server/src/prompts/index.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function registerPrompts(server: McpServer): void {
|
||||
server.prompt(
|
||||
'claude-code-expert',
|
||||
'Activate Claude Code expert mode with the optimal workflow for answering questions using the Ultimate Guide.',
|
||||
{
|
||||
question: z.string().optional().describe('Optional question to answer immediately'),
|
||||
},
|
||||
async ({ question }) => {
|
||||
const systemPrompt = `You are an expert on Claude Code (Anthropic's CLI tool) with access to the Claude Code Ultimate Guide — a comprehensive 20,000+ line reference covering every feature, workflow, and best practice.
|
||||
|
||||
## How to answer Claude Code questions
|
||||
|
||||
**Step 1 — Fast path (targeted questions)**
|
||||
Use search_guide(query) with 1-3 keywords:
|
||||
- "hooks" not "how do I configure hooks"
|
||||
- "cost optimization" not "how to reduce token usage"
|
||||
- "custom agents" not "how to create agent files"
|
||||
|
||||
If results have score > 10, follow the deep_dive links with read_section().
|
||||
|
||||
**Step 2 — Fallback (broad questions or insufficient search results)**
|
||||
Read the resource claude-code-guide://reference — it's 94KB of structured YAML with ~900 indexed entries. Parse it directly to find what you need.
|
||||
|
||||
**Step 3 — Templates**
|
||||
Use get_example(name) for production-ready code:
|
||||
- Agents: get_example("code-reviewer"), get_example("backend-architect")
|
||||
- Hooks: get_example("pre-commit"), get_example("notification")
|
||||
- Commands: get_example("release"), get_example("audit")
|
||||
- Skills: get_example("commit"), get_example("review-pr")
|
||||
|
||||
## Rules
|
||||
- Always cite the source file and line number
|
||||
- Never invent Claude Code features — if you're not sure, say so
|
||||
- If a feature isn't in the guide, check claude-code-guide://releases for recent additions
|
||||
- Prefer concrete examples over abstract explanations
|
||||
- For version-specific questions, check the releases resource first
|
||||
- **Respond in the same language the user used** — if they ask in French, answer in French; if English, answer in English. Tool output is in English but your response should match the user's language.
|
||||
|
||||
## Guide structure
|
||||
- guide/ultimate-guide.md — Main reference (20K+ lines)
|
||||
- guide/cheatsheet.md — Quick reference
|
||||
- guide/architecture.md — How Claude Code works internally
|
||||
- examples/agents/ — Custom agent templates
|
||||
- examples/commands/ — Slash command templates
|
||||
- examples/hooks/ — Event hook examples
|
||||
- examples/skills/ — Skill module templates
|
||||
- machine-readable/reference.yaml — Structured index (available via resource)`;
|
||||
|
||||
const messages = [
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: {
|
||||
type: 'text' as const,
|
||||
text: question
|
||||
? `${systemPrompt}\n\n---\n\nQuestion: ${question}`
|
||||
: systemPrompt,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
description: 'Claude Code expert mode activated',
|
||||
messages,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
70
mcp-server/src/resources/index.ts
Normal file
70
mcp-server/src/resources/index.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { getReferenceYamlRaw, getReleasesYamlRaw, loadLlmsTxt } from '../lib/content.js';
|
||||
|
||||
export function registerResources(server: McpServer): void {
|
||||
// Full reference YAML — the fallback when search isn't enough
|
||||
server.resource(
|
||||
'reference',
|
||||
'claude-code-guide://reference',
|
||||
{
|
||||
description: 'Complete structured index of the Claude Code Ultimate Guide (94KB YAML, ~900 entries). Use as fallback when search_guide() results are insufficient.',
|
||||
mimeType: 'text/yaml',
|
||||
},
|
||||
async () => {
|
||||
const content = getReferenceYamlRaw();
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'claude-code-guide://reference',
|
||||
mimeType: 'text/yaml',
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Releases history
|
||||
server.resource(
|
||||
'releases',
|
||||
'claude-code-guide://releases',
|
||||
{
|
||||
description: 'Claude Code official releases history — condensed highlights and breaking changes for each version.',
|
||||
mimeType: 'text/yaml',
|
||||
},
|
||||
async () => {
|
||||
const content = getReleasesYamlRaw();
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'claude-code-guide://releases',
|
||||
mimeType: 'text/yaml',
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Guide identity file
|
||||
server.resource(
|
||||
'llms',
|
||||
'claude-code-guide://llms',
|
||||
{
|
||||
description: 'llms.txt — machine-readable identity and navigation file for the Claude Code Ultimate Guide.',
|
||||
mimeType: 'text/plain',
|
||||
},
|
||||
async () => {
|
||||
const content = loadLlmsTxt();
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: 'claude-code-guide://llms',
|
||||
mimeType: 'text/plain',
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
56
mcp-server/src/server.ts
Normal file
56
mcp-server/src/server.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { registerSearchGuide } from './tools/search-guide.js';
|
||||
import { registerReadSection } from './tools/read-section.js';
|
||||
import { registerListTopics } from './tools/list-topics.js';
|
||||
import { registerGetExample } from './tools/get-example.js';
|
||||
import { registerChangelog } from './tools/changelog.js';
|
||||
import { registerCheatsheet } from './tools/cheatsheet.js';
|
||||
import { registerListExamples } from './tools/list-examples.js';
|
||||
import { registerReleases } from './tools/releases.js';
|
||||
import { registerCompareVersions } from './tools/compare-versions.js';
|
||||
import { registerGetThreat, registerListThreats } from './tools/threats.js';
|
||||
import { registerSearchExamples } from './tools/search-examples.js';
|
||||
import { registerResources } from './resources/index.js';
|
||||
import { registerPrompts } from './prompts/index.js';
|
||||
import { loadReference, loadReleases, isDevMode } from './lib/content.js';
|
||||
|
||||
export function createServer(): McpServer {
|
||||
const server = new McpServer({
|
||||
name: 'claude-code-ultimate-guide',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
// Pre-load YAML indexes into memory at startup
|
||||
try {
|
||||
const ref = loadReference();
|
||||
const releases = loadReleases();
|
||||
const mode = isDevMode() ? 'dev (local files)' : 'production (GitHub lazy fetch)';
|
||||
process.stderr.write(
|
||||
`[claude-code-guide] Loaded ${ref.entries.length} index entries | ${releases.releases.length} releases | mode: ${mode}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
process.stderr.write(`[claude-code-guide] Warning: Failed to pre-load indexes: ${err}\n`);
|
||||
}
|
||||
|
||||
// Register all tools
|
||||
registerSearchGuide(server);
|
||||
registerReadSection(server);
|
||||
registerListTopics(server);
|
||||
registerGetExample(server);
|
||||
registerChangelog(server);
|
||||
registerCheatsheet(server);
|
||||
registerListExamples(server);
|
||||
registerReleases(server);
|
||||
registerCompareVersions(server);
|
||||
registerGetThreat(server);
|
||||
registerListThreats(server);
|
||||
registerSearchExamples(server);
|
||||
|
||||
// Register resources
|
||||
registerResources(server);
|
||||
|
||||
// Register prompts
|
||||
registerPrompts(server);
|
||||
|
||||
return server;
|
||||
}
|
||||
179
mcp-server/src/tools/changelog.ts
Normal file
179
mcp-server/src/tools/changelog.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { readSection } from '../lib/section-reader.js';
|
||||
import { parseChangelog, filterByPeriod } from '../lib/changelog-parser.js';
|
||||
import { githubUrl, guideSiteUrl } from '../lib/urls.js';
|
||||
|
||||
const CHANGELOG_PATH = 'CHANGELOG.md';
|
||||
const CHANGELOG_GITHUB = githubUrl(CHANGELOG_PATH);
|
||||
|
||||
// ─── File path extractor ──────────────────────────────────────────────────────
|
||||
|
||||
// Matches paths like: guide/ultimate-guide.md, docs/resource-evaluations/xxx.md,
|
||||
// examples/agents/foo.md, machine-readable/reference.yaml, guide/cheatsheet.md
|
||||
const FILE_PATH_RE =
|
||||
/\b((?:guide|docs|examples|machine-readable|whitepapers)\/[a-zA-Z0-9_./\-]+\.(?:md|yaml|yml|json|sh|ts|txt))/g;
|
||||
|
||||
interface ResourceLink {
|
||||
path: string;
|
||||
github: string;
|
||||
site: string | null;
|
||||
}
|
||||
|
||||
function extractResourceLinks(text: string): ResourceLink[] {
|
||||
const seen = new Set<string>();
|
||||
const links: ResourceLink[] = [];
|
||||
for (const match of text.matchAll(FILE_PATH_RE)) {
|
||||
const path = match[1];
|
||||
if (seen.has(path)) continue;
|
||||
seen.add(path);
|
||||
links.push({
|
||||
path,
|
||||
github: githubUrl(path),
|
||||
site: guideSiteUrl(path),
|
||||
});
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
async function fetchChangelog(): Promise<string | null> {
|
||||
const result = await readSection(CHANGELOG_PATH, 1, 500);
|
||||
if (!result) return null;
|
||||
|
||||
// If truncated, fetch more until we have everything (max 3000 lines)
|
||||
if (!result.hasMore) return result.content;
|
||||
|
||||
let full = result.content;
|
||||
let offset = result.nextOffset!;
|
||||
for (let i = 0; i < 5 && offset; i++) {
|
||||
const next = await readSection(CHANGELOG_PATH, offset, 500);
|
||||
if (!next) break;
|
||||
full += '\n' + next.content;
|
||||
offset = next.nextOffset ?? 0;
|
||||
if (!next.hasMore) break;
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
export function registerChangelog(server: McpServer): void {
|
||||
// ── get_changelog ─────────────────────────────────────────────────────────
|
||||
server.tool(
|
||||
'get_changelog',
|
||||
'Return the last N entries from the Claude Code Ultimate Guide CHANGELOG. Shows what changed in the guide itself (not Claude Code CLI releases — use get_release() for that).',
|
||||
{
|
||||
count: z.number().min(1).max(20).optional().default(5).describe('Number of recent changelog entries to return (default 5)'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ count }) => {
|
||||
const raw = await fetchChangelog();
|
||||
if (!raw) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'CHANGELOG.md unavailable (offline and no cache).' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entries = parseChangelog(raw);
|
||||
const slice = entries.slice(0, count ?? 5);
|
||||
|
||||
if (slice.length === 0) {
|
||||
return { content: [{ type: 'text', text: 'No changelog entries found.' }] };
|
||||
}
|
||||
|
||||
const combinedText = slice.map((e) => e.content).join('\n\n');
|
||||
const links = extractResourceLinks(combinedText);
|
||||
|
||||
const lines = [
|
||||
`# Guide CHANGELOG — last ${slice.length} entries`,
|
||||
`GitHub: ${CHANGELOG_GITHUB}`,
|
||||
'',
|
||||
combinedText,
|
||||
];
|
||||
|
||||
if (links.length > 0) {
|
||||
lines.push('', '---', '## Resources mentioned', '');
|
||||
for (const l of links) {
|
||||
lines.push(`**${l.path}**`);
|
||||
lines.push(` GitHub: ${l.github}`);
|
||||
if (l.site) lines.push(` Guide: ${l.site}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
|
||||
// ── get_digest ─────────────────────────────────────────────────────────────
|
||||
server.tool(
|
||||
'get_digest',
|
||||
'Return a digest of guide and Claude Code CLI changes for a given period. Combines guide CHANGELOG entries + official Claude Code releases in the time window.',
|
||||
{
|
||||
period: z
|
||||
.enum(['day', 'week', 'month'])
|
||||
.describe('Time window: "day" (24h), "week" (7 days), "month" (30 days)'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ period }) => {
|
||||
const labels = { day: 'last 24h', week: 'last 7 days', month: 'last 30 days' };
|
||||
|
||||
// Guide changelog
|
||||
const raw = await fetchChangelog();
|
||||
const guideEntries = raw ? filterByPeriod(parseChangelog(raw), period) : [];
|
||||
|
||||
// Claude Code releases
|
||||
const { loadReleases } = await import('../lib/content.js');
|
||||
const relData = loadReleases();
|
||||
const MS = { day: 86_400_000, week: 7 * 86_400_000, month: 30 * 86_400_000 };
|
||||
const cutoff = Date.now() - MS[period];
|
||||
const ccReleases = (relData.releases as Array<{ version: string; date: string; highlights: string[] }>)
|
||||
.filter((r) => new Date(r.date).getTime() >= cutoff);
|
||||
|
||||
const lines: string[] = [
|
||||
`# Digest — ${labels[period]}`,
|
||||
`Generated: ${new Date().toISOString().slice(0, 10)}`,
|
||||
'',
|
||||
];
|
||||
|
||||
// Guide section
|
||||
lines.push('## Guide changes');
|
||||
if (guideEntries.length === 0) {
|
||||
lines.push('No guide updates in this period.');
|
||||
lines.push('');
|
||||
} else {
|
||||
const guideText = guideEntries.map((e) => e.content).join('\n\n');
|
||||
lines.push(guideText);
|
||||
lines.push('');
|
||||
|
||||
// Resource links extracted from guide entries
|
||||
const links = extractResourceLinks(guideText);
|
||||
if (links.length > 0) {
|
||||
lines.push('### Resources mentioned');
|
||||
for (const l of links) {
|
||||
const siteStr = l.site ? ` | Guide: ${l.site}` : '';
|
||||
lines.push(`- \`${l.path}\` — GitHub: ${l.github}${siteStr}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
// CC releases section
|
||||
lines.push('## Claude Code CLI releases');
|
||||
if (ccReleases.length === 0) {
|
||||
lines.push('No Claude Code releases in this period.');
|
||||
} else {
|
||||
for (const r of ccReleases) {
|
||||
lines.push(`### v${r.version} (${r.date})`);
|
||||
for (const h of r.highlights ?? []) lines.push(`- ${h}`);
|
||||
// Link to the release tracking file
|
||||
lines.push(`GitHub: ${githubUrl('guide/claude-code-releases.md')}`);
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push(`Full changelog: ${CHANGELOG_GITHUB}`);
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
58
mcp-server/src/tools/cheatsheet.ts
Normal file
58
mcp-server/src/tools/cheatsheet.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { readSection } from '../lib/section-reader.js';
|
||||
import { formatLinks } from '../lib/urls.js';
|
||||
|
||||
const CHEATSHEET_PATH = 'guide/cheatsheet.md';
|
||||
|
||||
export function registerCheatsheet(server: McpServer): void {
|
||||
server.tool(
|
||||
'get_cheatsheet',
|
||||
'Return the Claude Code cheatsheet — a compact 1-page reference covering the most important commands, shortcuts, config options, and workflows.',
|
||||
{
|
||||
section: z.string().optional().describe('Optional: filter to a specific section (e.g. "installation", "hooks", "agents", "mcp")'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ section }) => {
|
||||
const result = await readSection(CHEATSHEET_PATH, 1, 500);
|
||||
if (!result) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'Cheatsheet unavailable (offline and no cache).' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
let content = result.content;
|
||||
|
||||
// Filter to section if requested
|
||||
if (section) {
|
||||
const sectionLower = section.toLowerCase();
|
||||
const lines = content.split('\n');
|
||||
const start = lines.findIndex(
|
||||
(l) => l.toLowerCase().includes(sectionLower) && l.startsWith('#'),
|
||||
);
|
||||
if (start !== -1) {
|
||||
// Find end of section (next heading of same or higher level)
|
||||
const startLevel = (lines[start].match(/^#+/) ?? [''])[0].length;
|
||||
let end = lines.length;
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
const m = lines[i].match(/^(#+)/);
|
||||
if (m && m[1].length <= startLevel) { end = i; break; }
|
||||
}
|
||||
content = lines.slice(start, end).join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
const header = [
|
||||
`# Claude Code Cheatsheet`,
|
||||
formatLinks(CHEATSHEET_PATH),
|
||||
section ? `Filtered: "${section}"` : `Lines: ${result.startLine}-${result.endLine} of ${result.totalLines}`,
|
||||
result.hasMore ? `Has more — use read_section("${CHEATSHEET_PATH}", ${result.nextOffset}) for rest` : '',
|
||||
'---',
|
||||
'',
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
return { content: [{ type: 'text', text: header + content }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
116
mcp-server/src/tools/compare-versions.ts
Normal file
116
mcp-server/src/tools/compare-versions.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadReleases } from '../lib/content.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
const RELEASES_GITHUB = githubUrl('guide/claude-code-releases.md');
|
||||
|
||||
interface ReleaseEntry {
|
||||
version: string;
|
||||
date: string;
|
||||
highlights: string[];
|
||||
breaking?: string[];
|
||||
}
|
||||
|
||||
function parseSemver(v: string): [number, number, number] {
|
||||
const parts = v.replace(/^v/, '').split('.').map(Number);
|
||||
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
||||
}
|
||||
|
||||
function semverCompare(a: string, b: string): number {
|
||||
const [am, an, ap] = parseSemver(a);
|
||||
const [bm, bn, bp] = parseSemver(b);
|
||||
if (am !== bm) return am - bm;
|
||||
if (an !== bn) return an - bn;
|
||||
return ap - bp;
|
||||
}
|
||||
|
||||
export function registerCompareVersions(server: McpServer): void {
|
||||
server.tool(
|
||||
'compare_versions',
|
||||
'Show what changed between two Claude Code CLI versions. Lists all releases in range with aggregated highlights and breaking changes.',
|
||||
{
|
||||
from: z.string().describe('Starting version (older), e.g. "2.1.50"'),
|
||||
to: z.string().optional().describe('Ending version (newer). Omit to use the latest.'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ from, to }) => {
|
||||
const data = loadReleases();
|
||||
const releases = data.releases as ReleaseEntry[];
|
||||
|
||||
const fromClean = from.replace(/^v/, '');
|
||||
const toClean = (to ?? data.latest).replace(/^v/, '');
|
||||
|
||||
// Ensure from <= to (by semver)
|
||||
const fromVer = fromClean;
|
||||
const toVer = toClean;
|
||||
const ordered = semverCompare(fromVer, toVer) <= 0
|
||||
? { older: fromVer, newer: toVer }
|
||||
: { older: toVer, newer: fromVer };
|
||||
|
||||
// Validate both versions exist
|
||||
const fromFound = releases.find((r) => r.version === ordered.older);
|
||||
const toFound = releases.find((r) => r.version === ordered.newer);
|
||||
|
||||
if (!fromFound) {
|
||||
const known = releases.slice(0, 10).map((r) => `v${r.version}`).join(', ');
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Version v${ordered.older} not found.\n\nRecent versions: ${known}\n\nFull history: ${RELEASES_GITHUB}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (!toFound) {
|
||||
const known = releases.slice(0, 10).map((r) => `v${r.version}`).join(', ');
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Version v${ordered.newer} not found.\n\nRecent versions: ${known}\n\nFull history: ${RELEASES_GITHUB}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// Collect versions in range (releases are newest-first)
|
||||
const inRange = releases.filter(
|
||||
(r) => semverCompare(r.version, ordered.older) >= 0 &&
|
||||
semverCompare(r.version, ordered.newer) <= 0,
|
||||
);
|
||||
|
||||
// Aggregate highlights and breaking changes
|
||||
const allHighlights: string[] = [];
|
||||
const allBreaking: string[] = [];
|
||||
|
||||
for (const r of inRange) {
|
||||
for (const h of r.highlights ?? []) allHighlights.push(h);
|
||||
for (const b of r.breaking ?? []) allBreaking.push(b);
|
||||
}
|
||||
|
||||
const versionList = inRange
|
||||
.slice()
|
||||
.sort((a, b) => semverCompare(b.version, a.version)) // newest first
|
||||
.map((r) => `v${r.version} (${r.date})`)
|
||||
.join(', ');
|
||||
|
||||
const lines = [
|
||||
`# Claude Code: v${ordered.older} → v${ordered.newer}`,
|
||||
RELEASES_GITHUB,
|
||||
'',
|
||||
`**${inRange.length} release${inRange.length !== 1 ? 's' : ''} in range**: ${versionList}`,
|
||||
'',
|
||||
'## What changed',
|
||||
...allHighlights.map((h) => `- ${h}`),
|
||||
];
|
||||
|
||||
if (allBreaking.length > 0) {
|
||||
lines.push('', '## Breaking changes');
|
||||
for (const b of allBreaking) lines.push(`- ⚠️ ${b}`);
|
||||
}
|
||||
|
||||
lines.push('', `---`);
|
||||
lines.push(`${allHighlights.length} highlight${allHighlights.length !== 1 ? 's' : ''} | ${allBreaking.length} breaking change${allBreaking.length !== 1 ? 's' : ''} | Use get_release(version) for per-release details.`);
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
140
mcp-server/src/tools/get-example.ts
Normal file
140
mcp-server/src/tools/get-example.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadReference, resolveDeepDive } from '../lib/content.js';
|
||||
import { readSection } from '../lib/section-reader.js';
|
||||
import { tokenizeQuery } from '../lib/search.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
export function registerGetExample(server: McpServer): void {
|
||||
server.tool(
|
||||
'get_example',
|
||||
'Fetch a production-ready template or example from the guide (agents, skills, commands, hooks, scripts). Pass a partial name to search for matching examples.',
|
||||
{
|
||||
name: z.string().describe('Example name or partial match (e.g. "code-reviewer", "pre-commit hook", "custom agent")'),
|
||||
},
|
||||
{
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
openWorldHint: false,
|
||||
},
|
||||
async ({ name }) => {
|
||||
const ref = loadReference();
|
||||
const nameLower = name.toLowerCase();
|
||||
const tokens = tokenizeQuery(name);
|
||||
|
||||
// Collect entries pointing to examples/
|
||||
interface ExampleMatch {
|
||||
key: string;
|
||||
path: string;
|
||||
score: number;
|
||||
}
|
||||
const matches: ExampleMatch[] = [];
|
||||
|
||||
for (const entry of ref.entries) {
|
||||
const target = entry.target ?? resolveDeepDive(entry.value);
|
||||
if (!target || target.type !== 'file') continue;
|
||||
if (!target.path.startsWith('examples/')) continue;
|
||||
|
||||
const pathLower = target.path.toLowerCase();
|
||||
const keyLower = entry.key.toLowerCase();
|
||||
let score = 0;
|
||||
|
||||
// Exact match in path
|
||||
if (pathLower.includes(nameLower)) score += 20;
|
||||
if (keyLower.includes(nameLower)) score += 15;
|
||||
|
||||
// Token match
|
||||
for (const token of tokens) {
|
||||
if (pathLower.includes(token)) score += 5;
|
||||
if (keyLower.includes(token)) score += 3;
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
matches.push({ key: entry.key, path: target.path, score });
|
||||
}
|
||||
}
|
||||
|
||||
matches.sort((a, b) => b.score - a.score);
|
||||
|
||||
if (matches.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: [
|
||||
`No examples found matching: "${name}"`,
|
||||
'',
|
||||
'Available example categories:',
|
||||
' • examples/agents/ — Custom agent templates',
|
||||
' • examples/commands/ — Slash command templates',
|
||||
' • examples/hooks/ — Event hook examples',
|
||||
' • examples/skills/ — Skill module templates',
|
||||
' • examples/scripts/ — Utility scripts',
|
||||
'',
|
||||
'Try: get_example("agent"), get_example("hook"), get_example("command")',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Single match: fetch and return content
|
||||
if (matches.length === 1 || matches[0].score >= 20) {
|
||||
const match = matches[0];
|
||||
const section = await readSection(match.path, 1, 500);
|
||||
|
||||
if (!section) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: [
|
||||
`Example found: ${match.path}`,
|
||||
`Key: ${match.key}`,
|
||||
'',
|
||||
'Content unavailable (offline or file not found).',
|
||||
`Try: read_section("${match.path}")`,
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: [
|
||||
`# ${match.path}`,
|
||||
`Key: ${match.key}`,
|
||||
`Lines: ${section.startLine}-${section.endLine} of ${section.totalLines}`,
|
||||
`GitHub: ${githubUrl(match.path)}`,
|
||||
'---',
|
||||
'',
|
||||
section.content,
|
||||
section.hasMore ? `\n\n[truncated — use read_section("${match.path}", ${section.nextOffset}) for more]` : '',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Multiple matches: list them
|
||||
const lines = [
|
||||
`Found ${matches.length} examples matching "${name}":`,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const match of matches.slice(0, 10)) {
|
||||
lines.push(`• ${match.path}`);
|
||||
lines.push(` Key: ${match.key} | Score: ${match.score}`);
|
||||
lines.push(` → get_example("${match.path.split('/').pop()?.replace(/\.(md|yaml|sh|ts)$/, '') ?? match.key}")`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: lines.join('\n') }],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
80
mcp-server/src/tools/list-examples.ts
Normal file
80
mcp-server/src/tools/list-examples.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadReference, resolveDeepDive } from '../lib/content.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
const CATEGORIES = ['agents', 'commands', 'hooks', 'skills', 'scripts'] as const;
|
||||
type Category = typeof CATEGORIES[number];
|
||||
|
||||
export function registerListExamples(server: McpServer): void {
|
||||
server.tool(
|
||||
'list_examples',
|
||||
'List all production-ready templates in the guide by category (agents, commands, hooks, skills, scripts). Use get_example(name) to fetch the content of any specific template.',
|
||||
{
|
||||
category: z
|
||||
.enum(CATEGORIES)
|
||||
.optional()
|
||||
.describe('Filter by category: agents | commands | hooks | skills | scripts. Omit for all.'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ category }) => {
|
||||
const ref = loadReference();
|
||||
|
||||
// Collect all example paths from reference.yaml
|
||||
const byCategory = new Map<Category, Array<{ key: string; path: string; description: string }>>();
|
||||
for (const cat of CATEGORIES) byCategory.set(cat, []);
|
||||
|
||||
for (const entry of ref.entries) {
|
||||
const target = entry.target ?? resolveDeepDive(entry.value);
|
||||
if (!target || target.type !== 'file') continue;
|
||||
if (!target.path.startsWith('examples/')) continue;
|
||||
|
||||
const parts = target.path.split('/'); // ['examples', 'agents', 'file.md']
|
||||
const cat = parts[1] as Category;
|
||||
if (!CATEGORIES.includes(cat)) continue;
|
||||
if (category && cat !== category) continue;
|
||||
|
||||
const list = byCategory.get(cat)!;
|
||||
// Deduplicate by path
|
||||
if (list.some((e) => e.path === target.path)) continue;
|
||||
|
||||
// Build description from key
|
||||
const desc = entry.key
|
||||
.replace(/^deep_dive_/, '')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
|
||||
list.push({ key: entry.key, path: target.path, description: desc });
|
||||
}
|
||||
|
||||
const lines: string[] = [
|
||||
`# Claude Code Ultimate Guide — Templates`,
|
||||
`GitHub: https://github.com/FlorianBruniaux/claude-code-ultimate-guide/tree/main/examples`,
|
||||
'',
|
||||
];
|
||||
|
||||
let total = 0;
|
||||
for (const cat of CATEGORIES) {
|
||||
if (category && cat !== category) continue;
|
||||
const items = byCategory.get(cat)!;
|
||||
if (items.length === 0) continue;
|
||||
|
||||
lines.push(`## ${cat.charAt(0).toUpperCase() + cat.slice(1)} (${items.length})`);
|
||||
for (const item of items) {
|
||||
const filename = item.path.split('/').pop() ?? item.path;
|
||||
const gh = githubUrl(item.path);
|
||||
lines.push(`- **${filename}** — ${item.description}`);
|
||||
lines.push(` GitHub: ${gh}`);
|
||||
lines.push(` → get_example("${filename.replace(/\.(md|yaml|sh|ts)$/, '')}")`);
|
||||
}
|
||||
lines.push('');
|
||||
total += items.length;
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push(`${total} template(s) total. Use get_example(name) to fetch any template.`);
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
57
mcp-server/src/tools/list-topics.ts
Normal file
57
mcp-server/src/tools/list-topics.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { loadReference } from '../lib/content.js';
|
||||
|
||||
export function registerListTopics(server: McpServer): void {
|
||||
server.tool(
|
||||
'list_topics',
|
||||
'List all top-level topics and categories in the Claude Code Ultimate Guide. Useful for exploring what the guide covers before searching.',
|
||||
{},
|
||||
{
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
openWorldHint: false,
|
||||
},
|
||||
async () => {
|
||||
const ref = loadReference();
|
||||
|
||||
// Group entries by first key segment (before first underscore)
|
||||
const categories = new Map<string, { count: number; samples: string[] }>();
|
||||
|
||||
for (const entry of ref.entries) {
|
||||
const category = entry.key.split('_')[0] ?? entry.key;
|
||||
if (!categories.has(category)) {
|
||||
categories.set(category, { count: 0, samples: [] });
|
||||
}
|
||||
const cat = categories.get(category)!;
|
||||
cat.count++;
|
||||
if (cat.samples.length < 5) {
|
||||
cat.samples.push(entry.key);
|
||||
}
|
||||
}
|
||||
|
||||
const sorted = Array.from(categories.entries()).sort((a, b) => b[1].count - a[1].count);
|
||||
|
||||
const lines: string[] = [
|
||||
`Claude Code Ultimate Guide — ${ref.entries.length} indexed entries across ${sorted.length} categories`,
|
||||
`Version: ${ref.version}`,
|
||||
'',
|
||||
'## Topics',
|
||||
'',
|
||||
];
|
||||
|
||||
for (const [category, { count, samples }] of sorted) {
|
||||
lines.push(`### ${category} (${count} entries)`);
|
||||
lines.push(`Examples: ${samples.join(', ')}`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push('Use search_guide("topic") to search within any category.');
|
||||
lines.push('Use read_section("guide/ultimate-guide.md") to browse the full guide.');
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: lines.join('\n') }],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
79
mcp-server/src/tools/read-section.ts
Normal file
79
mcp-server/src/tools/read-section.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { readSection } from '../lib/section-reader.js';
|
||||
import { resolveContentPath } from '../lib/content.js';
|
||||
import { formatLinks } from '../lib/urls.js';
|
||||
|
||||
export function registerReadSection(server: McpServer): void {
|
||||
server.tool(
|
||||
'read_section',
|
||||
'Read a section from a guide file (markdown, YAML, examples). Supports pagination via offset. Use after search_guide() to fetch the full content at a specific location.',
|
||||
{
|
||||
path: z.string().describe('Relative path from repo root (e.g. "guide/ultimate-guide.md", "examples/agents/code-reviewer.md")'),
|
||||
offset: z.number().min(1).optional().default(1).describe('Line number to start reading from (1-based, default 1)'),
|
||||
limit: z.number().min(1).max(500).optional().default(500).describe('Max lines to return (default 500, max 500)'),
|
||||
},
|
||||
{
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
openWorldHint: false,
|
||||
},
|
||||
async ({ path: filePath, offset, limit }) => {
|
||||
// Security: validate path before attempting read
|
||||
const resolved = resolveContentPath(filePath);
|
||||
if (resolved === null) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: Invalid path "${filePath}". Only paths within the guide repo with allowed extensions (.md, .yaml, .yml, .sh, .ts, .js, .json, .py, .txt) are permitted.`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await readSection(filePath, offset ?? 1, limit ?? 500);
|
||||
|
||||
if (result === null) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: [
|
||||
`File not found or unavailable: "${filePath}"`,
|
||||
'',
|
||||
'This may be because:',
|
||||
' • The file does not exist in the guide repo',
|
||||
' • Network unavailable (running offline without cache)',
|
||||
'',
|
||||
'Fallback: read the resource claude-code-guide://reference for inline summaries.',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const header = [
|
||||
`File: ${filePath}`,
|
||||
`Lines: ${result.startLine}-${result.endLine} of ${result.totalLines}`,
|
||||
result.hasMore
|
||||
? `Has more: yes — use offset=${result.nextOffset} for next section`
|
||||
: 'Has more: no',
|
||||
formatLinks(filePath, result.startLine),
|
||||
'---',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: header + result.content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
82
mcp-server/src/tools/releases.ts
Normal file
82
mcp-server/src/tools/releases.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadReleases } from '../lib/content.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
const RELEASES_GITHUB = githubUrl('guide/claude-code-releases.md');
|
||||
|
||||
interface ReleaseEntry {
|
||||
version: string;
|
||||
date: string;
|
||||
highlights: string[];
|
||||
breaking?: string[];
|
||||
}
|
||||
|
||||
export function registerReleases(server: McpServer): void {
|
||||
server.tool(
|
||||
'get_release',
|
||||
'Get details about Claude Code CLI official releases. Pass a version to get a specific release, or omit to get the latest and recent history.',
|
||||
{
|
||||
version: z.string().optional().describe('Specific version (e.g. "2.1.59"). Omit for latest + recent 5.'),
|
||||
count: z.number().min(1).max(30).optional().default(5).describe('Number of recent releases to show when no version specified (default 5)'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ version, count }) => {
|
||||
const data = loadReleases();
|
||||
const releases = data.releases as ReleaseEntry[];
|
||||
|
||||
if (version) {
|
||||
const found = releases.find(
|
||||
(r) => r.version === version || r.version === version.replace(/^v/, ''),
|
||||
);
|
||||
if (!found) {
|
||||
const versions = releases.slice(0, 10).map((r) => `v${r.version}`).join(', ');
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: `Release v${version} not found.\n\nRecent versions: ${versions}\n\nFull history: ${RELEASES_GITHUB}`,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`# Claude Code v${found.version} (${found.date})`,
|
||||
RELEASES_GITHUB,
|
||||
'',
|
||||
'## Highlights',
|
||||
...(found.highlights ?? []).map((h) => `- ${h}`),
|
||||
];
|
||||
|
||||
if (found.breaking?.length) {
|
||||
lines.push('', '## Breaking changes');
|
||||
for (const b of found.breaking) lines.push(`- ⚠️ ${b}`);
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
// Latest + recent N
|
||||
const recent = releases.slice(0, count ?? 5);
|
||||
const lines = [
|
||||
`# Claude Code Releases`,
|
||||
`Latest: v${data.latest} (updated: ${data.updated})`,
|
||||
RELEASES_GITHUB,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const r of recent) {
|
||||
lines.push(`## v${r.version} — ${r.date}`);
|
||||
for (const h of r.highlights ?? []) lines.push(`- ${h}`);
|
||||
if (r.breaking?.length) {
|
||||
for (const b of r.breaking) lines.push(` ⚠️ ${b}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push(`---`);
|
||||
lines.push(`Showing ${recent.length} of ${releases.length} tracked releases. Use get_release(version) for details.`);
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
133
mcp-server/src/tools/search-examples.ts
Normal file
133
mcp-server/src/tools/search-examples.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadReference, resolveDeepDive } from '../lib/content.js';
|
||||
import { tokenizeQuery } from '../lib/search.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
interface ExampleResult {
|
||||
path: string;
|
||||
key: string;
|
||||
score: number;
|
||||
description: string;
|
||||
githubUrl: string;
|
||||
}
|
||||
|
||||
export function registerSearchExamples(server: McpServer): void {
|
||||
server.tool(
|
||||
'search_examples',
|
||||
'Semantic search across all production-ready templates by intent (e.g. "hook lint", "agent code review"). Different from get_example (exact name) and list_examples (category browse).',
|
||||
{
|
||||
query: z.string().describe('Search query describing the template you need, e.g. "hook lint", "agent code review", "pre-commit typescript"'),
|
||||
limit: z.number().min(1).max(20).optional().default(10).describe('Max results to return (default 10, max 20)'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ query, limit }) => {
|
||||
const ref = loadReference();
|
||||
const tokens = tokenizeQuery(query);
|
||||
const queryLower = query.toLowerCase();
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Query too short or contained only stop words. Try: "hook lint", "agent security", "pre-commit".',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// Collect and score entries pointing to examples/
|
||||
const results: ExampleResult[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const entry of ref.entries) {
|
||||
const target = entry.target ?? resolveDeepDive(entry.value);
|
||||
if (!target || target.type !== 'file') continue;
|
||||
if (!target.path.startsWith('examples/')) continue;
|
||||
|
||||
// Deduplicate by path
|
||||
if (seen.has(target.path)) continue;
|
||||
|
||||
const pathLower = target.path.toLowerCase();
|
||||
const keyLower = entry.key.toLowerCase();
|
||||
const textLower = entry.searchableText.toLowerCase();
|
||||
|
||||
let score = 0;
|
||||
|
||||
// Exact substring match on full query (highest signal)
|
||||
if (pathLower.includes(queryLower)) score += 15;
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.length < 2) continue;
|
||||
if (pathLower.includes(token)) score += 10;
|
||||
else if (keyLower.includes(token)) score += 7;
|
||||
else if (textLower.includes(token)) score += 5;
|
||||
else {
|
||||
// Fuzzy: token length >= 5, levenshtein-like (segment starts with token)
|
||||
if (token.length >= 5) {
|
||||
const pathSegments = pathLower.replace(/[/_.-]/g, ' ').split(' ');
|
||||
for (const seg of pathSegments) {
|
||||
if (seg.length >= 4 && (seg.startsWith(token.slice(0, -1)) || token.startsWith(seg.slice(0, -1)))) {
|
||||
score += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
// Build human-readable description from key
|
||||
const description = entry.searchableText.slice(0, 120).replace(/\s+/g, ' ').trim();
|
||||
|
||||
seen.add(target.path);
|
||||
results.push({
|
||||
path: target.path,
|
||||
key: entry.key,
|
||||
score,
|
||||
description,
|
||||
githubUrl: githubUrl(target.path),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
results.sort((a, b) => b.score - a.score);
|
||||
const topResults = results.slice(0, limit ?? 10);
|
||||
|
||||
if (topResults.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: [
|
||||
`No examples found for: "${query}"`,
|
||||
'',
|
||||
'Try broader terms or browse by category:',
|
||||
' • list_examples("agents") — custom agent templates',
|
||||
' • list_examples("hooks") — event hook examples',
|
||||
' • list_examples("commands") — slash command templates',
|
||||
' • list_examples("skills") — skill module templates',
|
||||
' • list_examples("scripts") — utility scripts',
|
||||
].join('\n'),
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`# Examples matching "${query}"`,
|
||||
`Found ${topResults.length} result${topResults.length !== 1 ? 's' : ''} (of ${results.length} scored)`,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const r of topResults) {
|
||||
const name = r.path.split('/').pop()?.replace(/\.(md|yaml|sh|ts|js|py)$/, '') ?? r.key;
|
||||
lines.push(`## ${r.path}`);
|
||||
lines.push(`Score: ${r.score} | Key: ${r.key}`);
|
||||
lines.push(r.description);
|
||||
lines.push(`GitHub: ${r.githubUrl}`);
|
||||
lines.push(`→ get_example("${name}")`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
},
|
||||
);
|
||||
}
|
||||
86
mcp-server/src/tools/search-guide.ts
Normal file
86
mcp-server/src/tools/search-guide.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { searchGuide } from '../lib/search.js';
|
||||
import { formatLinks, githubUrl } from '../lib/urls.js';
|
||||
|
||||
export function registerSearchGuide(server: McpServer): void {
|
||||
server.tool(
|
||||
'search_guide',
|
||||
'Search the Claude Code Ultimate Guide by topic, keyword, or question. Returns ranked results with file locations and descriptions. Use this as the first step for any guide question.',
|
||||
{
|
||||
query: z.string().describe('Search query — topic, question, or keyword (e.g. "hooks", "custom agents", "cost optimization")'),
|
||||
limit: z.number().min(1).max(20).optional().default(10).describe('Max results to return (default 10, max 20)'),
|
||||
},
|
||||
{
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
openWorldHint: false,
|
||||
},
|
||||
async ({ query, limit }) => {
|
||||
const results = searchGuide(query, limit ?? 10);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: [
|
||||
`No results found for: "${query}"`,
|
||||
'',
|
||||
'Tips:',
|
||||
' • Try shorter keywords: "hooks" instead of "how do I use hooks"',
|
||||
' • Use the resource fallback: read claude-code-guide://reference for the full YAML index',
|
||||
' • Try related terms: "commands", "agents", "mcp", "security", "cost"',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const lines: string[] = [
|
||||
`Found ${results.length} result(s) for: "${query}"`,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const result of results) {
|
||||
lines.push(`## ${result.key} (score: ${result.score})`);
|
||||
lines.push(`Section: ${result.section}`);
|
||||
lines.push(`Location: ${result.hint}`);
|
||||
|
||||
if (result.target) {
|
||||
switch (result.target.type) {
|
||||
case 'line':
|
||||
lines.push(`→ read_section("${result.target.file}", ${result.target.line})`);
|
||||
lines.push(` ${formatLinks(result.target.file, result.target.line)}`);
|
||||
break;
|
||||
case 'file':
|
||||
lines.push(
|
||||
`→ read_section("${result.target.path}"${result.target.line ? `, ${result.target.line}` : ''})`,
|
||||
);
|
||||
lines.push(` ${formatLinks(result.target.path, result.target.line)}`);
|
||||
break;
|
||||
case 'url':
|
||||
lines.push(`→ External: ${result.target.url}`);
|
||||
break;
|
||||
case 'inline':
|
||||
lines.push(`→ ${result.target.text.slice(0, 200)}${result.target.text.length > 200 ? '…' : ''}`);
|
||||
break;
|
||||
case 'structured':
|
||||
lines.push(`→ [structured data — use read_section or view resource]`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push('Use read_section(path, line) to fetch the full content of any result.');
|
||||
lines.push('Use claude-code-guide://reference resource for the complete YAML index.');
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: lines.join('\n') }],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
264
mcp-server/src/tools/threats.ts
Normal file
264
mcp-server/src/tools/threats.ts
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { loadThreatDb } from '../lib/content.js';
|
||||
import { githubUrl } from '../lib/urls.js';
|
||||
|
||||
const THREAT_DB_GITHUB = githubUrl('examples/commands/resources/threat-db.yaml');
|
||||
|
||||
interface CveEntry {
|
||||
id: string;
|
||||
component: string;
|
||||
severity: string;
|
||||
cvss?: number;
|
||||
description: string;
|
||||
source: string;
|
||||
fixed_in?: string;
|
||||
mitigation?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
interface TechniqueEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
examples?: string[];
|
||||
campaigns?: string[];
|
||||
cves?: string[];
|
||||
mitigation?: string;
|
||||
}
|
||||
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
cves: 'CVE Database',
|
||||
authors: 'Malicious Authors',
|
||||
skills: 'Malicious Skills',
|
||||
techniques: 'Attack Techniques',
|
||||
mitigations: 'Minimum Safe Versions',
|
||||
sources: 'Research Sources',
|
||||
};
|
||||
|
||||
export function registerGetThreat(server: McpServer): void {
|
||||
server.tool(
|
||||
'get_threat',
|
||||
'Look up a specific threat by ID from the security threat database. Supports CVE IDs (e.g. "CVE-2025-53109") and technique IDs (e.g. "T001").',
|
||||
{
|
||||
id: z.string().describe('Threat ID: a CVE identifier (e.g. "CVE-2025-53109") or attack technique ID (e.g. "T001")'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ id }) => {
|
||||
const db = await loadThreatDb();
|
||||
const idUpper = id.toUpperCase();
|
||||
|
||||
// Search CVE database
|
||||
const cve = (db.cve_database as CveEntry[]).find(
|
||||
(c) => c.id.toUpperCase() === idUpper,
|
||||
);
|
||||
if (cve) {
|
||||
const lines = [
|
||||
`# ${cve.id} — ${cve.component}`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
`**Severity**: ${cve.severity.toUpperCase()}${cve.cvss ? ` (CVSS ${cve.cvss})` : ''}`,
|
||||
`**Component**: ${cve.component}`,
|
||||
`**Source**: ${cve.source}`,
|
||||
'',
|
||||
`## Description`,
|
||||
cve.description,
|
||||
];
|
||||
|
||||
if (cve.fixed_in) {
|
||||
lines.push('', `**Fixed in**: ${cve.fixed_in}`);
|
||||
}
|
||||
if (cve.mitigation) {
|
||||
lines.push('', `## Mitigation`, cve.mitigation);
|
||||
}
|
||||
if (cve.notes) {
|
||||
lines.push('', `## Notes`, cve.notes);
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
// Search attack techniques
|
||||
const technique = (db.attack_techniques as TechniqueEntry[]).find(
|
||||
(t) => t.id.toUpperCase() === idUpper,
|
||||
);
|
||||
if (technique) {
|
||||
const lines = [
|
||||
`# ${technique.id} — ${technique.name}`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
`## Description`,
|
||||
technique.description,
|
||||
];
|
||||
|
||||
if (technique.examples?.length) {
|
||||
lines.push('', '## Examples');
|
||||
for (const ex of technique.examples) lines.push(`- ${ex}`);
|
||||
}
|
||||
if (technique.campaigns?.length) {
|
||||
lines.push('', `**Campaigns**: ${technique.campaigns.join(', ')}`);
|
||||
}
|
||||
if (technique.cves?.length) {
|
||||
lines.push(`**Related CVEs**: ${technique.cves.join(', ')}`);
|
||||
}
|
||||
if (technique.mitigation) {
|
||||
lines.push('', `## Mitigation`, technique.mitigation);
|
||||
}
|
||||
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: [
|
||||
`Threat ID "${id}" not found in the database.`,
|
||||
'',
|
||||
'Supported formats:',
|
||||
' • CVE IDs: CVE-2025-53109, CVE-2026-24052, ...',
|
||||
' • Technique IDs: T001, T002, ...',
|
||||
'',
|
||||
`Use list_threats("cves") or list_threats("techniques") to browse all entries.`,
|
||||
`Full database: ${THREAT_DB_GITHUB}`,
|
||||
].join('\n'),
|
||||
}],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function registerListThreats(server: McpServer): void {
|
||||
server.tool(
|
||||
'list_threats',
|
||||
'Browse the security threat database. Without a category, returns a summary with counts. With a category, returns the full list for that section.',
|
||||
{
|
||||
category: z.enum(['cves', 'authors', 'skills', 'techniques', 'mitigations', 'sources'])
|
||||
.optional()
|
||||
.describe('Section to list: cves | authors | skills | techniques | mitigations | sources. Omit for a global summary.'),
|
||||
},
|
||||
{ readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
||||
async ({ category }) => {
|
||||
const db = await loadThreatDb();
|
||||
|
||||
if (!category) {
|
||||
// Global summary
|
||||
const lines = [
|
||||
`# Threat Database Summary`,
|
||||
`Version ${db.version} — updated ${db.updated}`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
'| Category | Count |',
|
||||
'|----------|-------|',
|
||||
`| CVEs | ${db.cve_database.length} |`,
|
||||
`| Malicious Authors | ${db.malicious_authors.length} |`,
|
||||
`| Malicious Skills | ${db.malicious_skills.length} |`,
|
||||
`| Attack Techniques | ${db.attack_techniques.length} |`,
|
||||
`| Minimum Safe Versions | ${Object.keys(db.minimum_safe_versions).length} |`,
|
||||
`| Research Sources | ${db.sources.length} |`,
|
||||
'',
|
||||
'Use list_threats(category) to browse a section, or get_threat(id) for details.',
|
||||
'Categories: cves | authors | skills | techniques | mitigations | sources',
|
||||
];
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
const label = CATEGORY_LABELS[category] ?? category;
|
||||
|
||||
if (category === 'cves') {
|
||||
const cves = db.cve_database as CveEntry[];
|
||||
const lines = [
|
||||
`# ${label} (${cves.length} entries)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
];
|
||||
for (const c of cves) {
|
||||
lines.push(`## ${c.id} — ${c.component}`);
|
||||
lines.push(`**Severity**: ${c.severity.toUpperCase()}${c.cvss ? ` (CVSS ${c.cvss})` : ''} | **Source**: ${c.source}`);
|
||||
lines.push(c.description);
|
||||
if (c.fixed_in) lines.push(`Fixed in: ${c.fixed_in}`);
|
||||
if (c.mitigation) lines.push(`Mitigation: ${c.mitigation}`);
|
||||
lines.push('');
|
||||
}
|
||||
lines.push(`---\nUse get_threat("CVE-XXXX-XXXXX") for full details on any CVE.`);
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
if (category === 'techniques') {
|
||||
const techniques = db.attack_techniques as TechniqueEntry[];
|
||||
const lines = [
|
||||
`# ${label} (${techniques.length} entries)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
];
|
||||
for (const t of techniques) {
|
||||
lines.push(`## ${t.id} — ${t.name}`);
|
||||
lines.push(t.description);
|
||||
if (t.mitigation) lines.push(`Mitigation: ${t.mitigation}`);
|
||||
lines.push('');
|
||||
}
|
||||
lines.push(`---\nUse get_threat("T001") for full details including examples and CVE links.`);
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
if (category === 'authors') {
|
||||
const authors = db.malicious_authors as Array<{ name: string; source?: string; notes?: string }>;
|
||||
const lines = [
|
||||
`# ${label} (${authors.length} confirmed)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
'Block ALL skills from these authors — confirmed malicious by security researchers.',
|
||||
'',
|
||||
];
|
||||
for (const a of authors) {
|
||||
lines.push(`- **${a.name}**${a.source ? ` — ${a.source}` : ''}${a.notes ? ` (${a.notes})` : ''}`);
|
||||
}
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
if (category === 'skills') {
|
||||
const skills = db.malicious_skills as Array<{ name: string; type?: string; source?: string; risk?: string; notes?: string }>;
|
||||
const lines = [
|
||||
`# ${label} (${skills.length} entries)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
];
|
||||
for (const s of skills) {
|
||||
const tags = [s.type, s.risk ? `risk:${s.risk}` : undefined].filter(Boolean).join(', ');
|
||||
lines.push(`- **${s.name}**${tags ? ` [${tags}]` : ''}${s.source ? ` — ${s.source}` : ''}${s.notes ? ` (${s.notes})` : ''}`);
|
||||
}
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
if (category === 'mitigations') {
|
||||
const versions = db.minimum_safe_versions;
|
||||
const entries = Object.entries(versions);
|
||||
const lines = [
|
||||
`# ${label} (${entries.length} entries)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
];
|
||||
for (const [component, minVersion] of entries) {
|
||||
lines.push(`- **${component}**: >= ${minVersion}`);
|
||||
}
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
if (category === 'sources') {
|
||||
const sources = db.sources as Array<{ name: string; url?: string; date?: string }>;
|
||||
const lines = [
|
||||
`# ${label} (${sources.length} entries)`,
|
||||
THREAT_DB_GITHUB,
|
||||
'',
|
||||
];
|
||||
for (const s of sources) {
|
||||
lines.push(`- **${s.name}**${s.date ? ` (${s.date})` : ''}${s.url ? `\n ${s.url}` : ''}`);
|
||||
}
|
||||
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Unknown category: "${category}". Use: cves | authors | skills | techniques | mitigations | sources` }],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
19
mcp-server/tsconfig.json
Normal file
19
mcp-server/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
mcp-server/tsup.config.ts
Normal file
14
mcp-server/tsup.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
target: 'es2022',
|
||||
outDir: 'dist',
|
||||
clean: true,
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node',
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue