From 261739a6c2f6d3f876294472abae773056b33176 Mon Sep 17 00:00:00 2001 From: nirholas Date: Tue, 31 Mar 2026 10:16:23 +0000 Subject: [PATCH] docs: add skill and agent operating guides for repository conventions --- .github/agents/agent.agent.md | 132 +++++++ .github/copilot-instructions.md | 65 ++++ .vscode/mcp.json | 12 + README.md | 28 ++ Skill.md | 218 +++++++++++ agent.md | 33 ++ gitpretty-apply.sh | 41 ++ mcp-server/README.md | 119 ++++++ mcp-server/package.json | 22 ++ mcp-server/src/index.ts | 656 ++++++++++++++++++++++++++++++++ mcp-server/tsconfig.json | 19 + 11 files changed, 1345 insertions(+) create mode 100644 .github/agents/agent.agent.md create mode 100644 .github/copilot-instructions.md create mode 100644 .vscode/mcp.json create mode 100644 Skill.md create mode 100644 agent.md create mode 100644 gitpretty-apply.sh create mode 100644 mcp-server/README.md create mode 100644 mcp-server/package.json create mode 100644 mcp-server/src/index.ts create mode 100644 mcp-server/tsconfig.json diff --git a/.github/agents/agent.agent.md b/.github/agents/agent.agent.md new file mode 100644 index 0000000..9944ce3 --- /dev/null +++ b/.github/agents/agent.agent.md @@ -0,0 +1,132 @@ +--- +description: "Use when: developing, debugging, refactoring, reviewing, exploring, or explaining code in the Claude Code CLI codebase. Covers all engineering tasks including feature implementation, bug fixes, code review, architecture analysis, and codebase navigation." +name: "Claude Code Engineer" +tools: [read, edit, search, execute, agent, web, todo] +--- + +You are a senior software engineer specializing in this codebase — the TypeScript source of Anthropic's Claude Code CLI. You have deep knowledge of its architecture, conventions, and patterns. + +## Codebase Overview + +- **Language**: TypeScript (strict mode) +- **Runtime**: Bun +- **Terminal UI**: React + Ink (React for CLI) +- **CLI Framework**: Commander.js +- **Validation**: Zod (`zod/v4`) +- **Module Format**: ESM with `.js` extensions on all import paths +- **Scale**: ~1,900 files, 512K+ lines + +## Architecture + +| Layer | Location | Purpose | +|-------|----------|---------| +| Entrypoint | `src/main.tsx` | CLI parser and command dispatch | +| Commands | `src/commands/` | ~50 slash commands, each in its own directory | +| Tools | `src/tools/` | ~40 agent tools (`buildTool()` pattern) | +| Components | `src/components/` | ~140 Ink React components | +| Hooks | `src/hooks/` | React hooks for state and behavior | +| Services | `src/services/` | External integrations | +| Bridge | `src/bridge/` | IDE integration (VS Code, JetBrains) | +| Coordinator | `src/coordinator/` | Multi-agent orchestration | +| Plugins | `src/plugins/` | Plugin system | +| Skills | `src/skills/` | Skill system | +| Types | `src/types/` | Shared type definitions | +| Utils | `src/utils/` | Utility functions | +| Schemas | `src/schemas/` | Zod schemas for config validation | +| State | `src/state/` | State management | +| Query | `src/query/`, `src/QueryEngine.ts` | LLM query pipeline and API caller | +| Context | `src/context/`, `src/context.ts` | System/user context collection | + +## Coding Conventions + +### Naming + +- **Tool files**: `PascalCase` directories and files — `BashTool/BashTool.ts` +- **Components**: `PascalCase.tsx` — `Spinner.tsx`, `MessageResponse.tsx` +- **Utilities**: `camelCase.ts` — `claudemd.ts`, `gitSettings.ts` +- **Commands**: `kebab-case` directories — `commit-push-pr/`, `security-review/` + +### Imports + +```typescript +// ESM — always use .js extension, even for .ts/.tsx source files +import { Item } from './file.js' +import type { TypeName } from './types.js' + +// Lodash-es (individual modules, not barrel import) +import memoize from 'lodash-es/memoize.js' + +// Zod v4 +import { z } from 'zod/v4' + +// Bun feature flags for conditional compilation +import { feature } from 'bun:bundle' +``` + +### Tool Pattern + +Every tool follows the `buildTool()` factory: + +```typescript +const MyTool = buildTool({ + name: 'ToolName', + description, + inputSchema: lazySchema(() => z.object({ /* ... */ })), + outputSchema: lazySchema(() => z.object({ /* ... */ })), + async execute(input, context) { /* ... */ }, + async checkPermissions(input, context) { /* ... */ }, + getPath?(input) { /* ... */ }, + isReadOnly() { /* ... */ }, + isConcurrencySafe() { /* ... */ }, +}) +``` + +### Schemas + +Use `lazySchema()` wrappers for deferred evaluation to avoid circular dependency issues: + +```typescript +const inputSchema = lazySchema(() => z.strictObject({ + path: z.string(), + content: z.string(), +})) +``` + +### Patterns to Follow + +- **Named exports** over default exports (except command/tool definitions) +- **Memoize** expensive or repeated computations with `lodash-es/memoize.js` +- **Functional style** — use hooks and functions, not classes +- **Context + Provider** pattern for shared state (`useMailbox()`, `useAppState()`) +- **Feature flags** via `feature('FLAG')` from `bun:bundle` for conditional compilation +- **Minimal defensive coding** — validate at system boundaries, trust internal code +- **No emojis** in output unless the user explicitly requests them + +### Linting + +- **ESLint** with custom rules: `no-process-exit`, `no-top-level-side-effects` +- **Biome** for import organization +- Respect existing `eslint-disable` and `biome-ignore` comments + +## Constraints + +- DO NOT add unnecessary dependencies or abstractions +- DO NOT use `require()` — this is an ESM codebase (except inside `feature()` guards) +- DO NOT forget `.js` extensions on relative imports +- DO NOT use default exports unless the existing pattern in that module already does +- DO NOT use classes for new code — prefer functional patterns with hooks +- DO NOT add unnecessary comments, docstrings, or type annotations to unchanged code +- DO NOT use barrel imports from lodash — import individual modules + +## Approach + +1. **Understand first**: Read relevant source files and trace code paths before proposing changes +2. **Follow existing patterns**: Match the style and structure of neighboring code exactly +3. **Minimal changes**: Only modify what is necessary — no drive-by refactors +4. **Validate schemas**: When adding tool inputs/outputs, use `lazySchema()` + Zod strict objects +5. **Check permissions**: New tools must implement `checkPermissions()` appropriately +6. **Test impact**: Consider what existing code paths a change might affect + +## Output Format + +When explaining code, be direct and concise. Reference specific files and line numbers. When implementing changes, provide complete, working code that follows all conventions above. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..5a675d8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,65 @@ +# Claude Code CLI — Agent Instructions + +## What This Is + +This is the **leaked source code** of Anthropic's Claude Code CLI (leaked 2026-03-31). It is a read-only reference codebase — there is no build system, no tests, and no package.json included. Treat it as a study/exploration resource. + +- **Language**: TypeScript / TSX +- **Runtime**: Bun (not Node.js) +- **Terminal UI**: React + Ink (React for CLI) +- **Scale**: ~1,900 files, 512,000+ lines across `src/` + +## Architecture + +### Core Pipeline + +1. **Entrypoint** — `src/main.tsx`: Commander.js CLI parser, service initialization, lazy feature loading via Bun feature flags +2. **Query Engine** — `src/QueryEngine.ts` (~46K lines): LLM API call handler — streaming, tool loops, thinking mode, retries, token counting +3. **Tool System** — `src/Tool.ts` (~29K lines) + `src/tools/`: ~50 tools (BashTool, FileEditTool, AgentTool, etc.), each self-contained with input schema, permissions, and execution logic +4. **Command System** — `src/commands.ts` (~25K lines) + `src/commands/`: ~50 slash commands (`/commit`, `/review`, `/config`, etc.) +5. **Context** — `src/context.ts`: Collects OS, shell, git, and user context for prompt construction + +### Key Subsystems + +| Directory | Purpose | +|-----------|---------| +| `src/bridge/` | IDE integration layer (VS Code, JetBrains) — bidirectional messaging, JWT auth, session management | +| `src/coordinator/` | Multi-agent orchestration | +| `src/services/` | External integrations — Anthropic API, MCP, OAuth, LSP, analytics, plugins | +| `src/hooks/` | React hooks including `toolPermission/` for per-tool permission checks | +| `src/components/` | ~140 Ink UI components | +| `src/plugins/` | Plugin system | +| `src/skills/` | Skill system | +| `src/tasks/` | Task management | +| `src/types/` | Centralized TypeScript type definitions | +| `src/utils/` | Shared utilities | +| `src/schemas/` | Zod-based config schemas | +| `src/memdir/` | Persistent memory directory | +| `src/voice/` | Voice input | +| `src/vim/` | Vim mode | +| `src/buddy/` | Companion sprite (Easter egg) | + +### Feature Flags (Dead Code Elimination) + +Bun strips inactive code at build time via `import { feature } from 'bun:bundle'`. Notable flags: `PROACTIVE`, `KAIROS`, `BRIDGE_MODE`, `DAEMON`, `VOICE_MODE`, `AGENT_TRIGGERS`, `MONITOR_TOOL`, `COORDINATOR_MODE`. + +## Code Style & Conventions + +- **Imports**: ESM with explicit `.js` extensions (e.g., `from './commands.js'`) +- **Files/Dirs**: kebab-case (`commit-push-pr.ts`, `add-dir/`) +- **Classes/Components**: PascalCase (`BashTool`, `QueryEngine`) +- **Functions**: camelCase (`getCommands()`, `getAllTools()`) +- **Constants**: UPPER_SNAKE_CASE (`ALLOWED_TOOLS`, `FRAME_INTERVAL_MS`) +- **Linter**: Biome (not ESLint). Look for `biome-ignore` directives +- **Ant-only code**: Some imports/features are gated behind `process.env.USER_TYPE === 'ant'` (Anthropic internal) +- **Index pattern**: Features export via `index.ts` files + +## Navigation Tips + +- **Find a tool**: Look in `src/tools//` — each tool is a directory with its implementation, UI, and permissions co-located +- **Find a command**: Look in `src/commands//` or `src/commands/.ts` +- **Understand permissions**: Start at `src/hooks/toolPermission/` +- **Trace an API call**: Start at `src/QueryEngine.ts` → `src/services/api/` +- **Understand types**: Centralized in `src/types/`, tool-specific types in `src/Tool.ts` +- **Follow the bridge**: IDE integration starts at `src/bridge/bridgeMain.ts` +- **MCP integration**: `src/services/mcp/` diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..e7db7f2 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,12 @@ +{ + "servers": { + "claude-code-explorer": { + "type": "stdio", + "command": "node", + "args": ["${workspaceFolder}/mcp-server/dist/index.js"], + "env": { + "CLAUDE_CODE_SRC_ROOT": "${workspaceFolder}/src" + } + } + } +} diff --git a/README.md b/README.md index 1f47c1f..718f97b 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,34 @@ Built-in and third-party plugins are loaded through the `plugins/` subsystem. --- +## GitPretty Setup (Per-File Pretty Commits) + +If you want GitHub's file UI to show visually distinct commit messages per file, use the helper script in this repo: + +```bash +bash ./gitpretty-apply.sh . +``` + +This will: + +1. Clone `gitpretty` into `~/.gitpretty` (first run only) +2. Make scripts executable +3. Run `emoji-file-commits.sh` against this repo + +Optional: install auto-emoji hooks for future commits: + +```bash +bash ./gitpretty-apply.sh . --hooks +``` + +After running, push as usual: + +```bash +git push origin main +``` + +--- + ## Disclaimer This repository archives source code that was leaked from Anthropic's npm registry on **2026-03-31**. All original source code is the property of [Anthropic](https://www.anthropic.com). Contact [nichxbt](https://www.x.com/nichxbt) for any comments. diff --git a/Skill.md b/Skill.md new file mode 100644 index 0000000..5fc7637 --- /dev/null +++ b/Skill.md @@ -0,0 +1,218 @@ +--- +name: claude-code-skill +description: Development conventions and architecture guide for the Claude Code CLI repository. +--- + +# Claude Code — Repository Skill + +## Project Overview + +Claude Code is Anthropic's CLI tool for interacting with Claude from the terminal. It supports file editing, shell commands, git workflows, code review, multi-agent coordination, IDE integration (VS Code, JetBrains), and Model Context Protocol (MCP). + +**Codebase:** ~1,900 files, 512,000+ lines of TypeScript under `src/`. + +## Tech Stack + +| Component | Technology | +|------------------|------------------------------------------------| +| Language | TypeScript (strict mode, ES modules) | +| Runtime | Bun (JSX support, `bun:bundle` feature flags) | +| Terminal UI | React + Ink (React for CLI) | +| CLI Parser | Commander.js (`@commander-js/extra-typings`) | +| API Client | `@anthropic-ai/sdk` | +| Validation | Zod v4 | +| Linter/Formatter | Biome | +| Analytics | GrowthBook (feature flags & A/B testing) | +| Protocol | Model Context Protocol (MCP) | + +## Architecture + +### Directory Map (`src/`) + +| Directory | Purpose | +|------------------|-----------------------------------------------------------------| +| `commands/` | ~50 slash commands (`/commit`, `/review`, `/config`, etc.) | +| `tools/` | ~40 agent tools (Bash, FileRead, FileWrite, Glob, Grep, etc.) | +| `components/` | ~140 Ink/React UI components for terminal rendering | +| `services/` | External integrations (API, OAuth, MCP, LSP, analytics, plugins)| +| `bridge/` | Bidirectional IDE communication layer | +| `state/` | React context + custom store (AppState) | +| `hooks/` | React hooks (permissions, keybindings, commands, settings) | +| `types/` | TypeScript type definitions | +| `utils/` | Utilities (shell, file ops, permissions, config, git) | +| `screens/` | Full-screen UIs (Doctor, REPL, Resume, Compact) | +| `skills/` | Bundled skills + skill loader system | +| `plugins/` | Plugin system (marketplace + bundled plugins) | +| `coordinator/` | Multi-agent coordination & supervisor logic | +| `tasks/` | Task management (shell tasks, agent tasks, teammates) | +| `context/` | React context providers (notifications, stats, FPS) | +| `memdir/` | Persistent memory system (CLAUDE.md, user/project memory) | +| `entrypoints/` | Initialization logic, Agent SDK, MCP entry | +| `voice/` | Voice input/output (STT, keyterms) | +| `vim/` | Vim mode keybinding support | +| `schemas/` | Zod configuration schemas | +| `keybindings/` | Keybinding configuration & resolver | +| `migrations/` | Config migrations between versions | +| `outputStyles/` | Output formatting & theming | +| `query/` | Query pipeline & processing | +| `server/` | Server/daemon mode | +| `remote/` | Remote session handling | + +### Key Files + +| File | Role | +|---------------------|-----------------------------------------------------| +| `src/main.tsx` | CLI entry point (Commander parser, startup profiling)| +| `src/QueryEngine.ts`| Core LLM API caller (streaming, tool-call loops) | +| `src/Tool.ts` | Tool type definitions & `buildTool` factory | +| `src/tools.ts` | Tool registry & presets | +| `src/commands.ts` | Command registry | +| `src/context.ts` | System/user context collection (git status, memory) | +| `src/cost-tracker.ts`| Token cost tracking | + +### Entry Points & Initialization Sequence + +1. `src/main.tsx` — Commander CLI parser, startup profiling +2. `src/entrypoints/init.ts` — Config, telemetry, OAuth, MDM +3. `src/entrypoints/cli.tsx` — CLI session orchestration +4. `src/entrypoints/mcp.ts` — MCP server mode +5. `src/entrypoints/sdk/` — Agent SDK (programmatic API) +6. `src/replLauncher.tsx` — REPL session launcher + +Startup performs parallel initialization: MDM policy reads, Keychain prefetch, feature flag checks, then core init. + +## Patterns & Conventions + +### Tool Definition + +Each tool lives in `src/tools/{ToolName}/` and uses `buildTool`: + +```typescript +export const MyTool = buildTool({ + name: 'MyTool', + aliases: ['my_tool'], + description: 'What this tool does', + inputSchema: z.object({ + param: z.string(), + }), + async call(args, context, canUseTool, parentMessage, onProgress) { + // Execute and return { data: result, newMessages?: [...] } + }, + async checkPermissions(input, context) { /* Permission checks */ }, + isConcurrencySafe(input) { /* Can run in parallel? */ }, + isReadOnly(input) { /* Non-destructive? */ }, + prompt(options) { /* System prompt injection */ }, + renderToolUseMessage(input, options) { /* UI for invocation */ }, + renderToolResultMessage(content, progressMessages, options) { /* UI for result */ }, +}) +``` + +**Directory structure per tool:** `{ToolName}.ts` or `.tsx` (main), `UI.tsx` (rendering), `prompt.ts` (system prompt), plus utility files. + +### Command Definition + +Commands live in `src/commands/` and follow three types: + +- **PromptCommand** — Sends a formatted prompt with injected tools (most commands) +- **LocalCommand** — Runs in-process, returns text +- **LocalJSXCommand** — Runs in-process, returns React JSX + +```typescript +const command = { + type: 'prompt', + name: 'my-command', + description: 'What this command does', + progressMessage: 'working...', + allowedTools: ['Bash(git *)', 'FileRead(*)'], + source: 'builtin', + async getPromptForCommand(args, context) { + return [{ type: 'text', text: '...' }] + }, +} satisfies Command +``` + +Commands are registered in `src/commands.ts` and invoked via `/command-name` in the REPL. + +### Component Structure + +- Functional React components with Ink primitives (`Box`, `Text`, `useInput()`) +- Styled with Chalk for terminal colors +- React Compiler for optimized re-renders +- Design system primitives in `src/components/design-system/` + +### State Management + +- `AppState` via React context + custom store (`src/state/AppStateStore.ts`) +- Mutable state object passed to tool contexts +- Selector functions for derived state +- Change observers in `src/state/onChangeAppState.ts` + +### Permission System + +- **Modes:** `default` (prompt per operation), `plan` (show plan, ask once), `bypassPermissions` (auto-approve), `auto` (ML classifier) +- **Rules:** Wildcard patterns — `Bash(git *)`, `FileEdit(/src/*)` +- Tools implement `checkPermissions()` returning `{ granted: boolean, reason?, prompt? }` + +### Feature Flags & Build + +Bun's `bun:bundle` feature flags enable dead-code elimination at build time: + +```typescript +import { feature } from 'bun:bundle' +if (feature('PROACTIVE')) { /* proactive agent tools */ } +``` + +Notable flags: `PROACTIVE`, `KAIROS`, `BRIDGE_MODE`, `VOICE_MODE`, `COORDINATOR_MODE`, `DAEMON`, `WORKFLOW_SCRIPTS`. + +Some features are also gated via `process.env.USER_TYPE === 'ant'`. + +## Naming Conventions + +| Element | Convention | Example | +|-------------|---------------------|----------------------------------| +| Files | PascalCase (exports) or kebab-case (commands) | `BashTool.tsx`, `commit-push-pr.ts` | +| Components | PascalCase | `App.tsx`, `PromptInput.tsx` | +| Types | PascalCase, suffix with Props/State/Context | `ToolUseContext` | +| Hooks | `use` prefix | `useCanUseTool`, `useSettings` | +| Constants | SCREAMING_SNAKE_CASE | `MAX_TOKENS`, `DEFAULT_TIMEOUT_MS`| + +## Import Practices + +- ES modules with `.js` extensions (Bun convention) +- Lazy imports for circular dependency breaking: `const getModule = () => require('./heavy.js')` +- Conditional imports via feature flags or `process.env` +- `biome-ignore` markers for manual import ordering where needed + +## Services + +| Service | Path | Purpose | +|--------------------|-------------------------------|-----------------------------------| +| API | `services/api/` | Anthropic SDK client, file uploads| +| MCP | `services/mcp/` | MCP client, tool/resource discovery| +| OAuth | `services/oauth/` | OAuth 2.0 auth flow | +| LSP | `services/lsp/` | Language Server Protocol manager | +| Analytics | `services/analytics/` | GrowthBook, telemetry, events | +| Plugins | `services/plugins/` | Plugin loader, marketplace | +| Compact | `services/compact/` | Context compression | +| Policy Limits | `services/policyLimits/` | Org rate limits, quota checking | +| Remote Settings | `services/remoteManagedSettings/` | Managed settings sync (Enterprise) | +| Token Estimation | `services/tokenEstimation.ts` | Token count estimation | + +## Configuration + +**Settings locations:** +- **Global:** `~/.claude/config.json`, `~/.claude/settings.json` +- **Project:** `.claude/config.json`, `.claude/settings.json` +- **System:** macOS Keychain + MDM, Windows Registry + MDM +- **Managed:** Remote sync for Enterprise users + +## Guidelines + +1. Read relevant source files before making changes — understand existing patterns first. +2. Follow the tool/command/component patterns above when adding new ones. +3. Keep edits minimal and focused — avoid unnecessary refactoring. +4. Use Zod for all input validation at system boundaries. +5. Gate experimental features behind `bun:bundle` feature flags or env checks. +6. Respect the permission system — tools that modify state must implement `checkPermissions()`. +7. Use lazy imports when adding dependencies that could create circular references. +8. Update this file as project conventions evolve. diff --git a/agent.md b/agent.md new file mode 100644 index 0000000..db7811d --- /dev/null +++ b/agent.md @@ -0,0 +1,33 @@ +--- +name: repository-agent +description: Agent operating guide for claude-code. +--- + +# Agent + +## Purpose +Define how an automated coding agent should operate in this repository. + +## Core Rules +- Keep changes small, targeted, and easy to review. +- Preserve existing command behavior unless a task explicitly asks for a behavior change. +- Favor existing patterns in `src/commands/`, `src/tools/`, and shared utility modules. +- Avoid broad refactors while fixing localized issues. + +## Workflow +1. Gather context from relevant files before editing. +2. Implement the smallest viable change. +3. Run focused validation (type checks/tests for changed areas). +4. Summarize what changed and any remaining risks. + +## Code Style +- Match existing TypeScript style and naming in nearby files. +- Prefer explicit, readable logic over compact clever code. +- Add brief comments only when logic is not obvious. + +## Validation +- Prefer targeted checks first, then broader checks if needed. +- If validation cannot run, clearly state what was skipped and why. + +## Notes +- Repository conventions may evolve; update this file when team norms change. \ No newline at end of file diff --git a/gitpretty-apply.sh b/gitpretty-apply.sh new file mode 100644 index 0000000..95ba0ef --- /dev/null +++ b/gitpretty-apply.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Apply gitpretty's per-file beautification so GitHub file history shows +# readable, themed commit messages for each file. + +REPO_PATH="${1:-.}" +INSTALL_HOOKS="${2:-}" +GITPRETTY_HOME="${HOME}/.gitpretty" + +if ! command -v git >/dev/null 2>&1; then + echo "git is required but was not found on PATH" + exit 1 +fi + +if [ ! -d "${REPO_PATH}/.git" ]; then + echo "Target is not a git repository: ${REPO_PATH}" + echo "Usage: $0 [repo-path] [--hooks]" + exit 1 +fi + +if [ ! -d "${GITPRETTY_HOME}" ]; then + echo "Installing gitpretty into ${GITPRETTY_HOME} ..." + git clone https://github.com/nirholas/gitpretty.git "${GITPRETTY_HOME}" +fi + +chmod +x "${GITPRETTY_HOME}"/*.sh "${GITPRETTY_HOME}"/scripts/*.sh + +if [ "${INSTALL_HOOKS}" = "--hooks" ]; then + echo "Installing gitpretty hooks in ${REPO_PATH} ..." + ( + cd "${REPO_PATH}" + "${GITPRETTY_HOME}"/scripts/emoji-hooks.sh install + ) +fi + +echo "Running per-file beautify commits in ${REPO_PATH} ..." +"${GITPRETTY_HOME}"/emoji-file-commits.sh "${REPO_PATH}" + +echo "Done. Review with: git -C ${REPO_PATH} log --oneline -n 20" diff --git a/mcp-server/README.md b/mcp-server/README.md new file mode 100644 index 0000000..53d2212 --- /dev/null +++ b/mcp-server/README.md @@ -0,0 +1,119 @@ +# Claude Code Explorer — MCP Server + +A standalone [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that lets any MCP-compatible client explore the Claude Code source code. + +## What It Does + +Exposes 7 tools and 3 resources for navigating the ~1,900-file, 512K+ line Claude Code codebase: + +### Tools + +| Tool | Description | +|------|-------------| +| `list_tools` | List all 40+ agent tools (BashTool, FileEditTool, etc.) | +| `list_commands` | List all 50+ slash commands (/commit, /review, etc.) | +| `get_tool_source` | Read a specific tool's implementation | +| `get_command_source` | Read a specific command's implementation | +| `read_source_file` | Read any file from `src/` by relative path | +| `search_source` | Regex search across the entire source tree | +| `list_directory` | List contents of any directory under `src/` | +| `get_architecture` | Get a full architecture overview | + +### Resources + +| URI | Description | +|-----|-------------| +| `claude-code://architecture` | README / architecture overview | +| `claude-code://tools` | Tool registry (JSON) | +| `claude-code://commands` | Command registry (JSON) | +| `claude-code://source/{path}` | Any source file (template) | + +## Setup + +```bash +cd mcp-server +npm install +npm run build +``` + +## Configuration + +### Claude Desktop + +Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): + +```json +{ + "mcpServers": { + "claude-code-explorer": { + "command": "node", + "args": ["/absolute/path/to/claude-code/mcp-server/dist/index.js"], + "env": { + "CLAUDE_CODE_SRC_ROOT": "/absolute/path/to/claude-code/src" + } + } + } +} +``` + +### VS Code (GitHub Copilot) + +Add to `.vscode/mcp.json` in your workspace: + +```json +{ + "servers": { + "claude-code-explorer": { + "type": "stdio", + "command": "node", + "args": ["${workspaceFolder}/mcp-server/dist/index.js"], + "env": { + "CLAUDE_CODE_SRC_ROOT": "${workspaceFolder}/src" + } + } + } +} +``` + +### Cursor + +Add to `~/.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "claude-code-explorer": { + "command": "node", + "args": ["/absolute/path/to/claude-code/mcp-server/dist/index.js"], + "env": { + "CLAUDE_CODE_SRC_ROOT": "/absolute/path/to/claude-code/src" + } + } + } +} +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `CLAUDE_CODE_SRC_ROOT` | `../src` (relative to dist/) | Path to the Claude Code `src/` directory | + +## Example Usage + +Once connected, you can ask your AI assistant things like: + +- "List all Claude Code tools" +- "Show me the BashTool implementation" +- "Search for how permissions are checked" +- "What files are in the bridge directory?" +- "Read the QueryEngine.ts file, lines 1-100" +- "How does the MCP client connection work?" + +## Development + +```bash +npm run dev # Watch mode — recompile on changes +npm run build # One-time build +npm start # Run the server +``` diff --git a/mcp-server/package.json b/mcp-server/package.json new file mode 100644 index 0000000..57a8832 --- /dev/null +++ b/mcp-server/package.json @@ -0,0 +1,22 @@ +{ + "name": "claude-code-explorer-mcp", + "version": "1.0.0", + "description": "MCP server for exploring the Claude Code source code", + "type": "module", + "main": "dist/index.js", + "bin": { + "claude-code-explorer-mcp": "dist/index.js" + }, + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts new file mode 100644 index 0000000..7b11767 --- /dev/null +++ b/mcp-server/src/index.ts @@ -0,0 +1,656 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + ListResourcesRequestSchema, + ReadResourceRequestSchema, + ListResourceTemplatesRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const SRC_ROOT = path.resolve( + process.env.CLAUDE_CODE_SRC_ROOT ?? path.join(__dirname, "..", "..", "src") +); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +async function dirExists(p: string): Promise { + try { + return (await fs.stat(p)).isDirectory(); + } catch { + return false; + } +} + +async function fileExists(p: string): Promise { + try { + return (await fs.stat(p)).isFile(); + } catch { + return false; + } +} + +/** List immediate children of a directory (files & dirs). */ +async function listDir(dir: string): Promise { + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + return entries.map((e: { isDirectory(): boolean; name: string }) => (e.isDirectory() ? e.name + "/" : e.name)).sort(); + } catch { + return []; + } +} + +/** Recursively collect all file paths under `root` (relative to root). */ +async function walkFiles(root: string, rel = ""): Promise { + const results: string[] = []; + let entries; + try { + entries = await fs.readdir(path.join(root, rel), { withFileTypes: true }); + } catch { + return results; + } + for (const e of entries) { + const child = rel ? `${rel}/${e.name}` : e.name; + if (e.isDirectory()) { + results.push(...(await walkFiles(root, child))); + } else { + results.push(child); + } + } + return results; +} + +/** Safely resolve a user-supplied relative path under SRC_ROOT. */ +function safePath(relPath: string): string | null { + const resolved = path.resolve(SRC_ROOT, relPath); + if (!resolved.startsWith(SRC_ROOT)) return null; // path traversal blocked + return resolved; +} + +// --------------------------------------------------------------------------- +// Tool & Command Metadata +// --------------------------------------------------------------------------- + +interface ToolInfo { + name: string; + directory: string; + files: string[]; +} + +interface CommandInfo { + name: string; + path: string; + isDirectory: boolean; + files?: string[]; +} + +async function getToolList(): Promise { + const toolsDir = path.join(SRC_ROOT, "tools"); + const entries = await fs.readdir(toolsDir, { withFileTypes: true }); + const tools: ToolInfo[] = []; + + for (const e of entries) { + if (!e.isDirectory() || e.name === "shared" || e.name === "testing") + continue; + const files = await listDir(path.join(toolsDir, e.name)); + tools.push({ name: e.name, directory: `tools/${e.name}`, files }); + } + return tools.sort((a, b) => a.name.localeCompare(b.name)); +} + +async function getCommandList(): Promise { + const cmdsDir = path.join(SRC_ROOT, "commands"); + const entries = await fs.readdir(cmdsDir, { withFileTypes: true }); + const commands: CommandInfo[] = []; + + for (const e of entries) { + if (e.isDirectory()) { + const files = await listDir(path.join(cmdsDir, e.name)); + commands.push({ + name: e.name, + path: `commands/${e.name}`, + isDirectory: true, + files, + }); + } else { + commands.push({ + name: e.name.replace(/\.(ts|tsx)$/, ""), + path: `commands/${e.name}`, + isDirectory: false, + }); + } + } + return commands.sort((a, b) => a.name.localeCompare(b.name)); +} + +// --------------------------------------------------------------------------- +// Server +// --------------------------------------------------------------------------- + +const server = new Server( + { name: "claude-code-explorer", version: "1.0.0" }, + { + capabilities: { + tools: {}, + resources: {}, + }, + } +); + +// ---- Resources ----------------------------------------------------------- + +server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "claude-code://architecture", + name: "Architecture Overview", + description: "High-level overview of the Claude Code source architecture", + mimeType: "text/markdown", + }, + { + uri: "claude-code://tools", + name: "Tool Registry", + description: "List of all agent tools with their files", + mimeType: "application/json", + }, + { + uri: "claude-code://commands", + name: "Command Registry", + description: "List of all slash commands", + mimeType: "application/json", + }, + ], +})); + +server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ + resourceTemplates: [ + { + uriTemplate: "claude-code://source/{path}", + name: "Source file", + description: "Read a source file from the Claude Code src/ directory", + mimeType: "text/plain", + }, + ], +})); + +server.setRequestHandler(ReadResourceRequestSchema, async (request: { params: { uri: string } }) => { + const { uri } = request.params; + + if (uri === "claude-code://architecture") { + const readmePath = path.resolve(SRC_ROOT, "..", "README.md"); + let text: string; + try { + text = await fs.readFile(readmePath, "utf-8"); + } catch { + text = "README.md not found."; + } + return { contents: [{ uri, mimeType: "text/markdown", text }] }; + } + + if (uri === "claude-code://tools") { + const tools = await getToolList(); + return { + contents: [ + { + uri, + mimeType: "application/json", + text: JSON.stringify(tools, null, 2), + }, + ], + }; + } + + if (uri === "claude-code://commands") { + const commands = await getCommandList(); + return { + contents: [ + { + uri, + mimeType: "application/json", + text: JSON.stringify(commands, null, 2), + }, + ], + }; + } + + // Source file template + if (uri.startsWith("claude-code://source/")) { + const relPath = uri.slice("claude-code://source/".length); + const abs = safePath(relPath); + if (!abs) throw new Error("Invalid path"); + const text = await fs.readFile(abs, "utf-8"); + return { contents: [{ uri, mimeType: "text/plain", text }] }; + } + + throw new Error(`Unknown resource: ${uri}`); +}); + +// ---- Tools --------------------------------------------------------------- + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "list_tools", + description: + "List all Claude Code agent tools (BashTool, FileReadTool, etc.) with their source files.", + inputSchema: { type: "object" as const, properties: {} }, + }, + { + name: "list_commands", + description: + "List all Claude Code slash commands (/commit, /review, /mcp, etc.) with their source files.", + inputSchema: { type: "object" as const, properties: {} }, + }, + { + name: "get_tool_source", + description: + "Read the full source code of a specific Claude Code tool implementation. Provide the tool directory name (e.g. 'BashTool', 'FileEditTool').", + inputSchema: { + type: "object" as const, + properties: { + toolName: { + type: "string", + description: + "Tool directory name, e.g. 'BashTool', 'FileReadTool'", + }, + fileName: { + type: "string", + description: + "Optional: specific file within the tool directory. If omitted, returns the main implementation file.", + }, + }, + required: ["toolName"], + }, + }, + { + name: "get_command_source", + description: + "Read the source code of a specific Claude Code slash command. Provide the command name (e.g. 'commit', 'review', 'mcp').", + inputSchema: { + type: "object" as const, + properties: { + commandName: { + type: "string", + description: "Command name, e.g. 'commit', 'review', 'mcp'", + }, + fileName: { + type: "string", + description: + "Optional: specific file within the command directory.", + }, + }, + required: ["commandName"], + }, + }, + { + name: "read_source_file", + description: + "Read any source file from the Claude Code src/ directory by relative path.", + inputSchema: { + type: "object" as const, + properties: { + path: { + type: "string", + description: + "Relative path from src/, e.g. 'QueryEngine.ts', 'services/mcp/types.ts'", + }, + startLine: { + type: "number", + description: "Optional 1-based start line to read from.", + }, + endLine: { + type: "number", + description: "Optional 1-based end line to read to.", + }, + }, + required: ["path"], + }, + }, + { + name: "search_source", + description: + "Search for a pattern (regex or plain text) across the Claude Code source code. Returns matching lines with file paths and line numbers.", + inputSchema: { + type: "object" as const, + properties: { + pattern: { + type: "string", + description: "Search pattern (regex supported).", + }, + filePattern: { + type: "string", + description: + "Optional glob-like filter for file extensions, e.g. '.ts', '.tsx'.", + }, + maxResults: { + type: "number", + description: "Maximum number of matches to return (default: 50).", + }, + }, + required: ["pattern"], + }, + }, + { + name: "list_directory", + description: + "List files and subdirectories within a directory under src/.", + inputSchema: { + type: "object" as const, + properties: { + path: { + type: "string", + description: + "Relative path from src/, e.g. 'services', 'tools/BashTool'. Use '' for the root.", + }, + }, + required: ["path"], + }, + }, + { + name: "get_architecture", + description: + "Get a high-level architecture overview of Claude Code including directory structure, core systems, and key files.", + inputSchema: { type: "object" as const, properties: {} }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request: { params: { name: string; arguments?: Record } }) => { + const { name, arguments: args } = request.params; + + switch (name) { + // ---- list_tools ---- + case "list_tools": { + const tools = await getToolList(); + return { + content: [{ type: "text" as const, text: JSON.stringify(tools, null, 2) }], + }; + } + + // ---- list_commands ---- + case "list_commands": { + const commands = await getCommandList(); + return { + content: [ + { type: "text" as const, text: JSON.stringify(commands, null, 2) }, + ], + }; + } + + // ---- get_tool_source ---- + case "get_tool_source": { + const toolName = (args as Record)?.toolName as string; + if (!toolName) throw new Error("toolName is required"); + const toolDir = safePath(`tools/${toolName}`); + if (!toolDir || !(await dirExists(toolDir))) + throw new Error(`Tool not found: ${toolName}`); + + let fileName = (args as Record)?.fileName as + | string + | undefined; + if (!fileName) { + // Find the main implementation file + const files = await listDir(toolDir); + const main = + files.find( + (f) => f === `${toolName}.ts` || f === `${toolName}.tsx` + ) ?? files.find((f) => f.endsWith(".ts") || f.endsWith(".tsx")); + if (!main) throw new Error(`No source files in ${toolName}`); + fileName = main; + } + + const filePath = safePath(`tools/${toolName}/${fileName}`); + if (!filePath || !(await fileExists(filePath))) + throw new Error(`File not found: tools/${toolName}/${fileName}`); + const content = await fs.readFile(filePath, "utf-8"); + return { + content: [ + { + type: "text" as const, + text: `// tools/${toolName}/${fileName}\n// ${content.split("\n").length} lines\n\n${content}`, + }, + ], + }; + } + + // ---- get_command_source ---- + case "get_command_source": { + const commandName = (args as Record) + ?.commandName as string; + if (!commandName) throw new Error("commandName is required"); + + // Try directory first, then .ts / .tsx + const candidates = [ + `commands/${commandName}`, + `commands/${commandName}.ts`, + `commands/${commandName}.tsx`, + ]; + let found: string | null = null; + let isDir = false; + for (const c of candidates) { + const abs = safePath(c); + if (abs && (await dirExists(abs))) { + found = abs; + isDir = true; + break; + } + if (abs && (await fileExists(abs))) { + found = abs; + break; + } + } + if (!found) throw new Error(`Command not found: ${commandName}`); + + if (!isDir) { + const content = await fs.readFile(found, "utf-8"); + return { + content: [{ type: "text" as const, text: content }], + }; + } + + const reqFile = (args as Record)?.fileName as + | string + | undefined; + if (reqFile) { + const filePath = safePath(`commands/${commandName}/${reqFile}`); + if (!filePath || !(await fileExists(filePath))) + throw new Error( + `File not found: commands/${commandName}/${reqFile}` + ); + const content = await fs.readFile(filePath, "utf-8"); + return { content: [{ type: "text" as const, text: content }] }; + } + + // Return directory listing when no specific file requested + const files = await listDir(found); + return { + content: [ + { + type: "text" as const, + text: `Command: ${commandName}\nFiles:\n${files.map((f) => ` ${f}`).join("\n")}`, + }, + ], + }; + } + + // ---- read_source_file ---- + case "read_source_file": { + const relPath = (args as Record)?.path as string; + if (!relPath) throw new Error("path is required"); + const abs = safePath(relPath); + if (!abs || !(await fileExists(abs))) + throw new Error(`File not found: ${relPath}`); + const content = await fs.readFile(abs, "utf-8"); + const lines = content.split("\n"); + const start = ((args as Record)?.startLine as number) ?? 1; + const end = ((args as Record)?.endLine as number) ?? lines.length; + const slice = lines.slice( + Math.max(0, start - 1), + Math.min(lines.length, end) + ); + return { + content: [ + { + type: "text" as const, + text: slice + .map((l: string, i: number) => `${(start + i).toString().padStart(5)} | ${l}`) + .join("\n"), + }, + ], + }; + } + + // ---- search_source ---- + case "search_source": { + const pattern = (args as Record)?.pattern as string; + if (!pattern) throw new Error("pattern is required"); + const filePattern = (args as Record)?.filePattern as + | string + | undefined; + const maxResults = + ((args as Record)?.maxResults as number) ?? 50; + + let regex: RegExp; + try { + regex = new RegExp(pattern, "i"); + } catch { + throw new Error(`Invalid regex pattern: ${pattern}`); + } + + const allFiles = await walkFiles(SRC_ROOT); + const filtered = filePattern + ? allFiles.filter((f) => f.endsWith(filePattern)) + : allFiles; + + const matches: string[] = []; + for (const file of filtered) { + if (matches.length >= maxResults) break; + const abs = path.join(SRC_ROOT, file); + let content: string; + try { + content = await fs.readFile(abs, "utf-8"); + } catch { + continue; + } + const lines = content.split("\n"); + for (let i = 0; i < lines.length; i++) { + if (matches.length >= maxResults) break; + if (regex.test(lines[i]!)) { + matches.push(`${file}:${i + 1}: ${lines[i]!.trim()}`); + } + } + } + + return { + content: [ + { + type: "text" as const, + text: matches.length > 0 + ? `Found ${matches.length} match(es):\n\n${matches.join("\n")}` + : "No matches found.", + }, + ], + }; + } + + // ---- list_directory ---- + case "list_directory": { + const relPath = ((args as Record)?.path as string) ?? ""; + const abs = safePath(relPath); + if (!abs || !(await dirExists(abs))) + throw new Error(`Directory not found: ${relPath}`); + const entries = await listDir(abs); + return { + content: [ + { + type: "text" as const, + text: entries.length > 0 ? entries.join("\n") : "(empty directory)", + }, + ], + }; + } + + // ---- get_architecture ---- + case "get_architecture": { + const topLevel = await listDir(SRC_ROOT); + const tools = await getToolList(); + const commands = await getCommandList(); + + const overview = `# Claude Code Architecture Overview + +## Source Root +${SRC_ROOT} + +## Top-Level Entries +${topLevel.map((e) => `- ${e}`).join("\n")} + +## Agent Tools (${tools.length}) +${tools.map((t) => `- **${t.name}** — ${t.files.length} files: ${t.files.join(", ")}`).join("\n")} + +## Slash Commands (${commands.length}) +${commands.map((c) => `- **${c.name}** ${c.isDirectory ? "(directory)" : "(file)"}${c.files ? ": " + c.files.join(", ") : ""}`).join("\n")} + +## Key Files +- **main.tsx** — CLI entrypoint (Commander.js) +- **QueryEngine.ts** — Core LLM API caller, streaming, tool loops +- **Tool.ts** — Base tool types, schemas, permission model +- **commands.ts** — Command registry and loader +- **tools.ts** — Tool registry and loader +- **context.ts** — System/user context collection + +## Core Subsystems +- **bridge/** — IDE integration (VS Code, JetBrains) +- **coordinator/** — Multi-agent orchestration +- **services/mcp/** — MCP client connections +- **services/api/** — Anthropic API client +- **plugins/** — Plugin system +- **skills/** — Skill system +- **tasks/** — Background task management +- **server/** — Server/remote mode +- **entrypoints/mcp.ts** — Built-in MCP server entrypoint +`; + return { content: [{ type: "text" as const, text: overview }] }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main() { + // Validate source root exists + if (!(await dirExists(SRC_ROOT))) { + console.error(`Error: Claude Code src/ directory not found at ${SRC_ROOT}`); + console.error( + "Set CLAUDE_CODE_SRC_ROOT environment variable to the src/ directory path." + ); + process.exit(1); + } + + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error(`Claude Code Explorer MCP server started (src: ${SRC_ROOT})`); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/mcp-server/tsconfig.json b/mcp-server/tsconfig.json new file mode 100644 index 0000000..f4624bd --- /dev/null +++ b/mcp-server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}