commit 598369e8942eb731651e391b01f60bb7329cc57c Author: thedotmack Date: Sat Sep 6 19:34:53 2025 +0000 Initial release v3.3.8 - Hook system for customization - Documentation and installation scripts - Multi-platform support via GitHub releases - Binaries available for Windows, Linux (x64/ARM64), macOS (Intel/Apple Silicon) Generated with Claude Code via Happy diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9666a58e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Binaries (distributed via GitHub releases) +*.exe +*.app +claude-mem +claude-mem-* + +# Temporary files +*.tmp +*.log +.DS_Store + +# Node modules (if any) +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9c41c425 --- /dev/null +++ b/LICENSE @@ -0,0 +1,43 @@ +# Claude Mem License + +Copyright (c) 2024 Alex Newman (@thedotmack) + +## Binary Distribution License + +The compiled binaries (claude-mem, claude-mem.exe, etc.) are provided free of charge for personal and commercial use under the following terms: + +1. **USE**: You may use the binaries for any purpose. +2. **DISTRIBUTION**: You may redistribute the unmodified binaries. +3. **NO REVERSE ENGINEERING**: You may not decompile, disassemble, or reverse engineer the binaries. +4. **NO MODIFICATION**: You may not modify the binary files. +5. **NO WARRANTY**: The software is provided "as is" without warranty of any kind. + +## Hook Files License (MIT) + +The hook files in the `/hooks` directory are licensed under the MIT License: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of these hook files and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## Contributions + +By submitting pull requests for hook files or documentation, you agree to license your contributions under the MIT License. + +## Trademark + +"Claude Mem" is a trademark of Alex Newman. You may use the name when referring to this software, but not in a way that implies endorsement or affiliation. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..7be0d6f2 --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +# Claude Memory System (claude-mem) + +**Truth + Context = Clarity** + +A revolutionary memory system that transforms your Claude Code conversations into a persistent, intelligent knowledge base. Never lose valuable insights, code patterns, or debugging solutions again. Your AI assistant finally has a memory that spans across all your projects and sessions. + +## πŸš€ Why Claude-Mem? + +### The Problem We Solve +- **Lost Context**: Starting every Claude Code session from scratch +- **Repeated Explanations**: Re-describing your codebase and architecture repeatedly +- **Fragmented Knowledge**: Valuable insights scattered across hundreds of conversations +- **Context Switching**: Losing progress when switching between projects or devices +- **Knowledge Decay**: Brilliant solutions forgotten and re-discovered multiple times + +### The Claude-Mem Solution +Transform your Claude Code experience from forgetful to persistent, from isolated sessions to connected knowledge, from starting over to building upon previous insights. + +## ✨ Key Features + +### 🧠 **Intelligent Memory Compression** +- Automatically extracts key learnings from your Claude Code conversations +- Identifies patterns, architectural decisions, and breakthrough moments +- Compresses hours of conversation into searchable, actionable knowledge +- Uses advanced AI analysis to understand context and significance + +### πŸ”„ **Seamless Integration** +- **One-command setup**: `claude-mem install` and you're ready +- **Zero friction**: Works invisibly in the background +- **Automatic triggers**: Memory compression on `/compact` and `/clear` +- **Instant context loading**: New sessions start with relevant memories + +### 🎯 **Smart Context Loading** +- Loads relevant memories when starting new sessions +- Project-aware context selection +- Semantic search finds related knowledge across all sessions +- Prevents re-explaining the same concepts repeatedly + +### πŸ“š **Comprehensive Knowledge Base** +- Stores technical implementations, bug fixes, and solutions +- Captures design patterns and architectural decisions +- Remembers tool configurations and setup procedures +- Archives complete conversation transcripts for detailed reference + +### πŸ” **Powerful Search & Retrieval** +- Vector-based semantic search finds related concepts +- Keyword search for specific terms and technologies +- Project filtering to focus on relevant memories +- Time-based filtering to find recent insights + +## πŸ›  Installation & Setup + +### Quick Install +```bash +# Install globally +npm install -g claude-mem + +# Set up Claude Code integration +claude-mem install + +# Restart Claude Code to activate +``` + +### Alternative Installation +```bash +# Use without installing globally +npx claude-mem install +``` + +### Verification +```bash +# Check installation status +claude-mem status +``` + +## πŸ’» How It Works + +### The Memory Lifecycle + +1. **🎬 Session Start**: Claude-mem loads relevant context from your knowledge base +2. **πŸ’¬ Active Session**: You work normally in Claude Code - no changes needed +3. **πŸ—œοΈ Memory Compression**: Use `/compact` or `/clear` to trigger intelligent compression +4. **🧠 Knowledge Extraction**: AI analysis extracts key learnings and patterns +5. **πŸ’Ύ Persistent Storage**: Memories stored in searchable vector database +6. **πŸ”„ Context Ready**: Next session starts with relevant memories loaded + +### Technical Architecture + +- **Vector Database**: ChromaDB for semantic search and storage +- **MCP Integration**: Model Context Protocol for Claude Code communication +- **AI Analysis**: Advanced prompt engineering for knowledge extraction +- **Local Storage**: All data stored locally in `~/.claude-mem/` + +## πŸ“‹ Commands Reference + +### Core Commands +```bash +claude-mem install # Set up Claude Code integration +claude-mem status # Check system status and configuration +claude-mem load-context # View and search stored memories +claude-mem logs # View system logs and debug information +claude-mem uninstall # Remove Claude Code hooks +``` + +### Advanced Usage +```bash +claude-mem compress # Manually compress a transcript file +claude-mem restore # Restore from backups +claude-mem trash-view # View deleted files (Smart Trash feature) +``` + +## πŸ“ Storage Structure + +Your claude-mem data is organized in `~/.claude-mem/`: + +``` +~/.claude-mem/ +β”œβ”€β”€ index/ # ChromaDB vector database +β”œβ”€β”€ archives/ # Original conversation transcripts +β”œβ”€β”€ hooks/ # Claude Code integration scripts +β”œβ”€β”€ trash/ # Smart Trash (deleted files) +└── logs/ # System logs and debug information +``` + +## 🌟 Real-World Benefits + +### For Individual Developers +- **Faster Problem Solving**: Find solutions you've used before instantly +- **Knowledge Accumulation**: Build expertise that persists across projects +- **Context Continuity**: Pick up where you left off, even weeks later +- **Pattern Recognition**: See how you've solved similar problems before + +### For Teams (Coming Soon) +- **Shared Knowledge**: Team-wide memory accessible to all members +- **Onboarding Acceleration**: New team members access collective knowledge +- **Best Practices**: Capture and share proven solutions +- **Institutional Memory**: Prevent knowledge loss when team members leave + +## πŸš€ Coming Soon: Cloud Sync + +### Individual Plan ($9.95/month) +- **Multi-device sync**: Access your memories on any device +- **Cloud backup**: Never lose your knowledge base +- **Enhanced search**: Advanced filtering and semantic search +- **API access**: Integrate with your own tools and workflows + +### Team Plan ($29.95/month, 3+ seats) +- **Shared memories**: Team-wide knowledge base +- **Role-based access**: Control what memories are shared +- **Admin dashboard**: Manage team members and usage +- **Priority support**: Direct access to our engineering team + +[**Join the waitlist**](https://claude-mem.ai) for early access to cloud features. + +## πŸ›‘οΈ Privacy & Security + +- **Local-first**: All data stored locally by default +- **No tracking**: We don't collect or transmit your conversations +- **Your data**: You own and control your knowledge base +- **Open architecture**: ChromaDB and MCP are open standards + +## πŸ†˜ Troubleshooting + +### Common Issues + +**Hook not triggering?** +```bash +claude-mem status # Check installation +claude-mem install --force # Reinstall hooks +``` + +**Context not loading?** +```bash +claude-mem load-context # Verify memories exist +claude-mem logs # Check for errors +``` + +**Performance issues?** +```bash +# ChromaDB maintenance (if needed) +claude-mem status # Check memory usage +``` + +## πŸ”§ Requirements + +- **Node.js**: 18.0 or higher +- **Claude Code**: Latest version recommended +- **Storage**: ~100MB for typical usage +- **Memory**: 2GB RAM minimum for large knowledge bases + +## πŸ“ž Support & Community + +- **Documentation**: Complete guides at [claude-mem.ai/docs](https://claude-mem.ai/docs) +- **Issues**: Report bugs at [GitHub Issues](https://github.com/thedotmack/claude-mem/issues) +- **Feature Requests**: [GitHub Discussions](https://github.com/thedotmack/claude-mem/discussions) +- **Community**: Join our [Discord](https://discord.gg/claude-mem) for tips and discussions + +## πŸ“„ License + +This software is free to use but is NOT open source. See [LICENSE](LICENSE) file for complete terms. + +--- + +## 🎯 Ready to Transform Your Claude Code Experience? + +```bash +npm install -g claude-mem +claude-mem install +``` + +**Your AI assistant is about to get a lot smarter.** 🧠✨ + +--- + +*Built with ❀️ for developers who believe AI assistants should remember and learn from every conversation.* \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..d9edc49b --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,40 @@ +# Claude Mem v3.3.8 + +## πŸš€ Installation + +### Quick Install (Recommended) +```bash +curl -fsSL https://raw.githubusercontent.com/thedotmack/claude-mem/main/install.sh | bash +``` + +### Manual Download +Download the appropriate binary for your platform from the [releases page](https://github.com/thedotmack/claude-mem/releases/latest): + +- **Windows x64**: `claude-mem.exe` +- **Linux x64**: `claude-mem-linux` +- **Linux ARM64**: `claude-mem-linux-arm64` +- **macOS ARM64** (Apple Silicon): `claude-mem-macos-arm64` +- **macOS x64** (Intel): `claude-mem-macos-x64` + +## πŸ“¦ What's Included + +- Multi-platform binaries for Windows, Linux, and macOS +- Hook system for customization +- Full documentation +- MCP server integration for Claude Code + +## πŸ”§ Supported Platforms + +| Platform | Architecture | Binary Name | +|----------|--------------|-------------| +| Windows | x64 | claude-mem.exe | +| Linux | x64 | claude-mem-linux | +| Linux | ARM64 | claude-mem-linux-arm64 | +| macOS | ARM64 (Apple Silicon) | claude-mem-macos-arm64 | +| macOS | x64 (Intel) | claude-mem-macos-x64 | + +## πŸ“ License + +Binary distribution under proprietary license (free to use). +Hook files under MIT license (open source). +See LICENSE file for details. diff --git a/docs/multi-platform-builds.md b/docs/multi-platform-builds.md new file mode 100644 index 00000000..343ee213 --- /dev/null +++ b/docs/multi-platform-builds.md @@ -0,0 +1,96 @@ +# Multi-Platform Build Guide + +This project now supports building binaries for multiple platforms using Bun's cross-compilation capabilities. + +## Supported Platforms + +- **Windows x64**: `claude-mem.exe` +- **Linux x64**: `claude-mem-linux` +- **Linux ARM64**: `claude-mem-linux-arm64` +- **macOS ARM64**: `claude-mem-macos-arm64` +- **macOS x64**: `claude-mem-macos-x64` + +## Building + +### Build All Platforms + +To build binaries for all supported platforms: + +```bash +npm run build:multiplatform +``` + +This will create binaries in the `releases/binaries/` directory. + +### Build for NPM Package + +To build a complete npm package with all platform binaries: + +```bash +npm run publish +``` + +This creates a package in `releases/npm-package/` that includes: +- Platform detection wrapper script +- All platform-specific binaries +- Hooks and configuration files + +## How Platform Detection Works + +The npm package includes a Node.js wrapper script (`claude-mem`) that: + +1. Detects the current platform using `process.platform` and `process.arch` +2. Maps the platform to the appropriate binary filename +3. Executes the correct binary with all command-line arguments + +### Platform Mapping + +| Platform | Architecture | Binary Filename | +|----------|-------------|------------------| +| Windows | x64 | `claude-mem.exe` | +| Linux | x64 | `claude-mem-linux` | +| Linux | arm64/aarch64 | `claude-mem-linux-arm64` | +| macOS | arm64 | `claude-mem-macos-arm64` | +| macOS | x64 | `claude-mem-macos-x64` | + +## Usage + +After installation via npm, users can run: + +```bash +npx claude-mem --help +``` + +The wrapper will automatically select and execute the correct binary for their platform. + +## Troubleshooting + +### Unsupported Platform Error + +If you see an "Unsupported platform" error, check that your platform/architecture combination is in the supported list above. + +### Binary Not Found Error + +This indicates the platform detection worked, but the expected binary file is missing from the package. This shouldn't happen with properly built packages. + +## Development + +### Adding New Platforms + +To add support for new platforms: + +1. Add the platform to the `PLATFORMS` array in `scripts/build-multiplatform.sh` +2. Update the platform detection logic in `scripts/claude-mem-wrapper.js` +3. Update this documentation + +### Testing Binaries + +Test that a specific binary works: + +```bash +# Test Linux binary +./releases/binaries/claude-mem-linux --help + +# Test Windows binary (on Windows or with Wine) +./releases/binaries/claude-mem.exe --help +``` \ No newline at end of file diff --git a/docs/reference/bun-single-executable.md b/docs/reference/bun-single-executable.md new file mode 100644 index 00000000..1fc4e1d1 --- /dev/null +++ b/docs/reference/bun-single-executable.md @@ -0,0 +1,584 @@ +Bun's bundler implements a `--compile` flag for generating a standalone binary from a TypeScript or JavaScript file. + +{% codetabs %} + +```bash +$ bun build ./cli.ts --compile --outfile mycli +``` + +```ts#cli.ts +console.log("Hello world!"); +``` + +{% /codetabs %} + +This bundles `cli.ts` into an executable that can be executed directly: + +``` +$ ./mycli +Hello world! +``` + +All imported files and packages are bundled into the executable, along with a copy of the Bun runtime. All built-in Bun and Node.js APIs are supported. + +## Cross-compile to other platforms + +The `--target` flag lets you compile your standalone executable for a different operating system, architecture, or version of Bun than the machine you're running `bun build` on. + +To build for Linux x64 (most servers): + +```sh +bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp + +# To support CPUs from before 2013, use the baseline version (nehalem) +bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp + +# To explicitly only support CPUs from 2013 and later, use the modern version (haswell) +# modern is faster, but baseline is more compatible. +bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp +``` + +To build for Linux ARM64 (e.g. Graviton or Raspberry Pi): + +```sh +# Note: the default architecture is x64 if no architecture is specified. +bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp +``` + +To build for Windows x64: + +```sh +bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp + +# To support CPUs from before 2013, use the baseline version (nehalem) +bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp + +# To explicitly only support CPUs from 2013 and later, use the modern version (haswell) +bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp + +# note: if no .exe extension is provided, Bun will automatically add it for Windows executables +``` + +To build for macOS arm64: + +```sh +bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp +``` + +To build for macOS x64: + +```sh +bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp +``` + +#### Supported targets + +The order of the `--target` flag does not matter, as long as they're delimited by a `-`. + +| --target | Operating System | Architecture | Modern | Baseline | Libc | +| --------------------- | ---------------- | ------------ | ------ | -------- | ----- | +| bun-linux-x64 | Linux | x64 | βœ… | βœ… | glibc | +| bun-linux-arm64 | Linux | arm64 | βœ… | N/A | glibc | +| bun-windows-x64 | Windows | x64 | βœ… | βœ… | - | +| ~~bun-windows-arm64~~ | Windows | arm64 | ❌ | ❌ | - | +| bun-darwin-x64 | macOS | x64 | βœ… | βœ… | - | +| bun-darwin-arm64 | macOS | arm64 | βœ… | N/A | - | +| bun-linux-x64-musl | Linux | x64 | βœ… | βœ… | musl | +| bun-linux-arm64-musl | Linux | arm64 | βœ… | N/A | musl | + +On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version. + +## Build-time constants + +Use the `--define` flag to inject build-time constants into your executable, such as version numbers, build timestamps, or configuration values: + +```bash +$ bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli +``` + +These constants are embedded directly into your compiled binary at build time, providing zero runtime overhead and enabling dead code elimination optimizations. + +{% callout type="info" %} +For comprehensive examples and advanced patterns, see the [Build-time constants guide](/guides/runtime/build-time-constants). +{% /callout %} + +## Deploying to production + +Compiled executables reduce memory usage and improve Bun's start time. + +Normally, Bun reads and transpiles JavaScript and TypeScript files on `import` and `require`. This is part of what makes so much of Bun "just work", but it's not free. It costs time and memory to read files from disk, resolve file paths, parse, transpile, and print source code. + +With compiled executables, you can move that cost from runtime to build-time. + +When deploying to production, we recommend the following: + +```sh +bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp +``` + +### Bytecode compilation + +To improve startup time, enable bytecode compilation: + +```sh +bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp +``` + +Using bytecode compilation, `tsc` starts 2x faster: + +{% image src="https://github.com/user-attachments/assets/dc8913db-01d2-48f8-a8ef-ac4e984f9763" width="689" /%} + +Bytecode compilation moves parsing overhead for large input files from runtime to bundle time. Your app starts faster, in exchange for making the `bun build` command a little slower. It doesn't obscure source code. + +**Experimental:** Bytecode compilation is an experimental feature introduced in Bun v1.1.30. Only `cjs` format is supported (which means no top-level-await). Let us know if you run into any issues! + +### What do these flags do? + +The `--minify` argument optimizes the size of the transpiled output code. If you have a large application, this can save megabytes of space. For smaller applications, it might still improve start time a little. + +The `--sourcemap` argument embeds a sourcemap compressed with zstd, so that errors & stacktraces point to their original locations instead of the transpiled location. Bun will automatically decompress & resolve the sourcemap when an error occurs. + +The `--bytecode` argument enables bytecode compilation. Every time you run JavaScript code in Bun, JavaScriptCore (the engine) will compile your source code into bytecode. We can move this parsing work from runtime to bundle time, saving you startup time. + +## Act as the Bun CLI + +{% note %} + +New in Bun v1.2.16 + +{% /note %} + +You can run a standalone executable as if it were the `bun` CLI itself by setting the `BUN_BE_BUN=1` environment variable. When this variable is set, the executable will ignore its bundled entrypoint and instead expose all the features of Bun's CLI. + +For example, consider an executable compiled from a simple script: + +```sh +$ cat such-bun.js +console.log("you shouldn't see this"); + +$ bun build --compile ./such-bun.js + [3ms] bundle 1 modules +[89ms] compile such-bun +``` + +Normally, running `./such-bun` with arguments would execute the script. However, with the `BUN_BE_BUN=1` environment variable, it acts just like the `bun` binary: + +```sh +# Executable runs its own entrypoint by default +$ ./such-bun install +you shouldn't see this + +# With the env var, the executable acts like the `bun` CLI +$ BUN_BE_BUN=1 ./such-bun install +bun install v1.2.16-canary.1 (1d1db811) +Checked 63 installs across 64 packages (no changes) [5.00ms] +``` + +This is useful for building CLI tools on top of Bun that may need to install packages, bundle dependencies, run different or local files and more without needing to download a separate binary or install bun. + +## Full-stack executables + +{% note %} + +New in Bun v1.2.17 + +{% /note %} + +Bun's `--compile` flag can create standalone executables that contain both server and client code, making it ideal for full-stack applications. When you import an HTML file in your server code, Bun automatically bundles all frontend assets (JavaScript, CSS, etc.) and embeds them into the executable. When Bun sees the HTML import on the server, it kicks off a frontend build process to bundle JavaScript, CSS, and other assets. + +{% codetabs %} + +```ts#server.ts +import { serve } from "bun"; +import index from "./index.html"; + +const server = serve({ + routes: { + "/": index, + "/api/hello": { GET: () => Response.json({ message: "Hello from API" }) }, + }, +}); + +console.log(`Server running at http://localhost:${server.port}`); +``` + +```html#index.html + + + + My App + + + +

Hello World

+ + + +``` + +```js#app.js +console.log("Hello from the client!"); +``` + +```css#styles.css +body { + background-color: #f0f0f0; +} +``` + +{% /codetabs %} + +To build this into a single executable: + +```sh +bun build --compile ./server.ts --outfile myapp +``` + +This creates a self-contained binary that includes: + +- Your server code +- The Bun runtime +- All frontend assets (HTML, CSS, JavaScript) +- Any npm packages used by your server + +The result is a single file that can be deployed anywhere without needing Node.js, Bun, or any dependencies installed. Just run: + +```sh +./myapp +``` + +Bun automatically handles serving the frontend assets with proper MIME types and cache headers. The HTML import is replaced with a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets. + +For more details on building full-stack applications with Bun, see the [full-stack guide](/docs/bundler/fullstack). + +## Worker + +To use workers in a standalone executable, add the worker's entrypoint to the CLI arguments: + +```sh +$ bun build --compile ./index.ts ./my-worker.ts --outfile myapp +``` + +Then, reference the worker in your code: + +```ts +console.log("Hello from Bun!"); + +// Any of these will work: +new Worker("./my-worker.ts"); +new Worker(new URL("./my-worker.ts", import.meta.url)); +new Worker(new URL("./my-worker.ts", import.meta.url).href); +``` + +As of Bun v1.1.25, when you add multiple entrypoints to a standalone executable, they will be bundled separately into the executable. + +In the future, we may automatically detect usages of statically-known paths in `new Worker(path)` and then bundle those into the executable, but for now, you'll need to add it to the shell command manually like the above example. + +If you use a relative path to a file not included in the standalone executable, it will attempt to load that path from disk relative to the current working directory of the process (and then error if it doesn't exist). + +## SQLite + +You can use `bun:sqlite` imports with `bun build --compile`. + +By default, the database is resolved relative to the current working directory of the process. + +```js +import db from "./my.db" with { type: "sqlite" }; + +console.log(db.query("select * from users LIMIT 1").get()); +``` + +That means if the executable is located at `/usr/bin/hello`, the user's terminal is located at `/home/me/Desktop`, it will look for `/home/me/Desktop/my.db`. + +``` +$ cd /home/me/Desktop +$ ./hello +``` + +## Embed assets & files + +Standalone executables support embedding files. + +To embed files into an executable with `bun build --compile`, import the file in your code. + +```ts +// this becomes an internal file path +import icon from "./icon.png" with { type: "file" }; +import { file } from "bun"; + +export default { + fetch(req) { + // Embedded files can be streamed from Response objects + return new Response(file(icon)); + }, +}; +``` + +Embedded files can be read using `Bun.file`'s functions or the Node.js `fs.readFile` function (in `"node:fs"`). + +For example, to read the contents of the embedded file: + +```js +import icon from "./icon.png" with { type: "file" }; +import { file } from "bun"; + +const bytes = await file(icon).arrayBuffer(); +// await fs.promises.readFile(icon) +// fs.readFileSync(icon) +``` + +### Embed SQLite databases + +If your application wants to embed a SQLite database, set `type: "sqlite"` in the import attribute and the `embed` attribute to `"true"`. + +```js +import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" }; + +console.log(myEmbeddedDb.query("select * from users LIMIT 1").get()); +``` + +This database is read-write, but all changes are lost when the executable exits (since it's stored in memory). + +### Embed N-API Addons + +As of Bun v1.0.23, you can embed `.node` files into executables. + +```js +const addon = require("./addon.node"); + +console.log(addon.hello()); +``` + +Unfortunately, if you're using `@mapbox/node-pre-gyp` or other similar tools, you'll need to make sure the `.node` file is directly required or it won't bundle correctly. + +### Embed directories + +To embed a directory with `bun build --compile`, use a shell glob in your `bun build` command: + +```sh +$ bun build --compile ./index.ts ./public/**/*.png +``` + +Then, you can reference the files in your code: + +```ts +import icon from "./public/assets/icon.png" with { type: "file" }; +import { file } from "bun"; + +export default { + fetch(req) { + // Embedded files can be streamed from Response objects + return new Response(file(icon)); + }, +}; +``` + +This is honestly a workaround, and we expect to improve this in the future with a more direct API. + +### Listing embedded files + +To get a list of all embedded files, use `Bun.embeddedFiles`: + +```js +import "./icon.png" with { type: "file" }; +import { embeddedFiles } from "bun"; + +console.log(embeddedFiles[0].name); // `icon-${hash}.png` +``` + +`Bun.embeddedFiles` returns an array of `Blob` objects which you can use to get the size, contents, and other properties of the files. + +```ts +embeddedFiles: Blob[] +``` + +The list of embedded files excludes bundled source code like `.ts` and `.js` files. + +#### Content hash + +By default, embedded files have a content hash appended to their name. This is useful for situations where you want to serve the file from a URL or CDN and have fewer cache invalidation issues. But sometimes, this is unexpected and you might want the original name instead: + +To disable the content hash, pass `--asset-naming` to `bun build --compile` like this: + +```sh +$ bun build --compile --asset-naming="[name].[ext]" ./index.ts +``` + +## Minification + +To trim down the size of the executable a little, pass `--minify` to `bun build --compile`. This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller. + +## Using Bun.build() API + +You can also generate standalone executables using the `Bun.build()` JavaScript API. This is useful when you need programmatic control over the build process. + +### Basic usage + +```js +await Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + compile: { + target: "bun-windows-x64", + outfile: "myapp.exe", + }, +}); +``` + +### Windows metadata with Bun.build() + +When targeting Windows, you can specify metadata through the `windows` object: + +```js +await Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + compile: { + target: "bun-windows-x64", + outfile: "myapp.exe", + windows: { + title: "My Application", + publisher: "My Company Inc", + version: "1.2.3.4", + description: "A powerful application built with Bun", + copyright: "Β© 2024 My Company Inc", + hideConsole: false, // Set to true for GUI applications + icon: "./icon.ico", // Path to icon file + }, + }, +}); +``` + +### Cross-compilation with Bun.build() + +You can cross-compile for different platforms: + +```js +// Build for multiple platforms +const platforms = [ + { target: "bun-windows-x64", outfile: "app-windows.exe" }, + { target: "bun-linux-x64", outfile: "app-linux" }, + { target: "bun-darwin-arm64", outfile: "app-macos" }, +]; + +for (const platform of platforms) { + await Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + compile: platform, + }); +} +``` + +## Windows-specific flags + +When compiling a standalone executable for Windows, there are several platform-specific options that can be used to customize the generated `.exe` file: + +### Visual customization + +- `--windows-icon=path/to/icon.ico` - Set the executable file icon +- `--windows-hide-console` - Disable the background terminal window (useful for GUI applications) + +### Metadata customization + +You can embed version information and other metadata into your Windows executable: + +- `--windows-title ` - Set the product name (appears in file properties) +- `--windows-publisher ` - Set the company name +- `--windows-version ` - Set the version number (e.g. "1.2.3.4") +- `--windows-description ` - Set the file description +- `--windows-copyright ` - Set the copyright information + +#### Example with all metadata flags: + +```sh +bun build --compile ./app.ts \ + --outfile myapp.exe \ + --windows-title "My Application" \ + --windows-publisher "My Company Inc" \ + --windows-version "1.2.3.4" \ + --windows-description "A powerful application built with Bun" \ + --windows-copyright "Β© 2024 My Company Inc" +``` + +This metadata will be visible in Windows Explorer when viewing the file properties: + +1. Right-click the executable in Windows Explorer +2. Select "Properties" +3. Go to the "Details" tab + +#### Version string format + +The `--windows-version` flag accepts version strings in the following formats: + +- `"1"` - Will be normalized to "1.0.0.0" +- `"1.2"` - Will be normalized to "1.2.0.0" +- `"1.2.3"` - Will be normalized to "1.2.3.0" +- `"1.2.3.4"` - Full version format + +Each version component must be a number between 0 and 65535. + +{% callout %} + +These flags currently cannot be used when cross-compiling because they depend on Windows APIs. They are only available when building on Windows itself. + +{% /callout %} + +## Code signing on macOS + +To codesign a standalone executable on macOS (which fixes Gatekeeper warnings), use the `codesign` command. + +```sh +$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp +``` + +We recommend including an `entitlements.plist` file with JIT permissions. + +```xml#entitlements.plist + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + +``` + +To codesign with JIT support, pass the `--entitlements` flag to `codesign`. + +```sh +$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp +``` + +After codesigning, verify the executable: + +```sh +$ codesign -vvv --verify ./myapp +./myapp: valid on disk +./myapp: satisfies its Designated Requirement +``` + +{% callout %} + +Codesign support requires Bun v1.2.4 or newer. + +{% /callout %} + +## Unsupported CLI arguments + +Currently, the `--compile` flag can only accept a single entrypoint at a time and does not support the following flags: + +- `--outdir` β€” use `outfile` instead. +- `--splitting` +- `--public-path` +- `--target=node` or `--target=browser` +- `--no-bundle` - we always bundle everything into the executable. diff --git a/docs/reference/cc-output-styles.md b/docs/reference/cc-output-styles.md new file mode 100644 index 00000000..ed823082 --- /dev/null +++ b/docs/reference/cc-output-styles.md @@ -0,0 +1,99 @@ +# Output styles + +> Adapt Claude Code for uses beyond software engineering + +Output styles allow you to use Claude Code as any type of agent while keeping +its core capabilities, such as running local scripts, reading/writing files, and +tracking TODOs. + +## Built-in output styles + +Claude Code's **Default** output style is the existing system prompt, designed +to help you complete software engineering tasks efficiently. + +There are two additional built-in output styles focused on teaching you the +codebase and how Claude operates: + +* **Explanatory**: Provides educational "Insights" in between helping you + complete software engineering tasks. Helps you understand implementation + choices and codebase patterns. + +* **Learning**: Collaborative, learn-by-doing mode where Claude will not only + share "Insights" while coding, but also ask you to contribute small, strategic + pieces of code yourself. Claude Code will add `TODO(human)` markers in your + code for you to implement. + +## How output styles work + +Output styles directly modify Claude Code's system prompt. + +* Non-default output styles exclude instructions specific to code generation and + efficient output normally built into Claude Code (such as responding concisely + and verifying code with tests). +* Instead, these output styles have their own custom instructions added to the + system prompt. + +## Change your output style + +You can either: + +* Run `/output-style` to access the menu and select your output style (this can + also be accessed from the `/config` menu) + +* Run `/output-style [style]`, such as `/output-style explanatory`, to directly + switch to a style + +These changes apply to the [local project level](/en/docs/claude-code/settings) +and are saved in `.claude/settings.local.json`. + +## Create a custom output style + +To set up a new output style with Claude's help, run +`/output-style:new I want an output style that ...` + +By default, output styles created through `/output-style:new` are saved as +markdown files at the user level in `~/.claude/output-styles` and can be used +across projects. They have the following structure: + +```markdown +--- +name: My Custom Style +description: + A brief description of what this style does, to be displayed to the user +--- + +# Custom Style Instructions + +You are an interactive CLI tool that helps users with software engineering +tasks. [Your custom instructions here...] + +## Specific Behaviors + +[Define how the assistant should behave in this style...] +``` + +You can also create your own output style Markdown files and save them either at +the user level (`~/.claude/output-styles`) or the project level +(`.claude/output-styles`). + +## Comparisons to related features + +### Output Styles vs. CLAUDE.md vs. --append-system-prompt + +Output styles completely β€œturn off” the parts of Claude Code’s default system +prompt specific to software engineering. Neither CLAUDE.md nor +`--append-system-prompt` edit Claude Code’s default system prompt. CLAUDE.md +adds the contents as a user message *following* Claude Code’s default system +prompt. `--append-system-prompt` appends the content to the system prompt. + +### Output Styles vs. [Agents](/en/docs/claude-code/sub-agents) + +Output styles directly affect the main agent loop and only affect the system +prompt. Agents are invoked to handle specific tasks and can include additional +settings like the model to use, the tools they have available, and some context +about when to use the agent. + +### Output Styles vs. [Custom Slash Commands](/en/docs/claude-code/slash-commands) + +You can think of output styles as β€œstored system prompts” and custom slash +commands as β€œstored prompts”. diff --git a/docs/reference/chroma-mcp-project-memory-example.md b/docs/reference/chroma-mcp-project-memory-example.md new file mode 100644 index 00000000..70b103fb --- /dev/null +++ b/docs/reference/chroma-mcp-project-memory-example.md @@ -0,0 +1,80 @@ +### Project Memory Example + +Claude's context window has limits - long conversations eventually get truncated, and chats don't persist between sessions. Using Chroma as an external memory store solves these limitations, allowing Claude to reference past conversations and maintain context across multiple sessions. + +First, tell Claude to use Chroma for memory as part of the project setup: +``` +Remember, you have access to Chroma tools. +At any point if the user references previous chats or memory, check chroma for similar conversations. +Try to use retrieved information where possible. +``` + +This prompt instructs Claude to: +- Proactively check Chroma when memory-related topics come up +- Search for semantically similar past conversations +- Incorporate relevant historical context into responses + +To store the current conversation: + +``` +Please chunk our conversation into small chunks and store it in Chroma for future reference. +``` + +or + +``` +can you chunk our entire conversation into small, embeddable text chunks (not the code, but describe it so you can recreate it if necessary). no longer than a couple lines each. then, add it to the chroma collection for this project. +``` + +Claude will: +1. Break the conversation into smaller chunks (typically 512-1024 tokens) + - Chunking is necessary because: + - Large texts are harder to search semantically + - Smaller chunks help retrieve more precise context + - It prevents token limits in future retrievals +2. Generate embeddings for each chunk +3. Add metadata like timestamps and detected topics +4. Store everything in your Chroma collection + +Later, you can access past conversations naturally: +``` +What did we discuss previously about the authentication system? +``` + +Claude will: +1. Search Chroma for chunks semantically related to authentication +2. Filter by timestamp metadata for last week's discussions +3. Incorporate the relevant historical context into its response + +This setup is particularly useful for: +- Long-running projects where context gets lost +- Teams where multiple people interact with Claude +- Complex discussions that reference past decisions +- Maintaining consistent context across multiple chat sessions + +### Advanced Features + +The Chroma MCP server supports: + +- **Collection Management**: Create and organize separate collections for different projects +- **Document Operations**: Add, update, or delete documents +- **Search Capabilities**: + - Vector similarity search + - Keyword-based search + - Metadata filtering +- **Batch Processing**: Efficient handling of multiple operations + +## Troubleshooting + +If you encounter issues: + +1. Verify your configuration file syntax +2. Ensure all paths are absolute and valid +3. Try using full paths for `uvx` with `which uvx` and using that path in the config +4. Check the Claude logs (paths listed above) + +## Resources + +- [Model Context Protocol Documentation](https://modelcontextprotocol.io/introduction) +- [Chroma MCP Server Documentation](https://github.com/chroma-core/chroma-mcp) +- [Claude Desktop Guide](https://docs.anthropic.com/claude/docs/claude-desktop) \ No newline at end of file diff --git a/docs/reference/chroma-mcp-team-example.md b/docs/reference/chroma-mcp-team-example.md new file mode 100644 index 00000000..1a2df8dd --- /dev/null +++ b/docs/reference/chroma-mcp-team-example.md @@ -0,0 +1,92 @@ +### Team Knowledge Base Example + +Let's say your team maintains a knowledge base of customer support interactions. By storing these in Chroma Cloud, team members can use Claude to quickly access and learn from past support cases. + +First, set up your shared knowledge base: + +```python +import chromadb +from datetime import datetime + +# Connect to Chroma Cloud +client = chromadb.HttpClient( + ssl=True, + host='api.trychroma.com', + tenant='your-tenant-id', + database='support-kb', + headers={ + 'x-chroma-token': 'YOUR_API_KEY' + } +) + +# Create a collection for support cases +collection = client.create_collection("support_cases") + +# Add some example support cases +support_cases = [ + { + "case": "Customer reported issues connecting their IoT devices to the dashboard.", + "resolution": "Guided customer through firewall configuration and port forwarding setup.", + "category": "connectivity", + "date": "2024-03-15" + }, + { + "case": "User couldn't access admin features after recent update.", + "resolution": "Discovered role permissions weren't migrated correctly. Applied fix and documented process.", + "category": "permissions", + "date": "2024-03-16" + } +] + +# Add documents to collection +collection.add( + documents=[case["case"] + "\n" + case["resolution"] for case in support_cases], + metadatas=[{ + "category": case["category"], + "date": case["date"] + } for case in support_cases], + ids=[f"case_{i}" for i in range(len(support_cases))] +) +``` + +Now team members can use Claude to access this knowledge. + +In your claude config, add the following: +```json +{ + "mcpServers": { + "chroma": { + "command": "uvx", + "args": [ + "chroma-mcp", + "--client-type", + "cloud", + "--tenant", + "your-tenant-id", + "--database", + "support-kb", + "--api-key", + "YOUR_API_KEY" + ] + } + } +} +``` + +Now you can use the knowledge base in your chats: +``` +Claude, I'm having trouble helping a customer with IoT device connectivity. +Can you check our support knowledge base for similar cases and suggest a solution? +``` + +Claude will: +1. Search the shared knowledge base for relevant cases +2. Consider the context and solutions from similar past issues +3. Provide recommendations based on previous successful resolutions + +This setup is particularly powerful because: +- All support team members have access to the same knowledge base +- Claude can learn from the entire team's experience +- Solutions are standardized across the organization +- New team members can quickly get up to speed on common issues + diff --git a/docs/reference/claude-code/cc-hooks.md b/docs/reference/claude-code/cc-hooks.md new file mode 100644 index 00000000..8a0e76de --- /dev/null +++ b/docs/reference/claude-code/cc-hooks.md @@ -0,0 +1,787 @@ +# Hooks reference + +> This page provides reference documentation for implementing hooks in Claude Code. + + + For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/docs/claude-code/hooks-guide). + + +## Configuration + +Claude Code hooks are configured in your [settings files](/en/docs/claude-code/settings): + +* `~/.claude/settings.json` - User settings +* `.claude/settings.json` - Project settings +* `.claude/settings.local.json` - Local project settings (not committed) +* Enterprise managed policy settings + +### Structure + +Hooks are organized by matchers, where each matcher can have multiple hooks: + +```json +{ + "hooks": { + "EventName": [ + { + "matcher": "ToolPattern", + "hooks": [ + { + "type": "command", + "command": "your-command-here" + } + ] + } + ] + } +} +``` + +* **matcher**: Pattern to match tool names, case-sensitive (only applicable for + `PreToolUse` and `PostToolUse`) + * Simple strings match exactly: `Write` matches only the Write tool + * Supports regex: `Edit|Write` or `Notebook.*` + * Use `*` to match all tools. You can also use empty string (`""`) or leave + `matcher` blank. +* **hooks**: Array of commands to execute when the pattern matches + * `type`: Currently only `"command"` is supported + * `command`: The bash command to execute (can use `$CLAUDE_PROJECT_DIR` + environment variable) + * `timeout`: (Optional) How long a command should run, in seconds, before + canceling that specific command. + +For events like `UserPromptSubmit`, `Notification`, `Stop`, and `SubagentStop` +that don't use matchers, you can omit the matcher field: + +```json +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "/path/to/prompt-validator.py" + } + ] + } + ] + } +} +``` + +### Project-Specific Hook Scripts + +You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when +Claude Code spawns the hook command) to reference scripts stored in your project, +ensuring they work regardless of Claude's current directory: + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-style.sh" + } + ] + } + ] + } +} +``` + +## Hook Events + +### PreToolUse + +Runs after Claude creates tool parameters and before processing the tool call. + +**Common matchers:** + +* `Task` - Subagent tasks (see [subagents documentation](/en/docs/claude-code/sub-agents)) +* `Bash` - Shell commands +* `Glob` - File pattern matching +* `Grep` - Content search +* `Read` - File reading +* `Edit`, `MultiEdit` - File editing +* `Write` - File writing +* `WebFetch`, `WebSearch` - Web operations + +### PostToolUse + +Runs immediately after a tool completes successfully. + +Recognizes the same matcher values as PreToolUse. + +### Notification + +Runs when Claude Code sends notifications. Notifications are sent when: + +1. Claude needs your permission to use a tool. Example: "Claude needs your + permission to use Bash" +2. The prompt input has been idle for at least 60 seconds. "Claude is waiting + for your input" + +### UserPromptSubmit + +Runs when the user submits a prompt, before Claude processes it. This allows you +to add additional context based on the prompt/conversation, validate prompts, or +block certain types of prompts. + +### Stop + +Runs when the main Claude Code agent has finished responding. Does not run if +the stoppage occurred due to a user interrupt. + +### SubagentStop + +Runs when a Claude Code subagent (Task tool call) has finished responding. + +### SessionEnd + +Runs when a Claude Code session ends. Useful for cleanup tasks, logging session +statistics, or saving session state. + +The `reason` field in the hook input will be one of: + +* `clear` - Session cleared with /clear command +* `logout` - User logged out +* `prompt_input_exit` - User exited while prompt input was visible +* `other` - Other exit reasons + +### PreCompact + +Runs before Claude Code is about to run a compact operation. + +**Matchers:** + +* `manual` - Invoked from `/compact` +* `auto` - Invoked from auto-compact (due to full context window) + +### SessionStart + +Runs when Claude Code starts a new session or resumes an existing session (which +currently does start a new session under the hood). Useful for loading in +development context like existing issues or recent changes to your codebase. + +**Matchers:** + +* `startup` - Invoked from startup +* `resume` - Invoked from `--resume`, `--continue`, or `/resume` +* `clear` - Invoked from `/clear` + +## Hook Input + +Hooks receive JSON data via stdin containing session information and +event-specific data: + +```typescript +{ + // Common fields + session_id: string + transcript_path: string // Path to conversation JSON + cwd: string // The current working directory when the hook is invoked + + // Event-specific fields + hook_event_name: string + ... +} +``` + +### PreToolUse Input + +The exact schema for `tool_input` depends on the tool. + +```json +{ + "session_id": "abc123", + "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "cwd": "/Users/...", + "hook_event_name": "PreToolUse", + "tool_name": "Write", + "tool_input": { + "file_path": "/path/to/file.txt", + "content": "file content" + } +} +``` + +### PostToolUse Input + +The exact schema for `tool_input` and `tool_response` depends on the tool. + +```json +{ + "session_id": "abc123", + "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "cwd": "/Users/...", + "hook_event_name": "PostToolUse", + "tool_name": "Write", + "tool_input": { + "file_path": "/path/to/file.txt", + "content": "file content" + }, + "tool_response": { + "filePath": "/path/to/file.txt", + "success": true + } +} +``` + +### Notification Input + +```json +{ + "session_id": "abc123", + "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "cwd": "/Users/...", + "hook_event_name": "Notification", + "message": "Task completed successfully" +} +``` + +### UserPromptSubmit Input + +```json +{ + "session_id": "abc123", + "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "cwd": "/Users/...", + "hook_event_name": "UserPromptSubmit", + "prompt": "Write a function to calculate the factorial of a number" +} +``` + +### Stop and SubagentStop Input + +`stop_hook_active` is true when Claude Code is already continuing as a result of +a stop hook. Check this value or process the transcript to prevent Claude Code +from running indefinitely. + +```json +{ + "session_id": "abc123", + "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "hook_event_name": "Stop", + "stop_hook_active": true +} +``` + +### PreCompact Input + +For `manual`, `custom_instructions` comes from what the user passes into +`/compact`. For `auto`, `custom_instructions` is empty. + +```json +{ + "session_id": "abc123", + "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "hook_event_name": "PreCompact", + "trigger": "manual", + "custom_instructions": "" +} +``` + +### SessionStart Input + +```json +{ + "session_id": "abc123", + "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "hook_event_name": "SessionStart", + "source": "startup" +} +``` + +### SessionEnd Input + +```json +{ + "session_id": "abc123", + "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl", + "cwd": "/Users/...", + "hook_event_name": "SessionEnd", + "reason": "exit" +} +``` + +## Hook Output + +There are two ways for hooks to return output back to Claude Code. The output +communicates whether to block and any feedback that should be shown to Claude +and the user. + +### Simple: Exit Code + +Hooks communicate status through exit codes, stdout, and stderr: + +* **Exit code 0**: Success. `stdout` is shown to the user in transcript mode + (CTRL-R), except for `UserPromptSubmit` and `SessionStart`, where stdout is + added to the context. +* **Exit code 2**: Blocking error. `stderr` is fed back to Claude to process + automatically. See per-hook-event behavior below. +* **Other exit codes**: Non-blocking error. `stderr` is shown to the user and + execution continues. + + + Reminder: Claude Code does not see stdout if the exit code is 0, except for + the `UserPromptSubmit` hook where stdout is injected as context. + + +#### Exit Code 2 Behavior + +| Hook Event | Behavior | +| ------------------ | ------------------------------------------------------------------ | +| `PreToolUse` | Blocks the tool call, shows stderr to Claude | +| `PostToolUse` | Shows stderr to Claude (tool already ran) | +| `Notification` | N/A, shows stderr to user only | +| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only | +| `Stop` | Blocks stoppage, shows stderr to Claude | +| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent | +| `PreCompact` | N/A, shows stderr to user only | +| `SessionStart` | N/A, shows stderr to user only | +| `SessionEnd` | N/A, shows stderr to user only | + +### Advanced: JSON Output + +Hooks can return structured JSON in `stdout` for more sophisticated control: + +#### Common JSON Fields + +All hook types can include these optional fields: + +```json +{ + "continue": true, // Whether Claude should continue after hook execution (default: true) + "stopReason": "string", // Message shown when continue is false + + "suppressOutput": true, // Hide stdout from transcript mode (default: false) + "systemMessage": "string" // Optional warning message shown to the user +} +``` + +If `continue` is false, Claude stops processing after the hooks run. + +* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which + only blocks a specific tool call and provides automatic feedback to Claude. +* For `PostToolUse`, this is different from `"decision": "block"`, which + provides automated feedback to Claude. +* For `UserPromptSubmit`, this prevents the prompt from being processed. +* For `Stop` and `SubagentStop`, this takes precedence over any + `"decision": "block"` output. +* In all cases, `"continue" = false` takes precedence over any + `"decision": "block"` output. + +`stopReason` accompanies `continue` with a reason shown to the user, not shown +to Claude. + +#### `PreToolUse` Decision Control + +`PreToolUse` hooks can control whether a tool call proceeds. + +* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown + to the user but not to Claude. +* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is + shown to Claude. +* `"ask"` asks the user to confirm the tool call in the UI. + `permissionDecisionReason` is shown to the user but not to Claude. + +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow" | "deny" | "ask", + "permissionDecisionReason": "My reason here" + } +} +``` + + + The `decision` and `reason` fields are deprecated for PreToolUse hooks. + Use `hookSpecificOutput.permissionDecision` and + `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields + `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. + + +#### `PostToolUse` Decision Control + +`PostToolUse` hooks can provide feedback to Claude after tool execution. + +* `"block"` automatically prompts Claude with `reason`. +* `undefined` does nothing. `reason` is ignored. +* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider. + +```json +{ + "decision": "block" | undefined, + "reason": "Explanation for decision", + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": "Additional information for Claude" + } +} +``` + +#### `UserPromptSubmit` Decision Control + +`UserPromptSubmit` hooks can control whether a user prompt is processed. + +* `"block"` prevents the prompt from being processed. The submitted prompt is + erased from context. `"reason"` is shown to the user but not added to context. +* `undefined` allows the prompt to proceed normally. `"reason"` is ignored. +* `"hookSpecificOutput.additionalContext"` adds the string to the context if not + blocked. + +```json +{ + "decision": "block" | undefined, + "reason": "Explanation for decision", + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": "My additional context here" + } +} +``` + +#### `Stop`/`SubagentStop` Decision Control + +`Stop` and `SubagentStop` hooks can control whether Claude must continue. + +* `"block"` prevents Claude from stopping. You must populate `reason` for Claude + to know how to proceed. +* `undefined` allows Claude to stop. `reason` is ignored. + +```json +{ + "decision": "block" | undefined, + "reason": "Must be provided when Claude is blocked from stopping" +} +``` + +#### `SessionStart` Decision Control + +`SessionStart` hooks allow you to load in context at the start of a session. + +* `"hookSpecificOutput.additionalContext"` adds the string to the context. +* Multiple hooks' `additionalContext` values are concatenated. + +```json +{ + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": "My additional context here" + } +} +``` + +#### `SessionEnd` Decision Control + +`SessionEnd` hooks run when a session ends. They cannot block session termination +but can perform cleanup tasks. + +#### Exit Code Example: Bash Command Validation + +```python +#!/usr/bin/env python3 +import json +import re +import sys + +# Define validation rules as a list of (regex pattern, message) tuples +VALIDATION_RULES = [ + ( + r"\bgrep\b(?!.*\|)", + "Use 'rg' (ripgrep) instead of 'grep' for better performance and features", + ), + ( + r"\bfind\s+\S+\s+-name\b", + "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance", + ), +] + + +def validate_command(command: str) -> list[str]: + issues = [] + for pattern, message in VALIDATION_RULES: + if re.search(pattern, command): + issues.append(message) + return issues + + +try: + input_data = json.load(sys.stdin) +except json.JSONDecodeError as e: + print(f"Error: Invalid JSON input: {e}", file=sys.stderr) + sys.exit(1) + +tool_name = input_data.get("tool_name", "") +tool_input = input_data.get("tool_input", {}) +command = tool_input.get("command", "") + +if tool_name != "Bash" or not command: + sys.exit(1) + +# Validate the command +issues = validate_command(command) + +if issues: + for message in issues: + print(f"β€’ {message}", file=sys.stderr) + # Exit code 2 blocks tool call and shows stderr to Claude + sys.exit(2) +``` + +#### JSON Output Example: UserPromptSubmit to Add Context and Validation + + + For `UserPromptSubmit` hooks, you can inject context using either method: + + * Exit code 0 with stdout: Claude sees the context (special case for `UserPromptSubmit`) + * JSON output: Provides more control over the behavior + + +```python +#!/usr/bin/env python3 +import json +import sys +import re +import datetime + +# Load input from stdin +try: + input_data = json.load(sys.stdin) +except json.JSONDecodeError as e: + print(f"Error: Invalid JSON input: {e}", file=sys.stderr) + sys.exit(1) + +prompt = input_data.get("prompt", "") + +# Check for sensitive patterns +sensitive_patterns = [ + (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"), +] + +for pattern, message in sensitive_patterns: + if re.search(pattern, prompt): + # Use JSON output to block with a specific reason + output = { + "decision": "block", + "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information." + } + print(json.dumps(output)) + sys.exit(0) + +# Add current time to context +context = f"Current time: {datetime.datetime.now()}" +print(context) + +""" +The following is also equivalent: +print(json.dumps({ + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": context, + }, +})) +""" + +# Allow the prompt to proceed with the additional context +sys.exit(0) +``` + +#### JSON Output Example: PreToolUse with Approval + +```python +#!/usr/bin/env python3 +import json +import sys + +# Load input from stdin +try: + input_data = json.load(sys.stdin) +except json.JSONDecodeError as e: + print(f"Error: Invalid JSON input: {e}", file=sys.stderr) + sys.exit(1) + +tool_name = input_data.get("tool_name", "") +tool_input = input_data.get("tool_input", {}) + +# Example: Auto-approve file reads for documentation files +if tool_name == "Read": + file_path = tool_input.get("file_path", "") + if file_path.endswith((".md", ".mdx", ".txt", ".json")): + # Use JSON output to auto-approve the tool call + output = { + "decision": "approve", + "reason": "Documentation file auto-approved", + "suppressOutput": True # Don't show in transcript mode + } + print(json.dumps(output)) + sys.exit(0) + +# For other cases, let the normal permission flow proceed +sys.exit(0) +``` + +## Working with MCP Tools + +Claude Code hooks work seamlessly with +[Model Context Protocol (MCP) tools](/en/docs/claude-code/mcp). When MCP servers +provide tools, they appear with a special naming pattern that you can match in +your hooks. + +### MCP Tool Naming + +MCP tools follow the pattern `mcp____`, for example: + +* `mcp__memory__create_entities` - Memory server's create entities tool +* `mcp__filesystem__read_file` - Filesystem server's read file tool +* `mcp__github__search_repositories` - GitHub server's search tool + +### Configuring Hooks for MCP Tools + +You can target specific MCP tools or entire MCP servers: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "mcp__memory__.*", + "hooks": [ + { + "type": "command", + "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log" + } + ] + }, + { + "matcher": "mcp__.*__write.*", + "hooks": [ + { + "type": "command", + "command": "/home/user/scripts/validate-mcp-write.py" + } + ] + } + ] + } +} +``` + +## Examples + + + For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/docs/claude-code/hooks-guide#more-examples) in the get started guide. + + +## Security Considerations + +### Disclaimer + +**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on +your system automatically. By using hooks, you acknowledge that: + +* You are solely responsible for the commands you configure +* Hooks can modify, delete, or access any files your user account can access +* Malicious or poorly written hooks can cause data loss or system damage +* Anthropic provides no warranty and assumes no liability for any damages + resulting from hook usage +* You should thoroughly test hooks in a safe environment before production use + +Always review and understand any hook commands before adding them to your +configuration. + +### Security Best Practices + +Here are some key practices for writing more secure hooks: + +1. **Validate and sanitize inputs** - Never trust input data blindly +2. **Always quote shell variables** - Use `"$VAR"` not `$VAR` +3. **Block path traversal** - Check for `..` in file paths +4. **Use absolute paths** - Specify full paths for scripts (use + `$CLAUDE_PROJECT_DIR` for the project path) +5. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc. + +### Configuration Safety + +Direct edits to hooks in settings files don't take effect immediately. Claude +Code: + +1. Captures a snapshot of hooks at startup +2. Uses this snapshot throughout the session +3. Warns if hooks are modified externally +4. Requires review in `/hooks` menu for changes to apply + +This prevents malicious hook modifications from affecting your current session. + +## Hook Execution Details + +* **Timeout**: 60-second execution limit by default, configurable per command. + * A timeout for an individual command does not affect the other commands. +* **Parallelization**: All matching hooks run in parallel +* **Deduplication**: Multiple identical hook commands are deduplicated automatically +* **Environment**: Runs in current directory with Claude Code's environment + * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the + absolute path to the project root directory (where Claude Code was started) +* **Input**: JSON via stdin +* **Output**: + * PreToolUse/PostToolUse/Stop/SubagentStop: Progress shown in transcript (Ctrl-R) + * Notification/SessionEnd: Logged to debug only (`--debug`) + * UserPromptSubmit/SessionStart: stdout added as context for Claude + +## Debugging + +### Basic Troubleshooting + +If your hooks aren't working: + +1. **Check configuration** - Run `/hooks` to see if your hook is registered +2. **Verify syntax** - Ensure your JSON settings are valid +3. **Test commands** - Run hook commands manually first +4. **Check permissions** - Make sure scripts are executable +5. **Review logs** - Use `claude --debug` to see hook execution details + +Common issues: + +* **Quotes not escaped** - Use `\"` inside JSON strings +* **Wrong matcher** - Check tool names match exactly (case-sensitive) +* **Command not found** - Use full paths for scripts + +### Advanced Debugging + +For complex hook issues: + +1. **Inspect hook execution** - Use `claude --debug` to see detailed hook + execution +2. **Validate JSON schemas** - Test hook input/output with external tools +3. **Check environment variables** - Verify Claude Code's environment is correct +4. **Test edge cases** - Try hooks with unusual file paths or inputs +5. **Monitor system resources** - Check for resource exhaustion during hook + execution +6. **Use structured logging** - Implement logging in your hook scripts + +### Debug Output Example + +Use `claude --debug` to see hook execution details: + +``` +[DEBUG] Executing hooks for PostToolUse:Write +[DEBUG] Getting matching hook commands for PostToolUse with query: Write +[DEBUG] Found 1 hook matchers in settings +[DEBUG] Matched 1 hooks for query "Write" +[DEBUG] Found 1 hook commands to execute +[DEBUG] Executing hook command: with timeout 60000ms +[DEBUG] Hook command completed with status 0: +``` + +Progress messages appear in transcript mode (Ctrl-R) showing: + +* Which hook is running +* Command being executed +* Success/failure status +* Output or error messages diff --git a/docs/reference/claude-code/cc-status-line.md b/docs/reference/claude-code/cc-status-line.md new file mode 100644 index 00000000..dfda839f --- /dev/null +++ b/docs/reference/claude-code/cc-status-line.md @@ -0,0 +1,202 @@ +# Status line configuration + +> Create a custom status line for Claude Code to display contextual information + +Make Claude Code your own with a custom status line that displays at the bottom of the Claude Code interface, similar to how terminal prompts (PS1) work in shells like Oh-my-zsh. + +## Create a custom status line + +You can either: + +* Run `/statusline` to ask Claude Code to help you set up a custom status line. By default, it will try to reproduce your terminal's prompt, but you can provide additional instructions about the behavior you want to Claude Code, such as `/statusline show the model name in orange` + +* Directly add a `statusLine` command to your `.claude/settings.json`: + +```json +{ + "statusLine": { + "type": "command", + "command": "~/.claude/statusline.sh", + "padding": 0 // Optional: set to 0 to let status line go to edge + } +} +``` + +## How it Works + +* The status line is updated when the conversation messages update +* Updates run at most every 300ms +* The first line of stdout from your command becomes the status line text +* ANSI color codes are supported for styling your status line +* Claude Code passes contextual information about the current session (model, directories, etc.) as JSON to your script via stdin + +## JSON Input Structure + +Your status line command receives structured data via stdin in JSON format: + +```json +{ + "hook_event_name": "Status", + "session_id": "abc123...", + "transcript_path": "/path/to/transcript.json", + "cwd": "/current/working/directory", + "model": { + "id": "claude-opus-4-1", + "display_name": "Opus" + }, + "workspace": { + "current_dir": "/current/working/directory", + "project_dir": "/original/project/directory" + }, + "version": "1.0.80", + "output_style": { + "name": "default" + }, + "cost": { + "total_cost_usd": 0.01234, + "total_duration_ms": 45000, + "total_api_duration_ms": 2300, + "total_lines_added": 156, + "total_lines_removed": 23 + } +} +``` + +## Example Scripts + +### Simple Status Line + +```bash +#!/bin/bash +# Read JSON input from stdin +input=$(cat) + +# Extract values using jq +MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name') +CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir') + +echo "[$MODEL_DISPLAY] πŸ“ ${CURRENT_DIR##*/}" +``` + +### Git-Aware Status Line + +```bash +#!/bin/bash +# Read JSON input from stdin +input=$(cat) + +# Extract values using jq +MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name') +CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir') + +# Show git branch if in a git repo +GIT_BRANCH="" +if git rev-parse --git-dir > /dev/null 2>&1; then + BRANCH=$(git branch --show-current 2>/dev/null) + if [ -n "$BRANCH" ]; then + GIT_BRANCH=" | 🌿 $BRANCH" + fi +fi + +echo "[$MODEL_DISPLAY] πŸ“ ${CURRENT_DIR##*/}$GIT_BRANCH" +``` + +### Python Example + +```python +#!/usr/bin/env python3 +import json +import sys +import os + +# Read JSON from stdin +data = json.load(sys.stdin) + +# Extract values +model = data['model']['display_name'] +current_dir = os.path.basename(data['workspace']['current_dir']) + +# Check for git branch +git_branch = "" +if os.path.exists('.git'): + try: + with open('.git/HEAD', 'r') as f: + ref = f.read().strip() + if ref.startswith('ref: refs/heads/'): + git_branch = f" | 🌿 {ref.replace('ref: refs/heads/', '')}" + except: + pass + +print(f"[{model}] πŸ“ {current_dir}{git_branch}") +``` + +### Node.js Example + +```javascript +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// Read JSON from stdin +let input = ''; +process.stdin.on('data', chunk => input += chunk); +process.stdin.on('end', () => { + const data = JSON.parse(input); + + // Extract values + const model = data.model.display_name; + const currentDir = path.basename(data.workspace.current_dir); + + // Check for git branch + let gitBranch = ''; + try { + const headContent = fs.readFileSync('.git/HEAD', 'utf8').trim(); + if (headContent.startsWith('ref: refs/heads/')) { + gitBranch = ` | 🌿 ${headContent.replace('ref: refs/heads/', '')}`; + } + } catch (e) { + // Not a git repo or can't read HEAD + } + + console.log(`[${model}] πŸ“ ${currentDir}${gitBranch}`); +}); +``` + +### Helper Function Approach + +For more complex bash scripts, you can create helper functions: + +```bash +#!/bin/bash +# Read JSON input once +input=$(cat) + +# Helper functions for common extractions +get_model_name() { echo "$input" | jq -r '.model.display_name'; } +get_current_dir() { echo "$input" | jq -r '.workspace.current_dir'; } +get_project_dir() { echo "$input" | jq -r '.workspace.project_dir'; } +get_version() { echo "$input" | jq -r '.version'; } +get_cost() { echo "$input" | jq -r '.cost.total_cost_usd'; } +get_duration() { echo "$input" | jq -r '.cost.total_duration_ms'; } +get_lines_added() { echo "$input" | jq -r '.cost.total_lines_added'; } +get_lines_removed() { echo "$input" | jq -r '.cost.total_lines_removed'; } + +# Use the helpers +MODEL=$(get_model_name) +DIR=$(get_current_dir) +echo "[$MODEL] πŸ“ ${DIR##*/}" +``` + +## Tips + +* Keep your status line concise - it should fit on one line +* Use emojis (if your terminal supports them) and colors to make information scannable +* Use `jq` for JSON parsing in Bash (see examples above) +* Test your script by running it manually with mock JSON input: `echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/test"}}' | ./statusline.sh` +* Consider caching expensive operations (like git status) if needed + +## Troubleshooting + +* If your status line doesn't appear, check that your script is executable (`chmod +x`) +* Ensure your script outputs to stdout (not stderr) diff --git a/docs/reference/claude-code/hook-configuration.md b/docs/reference/claude-code/hook-configuration.md new file mode 100644 index 00000000..b34cbbf0 --- /dev/null +++ b/docs/reference/claude-code/hook-configuration.md @@ -0,0 +1,173 @@ +# Claude Code Hook Configuration Documentation + +**LOCKED by @docs-agent | Change to πŸ”‘ to allow @docs-agent edits** + +## Official Documentation Reference + +- **Source**: Claude Code Hooks API Documentation +- **Version**: v2025 +- **Last Verified**: 2025-08-31 +- **Official URL**: https://docs.anthropic.com/en/docs/claude-code/hooks + +## Hook Configuration Structure + +### Two Categories of Hooks + +Claude Code hooks are divided into two distinct categories with different configuration structures: + +#### 1. Tool-Related Hooks +These hooks are triggered in relation to tool usage and require a `matcher` field: +- `PreToolUse`: Executed before a tool is invoked +- `PostToolUse`: Executed after a tool completes + +**Configuration Structure:** +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|MultiEdit|Write", + "hooks": [ + { + "type": "command", + "command": "/path/to/script.js", + "timeout": 60000 + } + ] + } + ] + } +} +``` + +#### 2. Non-Tool Hooks +These hooks are triggered by system events and **MUST NOT** have a `matcher` or `pattern` field: +- `PreCompact`: Before conversation compaction +- `SessionStart`: When a new session begins +- `SessionEnd`: When a session ends +- `UserPromptSubmit`: When user submits a prompt +- `Notification`: For system notifications +- `Stop`: When Claude is stopping +- `SubagentStop`: When a subagent is stopping + +**Configuration Structure:** +```json +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "/path/to/script.js", + "timeout": 30000 + } + ] + } + ] + } +} +``` + +## Common Configuration Mistakes + +### ❌ INCORRECT: Adding `pattern` field to non-tool hooks +```json +{ + "hooks": { + "PreCompact": [ + { + "pattern": "*", // WRONG: Non-tool hooks don't use patterns + "hooks": [...] + } + ] + } +} +``` + +### βœ… CORRECT: Non-tool hooks without matcher +```json +{ + "hooks": { + "PreCompact": [ + { + "hooks": [ + { + "type": "command", + "command": "/path/to/pre-compact.js", + "timeout": 180000 + } + ] + } + ] + } +} +``` + +## Hook Field Reference + +### Common Fields (All Hooks) +- `type`: Always `"command"` for external scripts +- `command`: Absolute path to the executable script +- `timeout`: Optional timeout in milliseconds (default: 60000) + +### Tool Hook Specific +- `matcher`: Regex pattern to match tool names + - Example: `"Edit|MultiEdit|Write"` + - Example: `"mcp__.*__write.*"` + - Example: `"Bash"` + +### Environment Variables Available to Hooks +- `$CLAUDE_PROJECT_DIR`: Project root directory +- Standard environment variables from the shell + +## Hook Input/Output + +### Input (via stdin) +All hooks receive JSON input with common fields: +```json +{ + "session_id": "string", + "transcript_path": "string", + "cwd": "string", + "hook_event_name": "string", + // Additional event-specific fields +} +``` + +### Output Options +Hooks can output: +1. **Plain text** (stdout): Added as context +2. **JSON** (stdout): Structured response for decisions +3. **Exit codes**: + - `0`: Success, continue normally + - `1`: General error + - `2`: Block operation (for PreToolUse) + +## Implementation Notes + +### File Locations +- User settings: `~/.claude/settings.json` +- Project settings: `./.claude/settings.json` +- Local settings: `./.claude/settings.local.json` + +### Settings Precedence (Highest to Lowest) +1. Enterprise managed policies +2. Command line arguments +3. Local project settings +4. Shared project settings +5. User settings + +## Cross-References + +- Code Implementation: `/Users/alexnewman/Scripts/claude-mem/src/commands/install.ts:263-320` +- Hook Files: `/Users/alexnewman/Scripts/claude-mem/hooks/` +- User Guide: `/Users/alexnewman/Scripts/claude-mem/README-npm.md` + +## Version History + +- **2025-08-31**: Fixed hook configuration to remove incorrect `pattern` field from non-tool hooks +- **2025-08-31**: Documented official hook structure requirements per Claude Code API + +--- +*This documentation is maintained by @docs-agent and verified against official Anthropic documentation.* \ No newline at end of file diff --git a/docs/reference/claude-code/hook-responses.md b/docs/reference/claude-code/hook-responses.md new file mode 100644 index 00000000..3297a8d3 --- /dev/null +++ b/docs/reference/claude-code/hook-responses.md @@ -0,0 +1,127 @@ +# Claude Code Hook Response Format Documentation +## Source: Official Claude Code Docs v2025 +## Last Verified: 2025-08-31 + +## Common Hook Response Fields + +All hooks can return these common fields: + +```json +{ + "continue": true, // Whether Claude should continue (default: true) + "stopReason": "string", // Message shown when continue is false + "suppressOutput": true, // Hide stdout from transcript (default: false) + "systemMessage": "string" // Optional warning message shown to user +} +``` + +## Hook-Specific Response Formats + +### PreCompact Hook +**IMPORTANT**: PreCompact does NOT support `hookSpecificOutput` + +```json +{ + "continue": true, + "suppressOutput": true +} +``` + +### SessionStart Hook +SessionStart DOES support `hookSpecificOutput`: + +```json +{ + "continue": true, + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": "Context string to add to session" + } +} +``` + +### PreToolUse Hook +```json +{ + "continue": true, + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow" | "deny" | "ask", + "permissionDecisionReason": "Reason for decision" + } +} +``` + +### PostToolUse Hook +```json +{ + "decision": "block", // Optional - blocks further processing + "reason": "Explanation", + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": "Additional information for Claude" + } +} +``` + +### UserPromptSubmit Hook +```json +{ + "decision": "block", // Optional - blocks the prompt + "reason": "Security policy violation", + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": "Additional context for the prompt" + } +} +``` + +## Exit Codes + +- `0`: Success - hook executed successfully +- `1`: Error - shown to user with stdout +- `2`: Error - shown to Claude with stderr + +## Common Mistakes to Avoid + +### \u274c INCORRECT: Using wrong field names +```javascript +// WRONG +{ + "decision": "block", // \u274c Wrong field + "reason": "Error message" // \u274c Wrong field +} +``` + +### \u2705 CORRECT: Using official field names +```javascript +// RIGHT +{ + "continue": false, + "stopReason": "Error message" +} +``` + +### \u274c INCORRECT: Adding hookSpecificOutput to PreCompact +```javascript +// WRONG - PreCompact doesn't support this +{ + "hookSpecificOutput": { + "hookEventName": "PreCompact", + "status": "success" + } +} +``` + +### \u2705 CORRECT: Simple response for PreCompact +```javascript +// RIGHT +{ + "continue": true, + "suppressOutput": true +} +``` + +## References +- Official Docs: https://docs.anthropic.com/en/docs/claude-code/hooks +- Hook Examples: https://docs.anthropic.com/en/docs/claude-code/hooks-guide \ No newline at end of file diff --git a/docs/reference/claude-code/hooks.md b/docs/reference/claude-code/hooks.md new file mode 100644 index 00000000..17bdd8b2 --- /dev/null +++ b/docs/reference/claude-code/hooks.md @@ -0,0 +1,175 @@ +# Claude Code Hooks Configuration Documentation +## Source: Official Claude Code Docs v2025 +## Last Verified: 2025-08-31 + +## Hook Configuration Structure + +### For Tool-Based Hooks (PreToolUse, PostToolUse) +These hooks use the `matcher` field to match tool patterns: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "ToolPattern", // Required for tool hooks + "hooks": [ + { + "type": "command", + "command": "your-command-here", + "timeout": 60000 // Optional, in milliseconds + } + ] + } + ] + } +} +``` + +### For Non-Tool Hooks (PreCompact, SessionStart, etc.) +These hooks DO NOT use matcher/pattern fields: + +```json +{ + "hooks": { + "PreCompact": [ + { + // NO matcher or pattern field! + "hooks": [ + { + "type": "command", + "command": "/path/to/script.js", + "timeout": 180000 + } + ] + } + ] + } +} +``` + +## Available Hook Events + +### Tool-Related Hooks (use matcher) +- **PreToolUse**: Before tool execution +- **PostToolUse**: After tool execution + +### System Event Hooks (no matcher) +- **PreCompact**: Before conversation compaction +- **SessionStart**: When session begins +- **SessionEnd**: When session ends (not in official docs) +- **UserPromptSubmit**: When user submits prompt +- **Notification**: When Claude needs user input +- **Stop**: When stop is requested +- **SubagentStop**: When subagent stop is requested + +## Hook Payload Structure + +### Common Fields (all hooks) +```json +{ + "session_id": "string", + "transcript_path": "string", + "hook_event_name": "string", + "cwd": "string" // Current working directory +} +``` + +### PreCompact Specific +```json +{ + "hook_event_name": "PreCompact", + "trigger": "manual" | "auto", + "custom_instructions": "string" +} +``` + +### SessionStart Specific +```json +{ + "hook_event_name": "SessionStart", + "source": "startup" | "compact" | "vscode" | "web" +} +``` + +### PreToolUse/PostToolUse Specific +```json +{ + "tool_name": "string", + "tool_input": { /* tool specific */ }, + "tool_response": { /* PostToolUse only */ } +} +``` + +## Common Configuration Mistakes + +### \u274c INCORRECT: Using 'pattern' for non-tool hooks +```json +{ + "hooks": { + "PreCompact": [{ + "pattern": "*", // \u274c WRONG - non-tool hooks don't use this + "hooks": [...] + }] + } +} +``` + +### \u2705 CORRECT: No matcher for non-tool hooks +```json +{ + "hooks": { + "PreCompact": [{ + // No pattern or matcher field + "hooks": [...] + }] + } +} +``` + +### \u274c INCORRECT: Wrong matcher field name +```json +{ + "hooks": { + "PreToolUse": [{ + "pattern": "Bash", // \u274c WRONG field name + "hooks": [...] + }] + } +} +``` + +### \u2705 CORRECT: Using 'matcher' for tool hooks +```json +{ + "hooks": { + "PreToolUse": [{ + "matcher": "Bash", // \u2705 Correct field name + "hooks": [...] + }] + } +} +``` + +## Matcher Patterns for Tool Hooks + +- **Exact match**: `"Bash"` - matches only Bash tool +- **Multiple tools**: `"Edit|MultiEdit|Write"` - matches any of these +- **MCP tools**: `"mcp__memory__.*"` - matches all memory server tools +- **All tools**: `"*"` - matches everything + +## Environment Variables + +Hooks have access to: +- `$CLAUDE_PROJECT_DIR` - Project root directory + +## Settings File Locations + +1. **User settings**: `~/.claude/settings.json` +2. **Project settings**: `./.claude/settings.json` +3. **Local settings**: `./.claude/settings.local.json` +4. **Managed settings**: `/Library/Application Support/ClaudeCode/managed-settings.json` + +## References +- Official Docs: https://docs.anthropic.com/en/docs/claude-code/hooks +- Hook Guide: https://docs.anthropic.com/en/docs/claude-code/hooks-guide \ No newline at end of file diff --git a/docs/reference/claude-code/mcp-configuration.md b/docs/reference/claude-code/mcp-configuration.md new file mode 100644 index 00000000..9df29ea5 --- /dev/null +++ b/docs/reference/claude-code/mcp-configuration.md @@ -0,0 +1,133 @@ +# MCP Configuration Documentation +## Source: Official Claude Code Docs v2025 +## Last Verified: 2025-08-31 + +## MCP Configuration File Locations + +### User Scope +- **File**: `~/.claude.json` +- **Purpose**: User-wide MCP servers available across all projects +- **Persistence**: Persists across projects +- **Example Path**: `/Users/username/.claude.json` + +### Project Scope +- **File**: `./.mcp.json` +- **Purpose**: Project-specific servers for team collaboration +- **Persistence**: Checked into version control +- **Example Path**: `/path/to/project/.mcp.json` + +### Local Scope +- **Status**: Not officially documented +- **Implementation**: Currently uses `~/.claude.json` (may need revision) + +## Configuration Structure + +```json +{ + "mcpServers": { + "server-name": { + "command": "command-to-run", + "args": ["arg1", "arg2"], + "env": { + "ENV_VAR": "value" + } + } + } +} +``` + +## Example Configurations + +### Memory Server (stdio) +```json +{ + "mcpServers": { + "claude-mem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-memory"] + } + } +} +``` + +### HTTP Server +```json +{ + "mcpServers": { + "api-server": { + "type": "sse", + "url": "${API_BASE_URL:-https://api.example.com}/mcp", + "headers": { + "Authorization": "Bearer ${API_KEY}" + } + } + } +} +``` + +## Environment Variable Expansion + +MCP configs support environment variable expansion: +- `${VAR}` - Direct expansion +- `${VAR:-default}` - With fallback value + +Applicable fields: +- `command` +- `args` +- `env` +- `url` +- `headers` + +## CLI Commands + +```bash +# Add a server +claude mcp add [args...] + +# Add with scope +claude mcp add --scope project /path/to/server +claude mcp add --scope user /path/to/server + +# List servers +claude mcp list + +# Get server details +claude mcp get + +# Remove server +claude mcp remove + +# Check status (within Claude Code) +/mcp +``` + +## Tool Naming Convention + +MCP tools follow the pattern: `mcp____` + +Example: +- Server: `claude-mem` +- Tool: `create_entities` +- Full name: `mcp__claude_mem__create_entities` + +## Security Considerations + +1. **Tool Permissions**: Must explicitly allow MCP tools via `--allowedTools` +2. **Server Trust**: Only use MCP servers from trusted sources +3. **Credential Management**: Use environment variables for sensitive data +4. **Audit Trail**: MCP operations can be monitored via hooks + +## Common Issues + +### Issue: MCP server not connecting +**Solution**: Check that the command and args are correct, and npx is in PATH + +### Issue: Tools not available +**Solution**: Ensure server is in allowed list and properly configured + +### Issue: Configuration not loading +**Solution**: Verify JSON syntax and file location + +## References +- Official Docs: https://docs.anthropic.com/en/docs/claude-code/mcp +- MCP Protocol: https://modelcontextprotocol.io/ \ No newline at end of file diff --git a/docs/reference/claude-code/session-start-hook.md b/docs/reference/claude-code/session-start-hook.md new file mode 100644 index 00000000..a291ef47 --- /dev/null +++ b/docs/reference/claude-code/session-start-hook.md @@ -0,0 +1,82 @@ +# SessionStart Hook Documentation + +## Official Documentation Reference +- **Source**: https://docs.anthropic.com/en/docs/claude-code/hooks#sessionstart +- **Last Verified**: 2025-08-31 +- **Version**: Claude Code v2025 + +## Hook Payload Structure + +The SessionStart hook receives the following JSON payload via stdin: + +```json +{ + "session_id": "string", + "transcript_path": "string", + "hook_event_name": "SessionStart", + "source": "startup" | "compact" | "vscode" | "web" +} +``` + +### Field Descriptions + +- **session_id**: Unique identifier for the Claude Code session +- **transcript_path**: Path to the conversation transcript JSONL file +- **hook_event_name**: Always "SessionStart" for this hook +- **source**: Indicates how the session was initiated: + - `"startup"`: New session started normally (should load context) + - `"compact"`: Session started after compaction (may skip context) + - `"vscode"`: Session initiated from VS Code extension + - `"web"`: Session initiated from web interface + +## Response Format + +The hook should output JSON in the following format to add context: + +```json +{ + "hookSpecificOutput": { + "hookEventName": "SessionStart", + "additionalContext": "string" + } +} +``` + +### Response Fields + +- **hookSpecificOutput**: Container for hook-specific output +- **hookEventName**: Must be "SessionStart" +- **additionalContext**: String content to add to the session context + +## Implementation Notes + +### Context Loading Strategy + +The hook should determine whether to load context based on the `source` field: + +1. **For "startup" source**: Load full context from memory +2. **For "compact" source**: Skip or load minimal context (session continuing after compaction) +3. **For "vscode"/"web" sources**: Load context as appropriate + +### Error Handling + +- If context loading fails, exit silently (exit code 0) +- Do not break the session start with errors +- Log errors to separate log file if needed + +## Common Mistakes + +### Incorrect Field Check (FIXED) +**Wrong**: Checking `payload.reason === 'continue'` +**Correct**: Checking `payload.source === 'compact'` + +The payload does not have a `reason` field. The `source` field indicates the session initiation context. + +## Code Location +- **File**: `/Users/alexnewman/Scripts/claude-mem/hooks/session-start.js` +- **Line**: 53-66 (field check and documentation) + +## Cross-References +- General Hooks Documentation: [docs/claude-code/hooks.md](./hooks.md) +- Hook Response Formats: [docs/claude-code/hook-responses.md](./hook-responses.md) +- MCP Configuration: [docs/claude-code/mcp-configuration.md](./mcp-configuration.md) \ No newline at end of file diff --git a/docs/reference/load-context-format-example.md b/docs/reference/load-context-format-example.md new file mode 100644 index 00000000..ac0426fb --- /dev/null +++ b/docs/reference/load-context-format-example.md @@ -0,0 +1,33 @@ + + +🧠 What's new: Thursday, September 4, 2025 at 06:13 PM EDT +==================================================================== +Established Claude memory management infrastructure with compression tools for session archiving. Optimized session summary generation with compact command overviews for improved retrieval efficiency. + +πŸ“š Recent Context +==================================================================== +πŸ‘€ in claude_mem_source_d096f650-76a3-4163-9b7a-19f36a13c648_{number}: + +1. Removed maxTurns:1 limitation that prevented Claude from completing multi-step compression with tool calls + β€” maxTurns, Claude SDK, compression fix, MCP tools + +3. Deployed steve-krug-ux agent to redesign prompt from pipe-separated to JSON with XMLResponse tags + β€” steve-krug-ux, JSON format, prompt redesign + +5. Expanded TranscriptCompressor to use all 13 claude-mem MCP tools for intelligent compression + β€” allowedTools, MCP tools expansion, TranscriptCompressor + +6. Traced index population failure through multiple system layers to identify root cause + β€” debugging, ContextTemplates, claude-mem-index, trace + +====================================================================== +πŸ‘€ in claude_mem_source_d096f650-76a3-4163-9b7a-19f36a13c648_{number}: + +1. Implemented Claude memory management and compression tools system + β€” memory management, compression, archiving, session context + +2. Generated compact command overview for session summaries + β€” command overview, session summaries, memory retrieval + +====================================================================== + \ No newline at end of file diff --git a/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md b/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md new file mode 100644 index 00000000..3e6f0245 --- /dev/null +++ b/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md @@ -0,0 +1,1323 @@ +# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk) + +## Table of Contents + +- [Overview](#overview) +- [Installation](#installation) +- [Quickstart](#quick-start) +- [What is MCP?](#what-is-mcp) +- [Core Concepts](#core-concepts) + - [Server](#server) + - [Resources](#resources) + - [Tools](#tools) + - [Prompts](#prompts) + - [Completions](#completions) + - [Sampling](#sampling) +- [Running Your Server](#running-your-server) + - [stdio](#stdio) + - [Streamable HTTP](#streamable-http) + - [Testing and Debugging](#testing-and-debugging) +- [Examples](#examples) + - [Echo Server](#echo-server) + - [SQLite Explorer](#sqlite-explorer) +- [Advanced Usage](#advanced-usage) + - [Dynamic Servers](#dynamic-servers) + - [Low-Level Server](#low-level-server) + - [Writing MCP Clients](#writing-mcp-clients) + - [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream) + - [Backwards Compatibility](#backwards-compatibility) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [License](#license) + +## Overview + +The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to: + +- Build MCP clients that can connect to any MCP server +- Create MCP servers that expose resources, prompts and tools +- Use standard transports like stdio and Streamable HTTP +- Handle all MCP protocol messages and lifecycle events + +## Installation + +```bash +npm install @modelcontextprotocol/sdk +``` + +> ⚠️ MCP requires Node.js v18.x or higher to work fine. + +## Quick Start + +Let's create a simple MCP server that exposes a calculator tool and some data: + +```typescript +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +// Create an MCP server +const server = new McpServer({ + name: "demo-server", + version: "1.0.0" +}); + +// Add an addition tool +server.registerTool("add", + { + title: "Addition Tool", + description: "Add two numbers", + inputSchema: { a: z.number(), b: z.number() } + }, + async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }] + }) +); + +// Add a dynamic greeting resource +server.registerResource( + "greeting", + new ResourceTemplate("greeting://{name}", { list: undefined }), + { + title: "Greeting Resource", // Display name for UI + description: "Dynamic greeting generator" + }, + async (uri, { name }) => ({ + contents: [{ + uri: uri.href, + text: `Hello, ${name}!` + }] + }) +); + +// Start receiving messages on stdin and sending messages on stdout +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +## What is MCP? + +The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: + +- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) +- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) +- Define interaction patterns through **Prompts** (reusable templates for LLM interactions) +- And more! + +## Core Concepts + +### Server + +The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: + +```typescript +const server = new McpServer({ + name: "my-app", + version: "1.0.0" +}); +``` + +### Resources + +Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: + +```typescript +// Static resource +server.registerResource( + "config", + "config://app", + { + title: "Application Config", + description: "Application configuration data", + mimeType: "text/plain" + }, + async (uri) => ({ + contents: [{ + uri: uri.href, + text: "App configuration here" + }] + }) +); + +// Dynamic resource with parameters +server.registerResource( + "user-profile", + new ResourceTemplate("users://{userId}/profile", { list: undefined }), + { + title: "User Profile", + description: "User profile information" + }, + async (uri, { userId }) => ({ + contents: [{ + uri: uri.href, + text: `Profile data for user ${userId}` + }] + }) +); + +// Resource with context-aware completion +server.registerResource( + "repository", + new ResourceTemplate("github://repos/{owner}/{repo}", { + list: undefined, + complete: { + // Provide intelligent completions based on previously resolved parameters + repo: (value, context) => { + if (context?.arguments?.["owner"] === "org1") { + return ["project1", "project2", "project3"].filter(r => r.startsWith(value)); + } + return ["default-repo"].filter(r => r.startsWith(value)); + } + } + }), + { + title: "GitHub Repository", + description: "Repository information" + }, + async (uri, { owner, repo }) => ({ + contents: [{ + uri: uri.href, + text: `Repository: ${owner}/${repo}` + }] + }) +); +``` + +### Tools + +Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: + +```typescript +// Simple tool with parameters +server.registerTool( + "calculate-bmi", + { + title: "BMI Calculator", + description: "Calculate Body Mass Index", + inputSchema: { + weightKg: z.number(), + heightM: z.number() + } + }, + async ({ weightKg, heightM }) => ({ + content: [{ + type: "text", + text: String(weightKg / (heightM * heightM)) + }] + }) +); + +// Async tool with external API call +server.registerTool( + "fetch-weather", + { + title: "Weather Fetcher", + description: "Get weather data for a city", + inputSchema: { city: z.string() } + }, + async ({ city }) => { + const response = await fetch(`https://api.weather.com/${city}`); + const data = await response.text(); + return { + content: [{ type: "text", text: data }] + }; + } +); + +// Tool that returns ResourceLinks +server.registerTool( + "list-files", + { + title: "List Files", + description: "List project files", + inputSchema: { pattern: z.string() } + }, + async ({ pattern }) => ({ + content: [ + { type: "text", text: `Found files matching "${pattern}":` }, + // ResourceLinks let tools return references without file content + { + type: "resource_link", + uri: "file:///project/README.md", + name: "README.md", + mimeType: "text/markdown", + description: 'A README file' + }, + { + type: "resource_link", + uri: "file:///project/src/index.ts", + name: "index.ts", + mimeType: "text/typescript", + description: 'An index file' + } + ] + }) +); +``` + +#### ResourceLinks + +Tools can return `ResourceLink` objects to reference resources without embedding their full content. This is essential for performance when dealing with large files or many resources - clients can then selectively read only the resources they need using the provided URIs. + +### Prompts + +Prompts are reusable templates that help LLMs interact with your server effectively: + +```typescript +import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; + +server.registerPrompt( + "review-code", + { + title: "Code Review", + description: "Review code for best practices and potential issues", + argsSchema: { code: z.string() } + }, + ({ code }) => ({ + messages: [{ + role: "user", + content: { + type: "text", + text: `Please review this code:\n\n${code}` + } + }] + }) +); + +// Prompt with context-aware completion +server.registerPrompt( + "team-greeting", + { + title: "Team Greeting", + description: "Generate a greeting for team members", + argsSchema: { + department: completable(z.string(), (value) => { + // Department suggestions + return ["engineering", "sales", "marketing", "support"].filter(d => d.startsWith(value)); + }), + name: completable(z.string(), (value, context) => { + // Name suggestions based on selected department + const department = context?.arguments?.["department"]; + if (department === "engineering") { + return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value)); + } else if (department === "sales") { + return ["David", "Eve", "Frank"].filter(n => n.startsWith(value)); + } else if (department === "marketing") { + return ["Grace", "Henry", "Iris"].filter(n => n.startsWith(value)); + } + return ["Guest"].filter(n => n.startsWith(value)); + }) + } + }, + ({ department, name }) => ({ + messages: [{ + role: "assistant", + content: { + type: "text", + text: `Hello ${name}, welcome to the ${department} team!` + } + }] + }) +); +``` + +### Completions + +MCP supports argument completions to help users fill in prompt arguments and resource template parameters. See the examples above for [resource completions](#resources) and [prompt completions](#prompts). + +#### Client Usage + +```typescript +// Request completions for any argument +const result = await client.complete({ + ref: { + type: "ref/prompt", // or "ref/resource" + name: "example" // or uri: "template://..." + }, + argument: { + name: "argumentName", + value: "partial" // What the user has typed so far + }, + context: { // Optional: Include previously resolved arguments + arguments: { + previousArg: "value" + } + } +}); + +``` + +### Display Names and Metadata + +All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name, while `name` remains the unique identifier. + +**Note:** The `register*` methods (`registerTool`, `registerPrompt`, `registerResource`) are the recommended approach for new code. The older methods (`tool`, `prompt`, `resource`) remain available for backwards compatibility. + +#### Title Precedence for Tools + +For tools specifically, there are two ways to specify a title: +- `title` field in the tool configuration +- `annotations.title` field (when using the older `tool()` method with annotations) + +The precedence order is: `title` β†’ `annotations.title` β†’ `name` + +```typescript +// Using registerTool (recommended) +server.registerTool("my_tool", { + title: "My Tool", // This title takes precedence + annotations: { + title: "Annotation Title" // This is ignored if title is set + } +}, handler); + +// Using tool with annotations (older API) +server.tool("my_tool", "description", { + title: "Annotation Title" // This is used as title +}, handler); +``` + +When building clients, use the provided utility to get the appropriate display name: + +```typescript +import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js"; + +// Automatically handles the precedence: title β†’ annotations.title β†’ name +const displayName = getDisplayName(tool); +``` + +### Sampling + +MCP servers can request LLM completions from connected clients that support sampling. + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +const mcpServer = new McpServer({ + name: "tools-with-sample-server", + version: "1.0.0", +}); + +// Tool that uses LLM sampling to summarize any text +mcpServer.registerTool( + "summarize", + { + description: "Summarize any text using an LLM", + inputSchema: { + text: z.string().describe("Text to summarize"), + }, + }, + async ({ text }) => { + // Call the LLM through MCP sampling + const response = await mcpServer.server.createMessage({ + messages: [ + { + role: "user", + content: { + type: "text", + text: `Please summarize the following text concisely:\n\n${text}`, + }, + }, + ], + maxTokens: 500, + }); + + return { + content: [ + { + type: "text", + text: response.content.type === "text" ? response.content.text : "Unable to generate summary", + }, + ], + }; + } +); + +async function main() { + const transport = new StdioServerTransport(); + await mcpServer.connect(transport); + console.log("MCP server is running..."); +} + +main().catch((error) => { + console.error("Server error:", error); + process.exit(1); +}); +``` + + +## Running Your Server + +MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport: + +### stdio + +For command-line tools and direct integrations: + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; + +const server = new McpServer({ + name: "example-server", + version: "1.0.0" +}); + +// ... set up server resources, tools, and prompts ... + +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +### Streamable HTTP + +For remote servers, set up a Streamable HTTP transport that handles both client requests and server-to-client notifications. + +#### With Session Management + +In some cases, servers need to be stateful. This is achieved by [session management](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management). + +```typescript +import express from "express"; +import { randomUUID } from "node:crypto"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js" + + + +const app = express(); +app.use(express.json()); + +// Map to store transports by session ID +const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; + +// Handle POST requests for client-to-server communication +app.post('/mcp', async (req, res) => { + // Check for existing session ID + const sessionId = req.headers['mcp-session-id'] as string | undefined; + let transport: StreamableHTTPServerTransport; + + if (sessionId && transports[sessionId]) { + // Reuse existing transport + transport = transports[sessionId]; + } else if (!sessionId && isInitializeRequest(req.body)) { + // New initialization request + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (sessionId) => { + // Store the transport by session ID + transports[sessionId] = transport; + }, + // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server + // locally, make sure to set: + // enableDnsRebindingProtection: true, + // allowedHosts: ['127.0.0.1'], + }); + + // Clean up transport when closed + transport.onclose = () => { + if (transport.sessionId) { + delete transports[transport.sessionId]; + } + }; + const server = new McpServer({ + name: "example-server", + version: "1.0.0" + }); + + // ... set up server resources, tools, and prompts ... + + // Connect to the MCP server + await server.connect(transport); + } else { + // Invalid request + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: No valid session ID provided', + }, + id: null, + }); + return; + } + + // Handle the request + await transport.handleRequest(req, res, req.body); +}); + +// Reusable handler for GET and DELETE requests +const handleSessionRequest = async (req: express.Request, res: express.Response) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const transport = transports[sessionId]; + await transport.handleRequest(req, res); +}; + +// Handle GET requests for server-to-client notifications via SSE +app.get('/mcp', handleSessionRequest); + +// Handle DELETE requests for session termination +app.delete('/mcp', handleSessionRequest); + +app.listen(3000); +``` + +> [!TIP] +> When using this in a remote environment, make sure to allow the header parameter `mcp-session-id` in CORS. Otherwise, it may result in a `Bad Request: No valid session ID provided` error. Read the following section for examples. + + +#### CORS Configuration for Browser-Based Clients + +If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it: + +```typescript +import cors from 'cors'; + +// Add CORS middleware before your MCP routes +app.use(cors({ + origin: '*', // Configure appropriately for production, for example: + // origin: ['https://your-remote-domain.com', 'https://your-other-remote-domain.com'], + exposedHeaders: ['Mcp-Session-Id'], + allowedHeaders: ['Content-Type', 'mcp-session-id'], +})); +``` + +This configuration is necessary because: +- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management +- Browsers restrict access to response headers unless explicitly exposed via CORS +- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses + +#### Without Session Management (Stateless) + +For simpler use cases where session management isn't needed: + +```typescript +const app = express(); +app.use(express.json()); + +app.post('/mcp', async (req: Request, res: Response) => { + // In stateless mode, create a new instance of transport and server for each request + // to ensure complete isolation. A single instance would cause request ID collisions + // when multiple clients connect concurrently. + + try { + const server = getServer(); + const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + res.on('close', () => { + console.log('Request closed'); + transport.close(); + server.close(); + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error('Error handling MCP request:', error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } +}); + +// SSE notifications not supported in stateless mode +app.get('/mcp', async (req: Request, res: Response) => { + console.log('Received GET MCP request'); + res.writeHead(405).end(JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: "Method not allowed." + }, + id: null + })); +}); + +// Session termination not needed in stateless mode +app.delete('/mcp', async (req: Request, res: Response) => { + console.log('Received DELETE MCP request'); + res.writeHead(405).end(JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32000, + message: "Method not allowed." + }, + id: null + })); +}); + + +// Start the server +const PORT = 3000; +setupServer().then(() => { + app.listen(PORT, (error) => { + if (error) { + console.error('Failed to start server:', error); + process.exit(1); + } + console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`); + }); +}).catch(error => { + console.error('Failed to set up the server:', error); + process.exit(1); +}); + +``` + +This stateless approach is useful for: + +- Simple API wrappers +- RESTful scenarios where each request is independent +- Horizontally scaled deployments without shared session state + +#### DNS Rebinding Protection + +The Streamable HTTP transport includes DNS rebinding protection to prevent security vulnerabilities. By default, this protection is **disabled** for backwards compatibility. + +**Important**: If you are running this server locally, enable DNS rebinding protection: + +```typescript +const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + enableDnsRebindingProtection: true, + + allowedHosts: ['127.0.0.1', ...], + allowedOrigins: ['https://yourdomain.com', 'https://www.yourdomain.com'] +}); +``` + +### Testing and Debugging + +To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information. + +## Examples + +### Echo Server + +A simple server demonstrating resources, tools, and prompts: + +```typescript +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +const server = new McpServer({ + name: "echo-server", + version: "1.0.0" +}); + +server.registerResource( + "echo", + new ResourceTemplate("echo://{message}", { list: undefined }), + { + title: "Echo Resource", + description: "Echoes back messages as resources" + }, + async (uri, { message }) => ({ + contents: [{ + uri: uri.href, + text: `Resource echo: ${message}` + }] + }) +); + +server.registerTool( + "echo", + { + title: "Echo Tool", + description: "Echoes back the provided message", + inputSchema: { message: z.string() } + }, + async ({ message }) => ({ + content: [{ type: "text", text: `Tool echo: ${message}` }] + }) +); + +server.registerPrompt( + "echo", + { + title: "Echo Prompt", + description: "Creates a prompt to process a message", + argsSchema: { message: z.string() } + }, + ({ message }) => ({ + messages: [{ + role: "user", + content: { + type: "text", + text: `Please process this message: ${message}` + } + }] + }) +); +``` + +### SQLite Explorer + +A more complex example showing database integration: + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import sqlite3 from "sqlite3"; +import { promisify } from "util"; +import { z } from "zod"; + +const server = new McpServer({ + name: "sqlite-explorer", + version: "1.0.0" +}); + +// Helper to create DB connection +const getDb = () => { + const db = new sqlite3.Database("database.db"); + return { + all: promisify(db.all.bind(db)), + close: promisify(db.close.bind(db)) + }; +}; + +server.registerResource( + "schema", + "schema://main", + { + title: "Database Schema", + description: "SQLite database schema", + mimeType: "text/plain" + }, + async (uri) => { + const db = getDb(); + try { + const tables = await db.all( + "SELECT sql FROM sqlite_master WHERE type='table'" + ); + return { + contents: [{ + uri: uri.href, + text: tables.map((t: {sql: string}) => t.sql).join("\n") + }] + }; + } finally { + await db.close(); + } + } +); + +server.registerTool( + "query", + { + title: "SQL Query", + description: "Execute SQL queries on the database", + inputSchema: { sql: z.string() } + }, + async ({ sql }) => { + const db = getDb(); + try { + const results = await db.all(sql); + return { + content: [{ + type: "text", + text: JSON.stringify(results, null, 2) + }] + }; + } catch (err: unknown) { + const error = err as Error; + return { + content: [{ + type: "text", + text: `Error: ${error.message}` + }], + isError: true + }; + } finally { + await db.close(); + } + } +); +``` + +## Advanced Usage + +### Dynamic Servers + +If you want to offer an initial set of tools/prompts/resources, but later add additional ones based on user action or external state change, you can add/update/remove them _after_ the Server is connected. This will automatically emit the corresponding `listChanged` notifications: + +```ts +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +const server = new McpServer({ + name: "Dynamic Example", + version: "1.0.0" +}); + +const listMessageTool = server.tool( + "listMessages", + { channel: z.string() }, + async ({ channel }) => ({ + content: [{ type: "text", text: await listMessages(channel) }] + }) +); + +const putMessageTool = server.tool( + "putMessage", + { channel: z.string(), message: z.string() }, + async ({ channel, message }) => ({ + content: [{ type: "text", text: await putMessage(channel, message) }] + }) +); +// Until we upgrade auth, `putMessage` is disabled (won't show up in listTools) +putMessageTool.disable() + +const upgradeAuthTool = server.tool( + "upgradeAuth", + { permission: z.enum(["write", "admin"])}, + // Any mutations here will automatically emit `listChanged` notifications + async ({ permission }) => { + const { ok, err, previous } = await upgradeAuthAndStoreToken(permission) + if (!ok) return {content: [{ type: "text", text: `Error: ${err}` }]} + + // If we previously had read-only access, 'putMessage' is now available + if (previous === "read") { + putMessageTool.enable() + } + + if (permission === 'write') { + // If we've just upgraded to 'write' permissions, we can still call 'upgradeAuth' + // but can only upgrade to 'admin'. + upgradeAuthTool.update({ + paramsSchema: { permission: z.enum(["admin"]) }, // change validation rules + }) + } else { + // If we're now an admin, we no longer have anywhere to upgrade to, so fully remove that tool + upgradeAuthTool.remove() + } + } +) + +// Connect as normal +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +### Improving Network Efficiency with Notification Debouncing + +When performing bulk updates that trigger notifications (e.g., enabling or disabling multiple tools in a loop), the SDK can send a large number of messages in a short period. To improve performance and reduce network traffic, you can enable notification debouncing. + +This feature coalesces multiple, rapid calls for the same notification type into a single message. For example, if you disable five tools in a row, only one `notifications/tools/list_changed` message will be sent instead of five. + +> [!IMPORTANT] +> This feature is designed for "simple" notifications that do not carry unique data in their parameters. To prevent silent data loss, debouncing is **automatically bypassed** for any notification that contains a `params` object or a `relatedRequestId`. Such notifications will always be sent immediately. + +This is an opt-in feature configured during server initialization. + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +const server = new McpServer( + { + name: "efficient-server", + version: "1.0.0" + }, + { + // Enable notification debouncing for specific methods + debouncedNotificationMethods: [ + 'notifications/tools/list_changed', + 'notifications/resources/list_changed', + 'notifications/prompts/list_changed' + ] + } +); + +// Now, any rapid changes to tools, resources, or prompts will result +// in a single, consolidated notification for each type. +server.registerTool("tool1", ...).disable(); +server.registerTool("tool2", ...).disable(); +server.registerTool("tool3", ...).disable(); +// Only one 'notifications/tools/list_changed' is sent. +``` + +### Low-Level Server + +For more control, you can use the low-level Server class directly: + +```typescript +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + ListPromptsRequestSchema, + GetPromptRequestSchema +} from "@modelcontextprotocol/sdk/types.js"; + +const server = new Server( + { + name: "example-server", + version: "1.0.0" + }, + { + capabilities: { + prompts: {} + } + } +); + +server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: [{ + name: "example-prompt", + description: "An example prompt template", + arguments: [{ + name: "arg1", + description: "Example argument", + required: true + }] + }] + }; +}); + +server.setRequestHandler(GetPromptRequestSchema, async (request) => { + if (request.params.name !== "example-prompt") { + throw new Error("Unknown prompt"); + } + return { + description: "Example prompt", + messages: [{ + role: "user", + content: { + type: "text", + text: "Example prompt text" + } + }] + }; +}); + +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +### Eliciting User Input + +MCP servers can request additional information from users through the elicitation feature. This is useful for interactive workflows where the server needs user input or confirmation: + +```typescript +// Server-side: Restaurant booking tool that asks for alternatives +server.tool( + "book-restaurant", + { + restaurant: z.string(), + date: z.string(), + partySize: z.number() + }, + async ({ restaurant, date, partySize }) => { + // Check availability + const available = await checkAvailability(restaurant, date, partySize); + + if (!available) { + // Ask user if they want to try alternative dates + const result = await server.server.elicitInput({ + message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`, + requestedSchema: { + type: "object", + properties: { + checkAlternatives: { + type: "boolean", + title: "Check alternative dates", + description: "Would you like me to check other dates?" + }, + flexibleDates: { + type: "string", + title: "Date flexibility", + description: "How flexible are your dates?", + enum: ["next_day", "same_week", "next_week"], + enumNames: ["Next day", "Same week", "Next week"] + } + }, + required: ["checkAlternatives"] + } + }); + + if (result.action === "accept" && result.content?.checkAlternatives) { + const alternatives = await findAlternatives( + restaurant, + date, + partySize, + result.content.flexibleDates as string + ); + return { + content: [{ + type: "text", + text: `Found these alternatives: ${alternatives.join(", ")}` + }] + }; + } + + return { + content: [{ + type: "text", + text: "No booking made. Original date not available." + }] + }; + } + + // Book the table + await makeBooking(restaurant, date, partySize); + return { + content: [{ + type: "text", + text: `Booked table for ${partySize} at ${restaurant} on ${date}` + }] + }; + } +); +``` + +Client-side: Handle elicitation requests + +```typescript +// This is a placeholder - implement based on your UI framework +async function getInputFromUser(message: string, schema: any): Promise<{ + action: "accept" | "decline" | "cancel"; + data?: Record; +}> { + // This should be implemented depending on the app + throw new Error("getInputFromUser must be implemented for your platform"); +} + +client.setRequestHandler(ElicitRequestSchema, async (request) => { + const userResponse = await getInputFromUser( + request.params.message, + request.params.requestedSchema + ); + + return { + action: userResponse.action, + content: userResponse.action === "accept" ? userResponse.data : undefined + }; +}); +``` + +**Note**: Elicitation requires client support. Clients must declare the `elicitation` capability during initialization. + +### Writing MCP Clients + +The SDK provides a high-level client interface: + +```typescript +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +const transport = new StdioClientTransport({ + command: "node", + args: ["server.js"] +}); + +const client = new Client( + { + name: "example-client", + version: "1.0.0" + } +); + +await client.connect(transport); + +// List prompts +const prompts = await client.listPrompts(); + +// Get a prompt +const prompt = await client.getPrompt({ + name: "example-prompt", + arguments: { + arg1: "value" + } +}); + +// List resources +const resources = await client.listResources(); + +// Read a resource +const resource = await client.readResource({ + uri: "file:///example.txt" +}); + +// Call a tool +const result = await client.callTool({ + name: "example-tool", + arguments: { + arg1: "value" + } +}); + +``` + +### Proxy Authorization Requests Upstream + +You can proxy OAuth requests to an external authorization provider: + +```typescript +import express from 'express'; +import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js'; +import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js'; + +const app = express(); + +const proxyProvider = new ProxyOAuthServerProvider({ + endpoints: { + authorizationUrl: "https://auth.external.com/oauth2/v1/authorize", + tokenUrl: "https://auth.external.com/oauth2/v1/token", + revocationUrl: "https://auth.external.com/oauth2/v1/revoke", + }, + verifyAccessToken: async (token) => { + return { + token, + clientId: "123", + scopes: ["openid", "email", "profile"], + } + }, + getClient: async (client_id) => { + return { + client_id, + redirect_uris: ["http://localhost:3000/callback"], + } + } +}) + +app.use(mcpAuthRouter({ + provider: proxyProvider, + issuerUrl: new URL("http://auth.external.com"), + baseUrl: new URL("http://mcp.example.com"), + serviceDocumentationUrl: new URL("https://docs.example.com/"), +})) +``` + +This setup allows you to: + +- Forward OAuth requests to an external provider +- Add custom token validation logic +- Manage client registrations +- Provide custom documentation URLs +- Maintain control over the OAuth flow while delegating to an external provider + +### Backwards Compatibility + +Clients and servers with StreamableHttp transport can maintain [backwards compatibility](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#backwards-compatibility) with the deprecated HTTP+SSE transport (from protocol version 2024-11-05) as follows + +#### Client-Side Compatibility + +For clients that need to work with both Streamable HTTP and older SSE servers: + +```typescript +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; +let client: Client|undefined = undefined +const baseUrl = new URL(url); +try { + client = new Client({ + name: 'streamable-http-client', + version: '1.0.0' + }); + const transport = new StreamableHTTPClientTransport( + new URL(baseUrl) + ); + await client.connect(transport); + console.log("Connected using Streamable HTTP transport"); +} catch (error) { + // If that fails with a 4xx error, try the older SSE transport + console.log("Streamable HTTP connection failed, falling back to SSE transport"); + client = new Client({ + name: 'sse-client', + version: '1.0.0' + }); + const sseTransport = new SSEClientTransport(baseUrl); + await client.connect(sseTransport); + console.log("Connected using SSE transport"); +} +``` + +#### Server-Side Compatibility + +For servers that need to support both Streamable HTTP and older clients: + +```typescript +import express from "express"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; + +const server = new McpServer({ + name: "backwards-compatible-server", + version: "1.0.0" +}); + +// ... set up server resources, tools, and prompts ... + +const app = express(); +app.use(express.json()); + +// Store transports for each session type +const transports = { + streamable: {} as Record, + sse: {} as Record +}; + +// Modern Streamable HTTP endpoint +app.all('/mcp', async (req, res) => { + // Handle Streamable HTTP transport for modern clients + // Implementation as shown in the "With Session Management" example + // ... +}); + +// Legacy SSE endpoint for older clients +app.get('/sse', async (req, res) => { + // Create SSE transport for legacy clients + const transport = new SSEServerTransport('/messages', res); + transports.sse[transport.sessionId] = transport; + + res.on("close", () => { + delete transports.sse[transport.sessionId]; + }); + + await server.connect(transport); +}); + +// Legacy message endpoint for older clients +app.post('/messages', async (req, res) => { + const sessionId = req.query.sessionId as string; + const transport = transports.sse[sessionId]; + if (transport) { + await transport.handlePostMessage(req, res, req.body); + } else { + res.status(400).send('No transport found for sessionId'); + } +}); + +app.listen(3000); +``` + +**Note**: The SSE transport is now deprecated in favor of Streamable HTTP. New implementations should use Streamable HTTP, and existing SSE implementations should plan to migrate. + +## Documentation + +- [Model Context Protocol documentation](https://modelcontextprotocol.io) +- [MCP Specification](https://spec.modelcontextprotocol.io) +- [Example Servers](https://github.com/modelcontextprotocol/servers) + +## Contributing + +Issues and pull requests are welcome on GitHub at . + +## License + +This project is licensed under the MIT Licenseβ€”see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/docs/reference/mcp-sdk/server-implementation.md b/docs/reference/mcp-sdk/server-implementation.md new file mode 100644 index 00000000..a550c53d --- /dev/null +++ b/docs/reference/mcp-sdk/server-implementation.md @@ -0,0 +1,286 @@ +# MCP TypeScript SDK Server Implementation Guide + +## Documentation Source +- **SDK Version**: @modelcontextprotocol/sdk v1.0.0 +- **Last Verified**: 2025-09-01 +- **Official Docs**: https://github.com/modelcontextprotocol/typescript-sdk + +## Server Creation Patterns + +### Low-Level Server vs McpServer + +The SDK provides two approaches for creating MCP servers: + +1. **Low-Level Server Class** (Used in claude-mem) + - Direct control over request handling + - Manual registration with `setRequestHandler` + - More flexibility for custom routing logic + +```typescript +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; + +const server = new Server( + { + name: 'server-name', + version: '1.0.0' + }, + { + capabilities: { + tools: {} // Declare tool capability + } + } +); +``` + +2. **High-Level McpServer Class** (Alternative approach) + - Simplified API with `registerTool`, `registerResource`, `registerPrompt` + - Automatic routing and validation + - Less boilerplate code + +```typescript +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; + +const server = new McpServer({ + name: 'server-name', + version: '1.0.0' +}); +``` + +## Tool Handler Registration + +### Pattern 1: Single Handler with CallToolRequestSchema (claude-mem approach) + +```typescript +import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + // Validate arguments exist + if (!args) { + throw new Error(`No arguments provided for tool: ${name}`); + } + + // Route to specific tool implementation + switch (name) { + case 'tool-name': + // Tool implementation + return { + content: [{ + type: 'text', + text: 'Result' + }] + }; + default: + throw new Error(`Unknown tool: ${name}`); + } +}); +``` + +### Pattern 2: Individual Tool Registration (McpServer approach) + +```typescript +server.registerTool( + 'tool-name', + { + title: 'Tool Title', + description: 'Tool description', + inputSchema: { param: z.string() } + }, + async ({ param }) => ({ + content: [{ + type: 'text', + text: `Result for ${param}` + }] + }) +); +``` + +## Stdio Transport Usage + +### Standard Pattern for CLI-based Servers + +```typescript +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +async function main() { + // 1. Initialize backend services first + await initializeBackend(); + + // 2. Create transport + const transport = new StdioServerTransport(); + + // 3. Connect server to transport + await server.connect(transport); + + // 4. Log to stderr (stdout is for protocol) + console.error('Server started on stdio'); +} +``` + +### Key Points: +- **Stdin**: Receives MCP protocol messages +- **Stdout**: Sends MCP protocol responses +- **Stderr**: Used for logging and diagnostics + +## Error Handling Patterns + +### Tool Error Response + +```typescript +try { + // Tool implementation + return { + content: [{ + type: 'text', + text: 'Success result' + }] + }; +} catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + // Log to stderr for debugging + console.error(`[Error] Tool: ${name}, Error: ${errorMessage}`); + + // Return error response with isError flag + return { + content: [{ + type: 'text', + text: `Error: ${errorMessage}` + }], + isError: true // Important: Indicates tool failure + }; +} +``` + +### Startup Error Handling + +```typescript +main().catch((error) => { + console.error('Startup error:', error); + process.exit(1); // Exit with error code +}); +``` + +## Response Formatting + +### Success Response Structure + +```typescript +{ + content: [ + { + type: 'text', + text: 'Response text' + } + ] +} +``` + +### Error Response Structure + +```typescript +{ + content: [ + { + type: 'text', + text: 'Error: Description' + } + ], + isError: true +} +``` + +### Resource Link Response + +```typescript +{ + content: [ + { + type: 'resource_link', + uri: 'file:///path/to/resource', + name: 'Resource Name', + mimeType: 'text/plain', + description: 'Resource description' + } + ] +} +``` + +## Lifecycle Management + +### Initialization Pattern + +```typescript +async function initializeServer(): Promise { + try { + // Initialize backend connections + await backend.connect(); + console.error('Backend initialized'); + } catch (error) { + console.error('Initialization failed:', error); + throw error; // Prevent server startup + } +} +``` + +### Shutdown Pattern + +```typescript +function setupShutdownHandlers(): void { + const handleShutdown = async (signal: string) => { + console.error(`Received ${signal}, shutting down...`); + + try { + await backend.disconnect(); + process.exit(0); // Clean exit + } catch (error) { + console.error('Shutdown error:', error); + process.exit(1); // Error exit + } + }; + + process.on('SIGINT', () => handleShutdown('SIGINT')); + process.on('SIGTERM', () => handleShutdown('SIGTERM')); + + // Handle unexpected errors + process.on('uncaughtException', async (error) => { + console.error('Uncaught exception:', error); + await backend.disconnect(); + process.exit(1); + }); +} +``` + +## Best Practices Summary + +1. **Server Creation** + - Use low-level Server for custom routing + - Use McpServer for standard implementations + +2. **Transport Usage** + - Initialize backends before connecting transport + - Use StdioServerTransport for CLI tools + - Log to stderr, not stdout + +3. **Error Handling** + - Always validate tool arguments + - Include isError flag in error responses + - Log errors to stderr with context + +4. **Response Format** + - Always return content array + - Use consistent type/text structure + - Include isError for failures + +5. **Lifecycle** + - Clean initialization sequence + - Graceful shutdown handlers + - Proper exit codes (0 for success, 1 for error) + +## References + +- [MCP TypeScript SDK README](https://github.com/modelcontextprotocol/typescript-sdk) +- [Low-Level Server Pattern](https://github.com/modelcontextprotocol/typescript-sdk#low-level-server-implementation) +- [Stdio Transport Example](https://github.com/modelcontextprotocol/typescript-sdk#stdio-transport) +- [Error Handling Examples](https://github.com/modelcontextprotocol/typescript-sdk#sqlite-explorer) \ No newline at end of file diff --git a/docs/reference/mcp-sdk/stdio-transport.md b/docs/reference/mcp-sdk/stdio-transport.md new file mode 100644 index 00000000..eeb5bcb8 --- /dev/null +++ b/docs/reference/mcp-sdk/stdio-transport.md @@ -0,0 +1,345 @@ +# MCP TypeScript SDK Stdio Transport Guide + +## Documentation Source +- **SDK Version**: @modelcontextprotocol/sdk v1.0.0 +- **Last Verified**: 2025-09-01 +- **Official Docs**: https://github.com/modelcontextprotocol/typescript-sdk + +## Stdio Transport Overview + +The StdioServerTransport enables MCP servers to communicate via standard input/output streams, making them ideal for CLI tools and direct integrations with Claude Code. + +## Communication Channels + +### Stream Usage +- **stdin**: Receives MCP protocol messages (JSON-RPC) +- **stdout**: Sends MCP protocol responses (JSON-RPC) +- **stderr**: Logging and diagnostic output + +### Important Rules +1. **Never write non-protocol data to stdout** - This will break the protocol +2. **Always use console.error() for logging** - Goes to stderr +3. **Handle binary data carefully** - Protocol is text-based JSON + +## Implementation Patterns + +### Basic Stdio Server Setup + +```typescript +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +// Create server +const server = new Server( + { name: 'my-server', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +// Create and connect transport +const transport = new StdioServerTransport(); +await server.connect(transport); + +// Server is now listening on stdin/stdout +console.error('Server started'); // Note: console.error for logging +``` + +### With McpServer (High-Level API) + +```typescript +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +const server = new McpServer({ + name: 'my-server', + version: '1.0.0' +}); + +// Register tools, resources, prompts... +server.registerTool(...); + +// Connect to stdio +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +## CLI Entry Point Pattern + +### Proper Module Detection (ES Modules) + +```typescript +#!/usr/bin/env node + +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Only run if executed directly +if (process.argv[1] === __filename || + process.argv[1].endsWith('server.js')) { + main().catch((error) => { + console.error('Startup error:', error); + process.exit(1); + }); +} +``` + +### Main Function Pattern + +```typescript +async function main(): Promise { + try { + // 1. Initialize dependencies + await initializeDatabase(); + + // 2. Create server + const server = createServer(); + + // 3. Create transport + const transport = new StdioServerTransport(); + + // 4. Connect + await server.connect(transport); + + // 5. Setup shutdown handlers + setupShutdownHandlers(); + + // 6. Log readiness to stderr + console.error('MCP server ready on stdio'); + + } catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +} +``` + +## Shutdown Handling + +### Graceful Shutdown Pattern + +```typescript +function setupShutdownHandlers(): void { + const shutdown = async (signal: string) => { + console.error(`\nReceived ${signal}, shutting down...`); + + try { + // Clean up resources + await cleanupResources(); + + // Note: Transport cleanup is handled automatically + process.exit(0); + } catch (error) { + console.error('Shutdown error:', error); + process.exit(1); + } + }; + + // Handle termination signals + process.on('SIGINT', () => shutdown('SIGINT')); // Ctrl+C + process.on('SIGTERM', () => shutdown('SIGTERM')); // Kill + process.on('SIGHUP', () => shutdown('SIGHUP')); // Terminal closed + + // Handle errors + process.on('uncaughtException', (error) => { + console.error('Uncaught exception:', error); + process.exit(1); + }); + + process.on('unhandledRejection', (reason) => { + console.error('Unhandled rejection:', reason); + process.exit(1); + }); +} +``` + +## Logging Best Practices + +### Do's and Don'ts + +```typescript +// βœ… DO: Log to stderr +console.error('Server initialized'); +console.error('Processing request:', requestId); +console.error('Debug info:', { data }); + +// ❌ DON'T: Log to stdout +console.log('This breaks the protocol!'); // NEVER DO THIS + +// βœ… DO: Use structured logging to stderr +const log = (level: string, message: string, data?: any) => { + console.error(JSON.stringify({ + timestamp: new Date().toISOString(), + level, + message, + ...data + })); +}; + +log('info', 'Server started', { port: 'stdio' }); +``` + +### Debug Mode Pattern + +```typescript +const DEBUG = process.env.DEBUG === 'true'; + +const debug = (message: string, ...args: any[]) => { + if (DEBUG) { + console.error(`[DEBUG] ${message}`, ...args); + } +}; + +// Usage +debug('Request received:', request); +``` + +## Testing Stdio Servers + +### Manual Testing + +```bash +# Start server and interact manually +node dist/server.js + +# With debug logging +DEBUG=true node dist/server.js + +# Pipe test input +echo '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' | node dist/server.js +``` + +### Automated Testing Pattern + +```typescript +import { spawn } from 'child_process'; + +function testServer() { + const server = spawn('node', ['dist/server.js']); + + // Capture stderr for logs + server.stderr.on('data', (data) => { + console.log('Server log:', data.toString()); + }); + + // Capture stdout for protocol + let response = ''; + server.stdout.on('data', (data) => { + response += data.toString(); + // Parse and validate response + }); + + // Send test request + server.stdin.write(JSON.stringify({ + jsonrpc: '2.0', + method: 'initialize', + params: {}, + id: 1 + })); + server.stdin.end(); +} +``` + +## Common Issues and Solutions + +### Issue 1: Protocol Corruption + +**Problem**: Random text in stdout breaks communication +**Solution**: Always use console.error() for logging + +```typescript +// Wrong +console.log('Debug:', data); // Breaks protocol + +// Right +console.error('Debug:', data); // Safe for debugging +``` + +### Issue 2: Server Not Responding + +**Problem**: Server starts but doesn't respond to requests +**Solution**: Ensure transport is connected + +```typescript +// Check connection is awaited +await server.connect(transport); // Must await! +console.error('Transport connected'); +``` + +### Issue 3: Premature Exit + +**Problem**: Server exits immediately +**Solution**: Don't close stdin/stdout + +```typescript +// Wrong +process.stdin.end(); // Don't do this + +// Right +// Let the transport manage streams +``` + +## Integration with Claude Code + +### Configuration in .claude.json + +```json +{ + "mcpServers": { + "my-server": { + "command": "node", + "args": ["/path/to/dist/server.js"], + "env": { + "DEBUG": "false" + } + } + } +} +``` + +### Best Practices for Claude Code Integration + +1. **Startup Messages**: Log clear startup messages to stderr +2. **Error Messages**: Provide actionable error messages +3. **Ready Signal**: Log when server is ready to accept requests +4. **Version Info**: Include version in startup logs + +```typescript +console.error(`Starting ${serverName} v${version}`); +console.error('Initializing...'); +// ... initialization ... +console.error(`${serverName} ready on stdio`); +``` + +## Performance Considerations + +### Buffering and Streaming + +```typescript +// For large responses, consider streaming +import { Transform } from 'stream'; + +class ResponseStream extends Transform { + _transform(chunk: any, encoding: string, callback: Function) { + // Process chunk + this.push(JSON.stringify(chunk)); + callback(); + } +} +``` + +### Memory Management + +```typescript +// Clear large objects after use +let largeData = await processData(); +// Use data... +largeData = null; // Allow GC +``` + +## References + +- [StdioServerTransport Docs](https://github.com/modelcontextprotocol/typescript-sdk#stdio-transport) +- [Server Examples](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/src/examples/server) +- [MCP Protocol Specification](https://modelcontextprotocol.io/docs) \ No newline at end of file diff --git a/hooks/pre-compact.js b/hooks/pre-compact.js new file mode 100755 index 00000000..38e09407 --- /dev/null +++ b/hooks/pre-compact.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node + +/** + * Pre-Compact Hook for Claude Memory System + * + * Updated to use the centralized PromptOrchestrator and HookTemplates system. + * This hook validates the pre-compact request and executes compression using + * standardized response templates for consistent Claude Code integration. + */ + +import { loadCliCommand } from './shared/config-loader.js'; +import { + createHookResponse, + executeCliCommand, + validateHookPayload, + debugLog +} from './shared/hook-helpers.js'; + +const cliCommand = loadCliCommand(); + +// Read input from stdin +let input = ''; +process.stdin.on('data', chunk => { + input += chunk; +}); + +process.stdin.on('end', async () => { + try { + const payload = JSON.parse(input); + debugLog('Pre-compact hook started', { payload }); + + // Validate payload using centralized validation + const validation = validateHookPayload(payload, 'PreCompact'); + if (!validation.valid) { + const response = createHookResponse('PreCompact', false, { reason: validation.error }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + // Check for environment-based blocking conditions + if (payload.trigger === 'auto' && process.env.DISABLE_AUTO_COMPRESSION === 'true') { + debugLog('Auto-compression disabled by configuration'); + const response = createHookResponse('PreCompact', false, { + reason: 'Auto-compression disabled by configuration' + }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + // Execute compression using standardized CLI execution helper + debugLog('Executing compression command', { + command: cliCommand, + args: ['compress', payload.transcript_path] + }); + + const result = await executeCliCommand(cliCommand, ['compress', payload.transcript_path]); + + if (!result.success) { + debugLog('Compression command failed', { stderr: result.stderr }); + const response = createHookResponse('PreCompact', false, { + reason: `Compression failed: ${result.stderr || 'Unknown error'}` + }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + // Success - create standardized approval response using HookTemplates + debugLog('Compression completed successfully'); + const response = createHookResponse('PreCompact', true); + console.log(JSON.stringify(response)); + process.exit(0); + + } catch (error) { + debugLog('Pre-compact hook error', { error: error.message }); + const response = createHookResponse('PreCompact', false, { + reason: `Hook execution error: ${error.message}` + }); + console.log(JSON.stringify(response)); + process.exit(1); + } +}); \ No newline at end of file diff --git a/hooks/session-end.js b/hooks/session-end.js new file mode 100644 index 00000000..68d1783f --- /dev/null +++ b/hooks/session-end.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +/** + * Session End Hook - Handles session end events including /clear + */ + +import { loadCliCommand } from './shared/config-loader.js'; +import { execSync } from 'child_process'; +import { join } from 'path'; +import { homedir } from 'os'; +import { existsSync, readFileSync } from 'fs'; + +const cliCommand = loadCliCommand(); + +// Check if save-on-clear is enabled +function isSaveOnClearEnabled() { + const settingsPath = join(homedir(), '.claude-mem', 'settings.json'); + if (existsSync(settingsPath)) { + try { + const settings = JSON.parse(readFileSync(settingsPath, 'utf8')); + return settings.saveMemoriesOnClear === true; + } catch (error) { + return false; + } + } + return false; +} + +// Read input +let input = ''; +process.stdin.on('data', chunk => { + input += chunk; +}); + +process.stdin.on('end', async () => { + const data = JSON.parse(input); + + // Check if this is a clear event and save-on-clear is enabled + if (data.reason === 'clear' && isSaveOnClearEnabled()) { + console.error('🧠 Saving memories before clearing context...'); + + try { + // Use the CLI to compress current transcript + execSync(`${cliCommand} compress --output ${homedir()}/.claude-mem/archives`, { + stdio: 'inherit', + env: { ...process.env, CLAUDE_MEM_SILENT: 'true' } + }); + + console.error('βœ… Memories saved successfully'); + } catch (error) { + console.error('[session-end] Failed to save memories:', error.message); + // Don't block the clear operation if memory saving fails + } + } + + // Always continue + console.log(JSON.stringify({ continue: true })); +}); \ No newline at end of file diff --git a/hooks/session-start.js b/hooks/session-start.js new file mode 100755 index 00000000..b690511d --- /dev/null +++ b/hooks/session-start.js @@ -0,0 +1,166 @@ +#!/usr/bin/env node + +/** + * Session Start Hook - Load context when Claude Code starts + * + * Updated to use the centralized PromptOrchestrator and HookTemplates system. + * This hook loads previous session context using standardized formatting and + * provides rich context messages for Claude Code integration. + */ + +import path from 'path'; +import { loadCliCommand } from './shared/config-loader.js'; +import { + createHookResponse, + formatSessionStartContext, + executeCliCommand, + parseContextData, + validateHookPayload, + debugLog +} from './shared/hook-helpers.js'; + +const cliCommand = loadCliCommand(); + +// Read input from stdin +let input = ''; +process.stdin.on('data', chunk => { + input += chunk; +}); + +process.stdin.on('end', async () => { + try { + const payload = JSON.parse(input); + debugLog('Session start hook started', { payload }); + + // Validate payload using centralized validation + const validation = validateHookPayload(payload, 'SessionStart'); + if (!validation.valid) { + debugLog('Payload validation failed', { error: validation.error }); + // For session start, continue even with invalid payload but log the error + const response = createHookResponse('SessionStart', false, { + error: `Payload validation failed: ${validation.error}` + }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + // Skip load-context when source is "resume" to avoid duplicate context + if (payload.source === 'resume') { + debugLog('Skipping load-context for resume source'); + // Output nothing at all for resume - no message, no context + process.exit(0); + } + + // Extract project name from current working directory and sanitize + const rawProjectName = path.basename(process.cwd()); + const projectName = rawProjectName.replace(/-/g, '_'); + debugLog('Extracted project name', { rawProjectName, projectName }); + + // Load context using standardized CLI execution helper + const contextResult = await executeCliCommand(cliCommand, [ + 'load-context', + '--format', 'session-start', + '--project', projectName + ]); + + if (!contextResult.success) { + debugLog('Context loading failed', { stderr: contextResult.stderr }); + // Don't fail the session start, just provide error context + const response = createHookResponse('SessionStart', false, { + error: contextResult.stderr || 'Failed to load context' + }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + const rawContext = contextResult.stdout; + debugLog('Raw context loaded', { contextLength: rawContext.length }); + + // Check if the output is actually an error message (starts with ❌) + if (rawContext && rawContext.trim().startsWith('❌')) { + debugLog('Detected error message in stdout', { rawContext }); + // Extract the clean error message without the emoji and format + const errorMatch = rawContext.match(/❌\s*[^:]+:\s*([^\n]+)(?:\n\nπŸ’‘\s*(.+))?/); + let errorMsg = 'No previous memories found'; + let suggestion = ''; + + if (errorMatch) { + errorMsg = errorMatch[1] || errorMsg; + suggestion = errorMatch[2] || ''; + } + + // Create a clean response without duplicating the error formatting + const response = createHookResponse('SessionStart', false, { + error: errorMsg + (suggestion ? `. ${suggestion}` : '') + }); + console.log(JSON.stringify(response)); + process.exit(0); + } + + if (!rawContext || !rawContext.trim()) { + debugLog('No context available, creating empty response'); + // No context available - use standardized empty response + const response = createHookResponse('SessionStart', true); + console.log(JSON.stringify(response)); + process.exit(0); + } + + // Parse context data and format using centralized templates + const contextData = parseContextData(rawContext); + contextData.projectName = projectName; + + // If we have raw context (not structured data), use it directly + let formattedContext; + if (contextData.rawContext) { + formattedContext = contextData.rawContext; + } else { + // Use standardized formatting for structured context + formattedContext = formatSessionStartContext(contextData); + } + + debugLog('Context formatted successfully', { + memoryCount: contextData.memoryCount, + hasStructuredData: !contextData.rawContext + }); + + // Create standardized session start response using HookTemplates + const response = createHookResponse('SessionStart', true, { + context: formattedContext + }); + + console.log(JSON.stringify(response)); + process.exit(0); + + } catch (error) { + debugLog('Session start hook error', { error: error.message }); + // Even on error, continue the session with error information + const response = createHookResponse('SessionStart', false, { + error: `Hook execution error: ${error.message}` + }); + console.log(JSON.stringify(response)); + process.exit(0); + } +}); + +/** + * Extracts project name from transcript path + * @param {string} transcriptPath - Path to transcript file + * @returns {string|null} Extracted project name or null + */ +function extractProjectName(transcriptPath) { + if (!transcriptPath) return null; + + // Look for project pattern: /path/to/PROJECT_NAME/.claude/ + // Need to get PROJECT_NAME, not the parent directory + const parts = transcriptPath.split('/'); + const claudeIndex = parts.indexOf('.claude'); + + if (claudeIndex > 0) { + // Get the directory immediately before .claude + return parts[claudeIndex - 1]; + } + + // Fall back to directory containing the transcript + const dir = path.dirname(transcriptPath); + return path.basename(dir); +} \ No newline at end of file diff --git a/hooks/shared/config-loader.js b/hooks/shared/config-loader.js new file mode 100644 index 00000000..099746d2 --- /dev/null +++ b/hooks/shared/config-loader.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +/** + * Shared configuration loader utility for Claude Memory hooks + * Loads CLI command name from config.json with proper fallback handling + */ + +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { readFileSync, existsSync } from 'fs'; + +/** + * Loads the CLI command name from the hooks config.json file + * @returns {string} The CLI command name (defaults to 'claude-mem') + */ +export function loadCliCommand() { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const configPath = join(__dirname, '..', 'config.json'); + + if (existsSync(configPath)) { + const config = JSON.parse(readFileSync(configPath, 'utf-8')); + return config.cliCommand || 'claude-mem'; + } + + return 'claude-mem'; +} \ No newline at end of file diff --git a/hooks/shared/hook-helpers.js b/hooks/shared/hook-helpers.js new file mode 100644 index 00000000..d07a7f6f --- /dev/null +++ b/hooks/shared/hook-helpers.js @@ -0,0 +1,230 @@ +#!/usr/bin/env node + +/** + * Hook Helper Functions + * + * This module provides JavaScript wrappers around the TypeScript PromptOrchestrator + * and HookTemplates system, making them accessible to the JavaScript hook scripts. + */ + +import { spawn } from 'child_process'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** + * Creates a standardized hook response using the HookTemplates system + * @param {string} hookType - Type of hook ('PreCompact' or 'SessionStart') + * @param {boolean} success - Whether the operation was successful + * @param {Object} options - Additional options + * @returns {Object} Formatted hook response + */ +export function createHookResponse(hookType, success, options = {}) { + if (hookType === 'PreCompact') { + if (success) { + return { + continue: true, + suppressOutput: true + }; + } else { + return { + continue: false, + stopReason: options.reason || 'Pre-compact operation failed', + suppressOutput: true + }; + } + } + + if (hookType === 'SessionStart') { + if (success && options.context) { + return { + continue: true, + suppressOutput: true, + hookSpecificOutput: { + hookEventName: 'SessionStart', + additionalContext: options.context + } + }; + } else if (success) { + return { + continue: true, + suppressOutput: true, + hookSpecificOutput: { + hookEventName: 'SessionStart', + additionalContext: 'Starting fresh session - no previous context available' + } + }; + } else { + return { + continue: true, // Continue even on context loading failure + suppressOutput: true, + hookSpecificOutput: { + hookEventName: 'SessionStart', + additionalContext: `Context loading encountered an issue: ${options.error || 'Unknown error'}. Starting without previous context.` + } + }; + } + } + + // Generic response for unknown hook types + return { + continue: success, + suppressOutput: true, + ...(options.reason && !success ? { stopReason: options.reason } : {}) + }; +} + +/** + * Formats a session start context message using standardized templates + * @param {Object} contextData - Context information + * @returns {string} Formatted context message + */ +export function formatSessionStartContext(contextData) { + const { + projectName = 'unknown project', + memoryCount = 0, + lastSessionTime, + recentComponents = [], + recentDecisions = [] + } = contextData; + + const timeInfo = lastSessionTime ? ` (last worked: ${lastSessionTime})` : ''; + const contextParts = []; + + contextParts.push(`🧠 Loaded ${memoryCount} memories from previous sessions for ${projectName}${timeInfo}`); + + if (recentComponents.length > 0) { + contextParts.push(`\n🎯 Recent components: ${recentComponents.slice(0, 3).join(', ')}`); + } + + if (recentDecisions.length > 0) { + contextParts.push(`\nπŸ”„ Recent decisions: ${recentDecisions.slice(0, 2).join(', ')}`); + } + + if (memoryCount > 0) { + contextParts.push('\nπŸ’‘ Use search_nodes("keywords") to find related work or open_nodes(["entity_name"]) to load specific components'); + } + + return contextParts.join(''); +} + +/** + * Executes a CLI command and returns the result + * @param {string} command - CLI command to execute + * @param {Array} args - Command arguments + * @param {Object} options - Spawn options + * @returns {Promise<{stdout: string, stderr: string, success: boolean}>} + */ +export async function executeCliCommand(command, args = [], options = {}) { + return new Promise((resolve) => { + const process = spawn(command, args, { + stdio: ['ignore', 'pipe', 'pipe'], + ...options + }); + + let stdout = ''; + let stderr = ''; + + if (process.stdout) { + process.stdout.on('data', (data) => { + stdout += data.toString(); + }); + } + + if (process.stderr) { + process.stderr.on('data', (data) => { + stderr += data.toString(); + }); + } + + process.on('close', (code) => { + resolve({ + stdout: stdout.trim(), + stderr: stderr.trim(), + success: code === 0 + }); + }); + + process.on('error', (error) => { + resolve({ + stdout: '', + stderr: error.message, + success: false + }); + }); + }); +} + +/** + * Parses context data from CLI output + * @param {string} output - Raw CLI output + * @returns {Object} Parsed context data + */ +export function parseContextData(output) { + if (!output || !output.trim()) { + return { + memoryCount: 0, + recentComponents: [], + recentDecisions: [] + }; + } + + // Try to parse as JSON first (if CLI outputs structured data) + try { + const parsed = JSON.parse(output); + return { + memoryCount: parsed.memoryCount || 0, + recentComponents: parsed.recentComponents || [], + recentDecisions: parsed.recentDecisions || [], + lastSessionTime: parsed.lastSessionTime + }; + } catch (e) { + // If not JSON, treat as plain text context + const lines = output.split('\n').filter(line => line.trim()); + return { + memoryCount: lines.length, + recentComponents: [], + recentDecisions: [], + rawContext: output + }; + } +} + +/** + * Validates hook payload structure + * @param {Object} payload - Hook payload to validate + * @param {string} expectedHookType - Expected hook event name + * @returns {{valid: boolean, error?: string}} Validation result + */ +export function validateHookPayload(payload, expectedHookType) { + if (!payload || typeof payload !== 'object') { + return { valid: false, error: 'Payload must be a valid object' }; + } + + if (!payload.session_id || typeof payload.session_id !== 'string') { + return { valid: false, error: 'Missing or invalid session_id' }; + } + + if (!payload.transcript_path || typeof payload.transcript_path !== 'string') { + return { valid: false, error: 'Missing or invalid transcript_path' }; + } + + if (expectedHookType && payload.hook_event_name !== expectedHookType) { + return { valid: false, error: `Expected hook_event_name to be ${expectedHookType}` }; + } + + return { valid: true }; +} + +/** + * Logs debug information if debug mode is enabled + * @param {string} message - Debug message + * @param {Object} data - Additional data to log + */ +export function debugLog(message, data = {}) { + if (process.env.DEBUG === 'true' || process.env.CLAUDE_MEM_DEBUG === 'true') { + const timestamp = new Date().toISOString(); + console.error(`[${timestamp}] HOOK DEBUG: ${message}`, data); + } +} \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..31878c5d --- /dev/null +++ b/install.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Claude Mem Installation Script + +set -e + +VERSION="${1:-latest}" +PLATFORM="" +ARCH=$(uname -m) +OS=$(uname -s) + +# Detect platform +case "$OS" in + Darwin) + if [ "$ARCH" = "arm64" ]; then + PLATFORM="macos-arm64" + BINARY="claude-mem-macos-arm64" + else + PLATFORM="macos-x64" + BINARY="claude-mem-macos-x64" + fi + ;; + Linux) + if [ "$ARCH" = "aarch64" ]; then + PLATFORM="linux-arm64" + BINARY="claude-mem-linux-arm64" + else + PLATFORM="linux-x64" + BINARY="claude-mem-linux" + fi + ;; + MINGW*|MSYS*|CYGWIN*) + PLATFORM="windows-x64" + BINARY="claude-mem.exe" + ;; + *) + echo "Unsupported platform: $OS $ARCH" + exit 1 + ;; +esac + +echo "πŸ“₯ Downloading Claude Mem for $PLATFORM..." + +# Download binary from GitHub releases +if [ "$VERSION" = "latest" ]; then + DOWNLOAD_URL="https://github.com/thedotmack/claude-mem/releases/latest/download/${BINARY}" +else + DOWNLOAD_URL="https://github.com/thedotmack/claude-mem/releases/download/${VERSION}/${BINARY}" +fi + +curl -L -o claude-mem "$DOWNLOAD_URL" + +# Make executable (non-Windows) +if [ "$OS" != "MINGW" ] && [ "$OS" != "MSYS" ] && [ "$OS" != "CYGWIN" ]; then + chmod +x claude-mem +fi + +echo "βœ… Claude Mem installed successfully!" +echo "Run ./claude-mem --help to get started"