Skip to content

Commit 8b510ae

Browse files
committed
feat: server
1 parent 4e3e104 commit 8b510ae

File tree

6 files changed

+307
-0
lines changed

6 files changed

+307
-0
lines changed

mcp-server/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
HELP.md
2+
.gradle
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
!**/src/main/**/build/
6+
!**/src/test/**/build/
7+
src/main/resources/static/**
8+
### STS ###
9+
.apt_generated
10+
.classpath
11+
.factorypath
12+
.project
13+
.settings
14+
.springBeans
15+
.sts4-cache
16+
bin/
17+
!**/src/main/**/bin/
18+
!**/src/test/**/bin/
19+
20+
### IntelliJ IDEA ###
21+
.idea
22+
*.iws
23+
*.iml
24+
*.ipr
25+
out/
26+
!**/src/main/**/out/
27+
!**/src/test/**/out/
28+
29+
### NetBeans ###
30+
/nbproject/private/
31+
/nbbuild/
32+
/dist/
33+
/nbdist/
34+
/.nb-gradle/
35+
36+
### VS Code ###
37+
.vscode/

mcp-server/build.gradle

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright 2023 Sven Loesekann
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
plugins {
14+
id 'java'
15+
id 'org.springframework.boot' version '3.5.0'
16+
id 'io.spring.dependency-management' version '1.1.7'
17+
}
18+
19+
group = 'ch.xxx'
20+
version = '0.0.1-SNAPSHOT'
21+
22+
java {
23+
toolchain {
24+
languageVersion = JavaLanguageVersion.of(24)
25+
}
26+
}
27+
28+
repositories {
29+
mavenCentral()
30+
maven { url = 'https://repo.spring.io/snapshot' }
31+
}
32+
33+
dependencies {
34+
implementation platform("org.springframework.ai:spring-ai-bom:1.0.0")
35+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
36+
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
37+
implementation 'org.springframework.boot:spring-boot-starter-security'
38+
implementation 'org.springframework.boot:spring-boot-starter-web'
39+
implementation 'org.springframework.ai:spring-ai-starter-mcp-client'
40+
implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc'
41+
implementation 'org.springframework.ai:spring-ai-tika-document-reader'
42+
implementation 'org.liquibase:liquibase-core'
43+
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv'
44+
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
45+
implementation 'net.javacrumbs.shedlock:shedlock-spring:6.0.1'
46+
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:6.0.1'
47+
implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
48+
implementation 'org.springframework.ai:spring-ai-starter-model-transformers'
49+
implementation 'org.springframework.ai:spring-ai-starter-model-ollama'
50+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
51+
testImplementation 'org.springframework.security:spring-security-test'
52+
testImplementation 'com.tngtech.archunit:archunit-junit5:1.4.0'
53+
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
54+
}
55+
56+
bootJar {
57+
archiveFileName = 'aidocumentlibrarychat.jar'
58+
}
59+
60+
tasks.named('test') {
61+
useJUnitPlatform()
62+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import org.springframework.ai.tool.ToolCallbackProvider;
3+
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
4+
import org.springframework.boot.SpringApplication;
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.context.annotation.Bean;
7+
8+
import ch.xxx.aidoclibchat.adapter.config.FunctionConfig;
9+
10+
/*
11+
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
12+
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
13+
*/
14+
15+
/**
16+
*
17+
* @author sven
18+
*/
19+
@SpringBootApplication
20+
public class McpServerApplication {
21+
public static void main(String[] args) {
22+
SpringApplication.run(McpServerApplication.class, args);
23+
}
24+
25+
@Bean
26+
public ToolCallbackProvider myTools(FunctionConfig functionConfig) {
27+
return MethodToolCallbackProvider.builder().toolObjects(functionConfig).build();
28+
}
29+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright 2023 Sven Loesekann
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package ch.xxx.aidoclibchat.adapter.client;
14+
15+
import java.net.URLEncoder;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.List;
18+
import java.util.Optional;
19+
import java.util.function.Predicate;
20+
import java.util.stream.Collectors;
21+
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
import org.springframework.beans.factory.annotation.Value;
25+
import org.springframework.stereotype.Component;
26+
import org.springframework.web.client.RestClient;
27+
28+
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient;
29+
30+
@Component
31+
public class OpenLibraryRestClient implements OpenLibraryClient {
32+
private static final Logger LOGGER = LoggerFactory.getLogger(OpenLibraryRestClient.class);
33+
private final String baseUrl = "https://openlibrary.org/search.json";
34+
private final RestClient restClient;
35+
@Value("${openlibrary.result-size:5}")
36+
private int resultLimit;
37+
38+
public OpenLibraryRestClient(RestClient restClient) {
39+
this.restClient = restClient;
40+
}
41+
42+
@Override
43+
public Response apply(Request request) {
44+
var authorOpt = this.createParamOpt(request.author(), "author");
45+
var titleOpt = this.createParamOpt(request.title(), "title");
46+
var subjectOpt = this.createParamOpt(request.subject(), "subject");
47+
var paramsStr = List.of(authorOpt, titleOpt, subjectOpt).stream().flatMap(Optional::stream)
48+
.collect(Collectors.joining("&"));
49+
var urlStr = String.format("%s?%s&fields=*&limit=%d", this.baseUrl, paramsStr, this.resultLimit);
50+
LOGGER.info(urlStr);
51+
var response = this.restClient.get().uri(urlStr).retrieve().body(Response.class);
52+
return response;
53+
}
54+
55+
private Optional<String> createParamOpt(String valueStr, String keyStr) {
56+
return Optional.ofNullable(valueStr).stream().filter(Predicate.not(String::isBlank))
57+
.map(myAuthor -> String.format("%s=%s", keyStr, URLEncoder.encode(myAuthor, StandardCharsets.UTF_8)))
58+
.findFirst();
59+
}
60+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright 2023 Sven Loesekann
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package ch.xxx.aidoclibchat.adapter.client;
14+
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
import org.springframework.beans.factory.annotation.Value;
18+
import org.springframework.boot.context.event.ApplicationReadyEvent;
19+
import org.springframework.stereotype.Component;
20+
import org.springframework.web.client.RestClient;
21+
22+
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
25+
import ch.xxx.aidoclibchat.domain.client.TmdbClient;
26+
27+
@Component
28+
public class TmdbRestClient implements TmdbClient {
29+
private static final Logger LOG = LoggerFactory.getLogger(TmdbRestClient.class);
30+
private static final String BASE_URL = "https://api.themoviedb.org/3/";
31+
private final RestClient restClient;
32+
@Value("${tmdb.api.key:}")
33+
private String apiKey;
34+
35+
public TmdbRestClient(RestClient restClient) {
36+
this.restClient = restClient;
37+
}
38+
39+
//@EventListener
40+
public void onApplicationEvent(ApplicationReadyEvent event) {
41+
var request = new Request("Alien");
42+
var response = this.apply(request);
43+
LOG.info("TMDB Response: {}", toJson(response));
44+
}
45+
46+
@Override
47+
public Response apply(Request request) {
48+
var url = BASE_URL + "search/movie?query=" + request.query();
49+
var response = restClient.get()
50+
.uri(url)
51+
.header("accept", "application/json")
52+
.header("Authorization", "Bearer "+this.apiKey)
53+
.retrieve()
54+
.body(Response.class);
55+
//LOG.info("TMDB Response: {}", toJson(response));
56+
return response;
57+
}
58+
59+
private static String toJson(Object obj) {
60+
var result = "";
61+
try {
62+
result = new ObjectMapper().writeValueAsString(obj);
63+
} catch (JsonProcessingException e) {
64+
LOG.error("Error converting object to JSON", e);
65+
}
66+
return result;
67+
}
68+
69+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright 2023 Sven Loesekann
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package ch.xxx.aidoclibchat.adapter.config;
17+
18+
import java.util.function.Function;
19+
20+
import org.springframework.ai.tool.annotation.Tool;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
24+
import ch.xxx.aidoclibchat.domain.client.OpenLibraryClient;
25+
import ch.xxx.aidoclibchat.domain.client.TmdbClient;
26+
27+
@Configuration
28+
public class FunctionConfig {
29+
private final OpenLibraryClient openLibraryClient;
30+
private final TmdbClient tmdbClient;
31+
public static final String OPEN_LIBRARY_CLIENT = "openLibraryClient";
32+
public static final String THE_MOVIE_DATABASE_CLIENT = "theMovieDatabaseClient";
33+
34+
public FunctionConfig(OpenLibraryClient openLibraryClient, TmdbClient tmdbClient) {
35+
this.openLibraryClient = openLibraryClient;
36+
this.tmdbClient = tmdbClient;
37+
}
38+
39+
@Bean(OPEN_LIBRARY_CLIENT)
40+
@Tool(description = "Search for books by author, title or subject.")
41+
public Function<OpenLibraryClient.Request, OpenLibraryClient.Response> openLibraryClient() {
42+
return this.openLibraryClient::apply;
43+
}
44+
45+
@Bean(THE_MOVIE_DATABASE_CLIENT)
46+
@Tool(description = "Search for movies by title.")
47+
public Function<TmdbClient.Request, TmdbClient.Response> theMovieDatabaseClient() {
48+
return this.tmdbClient::apply;
49+
}
50+
}

0 commit comments

Comments
 (0)