aboutsummaryrefslogtreecommitdiff
path: root/packages/tools
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2025-09-29 13:40:56 -0700
committerDhravya Shah <[email protected]>2025-09-29 13:40:56 -0700
commit53bc296155b4f22f392dbef067c818eb939f3b91 (patch)
tree9a4468118e86abb7ee4dedfc13bbbd4287970c3d /packages/tools
parentfix: build (diff)
downloadsupermemory-53bc296155b4f22f392dbef067c818eb939f3b91.tar.xz
supermemory-53bc296155b4f22f392dbef067c818eb939f3b91.zip
feat: Claude memory integration
Diffstat (limited to 'packages/tools')
-rw-r--r--packages/tools/README.md69
-rw-r--r--packages/tools/package.json6
-rw-r--r--packages/tools/src/claude-memory-simple-example.ts71
-rw-r--r--packages/tools/src/claude-memory.ts564
-rw-r--r--packages/tools/test/anthropic-example.ts260
-rw-r--r--packages/tools/test/claude-memory-examples.ts317
-rw-r--r--packages/tools/test/claude-memory-real-example.ts334
-rw-r--r--packages/tools/test/claude-memory.test.ts449
-rw-r--r--packages/tools/test/test-memory-tool.ts187
-rw-r--r--packages/tools/tsdown.config.ts7
10 files changed, 2261 insertions, 3 deletions
diff --git a/packages/tools/README.md b/packages/tools/README.md
index 61f1c042..f981e44c 100644
--- a/packages/tools/README.md
+++ b/packages/tools/README.md
@@ -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
```
diff --git a/packages/tools/package.json b/packages/tools/package.json
index c7f4f21e..9aad412f 100644
--- a/packages/tools/package.json
+++ b/packages/tools/package.json
@@ -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"
},
diff --git a/packages/tools/src/claude-memory-simple-example.ts b/packages/tools/src/claude-memory-simple-example.ts
new file mode 100644
index 00000000..8ce21f83
--- /dev/null
+++ b/packages/tools/src/claude-memory-simple-example.ts
@@ -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()
+} \ No newline at end of file
diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts
new file mode 100644
index 00000000..49ff39a7
--- /dev/null
+++ b/packages/tools/src/claude-memory.ts
@@ -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)
+} \ No newline at end of file
diff --git a/packages/tools/test/anthropic-example.ts b/packages/tools/test/anthropic-example.ts
new file mode 100644
index 00000000..039f87ac
--- /dev/null
+++ b/packages/tools/test/anthropic-example.ts
@@ -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)
+} \ No newline at end of file
diff --git a/packages/tools/test/claude-memory-examples.ts b/packages/tools/test/claude-memory-examples.ts
new file mode 100644
index 00000000..b0602938
--- /dev/null
+++ b/packages/tools/test/claude-memory-examples.ts
@@ -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();
+} \ No newline at end of file
diff --git a/packages/tools/test/claude-memory-real-example.ts b/packages/tools/test/claude-memory-real-example.ts
new file mode 100644
index 00000000..64b51ee3
--- /dev/null
+++ b/packages/tools/test/claude-memory-real-example.ts
@@ -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()
+} \ No newline at end of file
diff --git a/packages/tools/test/claude-memory.test.ts b/packages/tools/test/claude-memory.test.ts
new file mode 100644
index 00000000..1a5fa164
--- /dev/null
+++ b/packages/tools/test/claude-memory.test.ts
@@ -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()
+} \ No newline at end of file
diff --git a/packages/tools/test/test-memory-tool.ts b/packages/tools/test/test-memory-tool.ts
new file mode 100644
index 00000000..21a7ccbe
--- /dev/null
+++ b/packages/tools/test/test-memory-tool.ts
@@ -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) \ No newline at end of file
diff --git a/packages/tools/tsdown.config.ts b/packages/tools/tsdown.config.ts
index 59be1b93..cebdda44 100644
--- a/packages/tools/tsdown.config.ts
+++ b/packages/tools/tsdown.config.ts
@@ -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",