#
tokens: 8333/50000 11/11 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docker-compose.yml
├── Dockerfile
├── LICENSE
├── package.json
├── README.md
├── src
│   ├── index.ts
│   ├── logger.ts
│   ├── manager.ts
│   ├── types.ts
│   └── utils.ts
├── test
│   └── test-script.js
├── tsconfig.json
└── tsup.config.ts
```

# Files

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM node:22-alpine

WORKDIR /app

COPY package.json ./
COPY tsconfig.json ./
COPY src ./src

RUN npm install
RUN npm run build

CMD ["npm", "start"]
```

--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------

```typescript
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm'],
  dts: true,
  clean: true,
  outExtension: () => ({
    js: '.mjs',
  }),
});
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
version: '3'

services:
    neo4j:
    image: neo4j:5.18.0
    container_name: ai-info-neo4j
    restart: always
    ports:
      - "7474:7474"  # HTTP
      - "7687:7687"  # Bolt
    environment:
      - NEO4J_AUTH=neo4j/password  # 用户名/密码
      - NEO4J_apoc_export_file_enabled=true
      - NEO4J_apoc_import_file_enabled=true
      - NEO4J_apoc_import_file_use__neo4j__config=true
      - NEO4J_PLUGINS=["apoc"]  # 启用APOC插件
      - NEO4J_dbms_security_procedures_unrestricted=apoc.*
      - NEO4J_dbms_security_procedures_allowlist=apoc.*
    volumes:
      - neo4j_data:/data
      - neo4j_logs:/logs
      - neo4j_import:/var/lib/neo4j/import
      - neo4j_plugins:/plugins
    healthcheck:
      test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7474" ]
      interval: 10s
      timeout: 10s
      retries: 5
      start_period: 40s
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";

// 实体对象模式
export const EntityObject = z.object({
  name: z.string().describe("The name of the entity"),
  entityType: z.string().describe("The type of the entity"),
  observations: z.array(z.string()).describe("An array of observation contents associated with the entity")
});

// 关系对象模式
export const RelationObject = z.object({
  from: z.string().describe("The name of the entity where the relation starts"),
  to: z.string().describe("The name of the entity where the relation ends"),
  relationType: z.string().describe("The type of the relation")
});

// 观察对象模式
export const ObservationObject = z.object({
  entityName: z.string().describe("The name of the entity to add the observations to"),
  contents: z.array(z.string()).describe("An array of observation contents to add")
});

// 实体类型
export type Entity = z.infer<typeof EntityObject>;

// 关系类型
export type Relation = z.infer<typeof RelationObject>;

// 观察类型
export type Observation = z.infer<typeof ObservationObject>;

// 知识图谱类型
export interface KnowledgeGraph {
  entities: Entity[];
  relations: Relation[];
}

// 删除观察类型
export interface ObservationDeletion {
  entityName: string;
  contents: string[];
}
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '22'
          registry-url: 'https://registry.npmjs.org'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false

      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - uses: actions/cache@v3
        name: Setup pnpm cache
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm build

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          publish: pnpm release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      neo4j:
        image: neo4j:5-community
        env:
          NEO4J_AUTH: neo4j/password
        ports:
          - 7474:7474
          - 7687:7687
        options: >-
          --health-cmd "cypher-shell -u neo4j -p password 'RETURN 1;'"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '22'
          
      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8
          run_install: false
          
      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
          
      - uses: actions/cache@v3
        name: Setup pnpm cache
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-
            
      - name: Install dependencies
        run: pnpm install
        
      - name: Build
        run: pnpm build
        
      - name: Run tests
        run: pnpm test
        env:
          NEO4J_URI: bolt://localhost:7687
          NEO4J_USER: neo4j
          NEO4J_PASSWORD: password
          NEO4J_DATABASE: neo4j
```

--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------

```typescript
// 日志级别枚举
export enum LogLevel {
  DEBUG = "debug",
  INFO = "info",
  WARN = "warning",
  ERROR = "error",
}

// 日志记录器接口
export interface Logger {
  debug(message: string, payload?: any): void;
  info(message: string, payload?: any): void;
  warn(message: string, payload?: any): void;
  error(message: string, payload?: any): void;
  setLevel(level: LogLevel): void;
}

// 空日志记录器实现
export class NullLogger implements Logger {
  debug(message: string, payload?: any): void {}
  info(message: string, payload?: any): void {}
  warn(message: string, payload?: any): void {}
  error(message: string, payload?: any): void {}
  setLevel(level: LogLevel): void {}
}

// 控制台日志记录器实现
export class ConsoleLogger implements Logger {
  private level: LogLevel = LogLevel.INFO;

  setLevel(level: LogLevel): void {
    this.level = level;
  }

  debug(message: string, payload?: any): void {
    if (this.shouldLog(LogLevel.DEBUG)) {
      console.debug(message, payload);
    }
  }

  info(message: string, payload?: any): void {
    if (this.shouldLog(LogLevel.INFO)) {
      console.info(message, payload);
    }
  }

  warn(message: string, payload?: any): void {
    if (this.shouldLog(LogLevel.WARN)) {
      console.warn(message, payload);
    }
  }

  error(message: string, payload?: any): void {
    if (this.shouldLog(LogLevel.ERROR)) {
      console.error(message, payload);
    }
  }

  private shouldLog(messageLevel: LogLevel): boolean {
    const levels = [
      LogLevel.DEBUG,
      LogLevel.INFO,
      LogLevel.WARN,
      LogLevel.ERROR,
    ];
    return levels.indexOf(messageLevel) >= levels.indexOf(this.level);
  }
}
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@jovanhsu/mcp-neo4j-memory-server",
  "version": "1.0.0",
  "private": false,
  "description": "MCP Memory Server with Neo4j backend for AI knowledge graph storage",
  "homepage": "https://github.com/JovanHsu/mcp-neo4j-memory-server",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/JovanHsu/mcp-neo4j-memory-server.git"
  },
  "type": "module",
  "main": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "bin": {
    "mcp-neo4j-memory-server": "dist/index.mjs"
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "keywords": [
    "mcp",
    "memory",
    "knowledge",
    "graph",
    "neo4j",
    "ai",
    "claude",
    "anthropic",
    "knowledge-graph",
    "memory-server",
    "model-context-protocol"
  ],
  "author": "JovanHsu",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/JovanHsu/mcp-neo4j-memory-server/issues"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.0",
    "neo4j-driver": "^5.18.0",
    "fuse.js": "^7.1.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@changesets/cli": "^2.28.1",
    "@types/node": "^22.13.5",
    "prettier": "^3.5.2",
    "shx": "^0.3.4",
    "tsup": "^8.4.0",
    "typescript": "^5.7.3",
    "vitest": "^3.0.7"
  },
  "engines": {
    "node": ">=22.0.0"
  },
  "scripts": {
    "dev": "pnpm build && npx @modelcontextprotocol/inspector pnpm start",
    "build": "tsup src/index.ts && shx chmod +x dist/index.mjs",
    "start": "node dist/index.mjs 2>/dev/null",
    "test": "vitest run",
    "lint": "prettier --write .",
    "prepublishOnly": "pnpm build",
    "release": "pnpm build && changeset publish"
  },
  "publishConfig": {
    "access": "public"
  }
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { ConsoleLogger, LogLevel } from './logger.js';
import { Neo4jKnowledgeGraphManager } from './manager.js';
import { EntityObject, ObservationObject, RelationObject } from './types.js';
import { extractError } from './utils.js';

// 创建MCP服务器
const server = new McpServer({
  name: 'neo4j-memory-server',
  version: '1.0.0',
});

// 创建日志记录器,并设置为仅输出错误信息
const logger = new ConsoleLogger();
logger.setLevel(LogLevel.ERROR);

// 创建知识图谱管理器
const knowledgeGraphManager = new Neo4jKnowledgeGraphManager(
  /**
   * 根据环境变量获取Neo4j配置
   * @returns Neo4j配置
   */
  () => {
    return {
      uri: process.env.NEO4J_URI || 'bolt://localhost:7687',
      user: process.env.NEO4J_USER || 'neo4j',
      password: process.env.NEO4J_PASSWORD || 'password',
      database: process.env.NEO4J_DATABASE || 'neo4j',
    };
  },
  logger
);

// 注册创建实体工具
server.tool(
  'create_entities',
  'Create multiple new entities in the knowledge graph',
  {
    entities: z.array(EntityObject),
  },
  async ({ entities }) => ({
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          await knowledgeGraphManager.createEntities(entities),
          null,
          2
        ),
      },
    ],
  })
);

// 注册创建关系工具
server.tool(
  'create_relations',
  'Create multiple new relations between entities in the knowledge graph. Relations should be in active voice',
  {
    relations: z.array(RelationObject),
  },
  async ({ relations }) => ({
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          await knowledgeGraphManager.createRelations(relations),
          null,
          2
        ),
      },
    ],
  })
);

// 注册添加观察工具
server.tool(
  'add_observations',
  'Add new observations to existing entities in the knowledge graph',
  {
    observations: z.array(ObservationObject),
  },
  async ({ observations }) => ({
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          await knowledgeGraphManager.addObservations(observations),
          null,
          2
        ),
      },
    ],
  })
);

// 注册删除实体工具
server.tool(
  'delete_entities',
  'Delete multiple entities and their associated relations from the knowledge graph',
  {
    entityNames: z
      .array(z.string())
      .describe('An array of entity names to delete'),
  },
  async ({ entityNames }) => {
    await knowledgeGraphManager.deleteEntities(entityNames);
    return {
      content: [{ type: 'text', text: 'Entities deleted successfully' }],
    };
  }
);

// 注册删除观察工具
server.tool(
  'delete_observations',
  'Delete specific observations from entities in the knowledge graph',
  {
    deletions: z.array(
      z.object({
        entityName: z
          .string()
          .describe('The name of the entity containing the observations'),
        contents: z
          .array(z.string())
          .describe('An array of observations to delete'),
      })
    ),
  },
  async ({ deletions }) => {
    await knowledgeGraphManager.deleteObservations(deletions);
    return {
      content: [{ type: 'text', text: 'Observations deleted successfully' }],
    };
  }
);

// 注册删除关系工具
server.tool(
  'delete_relations',
  'Delete multiple relations from the knowledge graph',
  {
    relations: z
      .array(
        z.object({
          from: z
            .string()
            .describe('The name of the entity where the relation starts'),
          to: z
            .string()
            .describe('The name of the entity where the relation ends'),
          relationType: z.string().describe('The type of the relation'),
        })
      )
      .describe('An array of relations to delete'),
  },
  async ({ relations }) => {
    await knowledgeGraphManager.deleteRelations(relations);
    return {
      content: [{ type: 'text', text: 'Relations deleted successfully' }],
    };
  }
);

// 注册搜索节点工具
server.tool(
  'search_nodes',
  'Search for nodes in the knowledge graph based on a query',
  {
    query: z
      .string()
      .describe(
        'The search query to match against entity names, types, and observation content'
      ),
  },
  async ({ query }) => ({
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          await knowledgeGraphManager.searchNodes(query),
          null,
          2
        ),
      },
    ],
  })
);

// 注册打开节点工具
server.tool(
  'open_nodes',
  'Open specific nodes in the knowledge graph by their names',
  {
    names: z.array(z.string()).describe('An array of entity names to retrieve'),
  },
  async ({ names }) => ({
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          await knowledgeGraphManager.openNodes(names),
          null,
          2
        ),
      },
    ],
  })
);

// 主函数
const main = async () => {
  try {
    // 初始化知识图谱管理器
    await knowledgeGraphManager.initialize();
    
    // 创建传输层
    const transport = new StdioServerTransport();
    
    // 连接服务器
    await server.connect(transport);
    
    // 使用logger代替console.info
    logger.info('Neo4j Knowledge Graph MCP Server running on stdio');
  } catch (error) {
    // 使用logger代替console.error
    logger.error('Failed to start server:', extractError(error));
    process.exit(1);
  }
};

// 启动服务器
main().catch((error) => {
  // 使用logger代替console.error
  logger.error('Error during server startup:', extractError(error));
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/src/manager.ts:
--------------------------------------------------------------------------------

```typescript
import Fuse from 'fuse.js';
import neo4j, { Driver, Session } from 'neo4j-driver';
import { ConsoleLogger, Logger } from './logger.js';
import { Entity, KnowledgeGraph, Observation, ObservationDeletion, Relation } from './types.js';
import { extractError } from './utils.js';

/**
 * Neo4j知识图谱管理器
 */
export class Neo4jKnowledgeGraphManager {
  private driver: Driver | null = null;
  private fuse: Fuse<Entity>;
  private initialized = false;
  private logger: Logger;
  private uri: string;
  private user: string;
  private password: string;
  private database: string;

  /**
   * 构造函数
   * @param configResolver 配置解析器函数
   * @param logger 可选的日志记录器
   */
  constructor(
    configResolver: () => { 
      uri: string; 
      user: string; 
      password: string; 
      database: string;
    },
    logger?: Logger
  ) {
    const config = configResolver();
    this.uri = config.uri;
    this.user = config.user;
    this.password = config.password;
    this.database = config.database;
    this.logger = logger || new ConsoleLogger();

    this.fuse = new Fuse([], {
      keys: ['name', 'entityType', 'observations'],
      includeScore: true,
      threshold: 0.4, // 搜索严格度(越接近0越严格)
    });
  }

  /**
   * 获取会话
   * @returns Neo4j会话
   */
  private async getSession(): Promise<Session> {
    if (!this.driver) {
      await this.initialize();
    }
    return this.driver!.session({ database: this.database });
  }

  /**
   * 初始化数据库
   */
  public async initialize(): Promise<void> {
    if (this.initialized) return;

    try {
      if (!this.driver) {
        this.driver = neo4j.driver(
          this.uri,
          neo4j.auth.basic(this.user, this.password),
          { maxConnectionLifetime: 3 * 60 * 60 * 1000 } // 3小时
        );
      }

      const session = await this.getSession();
      try {
        // 创建约束和索引
        await session.run(`
          CREATE CONSTRAINT entity_name_unique IF NOT EXISTS
          FOR (e:Entity) REQUIRE e.name IS UNIQUE
        `);

        await session.run(`
          CREATE INDEX entity_type_index IF NOT EXISTS
          FOR (e:Entity) ON (e.entityType)
        `);

        // 使用新的语法创建全文索引
        await session.run(`
          CREATE FULLTEXT INDEX entity_fulltext IF NOT EXISTS
          FOR (e:Entity)
          ON EACH [e.name, e.entityType]
        `);

        // 加载所有实体到Fuse.js
        const entities = await this.getAllEntities();
        this.fuse.setCollection(entities);
        
        this.initialized = true;
      } finally {
        await session.close();
      }
    } catch (error) {
      this.logger.error('Failed to initialize database', extractError(error));
      throw error;
    }
  }

  /**
   * 获取所有实体
   * @returns 所有实体数组
   */
  private async getAllEntities(): Promise<Entity[]> {
    const session = await this.getSession();
    try {
      const result = await session.run(`
        MATCH (e:Entity)
        OPTIONAL MATCH (e)-[r:HAS_OBSERVATION]->(o)
        RETURN e.name AS name, e.entityType AS entityType, collect(o.content) AS observations
      `);

      const entities: Entity[] = result.records.map(record => {
        return {
          name: record.get('name'),
          entityType: record.get('entityType'),
          observations: record.get('observations').filter(Boolean)
        };
      });

      return entities;
    } catch (error) {
      this.logger.error('Error getting all entities', extractError(error));
      return [];
    } finally {
      await session.close();
    }
  }

  /**
   * 创建实体
   * @param entities 要创建的实体数组
   * @returns 创建的实体数组
   */
  public async createEntities(entities: Entity[]): Promise<Entity[]> {
    if (entities.length === 0) return [];

    const session = await this.getSession();
    try {
      const createdEntities: Entity[] = [];
      const tx = session.beginTransaction();

      try {
        // 获取现有实体名称
        const existingEntitiesResult = await tx.run(
          'MATCH (e:Entity) RETURN e.name AS name'
        );
        const existingNames = new Set(
          existingEntitiesResult.records.map(record => record.get('name'))
        );

        // 过滤出新实体
        const newEntities = entities.filter(
          entity => !existingNames.has(entity.name)
        );

        // 创建新实体
        for (const entity of newEntities) {
          await tx.run(
            `
            CREATE (e:Entity {name: $name, entityType: $entityType})
            WITH e
            UNWIND $observations AS observation
            CREATE (o:Observation {content: observation})
            CREATE (e)-[:HAS_OBSERVATION]->(o)
            RETURN e
            `,
            {
              name: entity.name,
              entityType: entity.entityType,
              observations: entity.observations
            }
          );
          createdEntities.push(entity);
        }

        await tx.commit();
        
        // 更新Fuse.js集合
        const allEntities = await this.getAllEntities();
        this.fuse.setCollection(allEntities);
        
        return createdEntities;
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error creating entities', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 创建关系
   * @param relations 要创建的关系数组
   * @returns 创建的关系数组
   */
  public async createRelations(relations: Relation[]): Promise<Relation[]> {
    if (relations.length === 0) return [];

    const session = await this.getSession();
    try {
      const tx = session.beginTransaction();
      try {
        // 获取所有实体名称
        const entityNamesResult = await tx.run(
          'MATCH (e:Entity) RETURN e.name AS name'
        );
        const entityNames = new Set(
          entityNamesResult.records.map(record => record.get('name'))
        );

        // 过滤出有效关系(源实体和目标实体都存在)
        const validRelations = relations.filter(
          relation => entityNames.has(relation.from) && entityNames.has(relation.to)
        );

        // 获取现有关系
        const existingRelationsResult = await tx.run(`
          MATCH (from:Entity)-[r]->(to:Entity)
          RETURN from.name AS fromName, to.name AS toName, type(r) AS relationType
        `);
        
        const existingRelations = existingRelationsResult.records.map(record => {
          return {
            from: record.get('fromName'),
            to: record.get('toName'),
            relationType: record.get('relationType')
          };
        });

        // 过滤出新关系
        const newRelations = validRelations.filter(
          newRel => !existingRelations.some(
            existingRel => 
              existingRel.from === newRel.from && 
              existingRel.to === newRel.to && 
              existingRel.relationType === newRel.relationType
          )
        );

        // 创建新关系
        for (const relation of newRelations) {
          await tx.run(
            `
            MATCH (from:Entity {name: $fromName})
            MATCH (to:Entity {name: $toName})
            CREATE (from)-[r:${relation.relationType}]->(to)
            RETURN r
            `,
            {
              fromName: relation.from,
              toName: relation.to
            }
          );
        }

        await tx.commit();
        return newRelations;
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error creating relations', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 添加观察
   * @param observations 要添加的观察数组
   * @returns 添加的观察数组
   */
  public async addObservations(observations: Observation[]): Promise<Observation[]> {
    if (observations.length === 0) return [];

    const session = await this.getSession();
    try {
      const addedObservations: Observation[] = [];
      const tx = session.beginTransaction();

      try {
        for (const observation of observations) {
          // 检查实体是否存在
          const entityResult = await tx.run(
            'MATCH (e:Entity {name: $name}) RETURN e',
            { name: observation.entityName }
          );

          if (entityResult.records.length > 0) {
            // 获取现有观察
            const existingObservationsResult = await tx.run(
              `
              MATCH (e:Entity {name: $name})-[:HAS_OBSERVATION]->(o:Observation)
              RETURN o.content AS content
              `,
              { name: observation.entityName }
            );

            const existingObservations = new Set(
              existingObservationsResult.records.map(record => record.get('content'))
            );

            // 过滤出新观察
            const newContents = observation.contents.filter(
              content => !existingObservations.has(content)
            );

            if (newContents.length > 0) {
              // 添加新观察
              await tx.run(
                `
                MATCH (e:Entity {name: $name})
                UNWIND $contents AS content
                CREATE (o:Observation {content: content})
                CREATE (e)-[:HAS_OBSERVATION]->(o)
                `,
                {
                  name: observation.entityName,
                  contents: newContents
                }
              );

              addedObservations.push({
                entityName: observation.entityName,
                contents: newContents
              });
            }
          }
        }

        await tx.commit();
        
        // 更新Fuse.js集合
        const allEntities = await this.getAllEntities();
        this.fuse.setCollection(allEntities);
        
        return addedObservations;
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error adding observations', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 删除实体
   * @param entityNames 要删除的实体名称数组
   */
  public async deleteEntities(entityNames: string[]): Promise<void> {
    if (entityNames.length === 0) return;

    const session = await this.getSession();
    try {
      const tx = session.beginTransaction();
      try {
        // 删除实体及其关联的观察和关系
        await tx.run(
          `
          UNWIND $names AS name
          MATCH (e:Entity {name: name})
          OPTIONAL MATCH (e)-[:HAS_OBSERVATION]->(o:Observation)
          DETACH DELETE e, o
          `,
          { names: entityNames }
        );

        await tx.commit();
        
        // 更新Fuse.js集合
        const allEntities = await this.getAllEntities();
        this.fuse.setCollection(allEntities);
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error deleting entities', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 删除观察
   * @param deletions 要删除的观察数组
   */
  public async deleteObservations(deletions: ObservationDeletion[]): Promise<void> {
    if (deletions.length === 0) return;

    const session = await this.getSession();
    try {
      const tx = session.beginTransaction();
      try {
        for (const deletion of deletions) {
          if (deletion.contents.length > 0) {
            await tx.run(
              `
              MATCH (e:Entity {name: $name})-[:HAS_OBSERVATION]->(o:Observation)
              WHERE o.content IN $contents
              DETACH DELETE o
              `,
              {
                name: deletion.entityName,
                contents: deletion.contents
              }
            );
          }
        }

        await tx.commit();
        
        // 更新Fuse.js集合
        const allEntities = await this.getAllEntities();
        this.fuse.setCollection(allEntities);
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error deleting observations', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 删除关系
   * @param relations 要删除的关系数组
   */
  public async deleteRelations(relations: Relation[]): Promise<void> {
    if (relations.length === 0) return;

    const session = await this.getSession();
    try {
      const tx = session.beginTransaction();
      try {
        for (const relation of relations) {
          await tx.run(
            `
            MATCH (from:Entity {name: $fromName})-[r:${relation.relationType}]->(to:Entity {name: $toName})
            DELETE r
            `,
            {
              fromName: relation.from,
              toName: relation.to
            }
          );
        }

        await tx.commit();
      } catch (error) {
        await tx.rollback();
        this.logger.error('Error deleting relations', extractError(error));
        throw error;
      }
    } finally {
      await session.close();
    }
  }

  /**
   * 搜索节点
   * @param query 搜索查询
   * @returns 包含匹配实体和关系的知识图谱
   */
  public async searchNodes(query: string): Promise<KnowledgeGraph> {
    if (!query || query.trim() === '') {
      return { entities: [], relations: [] };
    }

    const session = await this.getSession();
    try {
      // 使用Neo4j全文搜索
      const searchResult = await session.run(
        `
        CALL db.index.fulltext.queryNodes("entity_fulltext", $query)
        YIELD node, score
        RETURN node
        `,
        { query }
      );

      // 获取所有实体用于Fuse.js搜索
      const allEntities = await this.getAllEntities();
      this.fuse.setCollection(allEntities);
      
      // 使用Fuse.js进行模糊搜索
      const fuseResults = this.fuse.search(query);
      
      // 合并搜索结果
      const uniqueEntities = new Map<string, Entity>();
      
      // 添加Neo4j搜索结果
      for (const record of searchResult.records) {
        const node = record.get('node');
        const name = node.properties.name;
        
        if (!uniqueEntities.has(name)) {
          const entity = allEntities.find(e => e.name === name);
          if (entity) {
            uniqueEntities.set(name, entity);
          }
        }
      }
      
      // 添加Fuse.js搜索结果
      for (const result of fuseResults) {
        if (!uniqueEntities.has(result.item.name)) {
          uniqueEntities.set(result.item.name, result.item);
        }
      }
      
      const entities = Array.from(uniqueEntities.values());
      const entityNames = entities.map(entity => entity.name);
      
      if (entityNames.length === 0) {
        return { entities: [], relations: [] };
      }
      
      // 获取关系
      const relationsResult = await session.run(
        `
        MATCH (from:Entity)-[r]->(to:Entity)
        WHERE from.name IN $names OR to.name IN $names
        RETURN from.name AS fromName, to.name AS toName, type(r) AS relationType
        `,
        { names: entityNames }
      );
      
      const relations: Relation[] = relationsResult.records.map(record => {
        return {
          from: record.get('fromName'),
          to: record.get('toName'),
          relationType: record.get('relationType')
        };
      });
      
      return {
        entities,
        relations
      };
    } catch (error) {
      this.logger.error('Error searching nodes', extractError(error));
      return { entities: [], relations: [] };
    } finally {
      await session.close();
    }
  }

  /**
   * 打开节点
   * @param names 要打开的节点名称数组
   * @returns 包含匹配实体和关系的知识图谱
   */
  public async openNodes(names: string[]): Promise<KnowledgeGraph> {
    if (names.length === 0) {
      return { entities: [], relations: [] };
    }

    const session = await this.getSession();
    try {
      // 获取实体及其观察
      const entitiesResult = await session.run(
        `
        MATCH (e:Entity)
        WHERE e.name IN $names
        OPTIONAL MATCH (e)-[:HAS_OBSERVATION]->(o:Observation)
        RETURN e.name AS name, e.entityType AS entityType, collect(o.content) AS observations
        `,
        { names }
      );
      
      const entities: Entity[] = entitiesResult.records.map(record => {
        return {
          name: record.get('name'),
          entityType: record.get('entityType'),
          observations: record.get('observations').filter(Boolean)
        };
      });
      
      const entityNames = entities.map(entity => entity.name);
      
      if (entityNames.length > 0) {
        // 获取关系
        const relationsResult = await session.run(
          `
          MATCH (from:Entity)-[r]->(to:Entity)
          WHERE from.name IN $names OR to.name IN $names
          RETURN from.name AS fromName, to.name AS toName, type(r) AS relationType
          `,
          { names: entityNames }
        );
        
        const relations: Relation[] = relationsResult.records.map(record => {
          return {
            from: record.get('fromName'),
            to: record.get('toName'),
            relationType: record.get('relationType')
          };
        });
        
        return {
          entities,
          relations
        };
      } else {
        return {
          entities,
          relations: []
        };
      }
    } catch (error) {
      this.logger.error('Error opening nodes', extractError(error));
      return { entities: [], relations: [] };
    } finally {
      await session.close();
    }
  }

  /**
   * 关闭连接
   */
  public async close(): Promise<void> {
    if (this.driver) {
      await this.driver.close();
      this.driver = null;
    }
  }
}
```