-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLangChainMcpClient.java
More file actions
165 lines (149 loc) · 6.79 KB
/
LangChainMcpClient.java
File metadata and controls
165 lines (149 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/// usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 21+
//DEPS dev.langchain4j:langchain4j-mcp:1.10.0-beta18
//DEPS dev.langchain4j:langchain4j-google-ai-gemini:1.13.1
//DEPS dev.langchain4j:langchain4j-github-models:1.10.0-beta18
//DEPS org.slf4j:slf4j-simple:2.0.17
//FILES ./simplelogger.properties
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.http.StreamableHttpMcpTransport;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.service.AiServices;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.time.Duration;
import java.util.Arrays;
// Run with the following command
// GITHUB_TOKEN=ghp_YOUR_TOKEN jbang ./LangChainMcpClient.java
// Or with the following command to start the assistant integration example (you need a Kubernetes cluster .kube/context)
// GITHUB_TOKEN=ghp_YOUR_TOKEN jbang ./LangChainMcpClient.java --assistant
public final class LangChainMcpClient {
private static final String NPX = System.getProperty("os.name").toLowerCase().contains("win") ? "npx.cmd" : "npx";
private static McpClient initStdioClient(String... command) {
return new DefaultMcpClient.Builder()
// Optional client name to identify with the server, defaults to "langchain4j"
.clientName("blog.marcnuri.com")
// Optional MCP Protocol version, defaults to 2025-11-25
.protocolVersion("2025-11-25")
// Optional timeout for each individual tool execution, defaults to 60 seconds
.toolExecutionTimeout(Duration.ofSeconds(10))
// STDIO transport
.transport(new StdioMcpTransport.Builder()
// The command to execute the MCP server
.command(Arrays.asList(command))
// Optional, should the MCP server communication events be logged to the logger
// Check simplelogger.properties to enable output to the console
.logEvents(true)
.build())
.build();
}
private static McpClient initHttpClient(String httpUrl) {
return new DefaultMcpClient.Builder()
// Optional client name to identify with the server, defaults to "langchain4j"
.clientName("blog.marcnuri.com")
// Optional MCP Protocol version, defaults to 2025-11-25
.protocolVersion("2025-11-25")
// Optional timeout for each individual tool execution, defaults to 60 seconds
.toolExecutionTimeout(Duration.ofSeconds(10))
// Streamable HTTP transport (replaces deprecated SSE transport)
.transport(new StreamableHttpMcpTransport.Builder()
// The URL to connect to the MCP server
.url(httpUrl)
// Optional, should the MCP server requests be logged to the logger
// Check simplelogger.properties to enable output to the console
.logRequests(true)
// Optional, should the MCP server responses be logged to the logger
// Check simplelogger.properties to enable output to the console
.logResponses(true)
.build())
.build();
}
public static void main(String[] args) {
try {
checkRequirements();
System.out.println("Starting kubernetes-mcp-server in STDIO mode...");
try (var stdioClient = initStdioClient(NPX, "-y", "kubernetes-mcp-server@latest")) {
System.out.println("Available tools:");
stdioClient.listTools().stream()
.map(t -> " - " + t.name())
.forEach(System.out::println);
if (args.length > 0 && args[0].equals("--assistant")) {
final var assistant = assistantIntegrationExample(stdioClient);
System.out.println(assistant.chat("Run a Pod with the image marcnuri/chuck-norris and expose port 8080"));
System.out.println(assistant.chat("List the Pods running in my cluster as a markdown table"));
}
}
System.out.println("Starting kubernetes-mcp-server in HTTP mode...");
// Start the MCP server in a separate process
final var process = new ProcessBuilder(NPX, "-y", "kubernetes-mcp-server@latest", "--port=8080")
.inheritIO()
.start();
waitForPort("localhost", 8080, Duration.ofSeconds(10));
try (var httpClient = initHttpClient("http://localhost:8080/mcp")) {
System.out.println("Available tools:");
httpClient.listTools().stream()
.map(t -> " - " + t.name())
.forEach(System.out::println);
} finally {
killProcess(ProcessHandle.of(process.pid()).orElseThrow());
}
} catch (Exception e) {
System.err.println("LangChain MCP Client failed: " + e.getMessage());
}
}
private interface Assistant {
String chat(String userMessage);
}
private static Assistant assistantIntegrationExample(McpClient client) {
return AiServices.builder(Assistant.class)
// A bug in Google's API server prevents the use of the GoogleAiGeminiChatModel with tools
// --* GenerateContentRequest.tools[0].function_declarations[0].parameters.properties[params].properties: should be non-empty for OBJECT type--
.chatModel(GoogleAiGeminiChatModel.builder()
.apiKey(System.getenv("GOOGLE_API_KEY"))
.modelName("gemini-2.5-flash")
.build())
// .chatModel(GitHubModelsChatModel.builder()
// .gitHubToken(System.getenv("GITHUB_TOKEN"))
// .modelName("gpt-4o-mini")
// .build())
.toolProvider(McpToolProvider.builder().mcpClients(client).build())
.build();
}
private static void checkRequirements() {
// Check if npx is available by running npx --version
try {
new ProcessBuilder(NPX, "--version").start().waitFor();
} catch (Exception e) {
throw new RuntimeException("npx is required to run the LangChain MCP server");
}
}
private static void waitForPort(String host, int port, Duration timeout) throws InterruptedException {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < timeout.toMillis()) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), 1000);
return;
} catch (Exception e) {
Thread.sleep(100);
}
}
throw new RuntimeException("Timeout waiting for port " + port);
}
private static void killProcess(ProcessHandle process) {
process.children().forEach(LangChainMcpClient::killProcess);
process.destroyForcibly();
try {
if (System.getProperty("os.name").toLowerCase().contains("win")) {
Runtime.getRuntime().exec(new String[]{"taskkill.exe", "/T", "/F", "/PID", "" + process.pid()});
} else {
Runtime.getRuntime().exec(new String[]{"kill", "-9 ", "" + process.pid()});
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}