Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ Example:
- Relations between requested entities
- Silently skips non-existent nodes

### Resources

- **`memory://graph`** (`knowledge-graph`)
- The full knowledge graph (all entities and relations) as JSON
- MIME type: `application/json`
- Read-only; lets MCP clients attach the whole graph as context without calling the `read_graph` tool

# Usage with Claude Desktop

### Setup
Expand Down
48 changes: 47 additions & 1 deletion src/memory/__tests__/knowledge-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { KnowledgeGraphManager, Entity, Relation, KnowledgeGraph } from '../index.js';
import { KnowledgeGraphManager, Entity, Relation, KnowledgeGraph, readGraphResource } from '../index.js';

describe('KnowledgeGraphManager', () => {
let manager: KnowledgeGraphManager;
Expand Down Expand Up @@ -516,3 +516,49 @@ describe('KnowledgeGraphManager', () => {
});
});
});

describe('readGraphResource', () => {
let manager: KnowledgeGraphManager;
let testFilePath: string;

beforeEach(() => {
testFilePath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
`test-resource-${Date.now()}.jsonl`
);
manager = new KnowledgeGraphManager(testFilePath);
});

afterEach(async () => {
try {
await fs.unlink(testFilePath);
} catch {
// Ignore if the file doesn't exist
}
});

it('returns the full graph as a JSON resource', async () => {
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: ['likes coffee'] },
]);

const result = await readGraphResource(manager, 'memory://graph');

expect(result.contents).toHaveLength(1);
expect(result.contents[0].uri).toBe('memory://graph');
expect(result.contents[0].mimeType).toBe('application/json');

const data = JSON.parse(result.contents[0].text);
expect(data.entities).toHaveLength(1);
expect(data.entities[0].name).toBe('Alice');
expect(data).toHaveProperty('relations');
});

it('returns an empty graph when nothing has been stored', async () => {
const result = await readGraphResource(manager);

const data = JSON.parse(result.contents[0].text);
expect(data.entities).toEqual([]);
expect(data.relations).toEqual([]);
});
});
32 changes: 32 additions & 0 deletions src/memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,24 @@ const RelationSchema = z.object({
});

// The server instance and tools exposed to Claude
// Build the MCP resource contents for the full knowledge graph.
// Exported so it can be unit-tested independently of the server transport.
export async function readGraphResource(
manager: KnowledgeGraphManager,
uri: string = "memory://graph",
) {
const graph = await manager.readGraph();
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(graph, null, 2),
},
],
};
}

const server = new McpServer({
name: "memory-server",
version: "0.6.3",
Expand Down Expand Up @@ -523,6 +541,20 @@ server.registerTool(
}
);

// Expose the full knowledge graph as a readable resource (read-only),
// complementing the `read_graph` tool. Resources are the idiomatic way to
// surface read-only data so MCP clients can attach it as context.
server.registerResource(
"knowledge-graph",
"memory://graph",
{
title: "Knowledge Graph",
description: "The full knowledge graph (all entities and relations) as JSON",
mimeType: "application/json",
},
async (uri) => readGraphResource(knowledgeGraphManager, uri.href),
);

async function main() {
// Initialize memory file path with backward compatibility
MEMORY_FILE_PATH = await ensureMemoryFilePath();
Expand Down
Loading