From 5487daed20a9f7fbf777af2f33080a5662aed457 Mon Sep 17 00:00:00 2001 From: Ahmed Muhsin Date: Mon, 16 Mar 2026 16:46:06 -0500 Subject: [PATCH 1/5] Add structured content sample functions and MCP module dependency Add StructuredContentExamples.java demonstrating: - @McpContent-annotated POJO (automatic text + structured content) - ImageContent (single content block from MCP SDK) - List (multi-content response) Update pom.xml with: - azure-functions-java-mcp 1.0.0 dependency - mcp-json-jackson2 for MCP SDK JSON serialization - Updated azure-functions-java-library to 3.2.4 - Updated azure-functions-maven-plugin to 1.41.0 --- samples/FunctionsMcpTool/pom.xml | 14 +- .../function/StructuredContentExamples.java | 159 ++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 samples/FunctionsMcpTool/src/main/java/com/function/StructuredContentExamples.java diff --git a/samples/FunctionsMcpTool/pom.xml b/samples/FunctionsMcpTool/pom.xml index 56cfd29..8d9c36d 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.2.4 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/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") + ); + } +} From c25cc61574078f0b6afae9e51dc7b427dccd33ac Mon Sep 17 00:00:00 2001 From: Ahmed Muhsin Date: Tue, 21 Apr 2026 12:35:03 -0500 Subject: [PATCH 2/5] Add MCP prompt sample functions - PromptExamples.java: 4 sample prompt functions demonstrating McpPromptTrigger and McpPromptArgument usage (code review, summarize, no-args, and inline-args prompts) - local.settings.json: add staging CDN for extension bundle with prompt support --- samples/FunctionsMcpTool/local.settings.json | 3 +- .../java/com/function/PromptExamples.java | 110 ++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java 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/src/main/java/com/function/PromptExamples.java b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java new file mode 100644 index 0000000..18121c8 --- /dev/null +++ b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java @@ -0,0 +1,110 @@ +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", + argumentName = "code", + description = "The code to review", + isRequired = true) + String code, + @McpPromptArgument( + name = "language", + argumentName = "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", + argumentName = "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."; + } + + /** + * A prompt with inline promptArguments JSON instead of McpPromptArgument annotations. + * This is the alternative approach — useful when arguments are static/known at compile time. + */ + @FunctionName("InlineArgsPrompt") + public String inlineArgsPrompt( + @McpPromptTrigger( + name = "inline_args_prompt", + description = "A prompt with arguments defined via inline JSON", + title = "Inline Arguments Prompt", + promptArguments = "[{\"name\":\"topic\",\"description\":\"The topic to discuss\",\"required\":false}," + + "{\"name\":\"style\",\"description\":\"The writing style\",\"required\":false}]") + String context, + final ExecutionContext executionContext) { + + executionContext.getLogger().info("Generating inline-args prompt"); + return "Please write about the specified topic in the requested style."; + } +} From 1552ac34414f7ed6480ca887e5d75cf5a7419c4f Mon Sep 17 00:00:00 2001 From: Ahmed Muhsin Date: Tue, 21 Apr 2026 16:57:20 -0500 Subject: [PATCH 3/5] Remove argumentName from McpPromptArgument usages in prompt samples The McpPromptArgument annotation no longer has argumentName() as a separate property. The name() value is used as both the binding name and the MCP argument identifier. --- .../src/main/java/com/function/PromptExamples.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java index 18121c8..656ab5a 100644 --- a/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java +++ b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java @@ -27,13 +27,11 @@ public String codeReviewPrompt( String context, @McpPromptArgument( name = "code", - argumentName = "code", description = "The code to review", isRequired = true) String code, @McpPromptArgument( name = "language", - argumentName = "language", description = "The programming language") String language, final ExecutionContext executionContext) { @@ -60,7 +58,6 @@ public String summarizePrompt( String context, @McpPromptArgument( name = "text", - argumentName = "text", description = "The text to summarize", isRequired = true) String text, From 8772ef5188f7d82aba15d319a5bf2833a0e0ec36 Mon Sep 17 00:00:00 2001 From: Ahmed Muhsin Date: Tue, 28 Apr 2026 14:50:03 -0500 Subject: [PATCH 4/5] Remove InlineArgsPrompt sample function Steer users toward @McpPromptArgument annotations as the recommended approach for defining prompt arguments. --- .../java/com/function/PromptExamples.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java index 656ab5a..095d4dc 100644 --- a/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java +++ b/samples/FunctionsMcpTool/src/main/java/com/function/PromptExamples.java @@ -85,23 +85,4 @@ public String noArgsPrompt( executionContext.getLogger().info("Generating no-args prompt"); return "This prompt requires no arguments. Please provide general guidance."; } - - /** - * A prompt with inline promptArguments JSON instead of McpPromptArgument annotations. - * This is the alternative approach — useful when arguments are static/known at compile time. - */ - @FunctionName("InlineArgsPrompt") - public String inlineArgsPrompt( - @McpPromptTrigger( - name = "inline_args_prompt", - description = "A prompt with arguments defined via inline JSON", - title = "Inline Arguments Prompt", - promptArguments = "[{\"name\":\"topic\",\"description\":\"The topic to discuss\",\"required\":false}," - + "{\"name\":\"style\",\"description\":\"The writing style\",\"required\":false}]") - String context, - final ExecutionContext executionContext) { - - executionContext.getLogger().info("Generating inline-args prompt"); - return "Please write about the specified topic in the requested style."; - } } From 6575ee80e3d230b85c2baf63e15e9728a6255755 Mon Sep 17 00:00:00 2001 From: Ahmed Muhsin Date: Tue, 28 Apr 2026 18:00:15 -0500 Subject: [PATCH 5/5] Update azure-functions-java-library to 3.3.0 in samples --- samples/FunctionsMcpTool/pom.xml | 2 +- samples/McpWeatherApp/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/FunctionsMcpTool/pom.xml b/samples/FunctionsMcpTool/pom.xml index 8d9c36d..0b8308a 100644 --- a/samples/FunctionsMcpTool/pom.xml +++ b/samples/FunctionsMcpTool/pom.xml @@ -13,7 +13,7 @@ UTF-8 17 1.42.0 - 3.2.4 + 3.3.0 HelloWorld-MCP 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