Skip to content

Commit 9cb36dd

Browse files
Add MCP server (#145)
* Add MCP server * Add MCP to readme --------- Co-authored-by: Joseph Grogan <[email protected]>
1 parent e972e69 commit 9cb36dd

File tree

8 files changed

+148
-3
lines changed

8 files changed

+148
-3
lines changed

.github/workflows/integration-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v3
19-
- name: Set up JDK 11
19+
- name: Set up JDK 17
2020
uses: actions/setup-java@v3
2121
with:
22-
java-version: '11'
22+
java-version: '17'
2323
distribution: 'temurin'
2424
- name: Build
2525
run: make build

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
- name: Set up Java
1111
uses: actions/setup-java@v3
1212
with:
13-
java-version: '11'
13+
java-version: '17'
1414
distribution: 'adopt'
1515
- name: Validate Gradle wrapper
1616
uses: gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,22 @@ The CLI includes some additional commands. See `!intro`.
127127

128128
To use Hoptimator from Java code, or from anything that supports JDBC, use the `jdbc:hoptimator://` JDBC driver.
129129

130+
## The MCP Server
131+
132+
To use Hoptimator from an AI chat bot, agent, IDE, etc, you can use the Model Context Protocol Server. Just point your MCP configs at the server path:
133+
134+
```
135+
{
136+
"mcpServers": {
137+
"Hoptimator": {
138+
"command": "./hoptimator-mcp-server/start"
139+
}
140+
}
141+
```
142+
143+
You may need additional configuration, e.g. `JAVA_HOME`, depending on your environment.
144+
145+
130146
## The Operator
131147

132148
`hoptimator-operator` turns materialized views into real data pipelines. The name operator comes from the Kubernetes Operator pattern.

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ flink-table-planner = "org.apache.flink:flink-table-planner_2.12:1.18.1"
2020
flink-table-runtime = "org.apache.flink:flink-table-runtime:1.18.1"
2121
gson = "com.google.code.gson:gson:2.9.0"
2222
jackson = "com.fasterxml.jackson.core:jackson-core:2.15.0"
23+
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.15.0"
2324
jackson-dataformat-yaml = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.0"
2425
javax-annotation-api = "javax.annotation:javax.annotation-api:1.3.2"
2526
junit = "junit:junit:4.12"
2627
kafka-clients = "org.apache.kafka:kafka-clients:3.2.0"
2728
kubernetes-client = "io.kubernetes:client-java:18.0.0"
2829
kubernetes-extended-client = "io.kubernetes:client-java-extended:18.0.0"
30+
mcp-bom = "io.modelcontextprotocol.sdk:mcp-bom:0.10.0"
2931
slf4j-simple = "org.slf4j:slf4j-simple:2.0.11"
3032
slf4j-api = "org.slf4j:slf4j-api:2.0.11"
3133
sqlline = "sqlline:sqlline:1.12.0"

hoptimator-mcp-server/build.gradle

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
plugins {
2+
id 'java'
3+
id 'application'
4+
id 'idea'
5+
}
6+
7+
dependencies {
8+
implementation project(':hoptimator-jdbc-driver')
9+
implementation libs.slf4j.simple
10+
implementation project(':hoptimator-demodb')
11+
implementation project(':hoptimator-kafka')
12+
implementation project(':hoptimator-venice')
13+
14+
implementation libs.gson
15+
implementation libs.jackson.databind
16+
implementation platform(libs.mcp.bom)
17+
implementation 'io.modelcontextprotocol.sdk:mcp'
18+
}
19+
20+
application {
21+
mainClassName = 'com.linkedin.hoptimator.mcp.server.HoptimatorMcpServer'
22+
}
23+
24+
tasks.withType(JavaCompile).configureEach {
25+
options.release = 17
26+
options.compilerArgs << '-Xlint:deprecation'
27+
options.compilerArgs << '-Xlint:unchecked'
28+
}
29+
30+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.linkedin.hoptimator.mcp.server;
2+
3+
import java.sql.Connection;
4+
import java.sql.DriverManager;
5+
import java.sql.ResultSet;
6+
import java.sql.ResultSetMetaData;
7+
import java.sql.Statement;
8+
import java.sql.SQLException;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
import java.util.List;
13+
14+
import com.google.gson.Gson;
15+
import io.modelcontextprotocol.server.McpServer;
16+
import io.modelcontextprotocol.server.McpSyncServer;
17+
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
18+
19+
import static io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
20+
import static io.modelcontextprotocol.spec.McpSchema.CallToolResult;
21+
import static io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
22+
import static io.modelcontextprotocol.spec.McpSchema.Tool;
23+
24+
25+
public final class HoptimatorMcpServer {
26+
27+
private HoptimatorMcpServer() {
28+
}
29+
30+
public static void main(String[] args) throws Exception {
31+
Connection conn = DriverManager.getConnection("jdbc:hoptimator://fun=mysql");
32+
Gson gson = new Gson();
33+
34+
String sqlSchema = "{\"type\" : \"object\", \"id\" : \"urn:jsonschema:Sql\","
35+
+ "\"properties\" : {\"sql\" : {\"type\" : \"string\"}}}";
36+
StdioServerTransportProvider transportProvider = new StdioServerTransportProvider();
37+
SyncToolSpecification query = new SyncToolSpecification(
38+
new Tool("query", "SQL Query", sqlSchema), (x, args2) -> {
39+
try (Statement stmt = conn.createStatement()) {
40+
String sql = rewriteCommands((String) args2.get("sql"));
41+
ResultSet rs = stmt.executeQuery(sql);
42+
return new CallToolResult(gson.toJson(collect(rs)), false);
43+
} catch (Exception e) {
44+
return new CallToolResult("ERROR: " + e.toString(), true);
45+
}
46+
});
47+
48+
McpSyncServer server = McpServer.sync(transportProvider)
49+
.serverInfo("hoptimator", "0.0.0")
50+
.capabilities(ServerCapabilities.builder()
51+
.tools(true) // Enable tool support
52+
.build())
53+
.build();
54+
55+
server.addTool(query);
56+
while (true) {
57+
Thread.sleep(1000L);
58+
}
59+
}
60+
61+
private static String rewriteCommands(String sql) {
62+
if ("show tables".equalsIgnoreCase(sql)) {
63+
return "select * from \"metadata\".tables";
64+
}
65+
if ("show databases".equalsIgnoreCase(sql)) {
66+
return "select * from \"k8s\".databases";
67+
}
68+
if ("show pipelines".equalsIgnoreCase(sql)) {
69+
return "select * from \"k8s\".pipelines";
70+
}
71+
return sql;
72+
}
73+
74+
private static List<Map<String, String>> collect(ResultSet rs) throws SQLException {
75+
ResultSetMetaData meta = rs.getMetaData();
76+
int n = meta.getColumnCount();
77+
List<Map<String, String>> results = new ArrayList<>();
78+
while (rs.next()) {
79+
Map<String, String> row = new HashMap<>();
80+
for (int j = 1; j <= n; j++) {
81+
row.put(meta.getColumnName(j), rs.getString(j));
82+
}
83+
results.add(row);
84+
}
85+
return results;
86+
}
87+
}

hoptimator-mcp-server/start

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
3+
BASEDIR="$( cd "$( dirname "$0" )" && pwd )"
4+
5+
$BASEDIR/build/install/hoptimator-mcp-server/bin/hoptimator-mcp-server \
6+
-Dorg.slf4j.simpleLogger.showThreadName=false \
7+
-Dorg.slf4j.simpleLogger.showLogName=false \
8+
com.linkedin.hoptimator.mcp.server.HoptimatorMcpServer
9+

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ include 'hoptimator-jdbc-driver-int'
1313
include 'hoptimator-k8s'
1414
include 'hoptimator-kafka-controller'
1515
include 'hoptimator-kafka'
16+
include 'hoptimator-mcp-server'
1617
include 'hoptimator-models' // <-- marked for deletion
1718
include 'hoptimator-operator'
1819
include 'hoptimator-operator-integration'

0 commit comments

Comments
 (0)