Skip to content

Commit b24714b

Browse files
committed
Initial commit
1 parent 4426c8e commit b24714b

23 files changed

Lines changed: 1089 additions & 0 deletions

.github/workflows/ci-cd.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build-and-test:
11+
name: Build and Test
12+
runs-on: ubuntu-latest
13+
outputs:
14+
project_version: ${{ steps.get_version.outputs.version }}
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up JDK 21
20+
uses: actions/setup-java@v4
21+
with:
22+
java-version: '21'
23+
distribution: 'temurin'
24+
cache: 'maven'
25+
26+
- name: Extract Maven Version
27+
id: get_version
28+
run: |
29+
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
30+
echo "version=$VERSION" >> $GITHUB_OUTPUT
31+
echo "Detected project version: $VERSION"
32+
33+
- name: Build and Verify with Maven
34+
run: mvn clean verify
35+
36+
# Upload the built jar as an artifact to be used by the docker job
37+
# The artifact name includes the version for better traceability
38+
- name: Upload Artifact
39+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: memory-mcp-server-${{ steps.get_version.outputs.version }}
43+
path: target/*.jar
44+
retention-days: 1
45+
46+
docker-publish:
47+
name: Publish Docker Image
48+
needs: build-and-test
49+
runs-on: ubuntu-latest
50+
# Only run on push to main branch (after merge)
51+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
52+
53+
steps:
54+
- uses: actions/checkout@v4
55+
56+
- name: Download Artifact
57+
uses: actions/download-artifact@v4
58+
with:
59+
name: memory-mcp-server-${{ needs.build-and-test.outputs.project_version }}
60+
path: target
61+
62+
- name: Set up Docker Buildx
63+
uses: docker/setup-buildx-action@v3
64+
65+
- name: Login to Docker Hub
66+
uses: docker/login-action@v3
67+
with:
68+
username: ${{ secrets.DOCKERHUB_USERNAME }}
69+
password: ${{ secrets.DOCKERHUB_TOKEN }}
70+
71+
- name: Build and push
72+
uses: docker/build-push-action@v5
73+
with:
74+
context: .
75+
push: true
76+
# Push with multiple tags: latest, commit SHA, and the maven version
77+
tags: |
78+
${{ secrets.DOCKERHUB_USERNAME }}/memory-mcp-server:latest
79+
${{ secrets.DOCKERHUB_USERNAME }}/memory-mcp-server:${{ github.sha }}
80+
${{ secrets.DOCKERHUB_USERNAME }}/memory-mcp-server:${{ needs.build-and-test.outputs.project_version }}

Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Use a minimal JRE image
2+
FROM eclipse-temurin:21-jre-alpine
3+
4+
WORKDIR /app
5+
6+
# Create a non-root user for security
7+
RUN addgroup -S spring && adduser -S spring -G spring
8+
USER spring:spring
9+
10+
# Copy the jar file. The jar is expected to be built by the CI pipeline before this stage.
11+
COPY target/*.jar app.jar
12+
13+
# Expose the port (stdio transport doesn't technically use a port, but Neo4j/HTTP might)
14+
# Exposing 8080 just in case HTTP is enabled later
15+
EXPOSE 8080
16+
17+
# Configure the entrypoint
18+
ENTRYPOINT ["java", "-jar", "app.jar"]

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Memory MCP Server
2+
3+
A Java Spring Boot implementation of the Model Context Protocol (MCP) Memory Server, designed to be a drop-in replacement for the Anthropic reference implementation.
4+
5+
This project follows **Domain-Driven Design (DDD)** and **Hexagonal Architecture** (Ports and Adapters).
6+
7+
## Features
8+
9+
- **Knowledge Graph**: Stores entities and relations.
10+
- **MCP Tools**:
11+
- `create_entities`: Create new entities.
12+
- `create_relations`: Create relations between entities.
13+
- `read_graph`: Read the entire graph.
14+
- `search_nodes`: Search for entities.
15+
- `delete_entities`: Delete entities by name.
16+
- `delete_relations`: Delete relations.
17+
18+
## Architecture
19+
20+
- **Domain Layer (`com.example.memory.domain`)**: Contains the core business logic and entities (`Entity`, `Relation`). It defines the repository interface (`KnowledgeGraphRepository`).
21+
- **Application Layer (`com.example.memory.application`)**: Orchestrates the domain logic using services (`KnowledgeGraphService`).
22+
- **Infrastructure Layer (`com.example.memory.infrastructure`)**:
23+
- **Persistence**:
24+
- `InMemoryKnowledgeGraphRepository`: In-memory implementation (default).
25+
- `Neo4jKnowledgeGraphRepositoryAdapter`: Neo4j implementation (activates with `neo4j` profile).
26+
- **MCP**: Acts as the primary adapter, exposing tools via the `McpToolAdapter`.
27+
- **Config**: Configuration files.
28+
29+
## Prerequisites
30+
31+
- Java 21 or higher
32+
- Maven
33+
- Neo4j Database (if using `neo4j` profile without embedded setup)
34+
35+
## Building
36+
37+
```bash
38+
mvn clean package
39+
```
40+
41+
## Running
42+
43+
The server uses `stdio` transport by default.
44+
45+
### Default (In-Memory)
46+
```bash
47+
java -jar target/memory-mcp-server-0.0.1-SNAPSHOT.jar
48+
```
49+
50+
### With Neo4j (Embedded)
51+
This mode runs a Neo4j server inside the application process.
52+
53+
**Transient (Data lost on restart):**
54+
```bash
55+
java -Dspring.profiles.active=neo4j -Dspring.neo4j.uri=embedded -jar target/memory-mcp-server-0.0.1-SNAPSHOT.jar
56+
```
57+
58+
**Persistent (Data saved to file):**
59+
Set the `memory.neo4j.data-dir` property to a directory path.
60+
```bash
61+
java -Dspring.profiles.active=neo4j \
62+
-Dspring.neo4j.uri=embedded \
63+
-Dmemory.neo4j.data-dir=./neo4j-data \
64+
-jar target/memory-mcp-server-0.0.1-SNAPSHOT.jar
65+
```
66+
67+
### With Neo4j (External)
68+
Configure Neo4j connection details in `application.properties` or via environment variables, then run with the `neo4j` profile.
69+
70+
```bash
71+
export SPRING_NEO4J_URI=bolt://localhost:7687
72+
export SPRING_NEO4J_AUTHENTICATION_USERNAME=neo4j
73+
export SPRING_NEO4J_AUTHENTICATION_PASSWORD=secret
74+
java -Dspring.profiles.active=neo4j -jar target/memory-mcp-server-0.0.1-SNAPSHOT.jar
75+
```
76+
77+
## Configuration
78+
79+
Configuration is located in `src/main/resources/application.properties`.
80+
81+
```properties
82+
spring.ai.mcp.server.transport=stdio
83+
spring.ai.mcp.server.name=memory-server
84+
spring.ai.mcp.server.version=1.0.0
85+
```

pom.xml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>3.4.2</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.example</groupId>
12+
<artifactId>memory-mcp-server</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>memory-mcp-server</name>
15+
<description>MCP Memory Server using Spring AI</description>
16+
17+
<properties>
18+
<java.version>21</java.version>
19+
<spring-ai.version>1.1.2</spring-ai.version>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot-starter-web</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.springframework.ai</groupId>
29+
<artifactId>spring-ai-starter-mcp-server</artifactId>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>org.projectlombok</groupId>
34+
<artifactId>lombok</artifactId>
35+
<optional>true</optional>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-data-neo4j</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.neo4j.test</groupId>
43+
<artifactId>neo4j-harness</artifactId>
44+
<version>5.15.0</version>
45+
<exclusions>
46+
<exclusion>
47+
<groupId>org.slf4j</groupId>
48+
<artifactId>slf4j-nop</artifactId>
49+
</exclusion>
50+
</exclusions>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.springframework.boot</groupId>
54+
<artifactId>spring-boot-starter-test</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
</dependencies>
58+
59+
<dependencyManagement>
60+
<dependencies>
61+
<dependency>
62+
<groupId>org.springframework.ai</groupId>
63+
<artifactId>spring-ai-bom</artifactId>
64+
<version>${spring-ai.version}</version>
65+
<type>pom</type>
66+
<scope>import</scope>
67+
</dependency>
68+
</dependencies>
69+
</dependencyManagement>
70+
71+
<repositories>
72+
<repository>
73+
<id>spring-milestones</id>
74+
<name>Spring Milestones</name>
75+
<url>https://repo.spring.io/milestone</url>
76+
<snapshots>
77+
<enabled>false</enabled>
78+
</snapshots>
79+
</repository>
80+
</repositories>
81+
<pluginRepositories>
82+
<pluginRepository>
83+
<id>spring-milestones</id>
84+
<name>Spring Milestones</name>
85+
<url>https://repo.spring.io/milestone</url>
86+
<snapshots>
87+
<enabled>false</enabled>
88+
</snapshots>
89+
</pluginRepository>
90+
</pluginRepositories>
91+
92+
<build>
93+
<plugins>
94+
<plugin>
95+
<groupId>org.springframework.boot</groupId>
96+
<artifactId>spring-boot-maven-plugin</artifactId>
97+
<configuration>
98+
<excludes>
99+
<exclude>
100+
<groupId>org.projectlombok</groupId>
101+
<artifactId>lombok</artifactId>
102+
</exclude>
103+
</excludes>
104+
</configuration>
105+
</plugin>
106+
</plugins>
107+
</build>
108+
109+
</project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.example.memory;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class MemoryMcpServerApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(MemoryMcpServerApplication.class, args);
11+
}
12+
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.memory.application.service;
2+
3+
import com.example.memory.domain.model.Entity;
4+
import com.example.memory.domain.model.Relation;
5+
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public interface KnowledgeGraphService {
10+
List<Entity> createEntities(List<Entity> newEntities);
11+
List<Relation> createRelations(List<Relation> newRelations);
12+
Map<String, Object> readGraph();
13+
List<Entity> searchNodes(String query);
14+
void deleteEntities(List<String> names);
15+
void deleteRelations(List<Relation> relationsToDelete);
16+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.example.memory.application.service;
2+
3+
import com.example.memory.domain.model.Entity;
4+
import com.example.memory.domain.model.Relation;
5+
import com.example.memory.domain.repository.KnowledgeGraphRepository;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
@Service
13+
public class KnowledgeGraphServiceImpl implements KnowledgeGraphService {
14+
15+
private final KnowledgeGraphRepository repository;
16+
17+
public KnowledgeGraphServiceImpl(KnowledgeGraphRepository repository) {
18+
this.repository = repository;
19+
}
20+
21+
@Override
22+
public List<Entity> createEntities(List<Entity> newEntities) {
23+
List<Entity> created = new ArrayList<>();
24+
for (Entity e : newEntities) {
25+
created.add(repository.saveEntity(e));
26+
}
27+
return created;
28+
}
29+
30+
@Override
31+
public List<Relation> createRelations(List<Relation> newRelations) {
32+
for (Relation r : newRelations) {
33+
repository.saveRelation(r);
34+
}
35+
return newRelations;
36+
}
37+
38+
@Override
39+
public Map<String, Object> readGraph() {
40+
return repository.getGraph();
41+
}
42+
43+
@Override
44+
public List<Entity> searchNodes(String query) {
45+
return repository.searchEntities(query);
46+
}
47+
48+
@Override
49+
public void deleteEntities(List<String> names) {
50+
for (String name : names) {
51+
repository.deleteEntity(name);
52+
}
53+
}
54+
55+
@Override
56+
public void deleteRelations(List<Relation> relationsToDelete) {
57+
for (Relation r : relationsToDelete) {
58+
repository.deleteRelation(r);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)