# Directory Structure
```
├── .editorconfig
├── .github
│ └── workflows
│ └── publish.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .verdaccio
│ └── config.yml
├── .vscode
│ └── extensions.json
├── DEVELOP.md
├── eslint.config.js
├── jest.config.ts
├── jest.preset.js
├── libs
│ └── graphrag-memory
│ ├── eslint.config.js
│ ├── jest.config.ts
│ ├── package.json
│ ├── project.json
│ ├── README.md
│ ├── src
│ │ ├── index.ts
│ │ └── lib
│ │ ├── graphrag-memory.spec.ts
│ │ └── graphrag-memory.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── nx.json
├── package-lock.json
├── package.json
├── project.json
├── README.md
├── servers
│ ├── mcp-json-memory
│ │ ├── eslint.config.cjs
│ │ ├── jest.config.ts
│ │ ├── package.json
│ │ ├── project.json
│ │ ├── src
│ │ │ ├── assets
│ │ │ │ └── .gitkeep
│ │ │ ├── json-memory.ts
│ │ │ └── main.ts
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ └── tsconfig.spec.json
│ ├── mcp-neo4j-cypher
│ │ ├── .flake8
│ │ ├── .python-version
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── src
│ │ │ └── mcp_neo4j_cypher
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── server.cpython-312.pyc
│ │ │ └── server.py
│ │ └── uv.lock
│ └── mcp-neo4j-memory
│ ├── eslint.config.js
│ ├── jest.config.ts
│ ├── project.json
│ ├── src
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── main.ts
│ │ └── neo4j-memory.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
└── tsconfig.base.json
```
# Files
--------------------------------------------------------------------------------
/servers/mcp-json-memory/src/assets/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/src/assets/.gitkeep:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/.python-version:
--------------------------------------------------------------------------------
```
3.12.7
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
{
"singleQuote": true
}
```
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/.flake8:
--------------------------------------------------------------------------------
```
[flake8]
exclude =
.git,
__pycache__,
build,
dist,
.tox,
venv,
.venv,
.pytest_cache
max-line-length = 120
```
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
```
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
dist
tmp
/out-tsc
# dependencies
node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# python venc
.venv
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
.nx/cache
.nx/workspace-data
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/README.md:
--------------------------------------------------------------------------------
```markdown
# graphrag-memory
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build graphrag-memory` to build the library.
## Running unit tests
Run `nx test graphrag-memory` to execute the unit tests via [Jest](https://jestjs.io).
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Neo4j MCP Clients & Servers
Model Context Protocol (MCP) is a [standardized protocol](https://modelcontextprotocol.io/introduction) for managing context between large language models (LLMs) and external systems.
This lets you use Claude Desktop, or any MCP Client, to use natural language to accomplish things with Neo4j and your Aura account, e.g.:
* `What is in this graph?`
## Servers
### `mcp-neo4j-cypher` - natural language to Cypher queries
### `mcp-neo4j-memory` - knowledge graph memory stored in Neo4j
### `mcp-json-memory` - knowledge graph memory stored in a file
A reference server for modeling memory as a knowledge graph.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/README.md:
--------------------------------------------------------------------------------
```markdown
# Neo4j MCP Server
## Overview
A Model Context Protocol (MCP) server implementation that provides database interaction and allows graph exploration capabilities through neo4j. This server enables running Cypher graph queries, analyzing complex domain data, and automatically generating business insights that can be enhanced with Claude's analysis when an Anthropic API key is provided.
## Components
### Resources
### Prompts
The server provides a demonstration prompt:
- `mcp-demo`: Interactive prompt that guides users through database operations
- Generates appropriate database schemas and sample data
### Tools
The server offers six core tools:
#### Query Tools
- `read-neo4j-cypher`
- Execute Cypher read queries to read data from the database
- Input:
- `query` (string): The Cypher query to execute
- Returns: Query results as array of objects
- `write-neo4j-cypher`
- Execute updating Cypher queries
- Input:
- `query` (string): The Cypher update query
- Returns: a result summary counter with `{ nodes_updated: number, relationships_created: number, ... }`
#### Schema Tools
- `get-neo4j-schema`
- Get a list of all nodes types in the graph database, their attributes with name, type and relationships to other node types
- No input required
- Returns: List of node label with two dictionaries one for attributes and one for relationships
## Usage with Claude Desktop
### Released Package
Can be found on PyPi https://pypi.org/project/mcp-neo4j-cypher/
Add the server to your `claude_desktop_config.json` with configuration of
* db-url
* username
* password
```json
"mcpServers": {
"neo4j": {
"command": "uvx",
"args": [
"mcp-neo4j-cypher",
"--db-url",
"bolt://localhost",
"--username",
"neo4j",
"--password",
"<your-password>"
]
}
}
```
Here is an example connection for the movie database with Movie, Person (Actor, Director), Genre, User and ratings.
```json
{
"mcpServers": {
"movies-neo4j": {
"command": "uvx",
"args": ["mcp-neo4j-cypher",
"--db-url", "neo4j+s://demo.neo4jlabs.com",
"--user", "recommendations",
"--password", "recommendations"]
}
}
}
```
### Development
```json
# Add the server to your claude_desktop_config.json
"mcpServers": {
"neo4j": {
"command": "uv",
"args": [
"--directory",
"parent_of_servers_repo/servers/src/neo4j",
"run",
"mcp-neo4j-cypher",
"--db-url",
"bolt://localhost",
"--username",
"neo4j",
"--password",
"<your-password>"
]
}
}
```
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/package.json:
--------------------------------------------------------------------------------
```json
{
"type":"module"
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/src/index.ts:
--------------------------------------------------------------------------------
```typescript
export * from './lib/graphrag-memory';
```
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
```javascript
const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset };
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/eslint.config.cjs:
--------------------------------------------------------------------------------
```
const baseConfig = require('../../eslint.config.js');
module.exports = [...baseConfig];
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const baseConfig = require('../../eslint.config.js');
module.exports = [...baseConfig];
```
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
```typescript
import { getJestProjectsAsync } from '@nx/jest';
export default async () => ({
projects: await getJestProjectsAsync(),
});
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner"
]
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@neo4j/graphrag-memory",
"version": "0.0.1",
"dependencies": {},
"type": "commonjs",
"main": "./index.cjs",
"typings": "./index.d.ts"
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/src/lib/graphrag-memory.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { } from './graphrag-memory';
describe('graphragMemory does not yet have functionality', () => {
it('should be able to run tests', () => {
expect(true).toEqual(true);
});
});
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/tsconfig.lib.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/tsconfig.spec.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/tsconfig.spec.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/tsconfig.spec.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/tsconfig.app.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "es2022",
"outDir": "../../dist/out-tsc",
"module": "ES2022",
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/tsconfig.app.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "es2022",
"outDir": "../../dist/out-tsc",
"module": "ES2022",
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/jest.config.ts:
--------------------------------------------------------------------------------
```typescript
export default {
displayName: 'graphrag-memory',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/graphrag-memory',
};
```
--------------------------------------------------------------------------------
/project.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-neo4j/source",
"$schema": "node_modules/nx/schemas/project-schema.json",
"targets": {
"local-registry": {
"executor": "@nx/js:verdaccio",
"options": {
"port": 4873,
"config": ".verdaccio/config.yml",
"storage": "tmp/local-registry/storage"
}
}
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/jest.config.ts:
--------------------------------------------------------------------------------
```typescript
export default {
displayName: 'mcp-json-memory',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/servers/mcp-json-memory',
};
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/jest.config.ts:
--------------------------------------------------------------------------------
```typescript
export default {
displayName: 'mcp-neo4j-memory',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/servers/mcp-neo4j-memory',
};
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "mcp-neo4j-cypher"
version = "0.1.1"
description = "A simple Neo4j MCP server"
readme = "README.md"
requires-python = ">=3.10"
dependencies = ["mcp>=0.9.1", "neo4j>=5.26.0"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
dev-dependencies = ["pyright>=1.1.389"]
[project.scripts]
mcp-neo4j-cypher = "mcp_neo4j_cypher:main"
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const baseConfig = require('../../eslint.config.js');
module.exports = [
...baseConfig,
{
files: ['**/*.json'],
rules: {
'@nx/dependency-checks': [
'error',
{
ignoredFiles: [
'{projectRoot}/eslint.config.{js,cjs,mjs}',
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
],
},
],
},
languageOptions: {
parser: require('jsonc-eslint-parser'),
},
},
];
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
```json
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@neo4j/graphrag-memory": ["libs/graphrag-memory/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
}
```
--------------------------------------------------------------------------------
/.verdaccio/config.yml:
--------------------------------------------------------------------------------
```yaml
# path to a directory with all packages
storage: ../tmp/local-registry/storage
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
maxage: 60m
packages:
'**':
# give all users (including non-authenticated users) full access
# because it is a local registry
access: $all
publish: $all
unpublish: $all
# if package is not available locally, proxy requests to npm registry
proxy: npmjs
# log settings
log:
type: stdout
format: pretty
level: warn
publish:
allow_offline: true # set offline to true to allow publish offline
```
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
const nx = require('@nx/eslint-plugin');
module.exports = [
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
{
ignores: ['**/dist'],
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/src/mcp_neo4j_cypher/__init__.py:
--------------------------------------------------------------------------------
```python
from . import server
import asyncio
import argparse
import os
def main():
"""Main entry point for the package."""
parser = argparse.ArgumentParser(description='Neo4j Cypher MCP Server')
parser.add_argument('--db-url',
default="bolt://localhost:7687",
help='Neo4j connection URL')
parser.add_argument('--username',
default="neo4j",
help='Neo4j username')
parser.add_argument('--password',
default="password",
help='Neo4j password')
args = parser.parse_args()
asyncio.run(server.main(args.db_url, args.username, args.password))
# Optionally expose other important items at package level
__all__ = ["main", "server"]
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/project.json:
--------------------------------------------------------------------------------
```json
{
"name": "graphrag-memory",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/graphrag-memory/src",
"projectType": "library",
"release": {
"version": {
"generatorOptions": {
"packageRoot": "dist/{projectRoot}",
"currentVersionResolver": "git-tag"
}
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/graphrag-memory",
"main": "libs/graphrag-memory/src/index.ts",
"tsConfig": "libs/graphrag-memory/tsconfig.lib.json",
"format": ["cjs"],
"assets": ["libs/graphrag-memory/*.md"],
"generatePackageJson": true
}
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
}
}
}
```
--------------------------------------------------------------------------------
/libs/graphrag-memory/src/lib/graphrag-memory.ts:
--------------------------------------------------------------------------------
```typescript
export interface Entity {
name: string;
entityType: string;
observations: string[];
}
export interface Relation {
from: string;
to: string;
relationType: string;
}
export interface KnowledgeGraph {
entities: Entity[];
relations: Relation[];
}
// The KnowledgeGraphMemory interface contains all operations to interact with the knowledge graph
export interface KnowledgeGraphMemory {
createEntities(entities: Entity[]): Promise<Entity[]>;
createRelations(relations: Relation[]): Promise<Relation[]>;
addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]>;
deleteEntities(entityNames: string[]): Promise<void>;
deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void>;
deleteRelations(relations: Relation[]): Promise<void>;
readGraph(): Promise<KnowledgeGraph>;
// Very basic search function
searchNodes(query: string): Promise<KnowledgeGraph>;
openNodes(names: string[]): Promise<KnowledgeGraph>;
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-neo4j/source",
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"private": true,
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.1",
"axios": "^1.6.0",
"neo4j-driver": "^5.27.0"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@nx/esbuild": "20.1.4",
"@nx/eslint": "20.1.4",
"@nx/eslint-plugin": "20.1.4",
"@nx/jest": "20.1.4",
"@nx/js": "20.1.4",
"@nx/node": "20.1.4",
"@nx/workspace": "20.1.4",
"@nxlv/python": "^20.0.1",
"@swc-node/register": "~1.9.1",
"@swc/core": "~1.5.7",
"@swc/helpers": "~0.5.11",
"@types/jest": "^29.5.12",
"@types/node": "~18.16.9",
"esbuild": "^0.19.2",
"eslint": "^9.8.0",
"eslint-config-prettier": "^9.0.0",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"nx": "20.1.4",
"prettier": "^2.6.2",
"ts-jest": "^29.1.0",
"ts-node": "10.9.1",
"tslib": "^2.3.0",
"typescript": "~5.5.2",
"typescript-eslint": "^8.0.0",
"verdaccio": "^5.0.4"
},
"nx": {
"includedScripts": []
}
}
```
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.js",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/src/test-setup.[jt]s",
"!{projectRoot}/test-setup.[jt]s"
],
"sharedGlobals": ["{workspaceRoot}/.github/workflows/ci.yml"]
},
"nxCloudId": "6750280b426ab2447467d096",
"targetDefaults": {
"@nx/esbuild:esbuild": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
},
"plugins": [
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "lint"
}
},
{
"plugin": "@nx/jest/plugin",
"options": {
"targetName": "test"
},
"exclude": [
"apps/mcp-neo4j-e2e/**/*",
"mcp-neo4j-memory-e2e/**/*",
"servers/mcp-neo4j-memory-e2e/**/*",
"servers/mcp-json-memory-e2e/**/*"
]
},
{
"plugin":"@nxlv/python"
}
],
"release": {
"version": {
"preVersionCommand": "npx nx run-many -t build"
}
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/project.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-json-memory",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "servers/mcp-json-memory/src",
"projectType": "application",
"tags": [],
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"platform": "node",
"outputPath": "dist/servers/mcp-json-memory",
"format": ["esm"],
"bundle": false,
"main": "servers/mcp-json-memory/src/main.ts",
"tsConfig": "servers/mcp-json-memory/tsconfig.app.json",
"assets": ["servers/mcp-json-memory/src/assets"],
"generatePackageJson": true,
"esbuildOptions": {
"sourcemap": true,
"outExtension": {
".js": ".js"
}
}
},
"configurations": {
"development": {},
"production": {
"esbuildOptions": {
"sourcemap": false,
"outExtension": {
".js": ".js"
}
}
}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "mcp-json-memory:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "mcp-json-memory:build:development"
},
"production": {
"buildTarget": "mcp-json-memory:build:production"
}
}
}
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/project.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-neo4j-memory",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "servers/mcp-neo4j-memory/src",
"projectType": "application",
"tags": [],
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"platform": "node",
"outputPath": "dist/servers/mcp-neo4j-memory",
"format": ["esm"],
"bundle": false,
"main": "servers/mcp-neo4j-memory/src/main.ts",
"tsConfig": "servers/mcp-neo4j-memory/tsconfig.app.json",
"assets": ["servers/mcp-neo4j-memory/src/assets"],
"generatePackageJson": true,
"esbuildOptions": {
"sourcemap": true,
"outExtension": {
".js": ".js"
}
}
},
"configurations": {
"development": {},
"production": {
"esbuildOptions": {
"sourcemap": false,
"outExtension": {
".js": ".js"
}
}
}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"dependsOn": ["build"],
"options": {
"buildTarget": "mcp-neo4j-memory:build",
"runBuildTargetDependencies": false
},
"configurations": {
"development": {
"buildTarget": "mcp-neo4j-memory:build:development"
},
"production": {
"buildTarget": "mcp-neo4j-memory:build:production"
}
}
}
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
```yaml
# This workflow will upload a Python Package to PyPI when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Publish Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
release-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Build release distributions
run: |
cd servers/mcp-neo4j-cypher/
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: release-dists
path: servers/mcp-neo4j-cypher/dist/
pypi-publish:
runs-on: ubuntu-latest
needs:
- release-build
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
# Dedicated environments with protections for publishing are strongly recommended.
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
environment:
name: pypi
url: https://pypi.org/project/mcp-neo4j-cypher/
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: servers/mcp-neo4j-cypher/dist/
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: servers/mcp-neo4j-cypher/dist/
```
--------------------------------------------------------------------------------
/DEVELOP.md:
--------------------------------------------------------------------------------
```markdown
# McpNeo4j
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
✨ Your new, shiny [Nx workspace](https://nx.dev) is almost ready ✨.
[Learn more about this workspace setup and its capabilities](https://nx.dev/nx-api/node?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed!
Include python support via [@nxlv/python plugin](https://github.com/lucasvieirasilva/nx-plugins/tree/main/packages/nx-python)
## Finish your CI setup
[Click here to finish setting up your workspace!](https://cloud.nx.app/connect/Wx1erwy80w)
## Run tasks
To run the dev server for your app, use:
```sh
npx nx serve mcp-neo4j
```
To create a production bundle:
```sh
npx nx build mcp-neo4j
```
To see all available targets to run for a project, run:
```sh
npx nx show project mcp-neo4j
```
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
Use the plugin's generator to create new projects.
To generate a new application, use:
```sh
npx nx g @nx/node:app demo
```
To generate a new library, use:
```sh
npx nx g @nx/node:lib mylib
```
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Install Nx Console
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Useful links
Learn more:
- [Learn more about this workspace setup](https://nx.dev/nx-api/node?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
And join the Nx community:
- [Discord](https://go.nx.dev/community)
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-cypher/src/mcp_neo4j_cypher/server.py:
--------------------------------------------------------------------------------
```python
import neo4j
import logging
from logging.handlers import RotatingFileHandler
from contextlib import closing
from pathlib import Path
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio
from pydantic import AnyUrl
from typing import Any
from neo4j import GraphDatabase
import re
logger = logging.getLogger('mcp_neo4j_cypher')
logger.info("Starting MCP neo4j Server")
def is_write_query(query: str) -> bool:
return re.search(r"\b(MERGE|CREATE|SET|DELETE|REMOVE|ADD)\b", query, re.IGNORECASE) is not None
class neo4jDatabase:
def __init__(self, neo4j_uri: str, neo4j_username: str, neo4j_password: str):
"""Initialize connection to the neo4j database"""
logger.debug(f"Initializing database connection to {neo4j_uri}")
d = GraphDatabase.driver(neo4j_uri, auth=(neo4j_username, neo4j_password))
d.verify_connectivity()
self.driver = d
def _execute_query(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
"""Execute a Cypher query and return results as a list of dictionaries"""
logger.debug(f"Executing query: {query}")
try:
result = self.driver.execute_query(query, params)
counters = vars(result.summary.counters)
if is_write_query(query):
logger.debug(f"Write query affected {counters}")
return [counters]
else:
results = [dict(r) for r in result.records]
logger.debug(f"Read query returned {len(results)} rows")
return results
except Exception as e:
logger.error(f"Database error executing query: {e}\n{query}")
raise
async def main(neo4j_url: str, neo4j_username: str, neo4j_password: str):
logger.info(f"Connecting to neo4j MCP Server with DB URL: {neo4j_url}")
db = neo4jDatabase(neo4j_url, neo4j_username, neo4j_password)
server = Server("neo4j-manager")
# Register handlers
logger.debug("Registering handlers")
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""List available tools"""
return [
types.Tool(
name="read-neo4j-cypher",
description="Execute a Cypher query on the neo4j database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Cypher read query to execute"},
},
"required": ["query"],
},
),
types.Tool(
name="write-neo4j-cypher",
description="Execute a write Cypher query on the neo4j database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Cypher write query to execute"},
},
"required": ["query"],
},
),
types.Tool(
name="get-neo4j-schema",
description="List all node types, their attributes and their relationships TO other node-types in the neo4j database",
inputSchema={
"type": "object",
"properties": {},
},
)
]
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any] | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""Handle tool execution requests"""
try:
if name == "get-neo4j-schema":
results = db._execute_query(
"""
call apoc.meta.data() yield label, property, type, other, unique, index, elementType
where elementType = 'node' and not label starts with '_'
with label,
collect(case when type <> 'RELATIONSHIP' then [property, type + case when unique then " unique" else "" end + case when index then " indexed" else "" end] end) as attributes,
collect(case when type = 'RELATIONSHIP' then [property, head(other)] end) as relationships
RETURN label, apoc.map.fromPairs(attributes) as attributes, apoc.map.fromPairs(relationships) as relationships
"""
)
return [types.TextContent(type="text", text=str(results))]
elif name == "read-neo4j-cypher":
if is_write_query(arguments["query"]):
raise ValueError("Only MATCH queries are allowed for read-query")
results = db._execute_query(arguments["query"])
return [types.TextContent(type="text", text=str(results))]
elif name == "write-neo4j-cypher":
if not is_write_query(arguments["query"]):
raise ValueError("Only write queries are allowed for write-query")
results = db._execute_query(arguments["query"])
return [types.TextContent(type="text", text=str(results))]
else:
raise ValueError(f"Unknown tool: {name}")
except Exception as e:
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
logger.info("Server running with stdio transport")
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="neo4j",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/src/json-memory.ts:
--------------------------------------------------------------------------------
```typescript
import path from 'path';
import { fileURLToPath } from 'url';
import { promises as fs } from 'fs';
import { KnowledgeGraphMemory, Entity, KnowledgeGraph, Relation } from "@neo4j/graphrag-memory";
// Default path to the JSONL file, you can change this to your desired local path
// by passing in an arg to the memory-server
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DEFAULT_MEMORY_FILE_PATH = path.join(__dirname, 'memory.json');
// The JsonMemory class contains all operations to interact with the knowledge graph
export class JsonMemory implements KnowledgeGraphMemory {
private memoryFilePath: string;
constructor(args: string[]) {
this.memoryFilePath = (args.length > 0) ? args[0] : DEFAULT_MEMORY_FILE_PATH;
}
private async loadGraph(): Promise<KnowledgeGraph> {
try {
const data = await fs.readFile(this.memoryFilePath, "utf-8");
const lines = data.split("\n").filter(line => line.trim() !== "");
return lines.reduce((graph: KnowledgeGraph, line) => {
const item = JSON.parse(line);
if (item.type === "entity") graph.entities.push(item as Entity);
if (item.type === "relation") graph.relations.push(item as Relation);
return graph;
}, { entities: [], relations: [] });
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
console.error(`Error! (${error})`);
return { entities: [], relations: [] };
}
throw error;
}
}
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
const lines = [
...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
];
await fs.writeFile(this.memoryFilePath, lines.join("\n"));
}
async createEntities(entities: Entity[]): Promise<Entity[]> {
const graph = await this.loadGraph();
const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
graph.entities.push(...newEntities);
await this.saveGraph(graph);
return newEntities;
}
async createRelations(relations: Relation[]): Promise<Relation[]> {
const graph = await this.loadGraph();
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
existingRelation.from === r.from &&
existingRelation.to === r.to &&
existingRelation.relationType === r.relationType
));
graph.relations.push(...newRelations);
await this.saveGraph(graph);
return newRelations;
}
async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
const graph = await this.loadGraph();
const results = observations.map(o => {
const entity = graph.entities.find(e => e.name === o.entityName);
if (!entity) {
throw new Error(`Entity with name ${o.entityName} not found`);
}
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph);
return results;
}
async deleteEntities(entityNames: string[]): Promise<void> {
const graph = await this.loadGraph();
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
await this.saveGraph(graph);
}
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
const graph = await this.loadGraph();
deletions.forEach(d => {
const entity = graph.entities.find(e => e.name === d.entityName);
if (entity) {
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
}
});
await this.saveGraph(graph);
}
async deleteRelations(relations: Relation[]): Promise<void> {
const graph = await this.loadGraph();
graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
r.from === delRelation.from &&
r.to === delRelation.to &&
r.relationType === delRelation.relationType
));
await this.saveGraph(graph);
}
async readGraph(): Promise<KnowledgeGraph> {
return this.loadGraph();
}
// Very basic search function
async searchNodes(query: string): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e =>
e.name.toLowerCase().includes(query.toLowerCase()) ||
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
);
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
async openNodes(names: string[]): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/src/neo4j-memory.ts:
--------------------------------------------------------------------------------
```typescript
import path from 'path';
import { fileURLToPath } from 'url';
import { promises as fs } from 'fs';
import neo4j, { Integer, Node, Relationship, Driver as Neo4jDriver } from 'neo4j-driver'
import { KnowledgeGraphMemory, Entity, KnowledgeGraph, Relation } from "@neo4j/graphrag-memory";
type EntityNode = Node<Integer, Entity>
type EntityRelationship = Relationship<Integer, Relation>
interface EntityWithRelationsResult {
entity: EntityNode,
relations: EntityRelationship[]
}
export class Neo4jMemory implements KnowledgeGraphMemory {
constructor(private neo4jDriver: Neo4jDriver) { }
private async loadGraph(): Promise<KnowledgeGraph> {
const session = this.neo4jDriver.session()
try {
// Execute a Cypher statement in a Read Transaction
const res = await session.executeRead(tx => tx.run<EntityWithRelationsResult>(`
MATCH (entity:Memory)
OPTIONAL MATCH (entity)-[r]->(other)
RETURN entity, collect(r) as relations
`))
const kgMemory:KnowledgeGraph = res.records.reduce(
(kg, row) => {
const entityNode = row.get('entity');
const entityRelationships = row.get('relations');
kg.entities.push(entityNode.properties);
kg.relations.push(...entityRelationships.map(r => r.properties))
return kg
},
({entities:[], relations:[]} as KnowledgeGraph)
)
console.error(JSON.stringify(kgMemory.entities))
console.error(JSON.stringify(kgMemory.relations))
return kgMemory
} catch (error) {
console.error(error)
}
finally {
// Close the Session
await session.close()
}
return {
entities: [],
relations: []
};
}
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
const session = this.neo4jDriver.session()
return session.executeWrite(async txc => {
await txc.run(`
UNWIND $memoryGraph.entities as entity
MERGE (entityMemory:Memory { entityID: entity.name })
SET entityMemory += entity
`
,
{
memoryGraph:graph
}
)
await txc.run(`
UNWIND $memoryGraph.relations as relation
MATCH (from:Memory),(to:Memory)
WHERE from.entityID = relation.from
AND to.entityID = relation.to
MERGE (from)-[r:Memory {relationType:relation.relationType}]->(to)
`
,
{
memoryGraph:graph
}
)
})
}
async createEntities(entities: Entity[]): Promise<Entity[]> {
const graph = await this.loadGraph();
const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
graph.entities.push(...newEntities);
await this.saveGraph(graph);
return newEntities;
}
async createRelations(relations: Relation[]): Promise<Relation[]> {
const graph = await this.loadGraph();
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
existingRelation.from === r.from &&
existingRelation.to === r.to &&
existingRelation.relationType === r.relationType
));
graph.relations.push(...newRelations);
await this.saveGraph(graph);
return newRelations;
}
async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
const graph = await this.loadGraph();
const results = observations.map(o => {
const entity = graph.entities.find(e => e.name === o.entityName);
if (!entity) {
throw new Error(`Entity with name ${o.entityName} not found`);
}
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph);
return results;
}
async deleteEntities(entityNames: string[]): Promise<void> {
const graph = await this.loadGraph();
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
await this.saveGraph(graph);
}
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
const graph = await this.loadGraph();
deletions.forEach(d => {
const entity = graph.entities.find(e => e.name === d.entityName);
if (entity) {
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
}
});
await this.saveGraph(graph);
}
async deleteRelations(relations: Relation[]): Promise<void> {
const graph = await this.loadGraph();
graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
r.from === delRelation.from &&
r.to === delRelation.to &&
r.relationType === delRelation.relationType
));
await this.saveGraph(graph);
}
async readGraph(): Promise<KnowledgeGraph> {
return this.loadGraph();
}
// Very basic search function
async searchNodes(query: string): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e =>
query.toLowerCase().includes(e.name.toLowerCase()) ||
query.toLowerCase().includes(e.entityType.toLowerCase()) ||
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
);
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
async openNodes(names: string[]): Promise<KnowledgeGraph> {
const graph = await this.loadGraph();
// Filter entities
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
}
```
--------------------------------------------------------------------------------
/servers/mcp-json-memory/src/main.ts:
--------------------------------------------------------------------------------
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { KnowledgeGraphMemory, Entity, KnowledgeGraph, Relation } from "@neo4j/graphrag-memory";
import { JsonMemory } from './json-memory.js'
const args = process.argv.slice(2);
// Default to using the json file-based memory
const knowledgeGraphManager:KnowledgeGraphMemory = new JsonMemory(args);
// The server instance and tools exposed to Claude
const server = new Server({
name: "mcp-json-memory",
version: "1.0.1",
}, {
capabilities: {
tools: {},
},
},);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "create_entities",
description: "Create multiple new entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string", description: "The name of the entity" },
entityType: { type: "string", description: "The type of the entity" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observation contents associated with the entity"
},
},
required: ["name", "entityType", "observations"],
},
},
},
required: ["entities"],
},
},
{
name: "create_relations",
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
},
},
},
required: ["relations"],
},
},
{
name: "add_observations",
description: "Add new observations to existing entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
observations: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity to add the observations to" },
contents: {
type: "array",
items: { type: "string" },
description: "An array of observation contents to add"
},
},
required: ["entityName", "contents"],
},
},
},
required: ["observations"],
},
},
{
name: "delete_entities",
description: "Delete multiple entities and their associated relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
entityNames: {
type: "array",
items: { type: "string" },
description: "An array of entity names to delete"
},
},
required: ["entityNames"],
},
},
{
name: "delete_observations",
description: "Delete specific observations from entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
deletions: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity containing the observations" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observations to delete"
},
},
required: ["entityName", "observations"],
},
},
},
required: ["deletions"],
},
},
{
name: "delete_relations",
description: "Delete multiple relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
},
description: "An array of relations to delete"
},
},
required: ["relations"],
},
},
{
name: "read_graph",
description: "Read the entire knowledge graph",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "search_nodes",
description: "Search for nodes in the knowledge graph based on a query",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
},
required: ["query"],
},
},
{
name: "open_nodes",
description: "Open specific nodes in the knowledge graph by their names",
inputSchema: {
type: "object",
properties: {
names: {
type: "array",
items: { type: "string" },
description: "An array of entity names to retrieve",
},
},
required: ["names"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error(`No arguments provided for tool: ${name}`);
}
switch (name) {
case "create_entities":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[]), null, 2) }] };
case "create_relations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[]), null, 2) }] };
case "add_observations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] };
case "delete_entities":
await knowledgeGraphManager.deleteEntities(args.entityNames as string[]);
return { content: [{ type: "text", text: "Entities deleted successfully" }] };
case "delete_observations":
await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]);
return { content: [{ type: "text", text: "Observations deleted successfully" }] };
case "delete_relations":
await knowledgeGraphManager.deleteRelations(args.relations as Relation[]);
return { content: [{ type: "text", text: "Relations deleted successfully" }] };
case "read_graph":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] };
case "search_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string), null, 2) }] };
case "open_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[]), null, 2) }] };
default:
throw new Error(`Unknown tool: ${name}`);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Knowledge Graph Memory using Neo4j running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/servers/mcp-neo4j-memory/src/main.ts:
--------------------------------------------------------------------------------
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { driver as connectToNeo4j, auth as Neo4jAuth } from 'neo4j-driver'
import { KnowledgeGraphMemory, Entity, KnowledgeGraph, Relation } from "@neo4j/graphrag-memory";
import { Neo4jMemory } from './neo4j-memory.js'
// const args = process.argv.slice(2);
const neo4jDriver = connectToNeo4j(
'neo4j://localhost:7687',
Neo4jAuth.basic('neo4j', 'marwhompa')
)
const knowledgeGraphMemory:KnowledgeGraphMemory = new Neo4jMemory(neo4jDriver);
// The server instance and tools exposed to Claude
const server = new Server({
name: "mcp-neo4j-memory",
version: "1.0.1",
}, {
capabilities: {
tools: {},
},
},);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "create_entities",
description: "Create multiple new entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string", description: "The name of the entity" },
entityType: { type: "string", description: "The type of the entity" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observation contents associated with the entity"
},
},
required: ["name", "entityType", "observations"],
},
},
},
required: ["entities"],
},
},
{
name: "create_relations",
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
},
},
},
required: ["relations"],
},
},
{
name: "add_observations",
description: "Add new observations to existing entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
observations: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity to add the observations to" },
contents: {
type: "array",
items: { type: "string" },
description: "An array of observation contents to add"
},
},
required: ["entityName", "contents"],
},
},
},
required: ["observations"],
},
},
{
name: "delete_entities",
description: "Delete multiple entities and their associated relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
entityNames: {
type: "array",
items: { type: "string" },
description: "An array of entity names to delete"
},
},
required: ["entityNames"],
},
},
{
name: "delete_observations",
description: "Delete specific observations from entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
deletions: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity containing the observations" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observations to delete"
},
},
required: ["entityName", "observations"],
},
},
},
required: ["deletions"],
},
},
{
name: "delete_relations",
description: "Delete multiple relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
},
description: "An array of relations to delete"
},
},
required: ["relations"],
},
},
{
name: "read_graph",
description: "Read the entire knowledge graph",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "search_nodes",
description: "Search for nodes in the knowledge graph based on a query",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
},
required: ["query"],
},
},
{
name: "open_nodes",
description: "Open specific nodes in the knowledge graph by their names",
inputSchema: {
type: "object",
properties: {
names: {
type: "array",
items: { type: "string" },
description: "An array of entity names to retrieve",
},
},
required: ["names"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error(`No arguments provided for tool: ${name}`);
}
switch (name) {
case "create_entities":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.createEntities(args.entities as Entity[]), null, 2) }] };
case "create_relations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.createRelations(args.relations as Relation[]), null, 2) }] };
case "add_observations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] };
case "delete_entities":
await knowledgeGraphMemory.deleteEntities(args.entityNames as string[]);
return { content: [{ type: "text", text: "Entities deleted successfully" }] };
case "delete_observations":
await knowledgeGraphMemory.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]);
return { content: [{ type: "text", text: "Observations deleted successfully" }] };
case "delete_relations":
await knowledgeGraphMemory.deleteRelations(args.relations as Relation[]);
return { content: [{ type: "text", text: "Relations deleted successfully" }] };
case "read_graph":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.readGraph(), null, 2) }] };
case "search_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.searchNodes(args.query as string), null, 2) }] };
case "open_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphMemory.openNodes(args.names as string[]), null, 2) }] };
default:
throw new Error(`Unknown tool: ${name}`);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Knowledge Graph Memory using Neo4j running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```