diff --git a/samples/FunctionsMcpTool/local.settings.json b/samples/FunctionsMcpTool/local.settings.json index 72aefe8..453d10c 100644 --- a/samples/FunctionsMcpTool/local.settings.json +++ b/samples/FunctionsMcpTool/local.settings.json @@ -2,6 +2,7 @@ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "java" + "FUNCTIONS_WORKER_RUNTIME": "java", + "FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI": "https://cdn-staging.functions.azure.com/public" } } diff --git a/samples/FunctionsMcpTool/pom.xml b/samples/FunctionsMcpTool/pom.xml index 56cfd29..0b8308a 100644 --- a/samples/FunctionsMcpTool/pom.xml +++ b/samples/FunctionsMcpTool/pom.xml @@ -12,8 +12,8 @@ UTF-8 17 - 1.40.0 - 3.2.2 + 1.42.0 + 3.3.0 HelloWorld-MCP @@ -23,6 +23,16 @@ azure-functions-java-library ${azure.functions.java.library.version} + + com.microsoft.azure.functions + azure-functions-java-mcp + 1.0.0 + + + io.modelcontextprotocol.sdk + mcp-json-jackson2 + 1.1.0 + com.google.code.gson gson diff --git a/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java new file mode 100644 index 0000000..095d4dc --- /dev/null +++ b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java @@ -0,0 +1,88 @@ +package com.function; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.McpPromptArgument; +import com.microsoft.azure.functions.annotation.McpPromptTrigger; + +/** + * Demonstrates MCP Prompt functions that expose prompt templates to MCP clients. + * Clients discover prompts via prompts/list and invoke them via prompts/get. + * + * Return a plain string (auto-wrapped into a single user message by the host) + * or a JSON-serialized GetPromptResult for multi-message / rich content responses. + */ +public class PromptExamples { + + /** + * A code review prompt with multiple arguments (1 required, 1 optional). + * Uses McpPromptArgument annotations to define arguments in a strongly-typed way. + */ + @FunctionName("CodeReviewPrompt") + public String codeReviewPrompt( + @McpPromptTrigger( + name = "code_review", + description = "Generates a code review prompt for the given code snippet", + title = "Code Review") + String context, + @McpPromptArgument( + name = "code", + description = "The code to review", + isRequired = true) + String code, + @McpPromptArgument( + name = "language", + description = "The programming language") + String language, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Generating code review prompt"); + + String lang = (language != null && !language.isEmpty()) ? language : "unknown"; + String snippet = (code != null && !code.isEmpty()) ? code : "// no code provided"; + + return "Please review the following " + lang + " code and suggest improvements:\n\n```" + + lang + "\n" + snippet + "\n```"; + } + + /** + * A summarize prompt with a single required argument and plain string return. + * The host auto-wraps the returned string into a PromptMessage with role "user". + */ + @FunctionName("SummarizePrompt") + public String summarizePrompt( + @McpPromptTrigger( + name = "summarize", + description = "Summarizes the provided text", + title = "Summarize Text") + String context, + @McpPromptArgument( + name = "text", + description = "The text to summarize", + isRequired = true) + String text, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Generating summarize prompt"); + + String input = (text != null && !text.isEmpty()) ? text : "No text provided"; + return "Please provide a concise summary of the following text:\n\n" + input; + } + + /** + * A prompt with no arguments. Tests the edge case of a prompt + * that takes no user input. + */ + @FunctionName("NoArgsPrompt") + public String noArgsPrompt( + @McpPromptTrigger( + name = "no_args_prompt", + description = "A prompt that requires no arguments", + title = "No Arguments Prompt") + String context, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Generating no-args prompt"); + return "This prompt requires no arguments. Please provide general guidance."; + } +} diff --git a/samples/FunctionsMcpTool/src/main/java/com/function/StructuredContentExamples.java b/samples/FunctionsMcpTool/src/main/java/com/function/StructuredContentExamples.java new file mode 100644 index 0000000..5bc5b53 --- /dev/null +++ b/samples/FunctionsMcpTool/src/main/java/com/function/StructuredContentExamples.java @@ -0,0 +1,159 @@ +package com.function; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.McpToolProperty; +import com.microsoft.azure.functions.annotation.McpToolTrigger; +import com.microsoft.azure.functions.mcp.McpContent; +import com.microsoft.azure.functions.mcp.McpToolResult; +import io.modelcontextprotocol.spec.McpSchema.Content; +import io.modelcontextprotocol.spec.McpSchema.ImageContent; +import io.modelcontextprotocol.spec.McpSchema.TextContent; + +import java.util.Arrays; +import java.util.List; + +/** + * Demonstrates structured content support in Azure Functions MCP tools. + * + *

These examples show three approaches for returning rich content from MCP tools:

+ *
    + *
  1. {@link #getSnippetStructured} — {@code @McpContent}-annotated POJO (automatic text + structured content)
  2. + *
  3. {@link #renderImage} — Single content block (ImageContentBlock)
  4. + *
  5. {@link #getMultiContent} — Multiple content blocks (List of ContentBlock)
  6. + *
+ * + *

Note: These functions require the {@code azure-functions-java-mcp} dependency which provides + * the middleware that wraps return values into the MCP result envelope.

+ */ +public class StructuredContentExamples { + + // ======================================================================== + // Example 1: @McpContent-annotated POJO + // The middleware automatically creates both text content (for backwards + // compatibility) and structured content (for clients that support it). + // ======================================================================== + + /** + * A snippet POJO decorated with @McpContent. When returned from an MCP tool function, + * this will be serialized as both text content (JSON text) and structured content + * (JSON object), enabling MCP clients to parse the result programmatically. + */ + @McpContent + public static class SnippetResult { + private String name; + private String content; + private String language; + + public SnippetResult() { + } + + public SnippetResult(String name, String content, String language) { + this.name = name; + this.content = content; + this.language = language; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + public String getLanguage() { return language; } + public void setLanguage(String language) { this.language = language; } + } + + /** + * Returns a code snippet as structured content. The {@code @McpContent} annotation + * on {@code SnippetResult} tells the middleware to serialize the return value as both + * text content and structured content. + * + *

MCP clients that support structured content can parse the JSON object directly. + * Older clients will see the JSON as a text string.

+ */ + @FunctionName("GetSnippetStructured") + public SnippetResult getSnippetStructured( + @McpToolTrigger( + name = "getSnippetStructured", + description = "Gets a code snippet with structured content support.") + String context, + @McpToolProperty( + name = "snippetName", + propertyType = "string", + description = "The name of the snippet to retrieve.", + isRequired = true) + String snippetName, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Getting structured snippet: " + snippetName); + + return new SnippetResult( + snippetName, + "public class HelloWorld { public static void main(String[] args) { System.out.println(\"Hello!\"); } }", + "java" + ); + } + + // ======================================================================== + // Example 2: Single content block (ImageContentBlock) + // For returning rich content types like images. + // ======================================================================== + + /** + * Returns an image as a single content block. The middleware wraps this + * in an MCP result envelope automatically. + */ + @FunctionName("RenderImage") + public ImageContent renderImage( + @McpToolTrigger( + name = "renderImage", + description = "Returns a base64-encoded image.") + String context, + @McpToolProperty( + name = "data", + propertyType = "string", + description = "Base64-encoded image data.", + isRequired = true) + String data, + @McpToolProperty( + name = "mimeType", + propertyType = "string", + description = "MIME type of the image (e.g., image/png).") + String mimeType, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Rendering image with MIME type: " + mimeType); + + return new ImageContent(null, data, mimeType != null ? mimeType : "image/png"); + } + + // ======================================================================== + // Example 3: Multiple content blocks + // For returning a list of mixed content types in a single response. + // ======================================================================== + + /** + * Returns multiple content blocks — a text description followed by an image. + * The middleware wraps this as a multi-content result. + */ + @FunctionName("GetMultiContent") + public List getMultiContent( + @McpToolTrigger( + name = "getMultiContent", + description = "Returns multiple content blocks including text and an image.") + String context, + @McpToolProperty( + name = "imageData", + propertyType = "string", + description = "Base64-encoded image data.", + isRequired = true) + String imageData, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Generating multi-content response"); + + return Arrays.asList( + new TextContent("Here is the requested image:"), + new ImageContent(null, imageData, "image/png") + ); + } +} diff --git a/samples/McpWeatherApp/pom.xml b/samples/McpWeatherApp/pom.xml index 570b46d..59e6e99 100644 --- a/samples/McpWeatherApp/pom.xml +++ b/samples/McpWeatherApp/pom.xml @@ -13,7 +13,7 @@ UTF-8 17 1.41.0 - 3.2.4 + 3.3.0 McpWeatherApp