Skip to content

Commit e80d5f0

Browse files
Spring AI samples
1 parent d34d613 commit e80d5f0

File tree

67 files changed

+2299
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2299
-6
lines changed

core/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ dependencies {
77
// Environment configuration
88
implementation "io.temporal:temporal-envconfig:$javaSDKVersion"
99

10+
// Needed for SSL sample (AdvancedTlsX509KeyManager)
11+
implementation "io.grpc:grpc-util"
12+
1013
// Needed for SDK related functionality
1114
implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.2"))
1215
implementation "com.fasterxml.jackson.core:jackson-databind"

core/src/main/java/io/temporal/samples/ssl/Starter.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ public static void main(String[] args) throws Exception {
5050
if (refreshPeriod > 0) {
5151
AdvancedTlsX509KeyManager clientKeyManager = new AdvancedTlsX509KeyManager();
5252
// Reload credentials every minute
53-
clientKeyManager.updateIdentityCredentialsFromFile(
54-
clientKeyFile,
55-
clientCertFile,
56-
refreshPeriod,
57-
TimeUnit.MINUTES,
58-
Executors.newScheduledThreadPool(1));
53+
@SuppressWarnings("InlineMeInliner")
54+
var unused =
55+
clientKeyManager.updateIdentityCredentialsFromFile(
56+
clientKeyFile,
57+
clientCertFile,
58+
refreshPeriod,
59+
TimeUnit.MINUTES,
60+
Executors.newScheduledThreadPool(1));
5961
sslContext =
6062
GrpcSslContexts.configure(SslContextBuilder.forClient().keyManager(clientKeyManager))
6163
.build();

gradle/springai.gradle

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Shared configuration for all Spring AI sample modules.
2+
// Applied via: apply from: "$rootDir/gradle/springai.gradle"
3+
4+
apply plugin: 'org.springframework.boot'
5+
apply plugin: 'io.spring.dependency-management'
6+
7+
ext {
8+
springBootVersionForSpringAi = '3.5.3'
9+
springAiVersion = '1.1.0'
10+
}
11+
12+
java {
13+
sourceCompatibility = JavaVersion.VERSION_17
14+
targetCompatibility = JavaVersion.VERSION_17
15+
}
16+
17+
dependencyManagement {
18+
imports {
19+
mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersionForSpringAi"
20+
mavenBom "org.springframework.ai:spring-ai-bom:$springAiVersion"
21+
}
22+
}
23+
24+
dependencies {
25+
// Temporal
26+
implementation "io.temporal:temporal-spring-boot-starter:$javaSDKVersion"
27+
implementation "io.temporal:temporal-spring-ai:$javaSDKVersion"
28+
29+
// Spring Boot
30+
implementation 'org.springframework.boot:spring-boot-starter'
31+
32+
// Required at runtime: the SpringAiPlugin introspects for VectorStore/MCP support
33+
runtimeOnly 'org.springframework.ai:spring-ai-rag'
34+
runtimeOnly 'org.springframework.ai:spring-ai-mcp'
35+
36+
dependencies {
37+
errorproneJavac('com.google.errorprone:javac:9+181-r4173-1')
38+
errorprone('com.google.errorprone:error_prone_core:2.28.0')
39+
}
40+
}
41+
42+
bootJar {
43+
enabled = false
44+
}
45+
46+
jar {
47+
enabled = true
48+
}
49+
50+
bootRun {
51+
standardInput = System.in
52+
}

settings.gradle

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
rootProject.name = 'temporal-java-samples'
22
include 'core'
3+
include 'springai'
4+
include 'springai-mcp'
5+
include 'springai-multimodel'
6+
include 'springai-rag'
7+
include 'springai-sandboxing'
38
include 'springboot'
49
include 'springboot-basic'
10+
11+
// Include local sdk-java build for temporal-spring-ai (until published to Maven Central).
12+
// temporal-spring-ai requires the plugin API (SimplePlugin) which is not yet in a released SDK,
13+
// so we substitute all SDK modules from the local build.
14+
includeBuild('../sdk-java') {
15+
dependencySubstitution {
16+
substitute module('io.temporal:temporal-spring-ai') using project(':temporal-spring-ai')
17+
substitute module('io.temporal:temporal-sdk') using project(':temporal-sdk')
18+
substitute module('io.temporal:temporal-serviceclient') using project(':temporal-serviceclient')
19+
substitute module('io.temporal:temporal-spring-boot-autoconfigure') using project(':temporal-spring-boot-autoconfigure')
20+
substitute module('io.temporal:temporal-spring-boot-starter') using project(':temporal-spring-boot-starter')
21+
substitute module('io.temporal:temporal-testing') using project(':temporal-testing')
22+
substitute module('io.temporal:temporal-opentracing') using project(':temporal-opentracing')
23+
}
24+
}

springai-mcp/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apply from: "$rootDir/gradle/springai.gradle"
2+
3+
dependencies {
4+
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
5+
implementation 'org.springframework.ai:spring-ai-starter-mcp-client'
6+
implementation 'org.springframework.ai:spring-ai-rag'
7+
implementation 'org.springframework.boot:spring-boot-starter-webflux'
8+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package io.temporal.samples.springai.mcp;
2+
3+
import io.temporal.client.WorkflowClient;
4+
import io.temporal.client.WorkflowOptions;
5+
import java.util.Scanner;
6+
import java.util.UUID;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.boot.SpringApplication;
9+
import org.springframework.boot.autoconfigure.SpringBootApplication;
10+
import org.springframework.boot.context.event.ApplicationReadyEvent;
11+
import org.springframework.context.event.EventListener;
12+
13+
/**
14+
* Example application demonstrating MCP (Model Context Protocol) integration.
15+
*
16+
* <p>This application shows how to use tools from MCP servers within Temporal workflows. It
17+
* connects to a filesystem MCP server and provides an AI assistant that can read and write files.
18+
*
19+
* <h2>Usage</h2>
20+
*
21+
* <pre>
22+
* Commands:
23+
* tools - List available MCP tools
24+
* &lt;any message&gt; - Chat with the AI (it can use file tools)
25+
* quit - End the chat
26+
* </pre>
27+
*
28+
* <h2>Example Interactions</h2>
29+
*
30+
* <pre>
31+
* &gt; List files in the current directory
32+
* [AI uses list_directory tool and returns results]
33+
*
34+
* &gt; Create a file called hello.txt with "Hello from MCP!"
35+
* [AI uses write_file tool]
36+
*
37+
* &gt; Read the contents of hello.txt
38+
* [AI uses read_file tool]
39+
* </pre>
40+
*
41+
* <h2>Prerequisites</h2>
42+
*
43+
* <ol>
44+
* <li>Start a Temporal dev server: {@code temporal server start-dev}
45+
* <li>Set OPENAI_API_KEY environment variable
46+
* <li>Ensure Node.js/npx is available (for MCP server)
47+
* <li>Optionally set MCP_ALLOWED_PATH (defaults to /tmp/mcp-example)
48+
* <li>Run: {@code ./gradlew :example-mcp:bootRun}
49+
* </ol>
50+
*/
51+
@SpringBootApplication
52+
public class McpApplication {
53+
54+
private static final String TASK_QUEUE = "mcp-example-queue";
55+
56+
@Autowired private WorkflowClient workflowClient;
57+
58+
public static void main(String[] args) {
59+
SpringApplication.run(McpApplication.class, args);
60+
}
61+
62+
/** Runs after workers are started (ApplicationReadyEvent fires after CommandLineRunner). */
63+
@EventListener(ApplicationReadyEvent.class)
64+
public void onReady() throws Exception {
65+
// Start a new workflow
66+
String workflowId = "mcp-example-" + UUID.randomUUID().toString().substring(0, 8);
67+
McpWorkflow workflow =
68+
workflowClient.newWorkflowStub(
69+
McpWorkflow.class,
70+
WorkflowOptions.newBuilder()
71+
.setTaskQueue(TASK_QUEUE)
72+
.setWorkflowId(workflowId)
73+
.build());
74+
75+
// Start the workflow asynchronously
76+
WorkflowClient.start(workflow::run);
77+
78+
// Give the workflow time to initialize (first workflow task must complete)
79+
Thread.sleep(1000);
80+
81+
System.out.println("\n=== MCP Tools Demo ===");
82+
System.out.println("Workflow ID: " + workflowId);
83+
System.out.println("\nThis demo uses the filesystem MCP server.");
84+
System.out.println("The AI can read, write, and list files in the allowed directory.");
85+
System.out.println("\nCommands:");
86+
System.out.println(" tools - List available MCP tools");
87+
System.out.println(" <text> - Chat with the AI");
88+
System.out.println(" quit - End the chat");
89+
System.out.println();
90+
91+
// Get a workflow stub for sending signals/queries
92+
McpWorkflow workflowStub = workflowClient.newWorkflowStub(McpWorkflow.class, workflowId);
93+
94+
// Note: tools command may take a moment to work while workflow initializes
95+
System.out.println("Type 'tools' to list available MCP tools.\n");
96+
97+
Scanner scanner = new Scanner(System.in, java.nio.charset.StandardCharsets.UTF_8);
98+
while (true) {
99+
System.out.print("> ");
100+
String input = scanner.nextLine().trim();
101+
102+
if (input.equalsIgnoreCase("quit")) {
103+
workflowStub.end();
104+
System.out.println("Chat ended. Goodbye!");
105+
break;
106+
}
107+
108+
if (input.equalsIgnoreCase("tools")) {
109+
System.out.println(workflowStub.listTools());
110+
continue;
111+
}
112+
113+
if (input.isEmpty()) {
114+
continue;
115+
}
116+
117+
System.out.println("[Processing...]");
118+
119+
// Capture current response BEFORE sending, so we can detect when it changes
120+
String previousResponse = workflowStub.getLastResponse();
121+
122+
// Send the message via signal
123+
workflowStub.chat(input);
124+
125+
// Poll until the response changes (workflow has processed our message)
126+
for (int i = 0; i < 600; i++) { // Wait up to 60 seconds (MCP tools can be slow)
127+
String response = workflowStub.getLastResponse();
128+
if (!response.equals(previousResponse)) {
129+
System.out.println("\n[AI]: " + response + "\n");
130+
break;
131+
}
132+
Thread.sleep(100);
133+
}
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)