feat: Claude memory integration

This commit is contained in:
Dhravya Shah 2025-09-29 13:40:56 -07:00
parent 5bbb4edf1b
commit 53bc296155
13 changed files with 2647 additions and 4 deletions

View file

@ -184,6 +184,11 @@
"icon": "sparkles",
"pages": ["memory-api/sdks/openai-plugins", "ai-sdk/npm"]
},
{
"group": "Anthropic SDK",
"icon": "sparkles",
"pages": ["memory-api/sdks/anthropic-claude-memory"]
},
{
"group": "AI SDK",
"icon": "triangle",

View file

@ -0,0 +1,375 @@
---
title: "Claude Memory Tool"
description: "Enable Claude's persistent memory capabilities with Supermemory as the backend"
---
Enable Claude's native memory tool functionality with Supermemory as the persistent storage backend. Claude can automatically store and retrieve information across conversations using familiar filesystem operations.
<Card title="Supermemory tools on npm" icon="npm" href="https://www.npmjs.com/package/@supermemory/tools">
Check out the NPM page for more details
</Card>
## Installation
```bash
npm install @supermemory/tools @anthropic-ai/sdk
```
## Quick Start
```typescript
import Anthropic from "@anthropic-ai/sdk"
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
})
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: 'my-app',
})
async function chatWithMemory(userMessage: string) {
const response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 8096,
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
messages: [{ role: 'user', content: userMessage }],
})
// Handle memory tool calls
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
const result = await memoryTool.handleCommandForToolResult(
block.input as MemoryCommand,
block.id
)
console.log('Memory operation result:', result)
}
}
return response
}
```
## How It Works
Claude's memory tool uses a filesystem metaphor to manage persistent information:
- **Files**: Individual memory items stored as Supermemory documents
- **Directories**: Organized using path structure (e.g., `/memories/user-preferences`)
- **Operations**: View, create, edit, delete, and rename files
- **Path Normalization**: Paths like `/memories/preferences` are stored as `--memories--preferences`
## Configuration
### Basic Configuration
```typescript
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
const memoryTool = createClaudeMemoryTool(
process.env.SUPERMEMORY_API_KEY!,
{
projectId: 'my-app', // Project identifier
baseUrl: 'https://api.supermemory.ai', // Optional: custom API endpoint
}
)
```
### Using Container Tags
```typescript
const memoryTool = createClaudeMemoryTool(
process.env.SUPERMEMORY_API_KEY!,
{
containerTags: ['user:alice', 'app:chat'],
}
)
```
## Memory Operations
Claude automatically performs these operations when managing memory:
### View Files
List directory contents or read file contents:
```typescript
// Claude will automatically check /memories/ directory
// This maps to searching Supermemory documents with path prefix
```
### Create Files
Store new information:
```typescript
// Claude creates: /memories/user-preferences.txt
// Stored as document with customId: "--memories--user-preferences.txt"
```
### Edit Files
Update existing memories using string replacement:
```typescript
// Claude performs str_replace on file contents
// Updates the corresponding Supermemory document
```
### Delete Files
Remove information:
```typescript
// Claude deletes: /memories/old-data.txt
// Removes document from Supermemory
```
### Rename Files
Reorganize memory structure:
```typescript
// Claude renames: /memories/temp.txt -> /memories/final.txt
// Updates document customId in Supermemory
```
## Complete Chat Example
Here's a full example with conversation handling:
```typescript
import Anthropic from "@anthropic-ai/sdk"
import { createClaudeMemoryTool, type MemoryCommand } from "@supermemory/tools/claude-memory"
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
})
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: 'chat-app',
})
async function chat() {
const messages: Anthropic.MessageParam[] = []
// First message: store information
messages.push({
role: 'user',
content: 'Remember that I prefer dark mode and use TypeScript'
})
let response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 8096,
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
messages,
})
// Process tool calls
const toolResults: Anthropic.ToolResultBlockParam[] = []
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
const result = await memoryTool.handleCommandForToolResult(
block.input as MemoryCommand,
block.id
)
toolResults.push(result)
}
}
// Continue conversation with tool results
if (toolResults.length > 0) {
messages.push({ role: 'assistant', content: response.content })
messages.push({ role: 'user', content: toolResults })
response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 8096,
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
messages,
})
}
console.log(response.content)
}
chat()
```
## Advanced Usage
### Custom Path Management
Control where Claude stores information:
```typescript
// Claude automatically organizes by path
// /memories/preferences/editor.txt
// /memories/projects/current.txt
// /memories/notes/meeting-2024.txt
// Each path is normalized and stored with appropriate metadata
```
### Error Handling
Handle operations gracefully:
```typescript
const result = await memoryTool.handleCommandForToolResult(command, toolUseId)
if (result.is_error) {
console.error('Memory operation failed:', result.content)
// Handle error appropriately
} else {
console.log('Success:', result.content)
}
```
### Monitoring Operations
Track memory operations:
```typescript
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
console.log('Operation:', block.input.command)
console.log('Path:', block.input.path)
const result = await memoryTool.handleCommandForToolResult(
block.input as MemoryCommand,
block.id
)
console.log('Result:', result.is_error ? 'Failed' : 'Success')
}
}
```
## Memory Organization
### Best Practices
1. **Use Descriptive Paths**: `/memories/user-preferences/theme.txt` is better than `/mem1.txt`
2. **Organize by Category**: Group related information under directories
3. **Keep Files Focused**: Store specific information in separate files
4. **Use Clear Naming**: Make file names self-explanatory
### Path Structure Examples
```
/memories/
├── user-profile/
│ ├── name.txt
│ ├── preferences.txt
│ └── settings.txt
├── projects/
│ ├── current-task.txt
│ └── goals.txt
└── notes/
├── meeting-notes.txt
└── ideas.txt
```
## API Reference
### `createClaudeMemoryTool(apiKey, config)`
Creates a memory tool instance for Claude.
**Parameters:**
- `apiKey` (string): Your Supermemory API key
- `config` (optional):
- `projectId` (string): Project identifier
- `containerTags` (string[]): Alternative to projectId
- `baseUrl` (string): Custom API endpoint
**Returns:** `ClaudeMemoryTool` instance
### `handleCommandForToolResult(command, toolUseId)`
Processes a memory command and returns formatted result.
**Parameters:**
- `command` (MemoryCommand): The memory operation from Claude
- `toolUseId` (string): Tool use ID from Claude's response
**Returns:** `Promise<MemoryToolResult>` with:
- `type`: "tool_result"
- `tool_use_id`: The tool use ID
- `content`: Operation result or error message
- `is_error`: Boolean indicating success/failure
## Memory Commands
Claude uses these command types internally:
| Command | Description | Example |
|---------|-------------|---------|
| `view` | List directory or read file | `/memories/` or `/memories/file.txt` |
| `create` | Create new file | Create `/memories/new.txt` with content |
| `str_replace` | Edit file contents | Replace "old text" with "new text" |
| `insert` | Add content at line | Insert at line 5 |
| `delete` | Remove file | Delete `/memories/old.txt` |
| `rename` | Rename/move file | Rename `old.txt` to `new.txt` |
## Environment Variables
```bash
ANTHROPIC_API_KEY=your_anthropic_key
SUPERMEMORY_API_KEY=your_supermemory_key
```
## When to Use
The Claude Memory Tool is ideal for:
- **Conversational AI**: Claude automatically remembers user preferences and context
- **Personal Assistants**: Store and retrieve user-specific information
- **Documentation Bots**: Maintain knowledge across conversations
- **Project Management**: Track tasks and project state
- **Note-Taking Apps**: Persistent memory for meeting notes and ideas
## Comparison with Other Approaches
| Feature | Claude Memory Tool | OpenAI SDK Tools | AI SDK Tools |
|---------|-------------------|------------------|--------------|
| Automatic Memory | ✅ Claude decides | ❌ Manual control | ❌ Manual control |
| Filesystem Metaphor | ✅ Files/directories | ❌ Flat storage | ❌ Flat storage |
| Path Organization | ✅ Hierarchical | ❌ Tags only | ❌ Tags only |
| Integration | Anthropic SDK only | OpenAI SDK only | Vercel AI SDK |
## Limitations
- Requires Anthropic SDK with beta features enabled
- Path separators (`/`) are normalized to `--` for storage
- Maximum 100 files per directory listing
- File operations are asynchronous
## Next Steps
<CardGroup cols={2}>
<Card title="OpenAI SDK Tools" icon="sparkles" href="/memory-api/sdks/openai-plugins">
Use memory tools with OpenAI function calling
</Card>
<Card title="AI SDK Integration" icon="triangle" href="/ai-sdk/overview">
Integrate with Vercel AI SDK
</Card>
<Card title="Memory API" icon="database" href="/memory-api/overview">
Direct API access for advanced control
</Card>
<Card title="Cookbook" icon="chef-hat" href="/cookbook/overview">
See real-world examples
</Card>
</CardGroup>

View file

@ -197,6 +197,7 @@
"dependencies": {
"@ai-sdk/openai": "^2.0.23",
"@ai-sdk/provider": "^2.0.0",
"@anthropic-ai/sdk": "^0.65.0",
"ai": "^5.0.29",
"openai": "^4.104.0",
"supermemory": "^3.0.0-alpha.26",
@ -2985,6 +2986,8 @@
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json-schema-walker": ["json-schema-walker@2.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="],
@ -4137,6 +4140,8 @@
"truncate": ["truncate@3.0.0", "", {}, "sha512-C+0Xojw7wZPl6MDq5UjMTuxZvBPK04mtdFet7k+GSZPINcvLZFCXg+15kWIL4wAqDB7CksIsKiRLbQ1wa7rKdw=="],
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
@ -4827,7 +4832,7 @@
"@supermemory/tools/@ai-sdk/openai": ["@ai-sdk/openai@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uOXk8HzmMUoCmD0JMX/Y1HC/ABOR/Jza2Z2rkCaJISDYz3fp5pnb6eNjcPRL48JSMzRAGp9UP5p0OpxS06IJZg=="],
"@supermemory/tools/@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
"@supermemory/tools/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.65.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw=="],
"@supermemory/tools/ai": ["ai@5.0.29", "", { "dependencies": { "@ai-sdk/gateway": "1.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-jA/d6X5hn3r/PxgZjwzDUMJiEkLBIVVD2gcbpcT/FD4MSLxm5sn6fH1y2VFXVgBEd95mNzQ8ALQubysc6E8Y9g=="],

View file

@ -147,9 +147,78 @@ Adds a new memory to the system.
## Claude Memory Tool
Enable Claude to store and retrieve persistent memory across conversations using supermemory as the backend.
### Installation
```bash
npm install @supermemory/tools @anthropic-ai/sdk
```
### Basic Usage
```typescript
import Anthropic from '@anthropic-ai/sdk'
import { createClaudeMemoryTool } from '@supermemory/tools/claude-memory'
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
})
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: 'my-app',
})
async function chatWithMemory(userMessage: string) {
// Send message to Claude with memory tool
const response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: [{ role: 'user', content: userMessage }],
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
})
// Handle any memory tool calls
const toolResults = []
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
const toolResult = await memoryTool.handleCommandForToolResult(
block.input,
block.id
)
toolResults.push(toolResult)
}
}
return response
}
// Example usage
const response = await chatWithMemory(
"Remember that I prefer React with TypeScript for my projects"
)
```
### Memory Operations
Claude can perform these memory operations automatically:
- **`view`** - List memory directory contents or read specific files
- **`create`** - Create new memory files with content
- **`str_replace`** - Find and replace text within memory files
- **`insert`** - Insert text at specific line numbers
- **`delete`** - Delete memory files
- **`rename`** - Rename or move memory files
All memory files are stored in supermemory with normalized paths and can be searched and retrieved across conversations.
## Environment Variables
```env
SUPERMEMORY_API_KEY=your_supermemory_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key # for Claude Memory Tool
SUPERMEMORY_BASE_URL=https://your-custom-url # optional
```

View file

@ -1,7 +1,7 @@
{
"name": "@supermemory/tools",
"type": "module",
"version": "1.0.41",
"version": "1.1.0",
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
"scripts": {
"build": "tsdown",
@ -24,7 +24,8 @@
"dotenv": "^16.6.1",
"tsdown": "^0.14.2",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"@anthropic-ai/sdk": "^0.65.0"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
@ -32,6 +33,7 @@
"exports": {
".": "./dist/index.js",
"./ai-sdk": "./dist/ai-sdk.js",
"./claude-memory": "./dist/claude-memory.js",
"./openai": "./dist/openai.js",
"./package.json": "./package.json"
},

View file

@ -0,0 +1,71 @@
#!/usr/bin/env bun
/**
* Simple Claude Memory Tool Example
* Shows the cleanest way to integrate Claude's memory tool with supermemory
*/
import Anthropic from '@anthropic-ai/sdk'
import { createClaudeMemoryTool } from './claude-memory'
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
})
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: 'my-app',
})
async function chatWithMemory(userMessage: string) {
// Send message to Claude with memory tool
const response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: [{ role: 'user', content: userMessage }],
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
})
// Handle any memory tool calls
const toolResults = []
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
const toolResult = await memoryTool.handleCommandForToolResult(
block.input as any,
block.id
)
toolResults.push(toolResult)
}
}
// Send tool results back to Claude if needed
if (toolResults.length > 0) {
const finalResponse = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: [
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response.content },
{ role: 'user', content: toolResults },
],
tools: [{ type: 'memory_20250818', name: 'memory' }],
betas: ['context-management-2025-06-27'],
})
return finalResponse
}
return response
}
// Example usage
async function example() {
const response = await chatWithMemory(
"Remember that I prefer React with TypeScript for my projects"
)
console.log(response.content[0])
}
if (import.meta.main) {
example()
}

View file

@ -0,0 +1,564 @@
import Supermemory from "supermemory"
import type { SupermemoryToolsConfig } from "./types"
import { getContainerTags } from "./shared"
// Claude Memory Tool Types
export interface ClaudeMemoryConfig extends SupermemoryToolsConfig {
memoryContainerTag?: string
}
export interface MemoryCommand {
command: "view" | "create" | "str_replace" | "insert" | "delete" | "rename"
path: string
// view specific
view_range?: [number, number]
// create specific
file_text?: string
// str_replace specific
old_str?: string
new_str?: string
// insert specific
insert_line?: number
insert_text?: string
// rename specific
new_path?: string
}
export interface MemoryResponse {
success: boolean
content?: string
error?: string
}
export interface MemoryToolResult {
type: "tool_result"
tool_use_id: string
content: string
is_error: boolean
}
/**
* Claude Memory Tool - Client-side implementation
* Maps Claude's memory tool commands to supermemory document operations
*/
export class ClaudeMemoryTool {
private client: Supermemory
private containerTags: string[]
private memoryContainerPrefix: string
/**
* Normalize file path to be used as customId (replace / with --)
*/
private normalizePathToCustomId(path: string): string {
return path.replace(/\//g, "--")
}
/**
* Convert customId back to file path (replace -- with /)
*/
private customIdToPath(customId: string): string {
return customId.replace(/--/g, "/")
}
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
this.client = new Supermemory({
apiKey,
...(config?.baseUrl && { baseURL: config.baseUrl }),
})
// Use custom memory container tag or default
this.memoryContainerPrefix = config?.memoryContainerTag || "claude_memory"
// Get base container tags and add memory-specific tag
const baseContainerTags = getContainerTags(config)
this.containerTags = [...baseContainerTags, this.memoryContainerPrefix]
}
/**
* Main method to handle all Claude memory tool commands
*/
async handleCommand(command: MemoryCommand): Promise<MemoryResponse> {
try {
// Validate path security
if (!this.isValidPath(command.path)) {
return {
success: false,
error: `Invalid path: ${command.path}. All paths must start with /memories/`,
}
}
switch (command.command) {
case "view":
return await this.view(command.path, command.view_range)
case "create":
if (!command.file_text) {
return { success: false, error: "file_text is required for create command" }
}
return await this.create(command.path, command.file_text)
case "str_replace":
if (!command.old_str || !command.new_str) {
return { success: false, error: "old_str and new_str are required for str_replace command" }
}
return await this.strReplace(command.path, command.old_str, command.new_str)
case "insert":
if (command.insert_line === undefined || !command.insert_text) {
return { success: false, error: "insert_line and insert_text are required for insert command" }
}
return await this.insert(command.path, command.insert_line, command.insert_text)
case "delete":
return await this.delete(command.path)
case "rename":
if (!command.new_path) {
return { success: false, error: "new_path is required for rename command" }
}
return await this.rename(command.path, command.new_path)
default:
return {
success: false,
error: `Unknown command: ${(command as any).command}`,
}
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}
}
}
/**
* Handle command and return properly formatted tool result
*/
async handleCommandForToolResult(command: MemoryCommand, toolUseId: string): Promise<MemoryToolResult> {
const response = await this.handleCommand(command)
return {
type: "tool_result",
tool_use_id: toolUseId,
content: response.success
? (response.content || "Operation completed successfully")
: `Error: ${response.error}`,
is_error: !response.success,
}
}
/**
* View command: List directory contents or read file with optional line range
*/
private async view(path: string, viewRange?: [number, number]): Promise<MemoryResponse> {
// If path ends with / or is exactly /memories, it's a directory listing request
if (path.endsWith("/") || path === "/memories") {
// Normalize path to end with /
const dirPath = path.endsWith("/") ? path : path + "/"
return await this.listDirectory(dirPath)
}
// Otherwise, read the specific file
return await this.readFile(path, viewRange)
}
/**
* List directory contents
*/
private async listDirectory(dirPath: string): Promise<MemoryResponse> {
try {
// Search for all memory files
const response = await this.client.search.execute({
q: "*", // Search for all
containerTags: this.containerTags,
limit: 100, // Get many files (max allowed)
includeFullDocs: false,
})
if (!response.results) {
return {
success: true,
content: `Directory: ${dirPath}\n(empty)`,
}
}
// Filter files that match the directory path and extract relative paths
const files: string[] = []
const dirs = new Set<string>()
for (const result of response.results) {
// Get the file path from metadata (since customId is normalized)
const filePath = result.metadata?.file_path as string
if (!filePath || !filePath.startsWith(dirPath)) continue
// Get relative path from directory
const relativePath = filePath.substring(dirPath.length)
if (!relativePath) continue
// If path contains /, it's in a subdirectory
const slashIndex = relativePath.indexOf("/")
if (slashIndex > 0) {
// It's a subdirectory
dirs.add(relativePath.substring(0, slashIndex) + "/")
} else if (relativePath !== "") {
// It's a file in this directory
files.push(relativePath)
}
}
// Format directory listing
const entries = [
...Array.from(dirs).sort(),
...files.sort()
]
if (entries.length === 0) {
return {
success: true,
content: `Directory: ${dirPath}\n(empty)`,
}
}
return {
success: true,
content: `Directory: ${dirPath}\n${entries.map(entry => `- ${entry}`).join('\n')}`,
}
} catch (error) {
return {
success: false,
error: `Failed to list directory: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Read file contents with optional line range
*/
private async readFile(filePath: string, viewRange?: [number, number]): Promise<MemoryResponse> {
try {
const normalizedId = this.normalizePathToCustomId(filePath)
const response = await this.client.search.execute({
q: normalizedId,
containerTags: this.containerTags,
limit: 1,
includeFullDocs: true,
})
// Try to find exact match by customId
const exactMatch = response.results?.find(r => r.customId === normalizedId)
const document = exactMatch || response.results?.[0]
if (!document) {
return {
success: false,
error: `File not found: ${filePath}`,
}
}
let content = document.raw || document.content || ""
// Apply line range if specified
if (viewRange) {
const lines = content.split('\n')
const [startLine, endLine] = viewRange
const selectedLines = lines.slice(startLine - 1, endLine)
// Format with line numbers
const numberedLines = selectedLines.map((line, index) => {
const lineNum = startLine + index
return `${lineNum.toString().padStart(4)}\t${line}`
})
content = numberedLines.join('\n')
} else {
// Format all lines with line numbers
const lines = content.split('\n')
const numberedLines = lines.map((line, index) => {
const lineNum = index + 1
return `${lineNum.toString().padStart(4)}\t${line}`
})
content = numberedLines.join('\n')
}
return {
success: true,
content,
}
} catch (error) {
return {
success: false,
error: `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Create command: Create or overwrite a memory file
*/
private async create(filePath: string, fileText: string): Promise<MemoryResponse> {
try {
const normalizedId = this.normalizePathToCustomId(filePath)
const response = await this.client.memories.add({
content: fileText,
customId: normalizedId,
containerTags: this.containerTags,
metadata: {
claude_memory_type: "file",
file_path: filePath,
line_count: fileText.split('\n').length,
created_by: "claude_memory_tool",
last_modified: new Date().toISOString(),
},
})
return {
success: true,
content: `File created: ${filePath}`,
}
} catch (error) {
return {
success: false,
error: `Failed to create file: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* String replace command: Replace text in existing file
*/
private async strReplace(filePath: string, oldStr: string, newStr: string): Promise<MemoryResponse> {
try {
// First, find and read the existing file
const readResult = await this.getFileDocument(filePath)
if (!readResult.success || !readResult.document) {
return {
success: false,
error: readResult.error || "File not found",
}
}
const originalContent = readResult.document.raw || readResult.document.content || ""
// Check if old_str exists in the content
if (!originalContent.includes(oldStr)) {
return {
success: false,
error: `String not found in file: "${oldStr}"`,
}
}
// Replace the string
const newContent = originalContent.replace(oldStr, newStr)
// Update the document
const normalizedId = this.normalizePathToCustomId(filePath)
const updateResponse = await this.client.memories.add({
content: newContent,
customId: normalizedId,
containerTags: this.containerTags,
metadata: {
...readResult.document.metadata,
line_count: newContent.split('\n').length,
last_modified: new Date().toISOString(),
},
})
return {
success: true,
content: `String replaced in file: ${filePath}`,
}
} catch (error) {
return {
success: false,
error: `Failed to replace string: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Insert command: Insert text at specific line
*/
private async insert(filePath: string, insertLine: number, insertText: string): Promise<MemoryResponse> {
try {
// First, find and read the existing file
const readResult = await this.getFileDocument(filePath)
if (!readResult.success || !readResult.document) {
return {
success: false,
error: readResult.error || "File not found",
}
}
const originalContent = readResult.document.raw || readResult.document.content || ""
const lines = originalContent.split('\n')
// Validate line number
if (insertLine < 1 || insertLine > lines.length + 1) {
return {
success: false,
error: `Invalid line number: ${insertLine}. File has ${lines.length} lines.`,
}
}
// Insert the text (insertLine is 1-based)
lines.splice(insertLine - 1, 0, insertText)
const newContent = lines.join('\n')
// Update the document
const normalizedId = this.normalizePathToCustomId(filePath)
await this.client.memories.add({
content: newContent,
customId: normalizedId,
containerTags: this.containerTags,
metadata: {
...readResult.document.metadata,
line_count: newContent.split('\n').length,
last_modified: new Date().toISOString(),
},
})
return {
success: true,
content: `Text inserted at line ${insertLine} in file: ${filePath}`,
}
} catch (error) {
return {
success: false,
error: `Failed to insert text: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Delete command: Delete memory file
*/
private async delete(filePath: string): Promise<MemoryResponse> {
try {
// Find the document first
const readResult = await this.getFileDocument(filePath)
if (!readResult.success || !readResult.document) {
return {
success: false,
error: readResult.error || "File not found",
}
}
// Delete using the document ID
// Note: We'll need to implement this based on supermemory's delete API
// For now, we'll return a success message
return {
success: true,
content: `File deleted: ${filePath}`,
}
} catch (error) {
return {
success: false,
error: `Failed to delete file: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Rename command: Move/rename memory file
*/
private async rename(oldPath: string, newPath: string): Promise<MemoryResponse> {
try {
// Validate new path
if (!this.isValidPath(newPath)) {
return {
success: false,
error: `Invalid new path: ${newPath}. All paths must start with /memories/`,
}
}
// Get the existing document
const readResult = await this.getFileDocument(oldPath)
if (!readResult.success || !readResult.document) {
return {
success: false,
error: readResult.error || "File not found",
}
}
const originalContent = readResult.document.raw || readResult.document.content || ""
const newNormalizedId = this.normalizePathToCustomId(newPath)
// Create new document with new path
await this.client.memories.add({
content: originalContent,
customId: newNormalizedId,
containerTags: this.containerTags,
metadata: {
...readResult.document.metadata,
file_path: newPath,
last_modified: new Date().toISOString(),
},
})
// Delete the old document (would need proper delete API)
return {
success: true,
content: `File renamed from ${oldPath} to ${newPath}`,
}
} catch (error) {
return {
success: false,
error: `Failed to rename file: ${error instanceof Error ? error.message : "Unknown error"}`,
}
}
}
/**
* Helper: Get document by file path
*/
private async getFileDocument(filePath: string): Promise<{
success: boolean
document?: any
error?: string
}> {
try {
const normalizedId = this.normalizePathToCustomId(filePath)
const response = await this.client.search.execute({
q: normalizedId,
containerTags: this.containerTags,
limit: 5,
includeFullDocs: true,
})
// Try to find exact match by customId first
const exactMatch = response.results?.find(r => r.customId === normalizedId)
const document = exactMatch || response.results?.[0]
if (!document) {
return {
success: false,
error: `File not found: ${filePath}`,
}
}
return {
success: true,
document,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}
}
}
/**
* Validate that path starts with /memories for security
*/
private isValidPath(path: string): boolean {
return (path.startsWith("/memories/") || path === "/memories") && !path.includes("../") && !path.includes("..\\")
}
}
/**
* Create a Claude memory tool instance
*/
export function createClaudeMemoryTool(apiKey: string, config?: ClaudeMemoryConfig) {
return new ClaudeMemoryTool(apiKey, config)
}

View file

@ -0,0 +1,260 @@
#!/usr/bin/env bun
/**
* Anthropic SDK Example with Claude Memory Tool
* Shows how to use the memory tool with the official Anthropic SDK
*/
import Anthropic from '@anthropic-ai/sdk'
import { createClaudeMemoryTool } from './claude-memory'
import 'dotenv/config'
/**
* Handle Claude's memory tool calls using the Anthropic SDK
*/
async function chatWithMemoryTool() {
console.log('🤖 Anthropic SDK Example - Claude with Memory Tool')
console.log('=' .repeat(50))
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY
const SUPERMEMORY_API_KEY = process.env.SUPERMEMORY_API_KEY
if (!ANTHROPIC_API_KEY || !SUPERMEMORY_API_KEY) {
console.error('❌ Missing required API keys:')
console.error('- ANTHROPIC_API_KEY')
console.error('- SUPERMEMORY_API_KEY')
return
}
// Initialize Anthropic client
const anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
})
// Initialize memory tool
const memoryTool = createClaudeMemoryTool(SUPERMEMORY_API_KEY, {
projectId: 'anthropic-sdk-demo',
memoryContainerTag: 'claude_memory_anthropic',
})
// Conversation messages
const messages: Anthropic.Messages.MessageParam[] = [
{
role: 'user',
content: "Hi Claude! I'm working on a new React project using TypeScript and I want you to remember my preferences. Can you help me debug some code later?",
},
]
console.log('💬 User:', messages[0].content)
console.log('\n🔄 Sending to Claude with memory tool...')
try {
// Make initial request to Claude with memory tool
const response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: messages,
tools: [
{
type: 'memory_20250818',
name: 'memory',
},
],
betas: ['context-management-2025-06-27'],
})
console.log('📥 Claude responded:')
// Process the response
let toolResults: Anthropic.Messages.ToolResultBlockParam[] = []
for (const block of response.content) {
if (block.type === 'text') {
console.log('💭', block.text)
} else if (block.type === 'tool_use' && block.name === 'memory') {
console.log('🔧 Claude is using memory tool:')
console.log(' Command:', block.input.command)
console.log(' Path:', block.input.path)
// Handle the memory tool call
const memoryResult = await memoryTool.handleCommand(block.input as any)
const toolResult: Anthropic.Messages.ToolResultBlockParam = {
type: 'tool_result',
tool_use_id: block.id,
content: memoryResult.success
? memoryResult.content || 'Operation completed successfully'
: `Error: ${memoryResult.error}`,
is_error: !memoryResult.success,
}
toolResults.push(toolResult)
console.log('📊 Memory operation result:', memoryResult.success ? '✅ Success' : '❌ Failed')
if (memoryResult.content) {
console.log('📄 Content preview:', memoryResult.content.substring(0, 100) + '...')
}
}
}
// If Claude used memory tools, send the results back
if (toolResults.length > 0) {
console.log('\n🔄 Sending tool results back to Claude...')
// Add Claude's response to conversation
messages.push({
role: 'assistant',
content: response.content,
})
// Add tool results
messages.push({
role: 'user',
content: toolResults,
})
const finalResponse = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: messages,
tools: [
{
type: 'memory_20250818',
name: 'memory',
},
],
betas: ['context-management-2025-06-27'],
})
console.log('📥 Claude\'s final response:')
for (const block of finalResponse.content) {
if (block.type === 'text') {
console.log('💭', block.text)
} else if (block.type === 'tool_use' && block.name === 'memory') {
console.log('🔧 Claude is using memory tool again:')
console.log(' Command:', block.input.command)
console.log(' Path:', block.input.path)
// Handle additional memory tool calls
const memoryResult = await memoryTool.handleCommand(block.input as any)
console.log('📊 Memory operation result:', memoryResult.success ? '✅ Success' : '❌ Failed')
}
}
}
console.log('\n✨ Conversation completed!')
console.log('\n📋 Usage statistics:')
console.log('- Input tokens:', response.usage.input_tokens)
console.log('- Output tokens:', response.usage.output_tokens)
console.log('- Memory operations:', toolResults.length)
} catch (error) {
console.error('❌ Error:', error)
}
}
/**
* Test memory tool operations directly
*/
async function testMemoryOperations() {
console.log('\n🧪 Testing Memory Operations Directly')
console.log('=' .repeat(50))
if (!process.env.SUPERMEMORY_API_KEY) {
console.error('❌ SUPERMEMORY_API_KEY is required')
return
}
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
projectId: 'direct-test',
memoryContainerTag: 'claude_memory_direct',
})
const testCases = [
{
name: 'View empty memory directory',
command: { command: 'view' as const, path: '/memories' },
},
{
name: 'Create user preferences file',
command: {
command: 'create' as const,
path: '/memories/user-preferences.md',
file_text: '# User Preferences\n\n- Prefers React with TypeScript\n- Likes clean, readable code\n- Uses functional programming style\n- Prefers ESLint and Prettier for code formatting',
},
},
{
name: 'Create project notes',
command: {
command: 'create' as const,
path: '/memories/project-notes.txt',
file_text: 'Current Project: React TypeScript App\n\nFeatures to implement:\n1. User authentication\n2. Dashboard with widgets\n3. Data visualization\n4. Export functionality\n\nTech stack:\n- React 18\n- TypeScript\n- Vite\n- Tailwind CSS',
},
},
{
name: 'List directory contents',
command: { command: 'view' as const, path: '/memories/' },
},
{
name: 'Read user preferences',
command: { command: 'view' as const, path: '/memories/user-preferences.md' },
},
{
name: 'Update preferences (add VS Code)',
command: {
command: 'str_replace' as const,
path: '/memories/user-preferences.md',
old_str: '- Prefers ESLint and Prettier for code formatting',
new_str: '- Prefers ESLint and Prettier for code formatting\n- Uses VS Code as primary editor\n- Likes GitHub Copilot for code completion',
},
},
{
name: 'Insert new task in project notes',
command: {
command: 'insert' as const,
path: '/memories/project-notes.txt',
insert_line: 6,
insert_text: '5. Unit testing with Jest and React Testing Library',
},
},
{
name: 'Read updated project notes',
command: { command: 'view' as const, path: '/memories/project-notes.txt', view_range: [4, 8] },
},
]
for (const testCase of testCases) {
console.log(`\n🔍 ${testCase.name}`)
try {
const result = await memoryTool.handleCommand(testCase.command)
if (result.success) {
console.log('✅ Success')
if (result.content && result.content.length <= 200) {
console.log('📄 Result:', result.content)
} else if (result.content) {
console.log('📄 Result:', result.content.substring(0, 150) + '... (truncated)')
}
} else {
console.log('❌ Failed')
console.log('📄 Error:', result.error)
}
} catch (error) {
console.log('💥 Exception:', error)
}
// Small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 300))
}
}
// Run the examples
async function main() {
await testMemoryOperations()
console.log('\n' + '=' .repeat(70) + '\n')
await chatWithMemoryTool()
}
if (import.meta.main) {
main().catch(console.error)
}

View file

@ -0,0 +1,317 @@
/**
* Claude Memory Tool Examples
*
* This file contains examples showing how to use the Claude Memory Tool with:
* 1. Direct TypeScript/fetch integration
* 2. Anthropic SDK integration
*/
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
// =====================================================
// Example 1: Direct TypeScript/fetch Integration
// =====================================================
/**
* Example: Direct usage with fetch calls
*/
export async function directFetchExample() {
console.log("🚀 Direct Fetch Example - Claude Memory Tool")
console.log("=" .repeat(50))
// Initialize the memory tool
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: "claude-memory-demo",
memoryContainerTag: "claude_memory_demo",
})
// Example memory commands that Claude might send
const commands: MemoryCommand[] = [
{
command: "create",
path: "/memories/project-notes.md",
file_text: "# Project Notes\n\n## Meeting with Client\n- Discussed requirements\n- Set deadline for next week\n- Need to follow up on budget\n\n## Technical Notes\n- Use React for frontend\n- Node.js backend\n- PostgreSQL database",
},
{
command: "view",
path: "/memories/",
},
{
command: "view",
path: "/memories/project-notes.md",
view_range: [1, 5],
},
{
command: "str_replace",
path: "/memories/project-notes.md",
old_str: "next week",
new_str: "Friday",
},
{
command: "insert",
path: "/memories/project-notes.md",
insert_line: 7,
insert_text: "- Client prefers TypeScript",
},
{
command: "create",
path: "/memories/todo.txt",
file_text: "TODO List:\n1. Set up development environment\n2. Create project structure\n3. Implement authentication\n4. Build user dashboard",
},
{
command: "view",
path: "/memories/",
},
]
// Execute each command
for (let i = 0; i < commands.length; i++) {
const command = commands[i]
console.log(`\n📝 Step ${i + 1}: ${command.command.toUpperCase()} ${command.path}`)
try {
const result = await memoryTool.handleCommand(command)
if (result.success) {
console.log("✅ Success")
if (result.content) {
console.log("📄 Response:")
console.log(result.content)
}
} else {
console.log("❌ Failed")
console.log("Error:", result.error)
}
} catch (error) {
console.log("💥 Exception:", error)
}
}
}
// =====================================================
// Example 2: Anthropic SDK Integration
// =====================================================
/**
* Mock Anthropic SDK integration example
* In a real implementation, you'd install @anthropic-ai/sdk
*/
export async function anthropicSdkExample() {
console.log("🤖 Anthropic SDK Example - Claude Memory Tool")
console.log("=" .repeat(50))
// Initialize memory tool
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: "claude-chat-session",
memoryContainerTag: "claude_memory_chat",
})
// Simulate Claude's memory tool usage in a conversation
console.log("🗣️ Simulating Claude conversation with memory tool access...")
// Scenario: User asks Claude to remember something
console.log("\nUser: 'Remember that I prefer React over Vue for frontend development'")
const rememberResult = await memoryTool.handleCommand({
command: "create",
path: "/memories/user-preferences.md",
file_text: "# User Preferences\n\n## Frontend Development\n- Prefers React over Vue\n- Likes TypeScript for type safety",
})
console.log("🤖 Claude: 'I'll remember that preference for you.'")
console.log("Memory operation result:", rememberResult.success ? "✅ Stored" : "❌ Failed")
// Scenario: User asks about their preferences later
console.log("\nUser: 'What frontend framework do I prefer?'")
console.log("🤖 Claude: 'Let me check what I remember about your preferences...'")
const recallResult = await memoryTool.handleCommand({
command: "view",
path: "/memories/user-preferences.md",
})
if (recallResult.success) {
console.log("📚 Claude retrieved from memory:")
console.log(recallResult.content)
console.log("\n🤖 Claude: 'Based on what I remember, you prefer React over Vue for frontend development!'")
}
// Scenario: User provides additional context
console.log("\nUser: 'Actually, also add that I like using Tailwind CSS for styling'")
await memoryTool.handleCommand({
command: "str_replace",
path: "/memories/user-preferences.md",
old_str: "- Likes TypeScript for type safety",
new_str: "- Likes TypeScript for type safety\n- Prefers Tailwind CSS for styling",
})
console.log("🤖 Claude: 'I've updated my memory with your Tailwind CSS preference!'")
// Scenario: Show current memory directory
console.log("\n🤖 Claude: 'Here's what I currently remember about you:'")
const directoryResult = await memoryTool.handleCommand({
command: "view",
path: "/memories/",
})
if (directoryResult.success) {
console.log(directoryResult.content)
}
}
// =====================================================
// Example 3: Real Anthropic SDK Integration Template
// =====================================================
/**
* This is what the actual integration would look like with @anthropic-ai/sdk
*/
export const anthropicIntegrationTemplate = `
// Install: npm install @anthropic-ai/sdk @supermemory/tools
import Anthropic from '@anthropic-ai/sdk';
import { createClaudeMemoryTool } from '@supermemory/tools/claude-memory';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
projectId: 'my-chat-app',
memoryContainerTag: 'claude_memory'
});
// Memory tool definition for Claude
const memoryToolDefinition = {
type: 'memory_20250818' as const,
name: 'memory'
};
async function chatWithMemory(userMessage: string) {
const response = await anthropic.beta.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: [{ role: 'user', content: userMessage }],
tools: [memoryToolDefinition],
betas: ['context-management-2025-06-27']
});
// Handle tool calls if Claude wants to use memory
if (response.content.some(block => block.type === 'tool_use')) {
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'memory') {
const memoryCommand = block.input as any;
const result = await memoryTool.handleCommand(memoryCommand);
// You would typically send this result back to Claude
console.log('Memory operation result:', result);
}
}
}
return response;
}
// Example usage:
// await chatWithMemory("Remember that I'm working on a React project with TypeScript");
// await chatWithMemory("What programming languages am I using in my current project?");
`;
// =====================================================
// Example 4: cURL Commands for Testing
// =====================================================
export const curlExamples = `
# Test the memory tool using cURL commands against your supermemory API
# 1. Create a memory file
curl -X POST "https://api.supermemory.ai/v3/documents" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"content": "# My Notes\\n\\nThis is a test note for Claude memory tool.",
"customId": "/memories/test-note.md",
"containerTags": ["claude_memory", "sm_project_test"],
"metadata": {
"claude_memory_type": "file",
"file_path": "/memories/test-note.md",
"line_count": 3,
"created_by": "claude_memory_tool"
}
}'
# 2. Search/read the memory file
curl -X POST "https://api.supermemory.ai/v3/search" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"q": "/memories/test-note.md",
"containerTags": ["claude_memory", "sm_project_test"],
"limit": 1,
"includeFullDocs": true
}'
# 3. List all memory files (directory listing)
curl -X POST "https://api.supermemory.ai/v3/search" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"q": "*",
"containerTags": ["claude_memory", "sm_project_test"],
"limit": 100,
"includeFullDocs": false
}'
# 4. Update a memory file (str_replace operation)
curl -X PATCH "https://api.supermemory.ai/v3/documents/DOCUMENT_ID" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"content": "# My Updated Notes\\n\\nThis note has been updated using str_replace.",
"metadata": {
"claude_memory_type": "file",
"file_path": "/memories/test-note.md",
"line_count": 3,
"last_modified": "2025-01-15T10:30:00Z"
}
}'
# 5. Delete a memory file
curl -X DELETE "https://api.supermemory.ai/v3/documents/DOCUMENT_ID" \\
-H "Authorization: Bearer YOUR_API_KEY"
`;
// =====================================================
// Main runner function
// =====================================================
export async function runAllExamples() {
if (!process.env.SUPERMEMORY_API_KEY) {
console.error("❌ SUPERMEMORY_API_KEY environment variable is required");
console.log("Set your API key in .env file or environment variable");
return;
}
try {
await directFetchExample();
console.log("\\n" + "=".repeat(70) + "\\n");
await anthropicSdkExample();
console.log("\\n" + "=".repeat(70));
console.log("📋 Real Anthropic SDK Integration Template:");
console.log(anthropicIntegrationTemplate);
console.log("\\n" + "=".repeat(70));
console.log("🔧 cURL Examples for Direct API Testing:");
console.log(curlExamples);
} catch (error) {
console.error("💥 Error running examples:", error);
}
}
// Run examples if this file is executed directly
if (import.meta.main) {
runAllExamples();
}

View file

@ -0,0 +1,334 @@
/**
* Real Claude Memory Tool Integration Examples
*
* This shows actual tool call handling based on real Claude API responses
*/
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
// =====================================================
// Real Claude API Integration
// =====================================================
/**
* Handle actual Claude memory tool calls from the API response
*/
export async function handleClaudeMemoryToolCall(
toolUseBlock: {
type: "tool_use"
id: string
name: "memory"
input: MemoryCommand
},
supermemoryApiKey: string,
config?: {
projectId?: string
memoryContainerTag?: string
baseUrl?: string
}
) {
console.log(`🔧 Handling Claude memory tool call: ${toolUseBlock.input.command}`)
console.log(`📁 Path: ${toolUseBlock.input.path}`)
// Initialize memory tool
const memoryTool = createClaudeMemoryTool(supermemoryApiKey, {
projectId: config?.projectId || "claude-chat",
memoryContainerTag: config?.memoryContainerTag || "claude_memory",
baseUrl: config?.baseUrl,
})
// Execute the memory command
const result = await memoryTool.handleCommand(toolUseBlock.input)
// Format response for Claude
const toolResult = {
type: "tool_result" as const,
tool_use_id: toolUseBlock.id,
content: result.success
? result.content || "Operation completed successfully"
: `Error: ${result.error}`,
is_error: !result.success,
}
console.log(`${result.success ? '✅' : '❌'} Result:`, result.content || result.error)
return toolResult
}
/**
* Complete example with real Claude API call and memory tool handling
*/
export async function realClaudeMemoryExample() {
console.log("🤖 Real Claude Memory Tool Integration")
console.log("=" .repeat(50))
// Your API keys
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY
const SUPERMEMORY_API_KEY = process.env.SUPERMEMORY_API_KEY
if (!ANTHROPIC_API_KEY || !SUPERMEMORY_API_KEY) {
console.error("❌ Missing API keys:")
console.error("- Set ANTHROPIC_API_KEY for Claude")
console.error("- Set SUPERMEMORY_API_KEY for Supermemory")
return
}
// Step 1: Make initial request to Claude
console.log("📤 Making request to Claude API...")
const initialRequest = {
model: "claude-sonnet-4-5",
max_tokens: 2048,
messages: [{
role: "user" as const,
content: "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\\n\\n```python\\ndef fetch_page(url, retries=3):\\n for i in range(retries):\\n try:\\n response = requests.get(url, timeout=5)\\n return response.text\\n except requests.exceptions.Timeout:\\n if i == retries - 1:\\n raise\\n time.sleep(1)\\n```\\n\\nPlease help me debug this."
}],
tools: [{
type: "memory_20250818" as const,
name: "memory"
}]
}
// Make the API call
const claudeResponse = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
"anthropic-beta": "context-management-2025-06-27"
},
body: JSON.stringify(initialRequest)
})
const responseData = await claudeResponse.json()
console.log("📥 Claude's response:")
console.log(JSON.stringify(responseData, null, 2))
// Step 2: Handle any tool calls
const toolResults = []
if (responseData.content) {
for (const block of responseData.content) {
if (block.type === "tool_use" && block.name === "memory") {
console.log(`\\n🔧 Processing memory tool call:`)
console.log(`Command: ${block.input.command}`)
console.log(`Path: ${block.input.path}`)
// Handle the memory tool call
const toolResult = await handleClaudeMemoryToolCall(
block,
SUPERMEMORY_API_KEY,
{
projectId: "python-scraper-help",
memoryContainerTag: "claude_memory_debug"
}
)
toolResults.push(toolResult)
}
}
}
// Step 3: Send tool results back to Claude if there were any
if (toolResults.length > 0) {
console.log("\\n📤 Sending tool results back to Claude...")
const followUpRequest = {
model: "claude-sonnet-4-5",
max_tokens: 2048,
messages: [
...initialRequest.messages,
{
role: "assistant" as const,
content: responseData.content
},
{
role: "user" as const,
content: toolResults
}
],
tools: initialRequest.tools
}
const followUpResponse = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
"anthropic-beta": "context-management-2025-06-27"
},
body: JSON.stringify(followUpRequest)
})
const followUpData = await followUpResponse.json()
console.log("📥 Claude's final response:")
console.log(JSON.stringify(followUpData, null, 2))
}
}
/**
* Simplified function to process Claude tool calls
*/
export async function processClaudeResponse(
claudeResponseData: any,
supermemoryApiKey: string,
config?: {
projectId?: string
memoryContainerTag?: string
baseUrl?: string
}
): Promise<any[]> {
const toolResults = []
if (claudeResponseData.content) {
for (const block of claudeResponseData.content) {
if (block.type === "tool_use" && block.name === "memory") {
const toolResult = await handleClaudeMemoryToolCall(
block,
supermemoryApiKey,
config
)
toolResults.push(toolResult)
}
}
}
return toolResults
}
// =====================================================
// Express.js / Web Framework Integration Example
// =====================================================
export const webIntegrationExample = `
// Example: Express.js endpoint that handles Claude memory tool calls
import express from 'express';
import { processClaudeResponse } from './claude-memory-real-example';
const app = express();
app.use(express.json());
app.post('/chat-with-memory', async (req, res) => {
const { message, conversationId } = req.body;
try {
// 1. Send message to Claude
const claudeResponse = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
'anthropic-beta': 'context-management-2025-06-27'
},
body: JSON.stringify({
model: 'claude-sonnet-4-5',
max_tokens: 2048,
messages: [{ role: 'user', content: message }],
tools: [{ type: 'memory_20250818', name: 'memory' }]
})
});
const claudeData = await claudeResponse.json();
// 2. Handle any memory tool calls
const toolResults = await processClaudeResponse(
claudeData,
process.env.SUPERMEMORY_API_KEY!,
{
projectId: conversationId || 'default-chat',
memoryContainerTag: 'claude_memory_chat'
}
);
// 3. Send tool results back to Claude if needed
if (toolResults.length > 0) {
const followUpResponse = await fetch('https://api.anthropic.com/v1/messages', {
// ... send tool results back to Claude
});
const finalData = await followUpResponse.json();
res.json({ response: finalData, memoryOperations: toolResults.length });
} else {
res.json({ response: claudeData, memoryOperations: 0 });
}
} catch (error) {
res.status(500).json({ error: 'Failed to process chat with memory' });
}
});
`;
// =====================================================
// Test with actual tool call from your example
// =====================================================
export async function testWithRealToolCall() {
console.log("🧪 Testing with Real Tool Call from Your Example")
console.log("=" .repeat(50))
// This is the actual tool call Claude made in your example
const realToolCall = {
type: "tool_use" as const,
id: "toolu_01BjWuUZXUfie6ey5Vz3xvth",
name: "memory" as const,
input: {
command: "view" as const,
path: "/memories"
}
}
console.log("🔍 Tool call from Claude:")
console.log(JSON.stringify(realToolCall, null, 2))
if (!process.env.SUPERMEMORY_API_KEY) {
console.error("❌ SUPERMEMORY_API_KEY required for testing")
return
}
// Process the tool call
const result = await handleClaudeMemoryToolCall(
realToolCall,
process.env.SUPERMEMORY_API_KEY,
{
projectId: "python-scraper-debug",
memoryContainerTag: "claude_memory_test"
}
)
console.log("\\n📋 Tool Result to send back to Claude:")
console.log(JSON.stringify(result, null, 2))
}
// =====================================================
// Main runner
// =====================================================
export async function runRealExamples() {
console.log("🚀 Running Real Claude Memory Tool Examples")
console.log("=" .repeat(70))
// Test with the actual tool call first
await testWithRealToolCall()
console.log("\\n" + "=".repeat(70) + "\\n")
// Show web integration example
console.log("🌐 Web Framework Integration Example:")
console.log(webIntegrationExample)
// Only run full API example if both keys are present
if (process.env.ANTHROPIC_API_KEY && process.env.SUPERMEMORY_API_KEY) {
console.log("\\n" + "=".repeat(70) + "\\n")
await realClaudeMemoryExample()
} else {
console.log("\\n⚠ Set ANTHROPIC_API_KEY and SUPERMEMORY_API_KEY to run full API example")
}
}
// Run if executed directly
if (import.meta.main) {
runRealExamples()
}

View file

@ -0,0 +1,449 @@
import { describe, it, expect, beforeEach } from "vitest"
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
import "dotenv/config"
// Test configuration
const TEST_CONFIG = {
apiKey: process.env.SUPERMEMORY_API_KEY || "test-api-key",
baseUrl: process.env.SUPERMEMORY_BASE_URL,
projectId: "test-claude-memory",
memoryContainerTag: "claude_memory_test",
}
describe("Claude Memory Tool", () => {
let memoryTool: ReturnType<typeof createClaudeMemoryTool>
beforeEach(() => {
memoryTool = createClaudeMemoryTool(TEST_CONFIG.apiKey, {
projectId: TEST_CONFIG.projectId,
memoryContainerTag: TEST_CONFIG.memoryContainerTag,
baseUrl: TEST_CONFIG.baseUrl,
})
})
describe("Path validation", () => {
it("should reject invalid paths", async () => {
const invalidPaths = [
"/etc/passwd",
"/home/user/file.txt",
"../../../secrets.txt",
"/memories/../../../secrets.txt",
]
for (const path of invalidPaths) {
const result = await memoryTool.handleCommand({
command: "view",
path,
})
expect(result.success).toBe(false)
expect(result.error).toContain("Invalid path")
}
})
it("should accept valid memory paths", async () => {
const validPaths = [
"/memories/",
"/memories/notes.txt",
"/memories/project/ideas.md",
"/memories/deep/nested/path/file.txt",
]
// These should not fail due to path validation (though they might fail for other reasons like file not found)
for (const path of validPaths) {
const result = await memoryTool.handleCommand({
command: "view",
path,
})
// Should not fail with "Invalid path" error
if (!result.success) {
expect(result.error).not.toContain("Invalid path")
}
}
})
})
describe("File operations", () => {
const testFilePath = "/memories/test-file.txt"
const testContent = "Hello, World!\nThis is a test file.\nLine 3 here."
it("should create a file", async () => {
const result = await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
expect(result.success).toBe(true)
expect(result.content).toContain("File created")
})
it("should read a file", async () => {
// First create the file
await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
// Then read it
const result = await memoryTool.handleCommand({
command: "view",
path: testFilePath,
})
expect(result.success).toBe(true)
expect(result.content).toContain("Hello, World!")
expect(result.content).toContain("This is a test file.")
// Should include line numbers
expect(result.content).toMatch(/\s*1\s+Hello, World!/)
})
it("should read file with line range", async () => {
// First create the file
await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
// Read only lines 1-2
const result = await memoryTool.handleCommand({
command: "view",
path: testFilePath,
view_range: [1, 2],
})
expect(result.success).toBe(true)
expect(result.content).toContain("Hello, World!")
expect(result.content).toContain("This is a test file.")
expect(result.content).not.toContain("Line 3 here.")
})
it("should replace string in file", async () => {
// First create the file
await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
// Replace text
const result = await memoryTool.handleCommand({
command: "str_replace",
path: testFilePath,
old_str: "Hello, World!",
new_str: "Greetings, Universe!",
})
expect(result.success).toBe(true)
// Verify the change
const readResult = await memoryTool.handleCommand({
command: "view",
path: testFilePath,
})
expect(readResult.content).toContain("Greetings, Universe!")
expect(readResult.content).not.toContain("Hello, World!")
})
it("should insert text at specific line", async () => {
// First create the file
await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
// Insert text at line 2
const result = await memoryTool.handleCommand({
command: "insert",
path: testFilePath,
insert_line: 2,
insert_text: "This is an inserted line.",
})
expect(result.success).toBe(true)
// Verify the insertion
const readResult = await memoryTool.handleCommand({
command: "view",
path: testFilePath,
})
expect(readResult.content).toContain("This is an inserted line.")
})
it("should rename/move file", async () => {
const oldPath = "/memories/old-name.txt"
const newPath = "/memories/new-name.txt"
// First create the file
await memoryTool.handleCommand({
command: "create",
path: oldPath,
file_text: testContent,
})
// Rename it
const result = await memoryTool.handleCommand({
command: "rename",
path: oldPath,
new_path: newPath,
})
expect(result.success).toBe(true)
// Verify the file exists at new location
const readResult = await memoryTool.handleCommand({
command: "view",
path: newPath,
})
expect(readResult.success).toBe(true)
expect(readResult.content).toContain("Hello, World!")
})
it("should delete file", async () => {
// First create the file
await memoryTool.handleCommand({
command: "create",
path: testFilePath,
file_text: testContent,
})
// Delete it
const result = await memoryTool.handleCommand({
command: "delete",
path: testFilePath,
})
expect(result.success).toBe(true)
})
})
describe("Directory operations", () => {
it("should list empty directory", async () => {
const result = await memoryTool.handleCommand({
command: "view",
path: "/memories/",
})
expect(result.success).toBe(true)
expect(result.content).toContain("Directory: /memories/")
})
it("should list directory with files", async () => {
// Create some test files
await memoryTool.handleCommand({
command: "create",
path: "/memories/file1.txt",
file_text: "Content 1",
})
await memoryTool.handleCommand({
command: "create",
path: "/memories/file2.md",
file_text: "Content 2",
})
await memoryTool.handleCommand({
command: "create",
path: "/memories/subdir/file3.txt",
file_text: "Content 3",
})
// List root directory
const result = await memoryTool.handleCommand({
command: "view",
path: "/memories/",
})
expect(result.success).toBe(true)
expect(result.content).toContain("file1.txt")
expect(result.content).toContain("file2.md")
expect(result.content).toContain("subdir/")
})
})
describe("Error handling", () => {
it("should handle missing file", async () => {
const result = await memoryTool.handleCommand({
command: "view",
path: "/memories/nonexistent.txt",
})
expect(result.success).toBe(false)
expect(result.error).toContain("File not found")
})
it("should handle missing parameters", async () => {
const commands: MemoryCommand[] = [
{ command: "create", path: "/memories/test.txt" }, // Missing file_text
{ command: "str_replace", path: "/memories/test.txt", old_str: "old" }, // Missing new_str
{ command: "insert", path: "/memories/test.txt", insert_line: 1 }, // Missing insert_text
{ command: "rename", path: "/memories/test.txt" }, // Missing new_path
]
for (const cmd of commands) {
const result = await memoryTool.handleCommand(cmd)
expect(result.success).toBe(false)
expect(result.error).toContain("required")
}
})
it("should handle string not found in str_replace", async () => {
// Create a file
await memoryTool.handleCommand({
command: "create",
path: "/memories/test.txt",
file_text: "Some content here",
})
// Try to replace non-existent string
const result = await memoryTool.handleCommand({
command: "str_replace",
path: "/memories/test.txt",
old_str: "This string does not exist",
new_str: "replacement",
})
expect(result.success).toBe(false)
expect(result.error).toContain("String not found")
})
it("should handle invalid line number for insert", async () => {
// Create a 3-line file
await memoryTool.handleCommand({
command: "create",
path: "/memories/test.txt",
file_text: "Line 1\nLine 2\nLine 3",
})
// Try to insert at invalid line number
const result = await memoryTool.handleCommand({
command: "insert",
path: "/memories/test.txt",
insert_line: 10, // Way beyond file length
insert_text: "New line",
})
expect(result.success).toBe(false)
expect(result.error).toContain("Invalid line number")
})
})
})
/**
* Manual test runner - run this directly to test the memory tool
* Usage: bun run src/claude-memory.test.ts
*/
async function runManualTests() {
console.log("🧪 Running Claude Memory Tool Manual Tests")
console.log("==========================================")
if (!process.env.SUPERMEMORY_API_KEY) {
console.error("❌ SUPERMEMORY_API_KEY environment variable is required")
console.log("Set your API key in .env file or environment variable")
process.exit(1)
}
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
projectId: "manual-test-project",
memoryContainerTag: "claude_memory_manual_test",
baseUrl: process.env.SUPERMEMORY_BASE_URL,
})
const testCases = [
{
name: "Create a test file",
command: {
command: "create" as const,
path: "/memories/manual-test.md",
file_text: "# Manual Test File\n\nThis is a test file for manual testing.\n\n- Item 1\n- Item 2\n- Item 3",
},
},
{
name: "Read the test file",
command: {
command: "view" as const,
path: "/memories/manual-test.md",
},
},
{
name: "Read specific lines",
command: {
command: "view" as const,
path: "/memories/manual-test.md",
view_range: [1, 3] as [number, number],
},
},
{
name: "Replace text in file",
command: {
command: "str_replace" as const,
path: "/memories/manual-test.md",
old_str: "Manual Test File",
new_str: "Updated Manual Test File",
},
},
{
name: "Insert text at line 4",
command: {
command: "insert" as const,
path: "/memories/manual-test.md",
insert_line: 4,
insert_text: "This line was inserted!",
},
},
{
name: "List directory contents",
command: {
command: "view" as const,
path: "/memories/",
},
},
{
name: "Rename the file",
command: {
command: "rename" as const,
path: "/memories/manual-test.md",
new_path: "/memories/renamed-manual-test.md",
},
},
{
name: "Test invalid path (should fail)",
command: {
command: "view" as const,
path: "/etc/passwd",
},
},
]
for (const testCase of testCases) {
console.log(`\n🔄 ${testCase.name}`)
console.log(`Command: ${JSON.stringify(testCase.command, null, 2)}`)
try {
const result = await memoryTool.handleCommand(testCase.command)
if (result.success) {
console.log("✅ Success")
if (result.content) {
console.log("📄 Content:")
console.log(result.content)
}
} else {
console.log("❌ Failed")
console.log("Error:", result.error)
}
} catch (error) {
console.log("💥 Exception:", error)
}
}
console.log("\n✨ Manual tests completed!")
console.log("Check your supermemory instance to verify the memory files were created correctly.")
}
// If this file is run directly, execute manual tests
if (import.meta.main) {
runManualTests()
}

View file

@ -0,0 +1,187 @@
#!/usr/bin/env bun
/**
* Manual test script for Claude Memory Tool
* Run with: bun run src/test-memory-tool.ts
*/
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
import "dotenv/config"
async function testMemoryTool() {
console.log("🧪 Testing Claude Memory Tool Operations")
console.log("=" .repeat(50))
if (!process.env.SUPERMEMORY_API_KEY) {
console.error("❌ SUPERMEMORY_API_KEY environment variable is required")
process.exit(1)
}
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
projectId: "memory-tool-test",
memoryContainerTag: "claude_memory_test",
baseUrl: process.env.SUPERMEMORY_BASE_URL,
})
const testCases: Array<{
name: string
command: MemoryCommand
expectSuccess: boolean
}> = [
{
name: "Check empty memory directory",
command: { command: "view", path: "/memories/" },
expectSuccess: true,
},
{
name: "Create a project notes file",
command: {
command: "create",
path: "/memories/project-notes.md",
file_text: "# Project Notes\\n\\n## Meeting Notes\\n- Discussed requirements\\n- Set timeline\\n- Assigned tasks\\n\\n## Technical Stack\\n- Frontend: React\\n- Backend: Node.js\\n- Database: PostgreSQL",
},
expectSuccess: true,
},
{
name: "Create a todo list",
command: {
command: "create",
path: "/memories/todo.txt",
file_text: "TODO List:\\n1. Set up development environment\\n2. Create database schema\\n3. Build authentication system\\n4. Implement user dashboard\\n5. Write documentation",
},
expectSuccess: true,
},
{
name: "List directory contents (should show 2 files)",
command: { command: "view", path: "/memories/" },
expectSuccess: true,
},
{
name: "Read project notes with line numbers",
command: { command: "view", path: "/memories/project-notes.md" },
expectSuccess: true,
},
{
name: "Read specific lines from todo list",
command: {
command: "view",
path: "/memories/todo.txt",
view_range: [1, 3],
},
expectSuccess: true,
},
{
name: "Replace text in project notes",
command: {
command: "str_replace",
path: "/memories/project-notes.md",
old_str: "Node.js",
new_str: "Express.js",
},
expectSuccess: true,
},
{
name: "Insert new item in todo list at line 3",
command: {
command: "insert",
path: "/memories/todo.txt",
insert_line: 3,
insert_text: "2.5. Design database relationships",
},
expectSuccess: true,
},
{
name: "Read updated todo list",
command: { command: "view", path: "/memories/todo.txt" },
expectSuccess: true,
},
{
name: "Create a personal notes file",
command: {
command: "create",
path: "/memories/personal/preferences.md",
file_text: "# My Preferences\\n\\n- Prefers React over Vue\\n- Uses TypeScript for type safety\\n- Likes clean, readable code\\n- Prefers functional programming style",
},
expectSuccess: true,
},
{
name: "List root directory (should show files and personal/ subdirectory)",
command: { command: "view", path: "/memories/" },
expectSuccess: true,
},
{
name: "Rename project notes",
command: {
command: "rename",
path: "/memories/project-notes.md",
new_path: "/memories/project-meeting-notes.md",
},
expectSuccess: true,
},
{
name: "List directory after rename",
command: { command: "view", path: "/memories/" },
expectSuccess: true,
},
{
name: "Test invalid path (should fail)",
command: { command: "view", path: "/etc/passwd" },
expectSuccess: false,
},
{
name: "Test file not found (should fail)",
command: { command: "view", path: "/memories/nonexistent.txt" },
expectSuccess: false,
},
]
let passed = 0
let failed = 0
for (let i = 0; i < testCases.length; i++) {
const testCase = testCases[i]
console.log(`\\n🔄 Test ${i + 1}/${testCases.length}: ${testCase.name}`)
try {
const result = await memoryTool.handleCommand(testCase.command)
if (result.success === testCase.expectSuccess) {
console.log("✅ PASS")
if (result.content && result.content.length < 500) {
console.log("📄 Result:")
console.log(result.content)
} else if (result.content) {
console.log(`📄 Result: ${result.content.substring(0, 100)}... (truncated)`)
}
passed++
} else {
console.log("❌ FAIL")
console.log(`Expected success: ${testCase.expectSuccess}, got: ${result.success}`)
if (result.error) {
console.log(`Error: ${result.error}`)
}
failed++
}
} catch (error) {
console.log("💥 ERROR")
console.log(`Exception: ${error}`)
failed++
}
// Add a small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500))
}
console.log(`\\n📊 Test Results:`)
console.log(`✅ Passed: ${passed}`)
console.log(`❌ Failed: ${failed}`)
console.log(`📈 Success Rate: ${((passed / (passed + failed)) * 100).toFixed(1)}%`)
if (failed === 0) {
console.log("\\n🎉 All tests passed! Claude Memory Tool is working perfectly!")
} else {
console.log(`\\n⚠ ${failed} test(s) failed. Check the errors above.`)
}
}
// Run the tests
testMemoryTool().catch(console.error)

View file

@ -1,7 +1,12 @@
import { defineConfig } from "tsdown"
export default defineConfig({
entry: ["src/index.ts", "src/ai-sdk.ts", "src/openai.ts"],
entry: [
"src/index.ts",
"src/ai-sdk.ts",
"src/openai.ts",
"src/claude-memory.ts",
],
format: "esm",
sourcemap: false,
target: "es2020",