Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e80d5f0
Spring AI samples
donald-pinckney Apr 6, 2026
de64249
Fix sample configs and remove runtimeOnly workarounds
donald-pinckney Apr 7, 2026
ddc7720
Update samples for T15: remove @DeterministicTool and sandboxing sample
donald-pinckney Apr 13, 2026
df3ec0e
Clean up Spring AI samples PR for merge
donald-pinckney Apr 20, 2026
7011199
Use mavenLocal for temporal-spring-ai instead of composite build
donald-pinckney Apr 20, 2026
7f4acba
Add TASK_QUEUE.json tracking remaining PR work
donald-pinckney Apr 20, 2026
c941ccc
Updated after changes to Spring AI
donald-pinckney Apr 27, 2026
fbae80a
Add snipsync markers to Spring AI samples
donald-pinckney Apr 28, 2026
5354c42
spring-ai samples: pin to released temporal-spring-ai 1.35.0
donald-pinckney May 1, 2026
bb4f3cf
Merge remote-tracking branch 'origin/main' into d/20260406-164121
donald-pinckney May 1, 2026
a3c05a6
Group spring-ai samples under a single springai/ directory
donald-pinckney May 1, 2026
79a9966
springai/mcp: await initialization in chat signal handler
donald-pinckney May 1, 2026
3a4e2ab
springai/multimodel: drop redundant default: CLI prefix
donald-pinckney May 1, 2026
73bb79a
springai/rag: show usage when add/search are typed without args
donald-pinckney May 1, 2026
1b47bd6
Add ai-sdk to CODEOWNERS for springai
donald-pinckney May 1, 2026
2e02881
springai/multimodel: include "think" in chat() javadoc model names
donald-pinckney May 1, 2026
1e9c367
springai/basic: explain why run()'s systemPrompt parameter is unused
donald-pinckney May 1, 2026
6b8f7ac
gradle/springai: document the Spring Boot plugin/BOM version skew
donald-pinckney May 1, 2026
00ea6f3
.gitignore: collapse per-module build/out entries into globs
donald-pinckney May 1, 2026
bd76c1a
Merge remote-tracking branch 'origin/main' into d/20260406-164121
donald-pinckney May 1, 2026
7d3c067
README: document Spring AI samples and Java 17 requirement
donald-pinckney May 1, 2026
273a2e2
springai samples: address Copilot review nits
donald-pinckney May 1, 2026
903fd2c
Untrack .vscode/ IDE settings
donald-pinckney May 1, 2026
5292b27
undo gitignore change
donald-pinckney May 1, 2026
5f91882
springai/multimodel: use LinkedHashMap for chatClients
donald-pinckney May 1, 2026
4d32397
springai/rag: capture lastResponse before signaling, not after
donald-pinckney May 1, 2026
c3a3c88
springai/multimodel: capture previous response before signaling
donald-pinckney May 1, 2026
c606d2a
Remove TASK_QUEUE.json
donald-pinckney May 1, 2026
24ba8c0
Update README.md
donald-pinckney May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ dependencies {
// Environment configuration
implementation "io.temporal:temporal-envconfig:$javaSDKVersion"

// Needed for SSL sample (AdvancedTlsX509KeyManager)
implementation "io.grpc:grpc-util"
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated

// Needed for SDK related functionality
implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.2"))
implementation "com.fasterxml.jackson.core:jackson-databind"
Expand Down
14 changes: 8 additions & 6 deletions core/src/main/java/io/temporal/samples/ssl/Starter.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ public static void main(String[] args) throws Exception {
if (refreshPeriod > 0) {
AdvancedTlsX509KeyManager clientKeyManager = new AdvancedTlsX509KeyManager();
// Reload credentials every minute
clientKeyManager.updateIdentityCredentialsFromFile(
clientKeyFile,
clientCertFile,
refreshPeriod,
TimeUnit.MINUTES,
Executors.newScheduledThreadPool(1));
@SuppressWarnings("InlineMeInliner")
var unused =
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated
clientKeyManager.updateIdentityCredentialsFromFile(
clientKeyFile,
clientCertFile,
refreshPeriod,
TimeUnit.MINUTES,
Executors.newScheduledThreadPool(1));
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated
sslContext =
GrpcSslContexts.configure(SslContextBuilder.forClient().keyManager(clientKeyManager))
.build();
Expand Down
48 changes: 48 additions & 0 deletions gradle/springai.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Shared configuration for all Spring AI sample modules.
// Applied via: apply from: "$rootDir/gradle/springai.gradle"

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

ext {
springBootVersionForSpringAi = '3.5.3'
springAiVersion = '1.1.0'
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

dependencyManagement {
imports {
mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersionForSpringAi"
mavenBom "org.springframework.ai:spring-ai-bom:$springAiVersion"
}
Comment thread
donald-pinckney marked this conversation as resolved.
}

dependencies {
// Temporal
implementation "io.temporal:temporal-spring-boot-starter:$javaSDKVersion"
implementation "io.temporal:temporal-spring-ai:$javaSDKVersion"

// Spring Boot
implementation 'org.springframework.boot:spring-boot-starter'

dependencies {
errorproneJavac('com.google.errorprone:javac:9+181-r4173-1')
errorprone('com.google.errorprone:error_prone_core:2.28.0')
}
}

bootJar {
enabled = false
}

jar {
enabled = true
}

bootRun {
standardInput = System.in
}
20 changes: 20 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
rootProject.name = 'temporal-java-samples'
include 'core'
include 'springai'
include 'springai-mcp'
include 'springai-multimodel'
include 'springai-rag'
include 'springai-sandboxing'
include 'springboot'
include 'springboot-basic'

// Include local sdk-java build for temporal-spring-ai (until published to Maven Central).
// temporal-spring-ai requires the plugin API (SimplePlugin) which is not yet in a released SDK,
// so we substitute all SDK modules from the local build.
includeBuild('../sdk-java') {
dependencySubstitution {
substitute module('io.temporal:temporal-spring-ai') using project(':temporal-spring-ai')
substitute module('io.temporal:temporal-sdk') using project(':temporal-sdk')
substitute module('io.temporal:temporal-serviceclient') using project(':temporal-serviceclient')
substitute module('io.temporal:temporal-spring-boot-autoconfigure') using project(':temporal-spring-boot-autoconfigure')
substitute module('io.temporal:temporal-spring-boot-starter') using project(':temporal-spring-boot-starter')
substitute module('io.temporal:temporal-testing') using project(':temporal-testing')
substitute module('io.temporal:temporal-opentracing') using project(':temporal-opentracing')
Comment thread
donald-pinckney marked this conversation as resolved.
Outdated
}
}
8 changes: 8 additions & 0 deletions springai-mcp/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apply from: "$rootDir/gradle/springai.gradle"

dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-openai'
implementation 'org.springframework.ai:spring-ai-starter-mcp-client'
implementation 'org.springframework.ai:spring-ai-rag'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.temporal.samples.springai.mcp;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import java.util.Scanner;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

/**
* Example application demonstrating MCP (Model Context Protocol) integration.
*
* <p>This application shows how to use tools from MCP servers within Temporal workflows. It
* connects to a filesystem MCP server and provides an AI assistant that can read and write files.
*
* <h2>Usage</h2>
*
* <pre>
* Commands:
* tools - List available MCP tools
* &lt;any message&gt; - Chat with the AI (it can use file tools)
* quit - End the chat
* </pre>
*
* <h2>Example Interactions</h2>
*
* <pre>
* &gt; List files in the current directory
* [AI uses list_directory tool and returns results]
*
* &gt; Create a file called hello.txt with "Hello from MCP!"
* [AI uses write_file tool]
*
* &gt; Read the contents of hello.txt
* [AI uses read_file tool]
* </pre>
*
* <h2>Prerequisites</h2>
*
* <ol>
* <li>Start a Temporal dev server: {@code temporal server start-dev}
* <li>Set OPENAI_API_KEY environment variable
* <li>Ensure Node.js/npx is available (for MCP server)
* <li>Optionally set MCP_ALLOWED_PATH (defaults to /tmp/mcp-example)
* <li>Run: {@code ./gradlew :example-mcp:bootRun}
* </ol>
*/
@SpringBootApplication
public class McpApplication {

private static final String TASK_QUEUE = "mcp-example-queue";

@Autowired private WorkflowClient workflowClient;

public static void main(String[] args) {
SpringApplication.run(McpApplication.class, args);
}

/** Runs after workers are started (ApplicationReadyEvent fires after CommandLineRunner). */
@EventListener(ApplicationReadyEvent.class)
public void onReady() throws Exception {
// Start a new workflow
String workflowId = "mcp-example-" + UUID.randomUUID().toString().substring(0, 8);
McpWorkflow workflow =
workflowClient.newWorkflowStub(
McpWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue(TASK_QUEUE)
.setWorkflowId(workflowId)
.build());

// Start the workflow asynchronously
WorkflowClient.start(workflow::run);

// Give the workflow time to initialize (first workflow task must complete)
Thread.sleep(1000);

System.out.println("\n=== MCP Tools Demo ===");
System.out.println("Workflow ID: " + workflowId);
System.out.println("\nThis demo uses the filesystem MCP server.");
System.out.println("The AI can read, write, and list files in the allowed directory.");
System.out.println("\nCommands:");
System.out.println(" tools - List available MCP tools");
System.out.println(" <text> - Chat with the AI");
System.out.println(" quit - End the chat");
System.out.println();

// Get a workflow stub for sending signals/queries
McpWorkflow workflowStub = workflowClient.newWorkflowStub(McpWorkflow.class, workflowId);

// Note: tools command may take a moment to work while workflow initializes
System.out.println("Type 'tools' to list available MCP tools.\n");

Scanner scanner = new Scanner(System.in, java.nio.charset.StandardCharsets.UTF_8);
while (true) {
System.out.print("> ");
String input = scanner.nextLine().trim();

if (input.equalsIgnoreCase("quit")) {
workflowStub.end();
System.out.println("Chat ended. Goodbye!");
break;
}

if (input.equalsIgnoreCase("tools")) {
System.out.println(workflowStub.listTools());
continue;
}

if (input.isEmpty()) {
continue;
}

System.out.println("[Processing...]");

// Capture current response BEFORE sending, so we can detect when it changes
String previousResponse = workflowStub.getLastResponse();

// Send the message via signal
workflowStub.chat(input);

// Poll until the response changes (workflow has processed our message)
for (int i = 0; i < 600; i++) { // Wait up to 60 seconds (MCP tools can be slow)
String response = workflowStub.getLastResponse();
if (!response.equals(previousResponse)) {
System.out.println("\n[AI]: " + response + "\n");
break;
}
Thread.sleep(100);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.temporal.samples.springai.mcp;

import io.temporal.workflow.QueryMethod;
import io.temporal.workflow.SignalMethod;
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;

/**
* Workflow interface demonstrating MCP (Model Context Protocol) integration.
*
* <p>This workflow shows how to use tools from MCP servers within Temporal workflows. The AI model
* can call MCP tools (like file system operations) as durable activities.
*/
@WorkflowInterface
public interface McpWorkflow {

/**
* Runs the workflow until ended.
*
* @return summary of the chat session
*/
@WorkflowMethod
String run();

/**
* Sends a message to the AI assistant with MCP tools available.
*
* @param message the user message
*/
@SignalMethod
void chat(String message);

/**
* Gets the last response from the AI.
*
* @return the last response
*/
@QueryMethod
String getLastResponse();

/**
* Lists the available MCP tools.
*
* @return list of available tools
*/
@QueryMethod
String listTools();

/** Ends the chat session. */
@SignalMethod
void end();
}
Loading
Loading