mirror of
https://github.com/codeaashu/claude-code.git
synced 2026-04-08 22:28:48 +03:00
rebrand: rename and publish as warrioraashuu-codemaster
This commit is contained in:
7
mcp-server/.gitignore
vendored
Normal file
7
mcp-server/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.js
|
||||||
|
*.d.ts
|
||||||
|
*.js.map
|
||||||
|
*.d.ts.map
|
||||||
|
!api/
|
||||||
4
mcp-server/.npmignore
Normal file
4
mcp-server/.npmignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.mcpregistry_*
|
||||||
|
src/
|
||||||
|
tsconfig.json
|
||||||
|
*.ts.new
|
||||||
29
mcp-server/Dockerfile
Normal file
29
mcp-server/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM node:22-slim AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy full repo (src/ is needed at runtime for the MCP explorer)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build MCP server
|
||||||
|
WORKDIR /app/mcp-server
|
||||||
|
RUN npm ci && npm run build
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM node:22-slim
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built MCP server and source code it explores
|
||||||
|
COPY --from=build /app/mcp-server/dist /app/mcp-server/dist
|
||||||
|
COPY --from=build /app/mcp-server/node_modules /app/mcp-server/node_modules
|
||||||
|
COPY --from=build /app/mcp-server/package.json /app/mcp-server/package.json
|
||||||
|
COPY --from=build /app/src /app/src
|
||||||
|
COPY --from=build /app/README.md /app/README.md
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV CLAUDE_CODE_SRC_ROOT=/app/src
|
||||||
|
ENV PORT=3000
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
WORKDIR /app/mcp-server
|
||||||
|
CMD ["node", "dist/http.js"]
|
||||||
279
mcp-server/README.md
Normal file
279
mcp-server/README.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
# warrioraashuu Codemaster — MCP Server
|
||||||
|
|
||||||
|
A standalone [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that lets any MCP-compatible client explore the Claude Code source code. Rebranded and published by [warrioraashuu](https://www.npmjs.com/~warrioraashuu). Supports **STDIO**, **Streamable HTTP**, and **SSE** transports.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
Exposes 8 tools, 3 resources, and 5 prompts for navigating the ~1,900-file, 512K+ line Claude Code codebase. This is the official npm package: `warrioraashuu-codemaster`.
|
||||||
|
|
||||||
|
### Transports
|
||||||
|
|
||||||
|
| Transport | Endpoint | Best For |
|
||||||
|
|-----------|----------|----------|
|
||||||
|
| **STDIO** | `node dist/index.js` | Claude Desktop, local Claude Code, VS Code |
|
||||||
|
| **Streamable HTTP** | `POST/GET /mcp` | Modern MCP clients, remote hosting |
|
||||||
|
| **Legacy SSE** | `GET /sse` + `POST /messages` | Older MCP clients |
|
||||||
|
|
||||||
|
### 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) |
|
||||||
|
|
||||||
|
### Prompts
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `explain_tool` | Deep-dive explanation of a specific tool's purpose, schema, permissions, and flow |
|
||||||
|
| `explain_command` | Explanation of a specific slash command's behavior and implementation |
|
||||||
|
| `architecture_overview` | Guided tour of the full Claude Code architecture |
|
||||||
|
| `how_does_it_work` | Explain a feature/subsystem (permissions, MCP, bridge, etc.) |
|
||||||
|
| `compare_tools` | Side-by-side comparison of two tools |
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mcp-server
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Locally (STDIO)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
# or with custom source path:
|
||||||
|
CLAUDE_CODE_SRC_ROOT=/path/to/src npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Locally (HTTP)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start:http
|
||||||
|
# Streamable HTTP at http://localhost:3000/mcp
|
||||||
|
# Legacy SSE at http://localhost:3000/sse
|
||||||
|
# Health check at http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Authentication
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MCP_API_KEY=your-secret-token npm run start:http
|
||||||
|
# Clients must include: Authorization: Bearer your-secret-token
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Claude Desktop
|
||||||
|
|
||||||
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"warrioraashuu-codemaster": {
|
||||||
|
"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 |
|
||||||
|
| `PORT` | `3000` | HTTP server port (HTTP mode only) |
|
||||||
|
| `MCP_API_KEY` | _(none)_ | Bearer token for HTTP auth (optional) |
|
||||||
|
|
||||||
|
## Remote HTTP Client Configuration
|
||||||
|
|
||||||
|
For Claude Desktop connecting to a remote server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"claude-code-explorer": {
|
||||||
|
"url": "https://your-deployment.railway.app/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer your-secret-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Railway
|
||||||
|
|
||||||
|
1. Connect your GitHub repo to [Railway](https://railway.app)
|
||||||
|
2. Railway automatically detects the `mcp-server/Dockerfile`
|
||||||
|
3. Set environment variables in the Railway dashboard:
|
||||||
|
- `MCP_API_KEY` — a secret bearer token
|
||||||
|
- `PORT` is set automatically by Railway
|
||||||
|
4. Deploy — available at `your-app.railway.app`
|
||||||
|
|
||||||
|
Or via CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
railway init
|
||||||
|
railway up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vercel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
Set environment variables in the Vercel dashboard:
|
||||||
|
- `CLAUDE_CODE_SRC_ROOT` — path where src/ files are bundled
|
||||||
|
- `MCP_API_KEY` — bearer token
|
||||||
|
|
||||||
|
> **Note**: Vercel functions are stateless with execution time limits (10s hobby / 60s pro). Best for simple tool calls. For persistent SSE streams, use Railway or Docker.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From repo root
|
||||||
|
docker build -f mcp-server/Dockerfile -t claude-code-mcp .
|
||||||
|
docker run -p 3000:3000 -e MCP_API_KEY=your-secret claude-code-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Works on any Docker host: Fly.io, Render, AWS ECS, Google Cloud Run, etc.
|
||||||
|
|
||||||
|
## Prompts
|
||||||
|
|
||||||
|
The server also exposes prompt templates for guided exploration:
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `explain_tool` | Deep-dive explanation of a specific tool (input schema, permissions, execution flow) |
|
||||||
|
| `explain_command` | Explain how a slash command works |
|
||||||
|
| `architecture_overview` | Guided tour of the entire Claude Code architecture |
|
||||||
|
| `how_does_it_work` | Explain a feature or subsystem (e.g. "permission system", "MCP client", "query engine") |
|
||||||
|
| `compare_tools` | Side-by-side comparison of two tools |
|
||||||
|
|
||||||
|
## 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?"
|
||||||
|
- Use the `explain_tool` prompt with "FileEditTool" to get a full breakdown
|
||||||
|
- Use `how_does_it_work` with "bridge" to understand IDE integration
|
||||||
|
|
||||||
|
## Publishing to MCP Registry
|
||||||
|
|
||||||
|
This server is published to the [MCP Registry](https://registry.modelcontextprotocol.io) via GitHub Actions. On a tagged release (`v*`), the workflow:
|
||||||
|
|
||||||
|
1. Publishes the npm package to npmjs.org
|
||||||
|
2. Authenticates with the MCP Registry using GitHub OIDC
|
||||||
|
3. Publishes the `server.json` metadata to the registry
|
||||||
|
|
||||||
|
To publish manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install the MCP Publisher CLI
|
||||||
|
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
||||||
|
|
||||||
|
# Authenticate (GitHub OAuth)
|
||||||
|
./mcp-publisher login github
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
cd mcp-server
|
||||||
|
../mcp-publisher publish
|
||||||
|
```
|
||||||
|
|
||||||
|
Registry name: `io.github.codeaashu/claude-code-explorer-mcp`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev # Watch mode TypeScript compilation
|
||||||
|
npm run build # Compile TypeScript to dist/
|
||||||
|
npm start # Run STDIO server
|
||||||
|
npm run start:http # Run HTTP server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp-server/
|
||||||
|
├── src/
|
||||||
|
│ ├── server.ts — Shared MCP server (tools, resources, prompts) — transport-agnostic
|
||||||
|
│ ├── index.ts — STDIO entrypoint (local)
|
||||||
|
│ └── http.ts — HTTP + SSE entrypoint (remote)
|
||||||
|
├── api/
|
||||||
|
│ ├── index.ts — Vercel serverless function
|
||||||
|
│ └── vercelApp.ts — Express app for Vercel
|
||||||
|
├── Dockerfile — Docker build (Railway, Fly.io, etc.)
|
||||||
|
├── railway.json — Railway deployment config
|
||||||
|
├── package.json
|
||||||
|
└── tsconfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
21
mcp-server/api/index.ts
Normal file
21
mcp-server/api/index.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Vercel serverless function — proxies requests to the Express HTTP server.
|
||||||
|
*
|
||||||
|
* This file re-exports the Express app as a Vercel serverless handler.
|
||||||
|
* Vercel automatically routes /api/* to this function.
|
||||||
|
*
|
||||||
|
* Deploy:
|
||||||
|
* cd mcp-server && npx vercel
|
||||||
|
*
|
||||||
|
* Environment variables (set in Vercel dashboard):
|
||||||
|
* CLAUDE_CODE_SRC_ROOT — absolute path where src/ is deployed
|
||||||
|
* MCP_API_KEY — optional bearer token for auth
|
||||||
|
*
|
||||||
|
* NOTE: Vercel serverless functions are stateless, so the Streamable HTTP
|
||||||
|
* transport (which requires sessions) won't persist across invocations.
|
||||||
|
* For production use with session-based MCP clients, prefer Railway/Render/VPS.
|
||||||
|
* The legacy SSE transport and stateless tool calls work fine on Vercel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { app as default } from "./vercelApp.js";
|
||||||
|
|
||||||
96
mcp-server/api/vercelApp.ts
Normal file
96
mcp-server/api/vercelApp.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Express app exported for Vercel serverless.
|
||||||
|
* Shares the same logic as http.ts but is importable as a module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import express from "express";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||||
|
import { createServer, SRC_ROOT } from "../src/server.js";
|
||||||
|
|
||||||
|
const API_KEY = process.env.MCP_API_KEY;
|
||||||
|
|
||||||
|
export const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (!API_KEY || req.path === "/health" || req.path === "/api") return next();
|
||||||
|
const auth = req.headers.authorization;
|
||||||
|
if (!auth || auth !== `Bearer ${API_KEY}`) {
|
||||||
|
res.status(401).json({ error: "Unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health
|
||||||
|
app.get("/health", (_req, res) => {
|
||||||
|
res.json({ status: "ok", server: "claude-code-explorer", version: "1.1.0", srcRoot: SRC_ROOT });
|
||||||
|
});
|
||||||
|
app.get("/api", (_req, res) => {
|
||||||
|
res.json({ status: "ok", server: "claude-code-explorer", version: "1.1.0", srcRoot: SRC_ROOT });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Streamable HTTP
|
||||||
|
const transports = new Map<string, StreamableHTTPServerTransport>();
|
||||||
|
|
||||||
|
app.post("/mcp", async (req, res) => {
|
||||||
|
const sessionId = (req.headers["mcp-session-id"] as string) ?? undefined;
|
||||||
|
let transport = sessionId ? transports.get(sessionId) : undefined;
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
const server = createServer();
|
||||||
|
transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
|
||||||
|
await server.connect(transport);
|
||||||
|
transport.onclose = () => {
|
||||||
|
if (transport!.sessionId) transports.delete(transport!.sessionId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
|
||||||
|
if (transport.sessionId && !transports.has(transport.sessionId)) {
|
||||||
|
transports.set(transport.sessionId, transport);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/mcp", async (req, res) => {
|
||||||
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||||
|
if (!sessionId || !transports.has(sessionId)) {
|
||||||
|
res.status(400).json({ error: "Invalid or missing session ID" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await transports.get(sessionId)!.handleRequest(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/mcp", async (req, res) => {
|
||||||
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||||
|
if (sessionId && transports.has(sessionId)) {
|
||||||
|
await transports.get(sessionId)!.close();
|
||||||
|
transports.delete(sessionId);
|
||||||
|
}
|
||||||
|
res.status(200).json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legacy SSE
|
||||||
|
const sseTransports = new Map<string, SSEServerTransport>();
|
||||||
|
|
||||||
|
app.get("/sse", async (_req, res) => {
|
||||||
|
const server = createServer();
|
||||||
|
const transport = new SSEServerTransport("/messages", res);
|
||||||
|
sseTransports.set(transport.sessionId, transport);
|
||||||
|
transport.onclose = () => sseTransports.delete(transport.sessionId);
|
||||||
|
await server.connect(transport);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/messages", async (req, res) => {
|
||||||
|
const sessionId = req.query.sessionId as string;
|
||||||
|
const transport = sseTransports.get(sessionId);
|
||||||
|
if (!transport) {
|
||||||
|
res.status(400).json({ error: "Unknown session" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await transport.handlePostMessage(req, res, req.body);
|
||||||
|
});
|
||||||
2183
mcp-server/package-lock.json
generated
Normal file
2183
mcp-server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
mcp-server/package.json
Normal file
38
mcp-server/package.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "warrioraashuu-codemaster",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"mcpName": "io.github.warrioraashuu/warrioraashuu-codemaster",
|
||||||
|
"description": "MCP server for exploring the Claude Code source code — rebranded and published by warrioraashuu. STDIO, HTTP, and SSE transports.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/codeaashu/claude-code.git"
|
||||||
|
},
|
||||||
|
"author": "warrioraashuu <npmjs.com/~warrioraashuu>",
|
||||||
|
"homepage": "https://github.com/codeaashu/claude-code#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/codeaashu/claude-code/issues"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"warrioraashuu-codemaster": "dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"start:http": "node dist/http.js",
|
||||||
|
"dev": "npx tsx src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
|
"express": "^4.21.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
13
mcp-server/railway.json
Normal file
13
mcp-server/railway.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://railway.com/railway.schema.json",
|
||||||
|
"build": {
|
||||||
|
"builder": "DOCKERFILE",
|
||||||
|
"dockerfilePath": "mcp-server/Dockerfile"
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"startCommand": "node dist/http.js",
|
||||||
|
"healthcheckPath": "/health",
|
||||||
|
"restartPolicyType": "ON_FAILURE",
|
||||||
|
"restartPolicyMaxRetries": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
24
mcp-server/server.json
Normal file
24
mcp-server/server.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||||
|
"name": "io.github.codeaashu/claude-code-explorer-mcp",
|
||||||
|
"title": "Claude Code Explorer MCP",
|
||||||
|
"description": "Explore the Claude Code CLI source — browse tools, commands, search code, and more.",
|
||||||
|
"repository": {
|
||||||
|
"url": "https://github.com/codeaashu/claude-code",
|
||||||
|
"source": "github",
|
||||||
|
"subfolder": "mcp-server"
|
||||||
|
},
|
||||||
|
"version": "1.1.0",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"registryType": "npm",
|
||||||
|
"registryBaseUrl": "https://registry.npmjs.org",
|
||||||
|
"identifier": "claude-code-explorer-mcp",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"transport": {
|
||||||
|
"type": "stdio"
|
||||||
|
},
|
||||||
|
"runtimeHint": "node"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
172
mcp-server/src/http.ts
Normal file
172
mcp-server/src/http.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* HTTP + SSE entrypoint — for remote hosting (Railway, Render, VPS, etc.)
|
||||||
|
*
|
||||||
|
* Supports:
|
||||||
|
* - Streamable HTTP transport (POST /mcp for JSON-RPC, GET /mcp for SSE)
|
||||||
|
* - Health check at GET /health
|
||||||
|
*
|
||||||
|
* Environment:
|
||||||
|
* PORT — HTTP port (default: 3000)
|
||||||
|
* CLAUDE_CODE_SRC_ROOT — Path to Claude Code src/ directory
|
||||||
|
* MCP_API_KEY — Optional bearer token for authentication
|
||||||
|
*/
|
||||||
|
|
||||||
|
import express from "express";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||||
|
import { createServer, validateSrcRoot, SRC_ROOT } from "./server.js";
|
||||||
|
|
||||||
|
const PORT = parseInt(process.env.PORT ?? "3000", 10);
|
||||||
|
const API_KEY = process.env.MCP_API_KEY;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Auth middleware (optional — only active when MCP_API_KEY is set)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function authMiddleware(
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction
|
||||||
|
): void {
|
||||||
|
if (!API_KEY) return next();
|
||||||
|
// Skip auth for health check
|
||||||
|
if (req.path === "/health") return next();
|
||||||
|
|
||||||
|
const auth = req.headers.authorization;
|
||||||
|
if (!auth || auth !== `Bearer ${API_KEY}`) {
|
||||||
|
res.status(401).json({ error: "Unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Streamable HTTP transport (modern MCP protocol)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function startStreamableHTTP(app: express.Express): Promise<void> {
|
||||||
|
// Map of session ID -> transport
|
||||||
|
const transports = new Map<string, StreamableHTTPServerTransport>();
|
||||||
|
|
||||||
|
app.post("/mcp", async (req, res) => {
|
||||||
|
const sessionId =
|
||||||
|
(req.headers["mcp-session-id"] as string) ?? undefined;
|
||||||
|
let transport = sessionId ? transports.get(sessionId) : undefined;
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
// New session
|
||||||
|
const server = createServer();
|
||||||
|
transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: () => randomUUID(),
|
||||||
|
});
|
||||||
|
await server.connect(transport);
|
||||||
|
|
||||||
|
// Store session after first request so we can retrieve it later
|
||||||
|
transport.onclose = () => {
|
||||||
|
if (transport!.sessionId) {
|
||||||
|
transports.delete(transport!.sessionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
|
||||||
|
// After handling, store with the now-known session ID
|
||||||
|
if (transport.sessionId && !transports.has(transport.sessionId)) {
|
||||||
|
transports.set(transport.sessionId, transport);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SSE stream endpoint for Streamable HTTP
|
||||||
|
app.get("/mcp", async (req, res) => {
|
||||||
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||||
|
if (!sessionId || !transports.has(sessionId)) {
|
||||||
|
res.status(400).json({ error: "Invalid or missing session ID" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const transport = transports.get(sessionId)!;
|
||||||
|
await transport.handleRequest(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE — session cleanup
|
||||||
|
app.delete("/mcp", async (req, res) => {
|
||||||
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
||||||
|
if (sessionId && transports.has(sessionId)) {
|
||||||
|
const transport = transports.get(sessionId)!;
|
||||||
|
await transport.close();
|
||||||
|
transports.delete(sessionId);
|
||||||
|
}
|
||||||
|
res.status(200).json({ ok: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Legacy SSE transport (for older clients)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function startLegacySSE(app: express.Express): Promise<void> {
|
||||||
|
const transports = new Map<string, SSEServerTransport>();
|
||||||
|
|
||||||
|
app.get("/sse", async (_req, res) => {
|
||||||
|
const server = createServer();
|
||||||
|
const transport = new SSEServerTransport("/messages", res);
|
||||||
|
transports.set(transport.sessionId, transport);
|
||||||
|
transport.onclose = () => {
|
||||||
|
transports.delete(transport.sessionId);
|
||||||
|
};
|
||||||
|
await server.connect(transport);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/messages", async (req, res) => {
|
||||||
|
const sessionId = req.query.sessionId as string;
|
||||||
|
const transport = transports.get(sessionId);
|
||||||
|
if (!transport) {
|
||||||
|
res.status(400).json({ error: "Unknown session" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await transport.handlePostMessage(req, res, req.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Main
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
await validateSrcRoot();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(authMiddleware);
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
app.get("/health", (_req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: "ok",
|
||||||
|
server: "claude-code-explorer",
|
||||||
|
version: "1.1.0",
|
||||||
|
srcRoot: SRC_ROOT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register both transports
|
||||||
|
await startStreamableHTTP(app);
|
||||||
|
await startLegacySSE(app);
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Claude Code Explorer MCP (HTTP) listening on port ${PORT}`);
|
||||||
|
console.log(` Streamable HTTP: POST/GET http://localhost:${PORT}/mcp`);
|
||||||
|
console.log(` Legacy SSE: GET http://localhost:${PORT}/sse`);
|
||||||
|
console.log(` Health: GET http://localhost:${PORT}/health`);
|
||||||
|
console.log(` Source root: ${SRC_ROOT}`);
|
||||||
|
if (API_KEY) console.log(" Auth: Bearer token required");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Fatal error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
24
mcp-server/src/index.ts
Normal file
24
mcp-server/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* STDIO entrypoint — for local use with Claude Desktop, Claude Code, etc.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node dist/index.js
|
||||||
|
* CLAUDE_CODE_SRC_ROOT=/path/to/src node dist/index.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import { createServer, validateSrcRoot, SRC_ROOT } from "./server.js";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await validateSrcRoot();
|
||||||
|
const server = createServer();
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error(`Claude Code Explorer MCP (stdio) started — src: ${SRC_ROOT}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Fatal error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
24
mcp-server/src/index.ts.new
Normal file
24
mcp-server/src/index.ts.new
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* STDIO entrypoint — for local use with Claude Desktop, Claude Code, etc.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node dist/index.js
|
||||||
|
* CLAUDE_CODE_SRC_ROOT=/path/to/src node dist/index.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import { createServer, validateSrcRoot, SRC_ROOT } from "./server.js";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await validateSrcRoot();
|
||||||
|
const server = createServer();
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error(`Claude Code Explorer MCP (stdio) started — src: ${SRC_ROOT}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Fatal error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
958
mcp-server/src/server.ts
Normal file
958
mcp-server/src/server.ts
Normal file
@@ -0,0 +1,958 @@
|
|||||||
|
/**
|
||||||
|
* Shared MCP server definition — transport-agnostic.
|
||||||
|
*
|
||||||
|
* Exposes tools and resources for exploring the Claude Code source code.
|
||||||
|
* This module is imported by both the STDIO and HTTP entrypoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
ListResourcesRequestSchema,
|
||||||
|
ReadResourceRequestSchema,
|
||||||
|
ListResourceTemplatesRequestSchema,
|
||||||
|
ListPromptsRequestSchema,
|
||||||
|
GetPromptRequestSchema,
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
export const SRC_ROOT = path.resolve(
|
||||||
|
process.env.CLAUDE_CODE_SRC_ROOT ?? path.join(__dirname, "..", "..", "src")
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function dirExists(p: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return (await fs.stat(p)).isDirectory();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileExists(p: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return (await fs.stat(p)).isFile();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listDir(dir: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
return entries
|
||||||
|
.map((e) => (e.isDirectory() ? e.name + "/" : e.name))
|
||||||
|
.sort();
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function walkFiles(root: string, rel = ""): Promise<string[]> {
|
||||||
|
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 (blocks path traversal). */
|
||||||
|
function safePath(relPath: string): string | null {
|
||||||
|
const resolved = path.resolve(SRC_ROOT, relPath);
|
||||||
|
if (!resolved.startsWith(SRC_ROOT)) return null;
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Metadata Types
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ToolInfo {
|
||||||
|
name: string;
|
||||||
|
directory: string;
|
||||||
|
files: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandInfo {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
isDirectory: boolean;
|
||||||
|
files?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getToolList(): Promise<ToolInfo[]> {
|
||||||
|
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<CommandInfo[]> {
|
||||||
|
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 Factory
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function createServer(): Server {
|
||||||
|
const server = new Server(
|
||||||
|
{ name: "claude-code-explorer", version: "1.1.0" },
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
resources: {},
|
||||||
|
prompts: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- 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),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
toolName: {
|
||||||
|
type: "string",
|
||||||
|
description: "Tool directory name, e.g. 'BashTool'",
|
||||||
|
},
|
||||||
|
fileName: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Specific file within the tool directory. Omit for the main file.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["toolName"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get_command_source",
|
||||||
|
description:
|
||||||
|
"Read the source code of a specific Claude Code slash command.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
commandName: {
|
||||||
|
type: "string",
|
||||||
|
description: "Command name, e.g. 'commit', 'review'",
|
||||||
|
},
|
||||||
|
fileName: {
|
||||||
|
type: "string",
|
||||||
|
description: "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'",
|
||||||
|
},
|
||||||
|
startLine: {
|
||||||
|
type: "number",
|
||||||
|
description: "1-based start line.",
|
||||||
|
},
|
||||||
|
endLine: {
|
||||||
|
type: "number",
|
||||||
|
description: "1-based end line.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["path"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_source",
|
||||||
|
description:
|
||||||
|
"Search for a regex pattern across the Claude Code source. Returns matching lines with paths and line numbers.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
pattern: {
|
||||||
|
type: "string",
|
||||||
|
description: "Search pattern (regex).",
|
||||||
|
},
|
||||||
|
filePattern: {
|
||||||
|
type: "string",
|
||||||
|
description: "File extension filter, e.g. '.ts'",
|
||||||
|
},
|
||||||
|
maxResults: {
|
||||||
|
type: "number",
|
||||||
|
description: "Max matches (default: 50).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["pattern"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list_directory",
|
||||||
|
description: "List files and subdirectories under src/.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
path: {
|
||||||
|
type: "string",
|
||||||
|
description: "Relative path from src/, e.g. 'services'. '' for root.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["path"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get_architecture",
|
||||||
|
description:
|
||||||
|
"Get a high-level architecture overview of Claude Code.",
|
||||||
|
inputSchema: { type: "object" as const, properties: {} },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.setRequestHandler(
|
||||||
|
CallToolRequestSchema,
|
||||||
|
async (request: {
|
||||||
|
params: { name: string; arguments?: Record<string, unknown> };
|
||||||
|
}) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "list_tools": {
|
||||||
|
const tools = await getToolList();
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{ type: "text" as const, text: JSON.stringify(tools, null, 2) },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_commands": {
|
||||||
|
const commands = await getCommandList();
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text" as const,
|
||||||
|
text: JSON.stringify(commands, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "get_tool_source": {
|
||||||
|
const toolName = (args as Record<string, unknown>)
|
||||||
|
?.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<string, unknown>)?.fileName as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
if (!fileName) {
|
||||||
|
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}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "get_command_source": {
|
||||||
|
const commandName = (args as Record<string, unknown>)
|
||||||
|
?.commandName as string;
|
||||||
|
if (!commandName) throw new Error("commandName is required");
|
||||||
|
|
||||||
|
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<string, unknown>)?.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 }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await listDir(found);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Command: ${commandName}\nFiles:\n${files.map((f) => ` ${f}`).join("\n")}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "read_source_file": {
|
||||||
|
const relPath = (args as Record<string, unknown>)?.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<string, unknown>)?.startLine as number) ?? 1;
|
||||||
|
const end =
|
||||||
|
((args as Record<string, unknown>)?.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"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search_source": {
|
||||||
|
const pattern = (args as Record<string, unknown>)
|
||||||
|
?.pattern as string;
|
||||||
|
if (!pattern) throw new Error("pattern is required");
|
||||||
|
const filePattern = (args as Record<string, unknown>)
|
||||||
|
?.filePattern as string | undefined;
|
||||||
|
const maxResults =
|
||||||
|
((args as Record<string, unknown>)?.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.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_directory": {
|
||||||
|
const relPath =
|
||||||
|
((args as Record<string, unknown>)?.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)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
`;
|
||||||
|
return { content: [{ type: "text" as const, text: overview }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- Prompts -----------------------------------------------------------
|
||||||
|
|
||||||
|
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
||||||
|
prompts: [
|
||||||
|
{
|
||||||
|
name: "explain_tool",
|
||||||
|
description:
|
||||||
|
"Explain how a specific Claude Code tool works, including its input schema, permissions, and execution flow.",
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: "toolName",
|
||||||
|
description: "Tool directory name, e.g. 'BashTool', 'FileEditTool'",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explain_command",
|
||||||
|
description: "Explain how a specific Claude Code slash command works.",
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: "commandName",
|
||||||
|
description: "Command name, e.g. 'commit', 'review', 'mcp'",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "architecture_overview",
|
||||||
|
description:
|
||||||
|
"Get a guided tour of the Claude Code architecture with explanations of each subsystem.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "how_does_it_work",
|
||||||
|
description:
|
||||||
|
"Explain how a specific feature or subsystem of Claude Code works.",
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: "feature",
|
||||||
|
description:
|
||||||
|
"Feature or subsystem, e.g. 'permission system', 'MCP client', 'query engine', 'bridge'",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compare_tools",
|
||||||
|
description:
|
||||||
|
"Compare two Claude Code tools side by side — purpose, inputs, permissions, implementation.",
|
||||||
|
arguments: [
|
||||||
|
{ name: "tool1", description: "First tool name", required: true },
|
||||||
|
{ name: "tool2", description: "Second tool name", required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.setRequestHandler(
|
||||||
|
GetPromptRequestSchema,
|
||||||
|
async (request: {
|
||||||
|
params: { name: string; arguments?: Record<string, string> };
|
||||||
|
}) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "explain_tool": {
|
||||||
|
const toolName = args?.toolName;
|
||||||
|
if (!toolName) throw new Error("toolName argument is required");
|
||||||
|
const toolDir = safePath(`tools/${toolName}`);
|
||||||
|
if (!toolDir || !(await dirExists(toolDir)))
|
||||||
|
throw new Error(`Tool not found: ${toolName}`);
|
||||||
|
const files = await listDir(toolDir);
|
||||||
|
const mainFile =
|
||||||
|
files.find(
|
||||||
|
(f) => f === `${toolName}.ts` || f === `${toolName}.tsx`
|
||||||
|
) ?? files.find((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
||||||
|
let source = "";
|
||||||
|
if (mainFile) {
|
||||||
|
source = await fs.readFile(path.join(toolDir, mainFile), "utf-8");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
description: `Explanation of the ${toolName} tool`,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
content: {
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Analyze and explain this Claude Code tool implementation. Cover:\n1. Purpose\n2. Input Schema\n3. Permissions\n4. Execution Flow\n5. Output\n6. Safety characteristics\n\nFiles in tools/${toolName}/: ${files.join(", ")}\n\nMain source (${mainFile ?? "not found"}):\n\`\`\`typescript\n${source}\n\`\`\``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "explain_command": {
|
||||||
|
const commandName = args?.commandName;
|
||||||
|
if (!commandName)
|
||||||
|
throw new Error("commandName argument is required");
|
||||||
|
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}`);
|
||||||
|
let source = "";
|
||||||
|
let fileList = "";
|
||||||
|
if (isDir) {
|
||||||
|
const files = await listDir(found);
|
||||||
|
fileList = files.join(", ");
|
||||||
|
const indexFile = files.find(
|
||||||
|
(f) => f === "index.ts" || f === "index.tsx"
|
||||||
|
);
|
||||||
|
if (indexFile) {
|
||||||
|
source = await fs.readFile(
|
||||||
|
path.join(found, indexFile),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
source = await fs.readFile(found, "utf-8");
|
||||||
|
fileList = path.basename(found);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
description: `Explanation of the /${commandName} command`,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
content: {
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Analyze and explain this Claude Code slash command. Cover:\n1. Purpose\n2. Type (prompt vs action)\n3. Allowed Tools\n4. Arguments\n5. Implementation\n\nFiles: ${fileList}\n\nSource:\n\`\`\`typescript\n${source}\n\`\`\``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "architecture_overview": {
|
||||||
|
const readmePath = path.resolve(SRC_ROOT, "..", "README.md");
|
||||||
|
let readme = "";
|
||||||
|
try {
|
||||||
|
readme = await fs.readFile(readmePath, "utf-8");
|
||||||
|
} catch {
|
||||||
|
/* */
|
||||||
|
}
|
||||||
|
const topLevel = await listDir(SRC_ROOT);
|
||||||
|
const tools = await getToolList();
|
||||||
|
const commands = await getCommandList();
|
||||||
|
return {
|
||||||
|
description: "Architecture overview of Claude Code",
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
content: {
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Give a comprehensive guided tour of the Claude Code architecture.\n\n## README\n${readme}\n\n## src/ entries\n${topLevel.join("\n")}\n\n## Tools (${tools.length})\n${tools.map((t) => `- ${t.name}: ${t.files.join(", ")}`).join("\n")}\n\n## Commands (${commands.length})\n${commands.map((c) => `- ${c.name} ${c.isDirectory ? "(dir)" : "(file)"}`).join("\n")}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "how_does_it_work": {
|
||||||
|
const feature = args?.feature;
|
||||||
|
if (!feature) throw new Error("feature argument is required");
|
||||||
|
const featureMap: Record<string, string[]> = {
|
||||||
|
"permission system": [
|
||||||
|
"utils/permissions/",
|
||||||
|
"hooks/toolPermission/",
|
||||||
|
"Tool.ts",
|
||||||
|
],
|
||||||
|
permissions: [
|
||||||
|
"utils/permissions/",
|
||||||
|
"hooks/toolPermission/",
|
||||||
|
"Tool.ts",
|
||||||
|
],
|
||||||
|
"mcp client": [
|
||||||
|
"services/mcp/",
|
||||||
|
"tools/MCPTool/",
|
||||||
|
"tools/ListMcpResourcesTool/",
|
||||||
|
],
|
||||||
|
mcp: ["services/mcp/", "entrypoints/mcp.ts", "tools/MCPTool/"],
|
||||||
|
"tool system": ["Tool.ts", "tools.ts", "tools/"],
|
||||||
|
tools: ["Tool.ts", "tools.ts"],
|
||||||
|
"query engine": ["QueryEngine.ts", "query/"],
|
||||||
|
bridge: ["bridge/"],
|
||||||
|
"ide integration": ["bridge/"],
|
||||||
|
context: ["context.ts", "context/"],
|
||||||
|
commands: ["commands.ts", "types/command.ts"],
|
||||||
|
"command system": ["commands.ts", "types/command.ts", "commands/"],
|
||||||
|
plugins: ["plugins/"],
|
||||||
|
skills: ["skills/"],
|
||||||
|
tasks: ["tasks.ts", "tasks/", "tools/TaskCreateTool/"],
|
||||||
|
coordinator: ["coordinator/", "tools/AgentTool/"],
|
||||||
|
"multi-agent": ["coordinator/", "tools/AgentTool/"],
|
||||||
|
memory: ["memdir/", "commands/memory/"],
|
||||||
|
voice: ["voice/"],
|
||||||
|
server: ["server/"],
|
||||||
|
};
|
||||||
|
const paths = featureMap[feature.toLowerCase()] ?? [];
|
||||||
|
let contextFiles = "";
|
||||||
|
for (const p of paths) {
|
||||||
|
const abs = safePath(p);
|
||||||
|
if (!abs) continue;
|
||||||
|
try {
|
||||||
|
const stat = await fs.stat(abs);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const files = await listDir(abs);
|
||||||
|
contextFiles += `\n### ${p}\nFiles: ${files.join(", ")}\n`;
|
||||||
|
} else {
|
||||||
|
const content = await fs.readFile(abs, "utf-8");
|
||||||
|
const preview = content.split("\n").slice(0, 200).join("\n");
|
||||||
|
contextFiles += `\n### ${p} (first 200 lines)\n\`\`\`typescript\n${preview}\n\`\`\`\n`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* skip */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
description: `How ${feature} works in Claude Code`,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
content: {
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Explain how "${feature}" works in the Claude Code CLI.\n${contextFiles || "(No specific files mapped — use search_source and read_source_file to find relevant code.)"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "compare_tools": {
|
||||||
|
const tool1 = args?.tool1;
|
||||||
|
const tool2 = args?.tool2;
|
||||||
|
if (!tool1 || !tool2)
|
||||||
|
throw new Error("Both tool1 and tool2 arguments are required");
|
||||||
|
const sources: string[] = [];
|
||||||
|
for (const toolName of [tool1, tool2]) {
|
||||||
|
const toolDir = safePath(`tools/${toolName}`);
|
||||||
|
if (!toolDir || !(await dirExists(toolDir))) {
|
||||||
|
sources.push(`// Tool not found: ${toolName}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const files = await listDir(toolDir);
|
||||||
|
const mainFile =
|
||||||
|
files.find(
|
||||||
|
(f) => f === `${toolName}.ts` || f === `${toolName}.tsx`
|
||||||
|
) ?? files.find((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
||||||
|
if (mainFile) {
|
||||||
|
const content = await fs.readFile(
|
||||||
|
path.join(toolDir, mainFile),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
sources.push(`// tools/${toolName}/${mainFile}\n${content}`);
|
||||||
|
} else {
|
||||||
|
sources.push(`// No main source found for ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
description: `Comparison of ${tool1} vs ${tool2}`,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user" as const,
|
||||||
|
content: {
|
||||||
|
type: "text" as const,
|
||||||
|
text: `Compare these two Claude Code tools:\n\n## ${tool1}\n\`\`\`typescript\n${sources[0]}\n\`\`\`\n\n## ${tool2}\n\`\`\`typescript\n${sources[1]}\n\`\`\``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown prompt: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validate that SRC_ROOT exists. Exits with error if not. */
|
||||||
|
export async function validateSrcRoot(): Promise<void> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
21
mcp-server/tsconfig.json
Normal file
21
mcp-server/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "Node16",
|
||||||
|
"moduleResolution": "Node16",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": ".",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "api/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user