From ca5bfcf57f0db517a13f6fbbd6e7ed73f6371613 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 27 Feb 2026 23:40:59 -0500 Subject: [PATCH 1/2] Simplify and cleanup examples --- .../java/dev/arcade/example/AuthExample.java | 10 +-- .../arcade/example/PlaySpotifyExample.java | 31 ++++----- .../example/springai/SpringAIExample.java | 68 ++++++------------- .../example/springboot/SpringBootExample.java | 30 ++++---- 4 files changed, 55 insertions(+), 84 deletions(-) diff --git a/arcade-java-example/src/main/java/dev/arcade/example/AuthExample.java b/arcade-java-example/src/main/java/dev/arcade/example/AuthExample.java index 3511755..acf0d59 100644 --- a/arcade-java-example/src/main/java/dev/arcade/example/AuthExample.java +++ b/arcade-java-example/src/main/java/dev/arcade/example/AuthExample.java @@ -7,17 +7,13 @@ public class AuthExample { - /** - * - * @param args - */ public static void main(String[] args) { // As the developer, you must identify the user you're authorizing // and pass a unique identifier for them (e.g. an email or user ID) to Arcade: String userId = System.getenv("ARCADE_USER_ID"); if (userId == null) { - throw new IllegalArgumentException("Missing environment variable USER_ID"); + throw new IllegalArgumentException("Missing environment variable ARCADE_USER_ID"); } ArcadeClient client = ArcadeOkHttpClient.builder().fromEnv().build(); @@ -29,8 +25,8 @@ public static void main(String[] args) { authResponse .status() .filter(status -> status != AuthorizationResponse.Status.COMPLETED) - .ifPresent(status -> System.out.println( - "Click this link to authorize: " + authResponse.url().get())); + .flatMap(status -> authResponse.url()) + .ifPresent(url -> System.out.println("Click this link to authorize: " + url)); // if the authorization is NOT complete, you can wait using the following method (for CLI applications): client.auth().waitForCompletion(authResponse); diff --git a/arcade-java-example/src/main/java/dev/arcade/example/PlaySpotifyExample.java b/arcade-java-example/src/main/java/dev/arcade/example/PlaySpotifyExample.java index 1568965..8433ecb 100644 --- a/arcade-java-example/src/main/java/dev/arcade/example/PlaySpotifyExample.java +++ b/arcade-java-example/src/main/java/dev/arcade/example/PlaySpotifyExample.java @@ -4,19 +4,20 @@ import dev.arcade.client.okhttp.ArcadeOkHttpClient; import dev.arcade.models.tools.ExecuteToolRequest; import dev.arcade.models.tools.ExecuteToolResponse; -import dev.arcade.models.tools.ToolExecuteParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Example of calling a tool using the Arcade Java SDK. + *

+ * Both the {@code ARCADE_USER_ID} and {@code ARCADE_API_KEY} environment variables must be set. + * See the Getting Your API Key guide to create an API Key. + * Your username can be found in the lower left corner of your Arcade console. */ public class PlaySpotifyExample { - /** - * Executes the Spotify.ResumePlayback, both the ARCADE_USER_ID and ARCADE_API_KEY environment variables must be set. - * See the Getting Your API Key guide to create an API Key. - * Your username can be found in the lower left corner of your Arcade console. - * @param args Not used. - */ + private static final Logger logger = LoggerFactory.getLogger(PlaySpotifyExample.class); + public static void main(String[] args) { String userId = System.getenv("ARCADE_USER_ID"); // the Spotify tool requires a userId @@ -27,17 +28,15 @@ public static void main(String[] args) { // Configures using the `ARCADE_API_KEY` environment variable ArcadeClient client = ArcadeOkHttpClient.fromEnv(); - ToolExecuteParams params = ToolExecuteParams.builder() - .executeToolRequest(ExecuteToolRequest.builder() + ExecuteToolResponse response = client.tools() + .execute(ExecuteToolRequest.builder() .toolName("Spotify.ResumePlayback@1.0.2") .userId(userId) - .build()) - .build(); - ExecuteToolResponse executeToolResponse = client.tools().execute(params); - executeToolResponse - .output() + .build()); + + response.output() .ifPresentOrElse( - output -> System.out.println("Tool output: " + output._value()), - () -> System.out.println("No output for this tool")); + output -> logger.info("Tool output: {}", output._value()), + () -> logger.info("No output for this tool")); } } diff --git a/arcade-java-example/src/main/java/dev/arcade/example/springai/SpringAIExample.java b/arcade-java-example/src/main/java/dev/arcade/example/springai/SpringAIExample.java index edac092..2b4a6b7 100644 --- a/arcade-java-example/src/main/java/dev/arcade/example/springai/SpringAIExample.java +++ b/arcade-java-example/src/main/java/dev/arcade/example/springai/SpringAIExample.java @@ -1,13 +1,11 @@ package dev.arcade.example.springai; import dev.arcade.client.ArcadeClient; -import dev.arcade.core.JsonValue; import dev.arcade.models.AuthorizationResponse; import dev.arcade.models.tools.AuthorizeToolRequest; import dev.arcade.models.tools.ExecuteToolRequest; import dev.arcade.models.tools.ExecuteToolResponse; -import dev.arcade.models.tools.ToolAuthorizeParams; -import dev.arcade.models.tools.ToolExecuteParams; +import java.util.Map; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +31,7 @@ public class SpringAIExample { Error Handling: If a song fails to play or the tool returns an error, check the state again to see if the player is disconnected or if the track simply wasn't found. Tone: Be concise, upbeat, and music-focused. Chain of Thought: When a user asks for a song, your internal logic should be: Check State -> Inform User -> Execute Play. - """; + """; public static void main(String[] args) { SpringApplication.run(SpringAIExample.class, args); @@ -80,13 +78,7 @@ public static class ArcadeToolProvider { name = "play_song", description = "Plays a song by an artist and queues four more songs by the same artist") String play(@ToolParam(description = "The name of the artist to play") String name) { - return executeTool( - "Spotify.PlayArtistByName", - ExecuteToolRequest.Input.builder() - - // map the above input as "additional Properties" - .putAdditionalProperty("name", JsonValue.from(name)) - .build()); + return executeTool("Spotify.PlayArtistByName", Map.of("name", name)); } /** @@ -103,30 +95,25 @@ String play(@ToolParam(description = "The name of the artist to play") String na This tool does not perform any actions. Use other tools to control playback. """) String playbackState() { - return executeTool( - "Spotify.GetPlaybackState", - ExecuteToolRequest.Input.builder().build()); // this tool has no inputs + return executeTool("Spotify.GetPlaybackState", Map.of()); } /** - * Executes the specified tool with the provided input. + * Executes the specified tool with the provided input. Handles authorization and errors. * * @param toolName the name of the tool to be executed * @param input the input parameters required for tool execution * @return the result of the tool execution as a string; the result may include * the output of the tool, an error message, or an authorization requirement */ - private String executeTool(String toolName, ExecuteToolRequest.Input input) { + private String executeTool(String toolName, Map input) { log.debug("Executing tool {}, with input: {}", toolName, input); try { - // call the tool ExecuteToolResponse response = client.tools() - .execute(ToolExecuteParams.builder() - .executeToolRequest(ExecuteToolRequest.builder() - .toolName(toolName) - .userId(userId) - .input(input) - .build()) + .execute(ExecuteToolRequest.builder() + .toolName(toolName) + .userId(userId) + .input(input) .build()); log.debug( @@ -138,8 +125,7 @@ private String executeTool(String toolName, ExecuteToolRequest.Input input) { if (response.success().orElse(false)) { String result = response.output().map(o -> o._value().toString()).orElse("{}"); - - log.debug("Tool {} returned", result); + log.debug("Tool {} returned: {}", toolName, result); return result; } @@ -151,8 +137,8 @@ private String executeTool(String toolName, ExecuteToolRequest.Input input) { if (errorMessage.contains("authorization required")) { AuthorizationResult auth = requestAuthorization(toolName); - if (auth.url() != null) { - log.debug("Tool requires {} authorization, open a browser to {}", toolName, auth.url()); + if (auth.requiresAction()) { + log.debug("Tool {} requires authorization, open a browser to {}", toolName, auth.url()); return String.format( "The '%s' tool requires authorization, open a browser to %s to continue.", toolName, auth.url()); @@ -167,8 +153,8 @@ private String executeTool(String toolName, ExecuteToolRequest.Input input) { String message = e.getMessage(); if (message != null && message.contains("authorization")) { AuthorizationResult auth = requestAuthorization(toolName); - if (auth.url() != null) { - log.debug("Tool requires {} authorization, open a browser to {}", toolName, auth.url()); + if (auth.requiresAction()) { + log.debug("Tool {} requires authorization, open a browser to {}", toolName, auth.url()); return String.format( "The '%s' tool requires authorization, open a browser to %s to continue.", toolName, auth.url()); @@ -184,19 +170,15 @@ private String executeTool(String toolName, ExecuteToolRequest.Input input) { */ public AuthorizationResult requestAuthorization(String toolName) { try { - AuthorizeToolRequest authRequest = AuthorizeToolRequest.builder() - .toolName(toolName) - .userId(userId) - .build(); - AuthorizationResponse response = client.tools() - .authorize(ToolAuthorizeParams.builder() - .authorizeToolRequest(authRequest) + .authorize(AuthorizeToolRequest.builder() + .toolName(toolName) + .userId(userId) .build()); Optional url = response.url(); - Optional status = response.status(); - String statusValue = status.map(s -> s.value().name()).orElse("unknown"); + String statusValue = + response.status().map(s -> s.value().name()).orElse("unknown"); if ("PENDING".equalsIgnoreCase(statusValue) && url.isPresent()) { return new AuthorizationResult(toolName, url.get(), statusValue); @@ -213,15 +195,5 @@ public boolean requiresAction() { return url != null && "PENDING".equalsIgnoreCase(status); } } - - public enum ResultType { - album, - artist, - playlist, - track, - show, - episode, - audiobook - } } } diff --git a/arcade-java-example/src/main/java/dev/arcade/example/springboot/SpringBootExample.java b/arcade-java-example/src/main/java/dev/arcade/example/springboot/SpringBootExample.java index 580e0d4..61b7bd6 100644 --- a/arcade-java-example/src/main/java/dev/arcade/example/springboot/SpringBootExample.java +++ b/arcade-java-example/src/main/java/dev/arcade/example/springboot/SpringBootExample.java @@ -3,18 +3,24 @@ import dev.arcade.client.ArcadeClient; import dev.arcade.models.tools.ExecuteToolRequest; import dev.arcade.models.tools.ExecuteToolResponse; -import dev.arcade.models.tools.ToolExecuteParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; /** - * Example of calling a tool using the Arcade Java SDK. + * Example of calling a tool using the Arcade Java SDK with Spring Boot. + *

+ * The {@code ArcadeClient} bean is auto-configured when the {@code ARCADE_API_KEY} environment variable + * (or equivalent {@code application.properties} value) is set. */ @SpringBootApplication public class SpringBootExample { + private static final Logger logger = LoggerFactory.getLogger(SpringBootExample.class); + /** * Starts the Spring Boot application. * @param args All args are passed into the SpringApplication @@ -31,23 +37,21 @@ public static void main(String[] args) { @Bean ApplicationRunner appRunner(ArcadeClient client) { return args -> { - String userId = System.getenv("ARCADE_USER_ID"); // the Spotify tool require a userId + String userId = System.getenv("ARCADE_USER_ID"); // the Spotify tool requires a userId if (userId == null) { throw new IllegalArgumentException("Missing ARCADE_USER_ID environment variable"); } - ToolExecuteParams params = ToolExecuteParams.builder() - .executeToolRequest(ExecuteToolRequest.builder() - .toolName("Spotify.ResumePlayback@1.0.2") + ExecuteToolResponse response = client.tools() + .execute(ExecuteToolRequest.builder() + .toolName("Spotify.ResumePlayback") .userId(userId) - .build()) - .build(); - ExecuteToolResponse executeToolResponse = client.tools().execute(params); - executeToolResponse - .output() + .build()); + + response.output() .ifPresentOrElse( - output -> System.out.println("Tool output: " + output._value()), - () -> System.out.println("No output for this tool")); + output -> logger.info("Tool output: {}", output._value()), + () -> logger.info("No output for this tool")); }; } } From 68380478d573afa7d31f081de95de50f6b8bda17 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 27 Feb 2026 23:41:35 -0500 Subject: [PATCH 2/2] Add examples from documentation --- arcade-java-example/build.gradle.kts | 6 + .../java/dev/arcade/example/GmailExample.java | 65 +++++++++ .../dev/arcade/example/QuickStartExample.java | 126 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 arcade-java-example/src/main/java/dev/arcade/example/GmailExample.java create mode 100644 arcade-java-example/src/main/java/dev/arcade/example/QuickStartExample.java diff --git a/arcade-java-example/build.gradle.kts b/arcade-java-example/build.gradle.kts index de456c8..c5e56bc 100644 --- a/arcade-java-example/build.gradle.kts +++ b/arcade-java-example/build.gradle.kts @@ -18,6 +18,12 @@ dependencies { // only needed for SpringAIExample implementation("org.springframework.ai:spring-ai-starter-model-openai") implementation("org.apache.httpcomponents.client5:httpclient5:5.6") + + // Only needed for GmailExample + implementation("com.google.api-client:google-api-client:2.9.0") + implementation("com.google.oauth-client:google-oauth-client-jetty:1.39.0") + implementation("com.google.apis:google-api-services-gmail:v1-rev20260112-2.0.0") + } // only needed for SpringAIExample diff --git a/arcade-java-example/src/main/java/dev/arcade/example/GmailExample.java b/arcade-java-example/src/main/java/dev/arcade/example/GmailExample.java new file mode 100644 index 0000000..b4120ab --- /dev/null +++ b/arcade-java-example/src/main/java/dev/arcade/example/GmailExample.java @@ -0,0 +1,65 @@ +package dev.arcade.example; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.gmail.Gmail; +import com.google.api.services.gmail.model.Message; +import dev.arcade.client.ArcadeClient; +import dev.arcade.client.okhttp.ArcadeOkHttpClient; +import dev.arcade.models.AuthorizationContext; +import dev.arcade.models.AuthorizationResponse; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GmailExample { + + private static final Logger logger = LoggerFactory.getLogger(GmailExample.class); + + public static void main(String[] args) throws Exception { + + // As the developer, you must identify the user you're authorizing + // and pass a unique identifier for them (e.g. an email or user ID) to Arcade: + String userId = System.getenv("ARCADE_USER_ID"); + if (userId == null) { + throw new IllegalArgumentException("Missing environment variable ARCADE_USER_ID"); + } + + ArcadeClient client = ArcadeOkHttpClient.fromEnv(); + + // get the auth service and call start + AuthorizationResponse authResponse = client.auth() + .start(userId, "google", "oauth2", List.of("https://www.googleapis.com/auth/gmail.readonly")); + + // check the response status + authResponse + .status() + .filter(status -> status != AuthorizationResponse.Status.COMPLETED) + .flatMap(status -> authResponse.url()) + .ifPresent(url -> logger.info("Click this link to authorize: {}", url)); + + // if the authorization is NOT complete, you can wait using the following method (for CLI applications): + AuthorizationResponse completedAuthResponse = client.auth().waitForCompletion(authResponse); + + // Use the credential + String token = completedAuthResponse + .context() + .flatMap(AuthorizationContext::token) + .get(); + + JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); + HttpTransport httpTransport = new NetHttpTransport(); + GoogleCredential credential = new GoogleCredential().setAccessToken(token); + + Gmail gmail = new Gmail.Builder(httpTransport, jsonFactory, credential) + .setApplicationName("Your Application Name") + .build(); + + // List email messages + List messages = gmail.users().messages().list("me").execute().getMessages(); + logger.info("Messages: {}", messages); + } +} diff --git a/arcade-java-example/src/main/java/dev/arcade/example/QuickStartExample.java b/arcade-java-example/src/main/java/dev/arcade/example/QuickStartExample.java new file mode 100644 index 0000000..4bad391 --- /dev/null +++ b/arcade-java-example/src/main/java/dev/arcade/example/QuickStartExample.java @@ -0,0 +1,126 @@ +package dev.arcade.example; + +import dev.arcade.client.ArcadeClient; +import dev.arcade.client.okhttp.ArcadeOkHttpClient; +import dev.arcade.core.JsonValue; +import dev.arcade.models.AuthorizationResponse; +import dev.arcade.models.tools.AuthorizeToolRequest; +import dev.arcade.models.tools.ExecuteToolRequest; +import dev.arcade.models.tools.ExecuteToolResponse.Output; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuickStartExample { + + private static final Logger logger = LoggerFactory.getLogger(QuickStartExample.class); + + public static void main(String[] args) { + + // You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a + // parameter by using ArcadeOkHttpClient.fromEnv(). + ArcadeClient client = + ArcadeOkHttpClient.builder().apiKey("{arcade_api_key}").build(); + + // Arcade needs a unique identifier for your application user (this could be an email address, a UUID, etc). + // In this example, use the email you used to sign up for Arcade.dev: + String userId = "{arcade_user_id}"; + + Map searchResult = authorizeAndRunTool( + client, "GoogleNews.SearchNewsStories", Map.of("keywords", "MCP URL mode elicitation"), userId); + + // Extract the list of news results from the tool output + List news = searchResult + .getOrDefault("news_results", JsonValue.from(List.of())) + .toListOrEmpty(); + + String output = "latest news about MCP URL mode elicitation:\n" + + news.stream() + .map(item -> { + Map newsItem = item.toMapOrEmpty(); + return newsItem.get("source").asStringOrThrow() + " - " + + newsItem.get("title").asStringOrThrow() + "\n" + + newsItem.get("link").asStringOrThrow() + "\n\n"; + }) + .collect(Collectors.joining("\n")); + + // Create a Google Doc with the news results + // If the user has not previously authorized the Google Docs tool, they will be prompted to authorize the tool + // call. + Map createDocResult = authorizeAndRunTool( + client, + "GoogleDocs.CreateDocumentFromText", + Map.of("title", "News about MCP URL mode elicitation", "text_content", output), + userId); + + String googleDocUrl = createDocResult.get("documentUrl").asStringOrThrow(); + + String emailBody = + "You can find the news about MCP URL mode elicitation in the following Google Doc: " + googleDocUrl; + + Map sendEmailResult = authorizeAndRunTool( + client, + "Gmail.SendEmail", + Map.of("recipient", userId, "subject", "News about MCP URL mode elicitation", "body", emailBody), + userId); + + // Print the response from the tool call + logger.info( + """ + Success! Check your email at {} + + You just chained 3 tools together: + 1. Searched Google News for stories about MCP URL mode elicitation + 2. Created a Google Doc with the results + 3. Sent yourself an email with the document link + + Email metadata: {} + """, + userId, + sendEmailResult); + } + + /** + * Authorize (if needed) and execute a tool, returning the output as a Map. + */ + public static Map authorizeAndRunTool( + ArcadeClient client, String toolName, Map input, String userId) { + // Start the authorization process + AuthorizationResponse authResponse = client.tools() + .authorize(AuthorizeToolRequest.builder() + .toolName(toolName) + .userId(userId) + .build()); + + // If the authorization is not completed, print the authorization URL and wait for the user to authorize the + // app. + // Tools that do not require authorization will have the status "completed" already. + authResponse + .status() + .filter(status -> status != AuthorizationResponse.Status.COMPLETED) + .flatMap(status -> authResponse.url()) + .ifPresent(url -> logger.info( + """ + Click this link to authorize {}: + {}. + The process will continue once you have authorized the app. + """, + toolName, + url)); + client.auth().waitForCompletion(authResponse); + + // Execute the tool and extract the output as a Map + return client.tools() + .execute(ExecuteToolRequest.builder() + .toolName(toolName) + .input(input) + .userId(userId) + .includeErrorStacktrace(true) + .build()) + .output() + .flatMap(Output::valueAsObject) + .orElse(Map.of()); + } +}