diff --git a/03-GettingStarted/01-first-server/README.md b/03-GettingStarted/01-first-server/README.md index 6cf2c578c..1ae9145d9 100644 --- a/03-GettingStarted/01-first-server/README.md +++ b/03-GettingStarted/01-first-server/README.md @@ -199,18 +199,127 @@ dotnet add package Microsoft.Extensions.Hosting
Java -Add the following dependency to the *pom.xml* file: +For Java, create a Spring Boot project: -```java -# Using Maven - - io.modelcontextprotocol - mcp-sdk - latest - +```bash +curl https://start.spring.io/starter.zip \ + -d dependencies=web \ + -d javaVersion=21 \ + -d type=maven-project \ + -d groupId=com.example \ + -d artifactId=calculator-server \ + -d name=McpServer \ + -d packageName=com.microsoft.mcp.sample.server \ + -o calculator-server.zip +``` + +Extract the zip file: -# Using Gradle -implementation 'io.modelcontextprotocol:mcp-sdk:latest' +```bash +unzip calculator-server.zip -d calculator-server +cd calculator-server +# optional remove the unused test +rm -rf src/test/java +``` + +Add the following complete configuration to your *pom.xml* file: + +```xml + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + + + com.example + calculator-server + 0.0.1-SNAPSHOT + Calculator Server + Basic calculator MCP service for beginners + + + + 21 + 21 + 21 + + + + + + + org.springframework.ai + spring-ai-bom + 1.0.0-SNAPSHOT + pom + import + + + + + + + + org.springframework.ai + spring-ai-starter-mcp-server-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + ```
@@ -237,6 +346,16 @@ Now that you have your SDK installed, let's create a project next: ``` +
+Java + +```bash +cd calculator-server +./mvnw clean install -DskipTests +``` + +
+ ### -3- Create project files
@@ -295,6 +414,13 @@ dotnet new console
+
+Java + +For Java Spring Boot projects, the project structure is created automatically. + +
+ ### -4- Create server code @@ -359,6 +485,289 @@ await builder.Build().RunAsync(); +
+Java + +For Java, create the core server components. First, modify the main application class: + +*src/main/java/com/microsoft/mcp/sample/server/McpServerApplication.java*: + +```java +package com.microsoft.mcp.sample.server; + +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import com.microsoft.mcp.sample.server.service.CalculatorService; + +@SpringBootApplication +public class McpServerApplication { + + public static void main(String[] args) { + SpringApplication.run(McpServerApplication.class, args); + } + + @Bean + public ToolCallbackProvider calculatorTools(CalculatorService calculator) { + return MethodToolCallbackProvider.builder().toolObjects(calculator).build(); + } +} +``` + +Create the calculator service *src/main/java/com/microsoft/mcp/sample/server/service/CalculatorService.java*: + +```java +package com.microsoft.mcp.sample.server.service; + +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.stereotype.Service; + +/** + * Service for basic calculator operations. + * This service provides simple calculator functionality through MCP. + */ +@Service +public class CalculatorService { + + /** + * Add two numbers + * @param a The first number + * @param b The second number + * @return The sum of the two numbers + */ + @Tool(description = "Add two numbers together") + public String add(double a, double b) { + double result = a + b; + return formatResult(a, "+", b, result); + } + + /** + * Subtract one number from another + * @param a The number to subtract from + * @param b The number to subtract + * @return The result of the subtraction + */ + @Tool(description = "Subtract the second number from the first number") + public String subtract(double a, double b) { + double result = a - b; + return formatResult(a, "-", b, result); + } + + /** + * Multiply two numbers + * @param a The first number + * @param b The second number + * @return The product of the two numbers + */ + @Tool(description = "Multiply two numbers together") + public String multiply(double a, double b) { + double result = a * b; + return formatResult(a, "*", b, result); + } + + /** + * Divide one number by another + * @param a The numerator + * @param b The denominator + * @return The result of the division + */ + @Tool(description = "Divide the first number by the second number") + public String divide(double a, double b) { + if (b == 0) { + return "Error: Cannot divide by zero"; + } + double result = a / b; + return formatResult(a, "/", b, result); + } + + /** + * Calculate the power of a number + * @param base The base number + * @param exponent The exponent + * @return The result of raising the base to the exponent + */ + @Tool(description = "Calculate the power of a number (base raised to an exponent)") + public String power(double base, double exponent) { + double result = Math.pow(base, exponent); + return formatResult(base, "^", exponent, result); + } + + /** + * Calculate the square root of a number + * @param number The number to find the square root of + * @return The square root of the number + */ + @Tool(description = "Calculate the square root of a number") + public String squareRoot(double number) { + if (number < 0) { + return "Error: Cannot calculate square root of a negative number"; + } + double result = Math.sqrt(number); + return String.format("√%.2f = %.2f", number, result); + } + + /** + * Calculate the modulus (remainder) of division + * @param a The dividend + * @param b The divisor + * @return The remainder of the division + */ + @Tool(description = "Calculate the remainder when one number is divided by another") + public String modulus(double a, double b) { + if (b == 0) { + return "Error: Cannot divide by zero"; + } + double result = a % b; + return formatResult(a, "%", b, result); + } + + /** + * Calculate the absolute value of a number + * @param number The number to find the absolute value of + * @return The absolute value of the number + */ + @Tool(description = "Calculate the absolute value of a number") + public String absolute(double number) { + double result = Math.abs(number); + return String.format("|%.2f| = %.2f", number, result); + } + + /** + * Get help about available calculator operations + * @return Information about available operations + */ + @Tool(description = "Get help about available calculator operations") + public String help() { + return "Basic Calculator MCP Service\n\n" + + "Available operations:\n" + + "1. add(a, b) - Adds two numbers\n" + + "2. subtract(a, b) - Subtracts the second number from the first\n" + + "3. multiply(a, b) - Multiplies two numbers\n" + + "4. divide(a, b) - Divides the first number by the second\n" + + "5. power(base, exponent) - Raises a number to a power\n" + + "6. squareRoot(number) - Calculates the square root\n" + + "7. modulus(a, b) - Calculates the remainder of division\n" + + "8. absolute(number) - Calculates the absolute value\n\n" + + "Example usage: add(5, 3) will return 5 + 3 = 8"; + } + + /** + * Format the result of a calculation + */ + private String formatResult(double a, String operator, double b, double result) { + return String.format("%.2f %s %.2f = %.2f", a, operator, b, result); + } +} +``` + +**Optional components for a production-ready service:** + +Create a startup configuration *src/main/java/com/microsoft/mcp/sample/server/config/StartupConfig.java*: + +```java +package com.microsoft.mcp.sample.server.config; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class StartupConfig { + + @Bean + public CommandLineRunner startupInfo() { + return args -> { + System.out.println("\n" + "=".repeat(60)); + System.out.println("Calculator MCP Server is starting..."); + System.out.println("SSE endpoint: http://localhost:8080/sse"); + System.out.println("Health check: http://localhost:8080/actuator/health"); + System.out.println("=".repeat(60) + "\n"); + }; + } +} +``` + +Create a health controller *src/main/java/com/microsoft/mcp/sample/server/controller/HealthController.java*: + +```java +package com.microsoft.mcp.sample.server.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@RestController +public class HealthController { + + @GetMapping("/health") + public ResponseEntity> healthCheck() { + Map response = new HashMap<>(); + response.put("status", "UP"); + response.put("timestamp", LocalDateTime.now().toString()); + response.put("service", "Calculator MCP Server"); + return ResponseEntity.ok(response); + } +} +``` + +Create an exception handler *src/main/java/com/microsoft/mcp/sample/server/exception/GlobalExceptionHandler.java*: + +```java +package com.microsoft.mcp.sample.server.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) { + ErrorResponse error = new ErrorResponse( + "Invalid_Input", + "Invalid input parameter: " + ex.getMessage()); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + public static class ErrorResponse { + private String code; + private String message; + + public ErrorResponse(String code, String message) { + this.code = code; + this.message = message; + } + + // Getters + public String getCode() { return code; } + public String getMessage() { return message; } + } +} +``` + +Create a custom banner *src/main/resources/banner.txt*: + +```text +_____ _ _ _ + / ____| | | | | | | +| | __ _| | ___ _ _| | __ _| |_ ___ _ __ +| | / _` | |/ __| | | | |/ _` | __/ _ \| '__| +| |___| (_| | | (__| |_| | | (_| | || (_) | | + \_____\__,_|_|\___|\__,_|_|\__,_|\__\___/|_| + +Calculator MCP Server v1.0 +Spring Boot MCP Application +``` + +
+ ### -5- Adding a tool and a resource Add a tool and a resource by adding the following code: @@ -445,6 +854,13 @@ public static class CalculatorTool +
+Java + +The tools have already been created in the previous step. + +
+ ### -6 Final code Let's add the last code we need so the server can start: @@ -559,6 +975,38 @@ public static class CalculatorTool +
+Java + +Your complete main application class should look like this: + +```java +// McpServerApplication.java +package com.microsoft.mcp.sample.server; + +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import com.microsoft.mcp.sample.server.service.CalculatorService; + +@SpringBootApplication +public class McpServerApplication { + + public static void main(String[] args) { + SpringApplication.run(McpServerApplication.class, args); + } + + @Bean + public ToolCallbackProvider calculatorTools(CalculatorService calculator) { + return MethodToolCallbackProvider.builder().toolObjects(calculator).build(); + } +} +``` + +
+ ### -7- Test the server Start the server with the following command: @@ -589,6 +1037,16 @@ dotnet run +
+Java + +```bash +./mvnw clean install -DskipTests +java -jar target/calculator-server-0.0.1-SNAPSHOT.jar +``` + +
+ ### -8- Run using the inspector The inspector is a great tool that can start up your server and lets you interact with it so you can test that it works. Let's start it up: @@ -633,6 +1091,27 @@ npx @modelcontextprotocol/inspector dotnet run +
+Java + +Ensure you calculator server is running +The run the inspector: + +```cmd +npx @modelcontextprotocol/inspector +``` + +In the inspector web interface: +1. Select "SSE" as the transport type +2. Set the URL to: `http://localhost:8080/sse` +3. Click "Connect" + +![Connect](/03-GettingStarted/01-first-server/assets/tool.png) + +The Java server testing section is completed now + +
+ You should see the following user interface: ![Connect](/03-GettingStarted/01-first-server/assets/connect.png) diff --git a/03-GettingStarted/01-first-server/assets/tool.png b/03-GettingStarted/01-first-server/assets/tool.png new file mode 100644 index 000000000..0062cdee6 Binary files /dev/null and b/03-GettingStarted/01-first-server/assets/tool.png differ diff --git a/03-GettingStarted/01-first-server/solution/README.md b/03-GettingStarted/01-first-server/solution/README.md index 50bf83bf8..5c80b9f7d 100644 --- a/03-GettingStarted/01-first-server/solution/README.md +++ b/03-GettingStarted/01-first-server/solution/README.md @@ -2,4 +2,5 @@ Here's the solutions for each runtime: - [TypeScript](./typescript/README.md) - [Python](./python/README.md) -- [.NET](./dotnet/README.md) \ No newline at end of file +- [.NET](./dotnet/README.md) +- [Java](./java/README.md) \ No newline at end of file diff --git a/03-GettingStarted/01-first-server/solution/java/pom.xml b/03-GettingStarted/01-first-server/solution/java/pom.xml index 0876ce067..c2c73152a 100644 --- a/03-GettingStarted/01-first-server/solution/java/pom.xml +++ b/03-GettingStarted/01-first-server/solution/java/pom.xml @@ -35,7 +35,7 @@ pom import - + @@ -47,6 +47,11 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/03-GettingStarted/06-http-streaming/README.md b/03-GettingStarted/06-http-streaming/README.md index dc69982e2..3b1a555f2 100644 --- a/03-GettingStarted/06-http-streaming/README.md +++ b/03-GettingStarted/06-http-streaming/README.md @@ -2,18 +2,22 @@ This chapter provides a comprehensive guide to implementing secure, scalable, and real-time streaming with the Model Context Protocol (MCP) using HTTPS. It covers the motivation for streaming, the available transport mechanisms, how to implement streamable HTTP in MCP, security best practices, migration from SSE, and practical guidance for building your own streaming MCP applications. ---- +## Transport Mechanisms and Streaming in MCP -## 1. Transport Mechanisms and Streaming in MCP +This section explores the different transport mechanisms available in MCP and their role in enabling streaming capabilities for real-time communication between clients and servers. ### What is a Transport Mechanism? + A transport mechanism defines how data is exchanged between the client and server. MCP supports multiple transport types to suit different environments and requirements: - **stdio**: Standard input/output, suitable for local and CLI-based tools. Simple but not suitable for web or cloud. - **SSE (Server-Sent Events)**: Allows servers to push real-time updates to clients over HTTP. Good for web UIs, but limited in scalability and flexibility. - **Streamable HTTP**: Modern HTTP-based streaming transport, supporting notifications and better scalability. Recommended for most production and cloud scenarios. -#### Comparison Table +### Comparison Table + +Have a look at the comparison table below to understand the differences between these transport mechanisms: + | Transport | Real-time Updates | Streaming | Scalability | Use Case | |-------------------|------------------|-----------|-------------|-------------------------| | stdio | No | No | Low | Local CLI tools | @@ -22,29 +26,43 @@ A transport mechanism defines how data is exchanged between the client and serve > **Tip:** Choosing the right transport impacts performance, scalability, and user experience. **Streamable HTTP** is recommended for modern, scalable, and cloud-ready applications. ---- +Note the transports stdio and SSE that you were shown in the previous chapters and how streamable HTTP is the transport covered in this chapter. -## 2. Streaming: Concepts and Motivation +## Streaming: Concepts and Motivation + +Understanding the fundamental concepts and motivations behind streaming is essential for implementing effective real-time communication systems. **Streaming** is a technique in network programming that allows data to be sent and received in small, manageable chunks or as a sequence of events, rather than waiting for an entire response to be ready. This is especially useful for: -- Large files or datasets -- Real-time updates (e.g., chat, progress bars) -- Long-running computations where you want to keep the user informed +- Large files or datasets. +- Real-time updates (e.g., chat, progress bars). +- Long-running computations where you want to keep the user informed. + +Here's what you need to know about streaming at high level: + +- Data is delivered progressively, not all at once. +- The client can process data as it arrives. +- Reduces perceived latency and improves user experience. -#### Key Concepts -- Data is delivered progressively, not all at once -- The client can process data as it arrives -- Reduces perceived latency and improves user experience +### Why use streaming? + +The reasons for using streaming are the following: -#### Why use streaming? - Users get feedback immediately, not just at the end - Enables real-time applications and responsive UIs - More efficient use of network and compute resources -##### Simple Example: HTTP Streaming Server & Client +### Simple Example: HTTP Streaming Server & Client + +Here's a simple example of how streaming can be implemented: + +
+Python **Server (Python, using FastAPI and StreamingResponse):** +
+Python + ```python from fastapi import FastAPI from fastapi.responses import StreamingResponse @@ -62,7 +80,12 @@ def stream(): return StreamingResponse(event_stream(), media_type="text/event-stream") ``` +
+ **Client (Python, using requests):** +
+Python + ```python import requests @@ -72,6 +95,8 @@ with requests.get("http://localhost:8000/stream", stream=True) as r: print(line.decode()) ``` +
+ This example demonstrates a server sending a series of messages to the client as they become available, rather than waiting for all messages to be ready. **How it works:** @@ -83,10 +108,12 @@ This example demonstrates a server sending a series of messages to the client as - The client must process the response as a stream (`stream=True` in requests). - Content-Type is usually `text/event-stream` or `application/octet-stream`. ---- +
### Comparison: Classic Streaming vs MCP Streaming +The differences between how streaming works in a "classical" manner versus how it works in MCP can be depicted like so: + | Feature | Classic HTTP Streaming | MCP Streaming (Notifications) | |------------------------|-------------------------------|-------------------------------------| | Main response | Chunked | Single, at end | @@ -96,57 +123,109 @@ This example demonstrates a server sending a series of messages to the client as ### Key Differences Observed -1. **Communication Pattern:** +Additionally, here are some key differences: + +- **Communication Pattern:** - Classic HTTP streaming: Uses simple chunked transfer encoding to send data in chunks - MCP streaming: Uses a structured notification system with JSON-RPC protocol -2. **Message Format:** +- **Message Format:** - Classic HTTP: Plain text chunks with newlines - MCP: Structured LoggingMessageNotification objects with metadata -3. **Client Implementation:** +- **Client Implementation:** - Classic HTTP: Simple client that processes streaming responses - MCP: More sophisticated client with a message handler to process different types of messages -4. **Progress Updates:** +- **Progress Updates:** - Classic HTTP: The progress is part of the main response stream - MCP: Progress is sent via separate notification messages while the main response comes at the end ### Recommendations -Based on what we've observed: +There are some things we recommend when it comes to choosing between implementing classical streaming (as an endpoint we showed you above using `/stream`) versus choosing streaming via MCP. + +- **For simple streaming needs:** Classic HTTP streaming is simpler to implement and sufficient for basic streaming needs. -1. **For simple streaming needs:** Classic HTTP streaming is simpler to implement and sufficient for basic streaming needs. +- **For complex, interactive applications:** MCP streaming provides a more structured approach with richer metadata and separation between notifications and final results. -2. **For complex, interactive applications:** MCP streaming provides a more structured approach with richer metadata and separation between notifications and final results. +- **For AI applications:** MCP's notification system is particularly useful for long-running AI tasks where you want to keep users informed of progress. -3. **For AI applications:** MCP's notification system is particularly useful for long-running AI tasks where you want to keep users informed of progress. +## Streaming in MCP ---- +Ok, so you've seen some recommendations and comparisons so far on the difference between classical streaming and streaming in MCP. Let's get into detail exactly how you can leverage streaming in MCP. -## 3. Streaming in MCP +Understanding how streaming works within the MCP framework is essential for building responsive applications that provide real-time feedback to users during long-running operations. In MCP, streaming is not about sending the main response in chunks, but about sending **notifications** to the client while a tool is processing a request. These notifications can include progress updates, logs, or other events. -#### Key differences from traditional streaming -- The main result is still sent as a single response -- Notifications are sent as separate messages during processing -- The client must be able to handle and display these notifications +### How it works + +The main result is still sent as a single response.However, notfications can be sent as separate messages during processing and thereby update the client in real time. The client must be able to handle and display these notifications. + +## What is a Notification? + +We said "Notification", what does that mean in the context of MCP? -#### What is a Notification? A notification is a message sent from the server to the client to inform about progress, status, or other events during a long-running operation. Notifications improve transparency and user experience. -##### Why use notifications? -- Keep users informed about progress -- Enable real-time feedback and better UX -- Useful for debugging and monitoring +For example, a client is supposed to send a notification once the initial handshake with the server has been made. + +A notification looks like so as a JSON message: + +```json +{ + jsonrpc: "2.0"; + method: string; + params?: { + [key: string]: unknown; + }; +} +``` + +Notifications belongs to a topic in MCP referred to as ["Logging"](https://modelcontextprotocol.io/specification/draft/server/utilities/logging). + +To get logging to work, the server needs to enable it as feature/capability like so: + +```json +{ + "capabilities": { + "logging": {} + } +} +``` + +> [!NOTE] +> Depending on the SDK used, logging might be enabled by default, or you might need to explicitly enable it in your server configuration. + ---- +There different types of notifications: -## 4. Implementing Notifications in MCP +| Level | Description | Example Use Case | +|-----------|-------------------------------|---------------------------------| +| debug | Detailed debugging information | Function entry/exit points | +| info | General informational messages | Operation progress updates | +| notice | Normal but significant events | Configuration changes | +| warning | Warning conditions | Deprecated feature usage | +| error | Error conditions | Operation failures | +| critical | Critical conditions | System component failures | +| alert | Action must be taken immediately | Data corruption detected | +| emergency | System is unusable | Complete system failure | + + +## Implementing Notifications in MCP + +To implement notifications in MCP, you need to set up both the server and client sides to handle real-time updates. This allows your application to provide immediate feedback to users during long-running operations. ### Server-side: Sending Notifications -To send notifications from your MCP tool, use the context object (usually `ctx`) to call methods like `ctx.info()` or `ctx.log()`. These send messages to the client as the server processes a request. + +Let's start with the server side. In MCP, you define tools that can send notifications while processing requests. The server uses the context object (usually `ctx`) to send messages to the client. + +
+Python + +
+Python ```python @mcp.tool(description="A tool that sends progress notifications") @@ -157,52 +236,118 @@ async def process_files(message: str, ctx: Context) -> TextContent: return TextContent(type="text", text=f"Done: {message}") ``` +In the preceding example, the `process_files` tool sends three notifications to the client as it processes each file. The `ctx.info()` method is used to send informational messages. + +
+ +Additionally, to enable notifications, ensure your server uses a streaming transport (like `streamable-http`) and your client implements a message handler to process notifications. Here's how you can set up the server to use the `streamable-http` transport: + +```python +mcp.run(transport="streamable-http") +``` + +
+ ### Client-side: Receiving Notifications + The client must implement a message handler to process and display notifications as they arrive. +
+Python + ```python async def message_handler(message): if isinstance(message, types.ServerNotification): print("NOTIFICATION:", message) else: print("SERVER MESSAGE:", message) + +async with ClientSession( + read_stream, + write_stream, + logging_callback=logging_collector, + message_handler=message_handler, +) as session: ``` +In the preceding code, the `message_handler` function checks if the incoming message is a notification. If it is, it prints the notification; otherwise, it processes it as a regular server message. Also note how the `ClientSession` is initialized with the `message_handler` to handle incoming notifications. + +
+ To enable notifications, ensure your server uses a streaming transport (like `streamable-http`) and your client implements a message handler to process notifications. ---- +## Progress Notifications & Scenarios + +This section explains the concept of progress notifications in MCP, why they matter, and how to implement them using Streamable HTTP. You'll also find a practical assignment to reinforce your understanding. + +Progress notifications are real-time messages sent from the server to the client during long-running operations. Instead of waiting for the entire process to finish, the server keeps the client updated about the current status. This improves transparency, user experience, and makes debugging easier. + +**Example:** + +```text + +"Processing document 1/10" +"Processing document 2/10" +... +"Processing complete!" + +``` + +### Why Use Progress Notifications? + +Progress notifications are essential for several reasons: + +- **Better user experience:** Users see updates as work progresses, not just at the end. +- **Real-time feedback:** Clients can display progress bars or logs, making the app feel responsive. +- **Easier debugging and monitoring:** Developers and users can see where a process might be slow or stuck. + +### How to Implement Progress Notifications + +Here's how you can implement progress notifications in MCP: -## 5. Streamable HTTP Transport +
+Python -### What is Streamable HTTP? -Streamable HTTP is a transport mechanism in MCP that uses HTTP POST requests and supports streaming notifications (such as progress updates) from the server to the client. It is designed for modern web and cloud environments. +- **On the server:** Use `ctx.info()` or `ctx.log()` to send notifications as each item is processed. This sends a message to the client before the main result is ready. +- **On the client:** Implement a message handler that listens for and displays notifications as they arrive. This handler distinguishes between notifications and the final result. + +**Server Example:** + +
+Python + +```python +@mcp.tool(description="A tool that sends progress notifications") +async def process_files(message: str, ctx: Context) -> TextContent: + for i in range(1, 11): + await ctx.info(f"Processing document {i}/10") + await ctx.info("Processing complete!") + return TextContent(type="text", text=f"Done: {message}") +``` -### How It Works -- The client sends a request to the server using HTTP. -- The server can send notifications (e.g., progress, logs) back to the client while processing the request. -- The final response is sent when processing is complete. +
-### Benefits Over SSE and stdio -- **Better scalability**: Works well with load balancers and cloud deployments. -- **Stateless and stateful modes**: Supports both, with resumability. -- **Rich notifications**: Send progress, logs, and other events during processing. -- **Standard HTTP**: Easier to integrate with existing infrastructure. +**Client Example:** -### Implementation Details -- Uses JSON or SSE for response formats. -- Requires clients to implement a message handler to process notifications. -- Supports both synchronous and asynchronous tool execution. +
+Python -### Example Use Cases -- Long-running document processing with progress updates -- Real-time AI inference with streaming logs -- Multi-client collaborative tools +```python +async def message_handler(message): + if isinstance(message, types.ServerNotification): + print("NOTIFICATION:", message) + else: + print("SERVER MESSAGE:", message) +``` ---- +
-## 6. Security Considerations +## Security Considerations + +When implementing MCP servers with HTTP-based transports, security becomes a paramount concern that requires careful attention to multiple attack vectors and protection mechanisms. ### Overview + Security is critical when exposing MCP servers over HTTP. Streamable HTTP introduces new attack surfaces and requires careful configuration. ### Key Points @@ -221,134 +366,20 @@ Security is critical when exposing MCP servers over HTTP. Streamable HTTP introd - Balancing security with ease of development - Ensuring compatibility with various client environments ---- - -## 7. Implementation Guide -This section provides a step-by-step guide to building, running, and understanding an MCP streaming server and client using the streamable HTTP transport. +## Upgrading from SSE to Streamable HTTP -### Overview -- You will set up an MCP server that streams progress notifications to the client as it processes items. -- The client will display each notification in real time. -- This guide covers prerequisites, setup, running, and troubleshooting. - -### Prerequisites -- Python 3.9 or newer -- The `mcp` Python package (install with `pip install mcp`) - -### Installation & Setup -1. **Create and activate a virtual environment (recommended):** - ```pwsh - python -m venv venv - .\venv\Scripts\Activate.ps1 # On Windows - # or - source venv/bin/activate # On Linux/macOS - ``` -2. **Install required dependencies:** - ```pwsh - pip install mcp fastapi uvicorn requests - ``` - -### Example Files -- **Server:** [server.py](./server.py) -- **Client:** [client.py](./client.py) - -### Running the Classic HTTP Streaming Server -1. Navigate to the solution directory: - ```pwsh - cd 03-GettingStarted/06-http-streaming/solution - ``` -2. Start the classic HTTP streaming server: - ```pwsh - python server.py - ``` -3. The server will start and display: - ``` - Starting FastAPI server for classic HTTP streaming... - INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) - ``` - -### Running the Classic HTTP Streaming Client -1. Open a new terminal (activate the same virtual environment and directory): - ```pwsh - cd 03-GettingStarted/06-http-streaming/solution - python client.py - ``` -2. You should see streamed messages printed sequentially: - ``` - Running classic HTTP streaming client... - Connecting to http://localhost:8000/stream with message: hello - --- Streaming Progress --- - Processing file 1/3... - Processing file 2/3... - Processing file 3/3... - Here's the file content: hello - --- Stream Ended --- - ``` - -### Running the MCP Streaming Server -1. Navigate to the solution directory: - ```pwsh - cd 03-GettingStarted/06-http-streaming/solution - ``` -2. Start the MCP server with the streamable-http transport: - ```pwsh - python server.py mcp - ``` -3. The server will start and display: - ``` - Starting MCP server with streamable-http transport... - INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) - ``` - -### Running the MCP Streaming Client -1. Open a new terminal (activate the same virtual environment and directory): - ```pwsh - cd 03-GettingStarted/06-http-streaming/solution - python client.py mcp - ``` -2. You should see notifications printed in real time as the server processes each item: - ``` - Running MCP client... - Starting client... - Session ID before init: None - Session ID after init: a30ab7fca9c84f5fa8f5c54fe56c9612 - Session initialized, ready to call tools. - Received message: root=LoggingMessageNotification(...) - NOTIFICATION: root=LoggingMessageNotification(...) - ... - Tool result: meta=None content=[TextContent(type='text', text='Processed files: file_1.txt, file_2.txt, file_3.txt | Message: hello from client')] - ``` - -### Key Implementation Steps -1. **Create the MCP server using FastMCP.** -2. **Define a tool that processes a list and sends notifications using `ctx.info()` or `ctx.log()`.** -3. **Run the server with `transport="streamable-http"`.** -4. **Implement a client with a message handler to display notifications as they arrive.** - -### Code Walkthrough -- The server uses async functions and the MCP context to send progress updates. -- The client implements an async message handler to print notifications and the final result. - -### Tips & Troubleshooting -- Use `async/await` for non-blocking operations. -- Always handle exceptions in both server and client for robustness. -- Test with multiple clients to observe real-time updates. -- If you encounter errors, check your Python version and ensure all dependencies are installed. - ---- - -## 8. Upgrading from SSE to Streamable HTTP +For applications currently using Server-Sent Events (SSE), migrating to Streamable HTTP provides enhanced capabilities and better long-term sustainability for your MCP implementations. ### Why Upgrade? - Streamable HTTP offers better scalability, compatibility, and richer notification support than SSE. - It is the recommended transport for new MCP applications. ### Migration Steps -1. **Update server code** to use `transport="streamable-http"` in `mcp.run()`. -2. **Update client code** to use `streamablehttp_client` instead of SSE client. -3. **Implement a message handler** in the client to process notifications. -4. **Test for compatibility** with existing tools and workflows. +- **Update server code** to use `transport="streamable-http"` in `mcp.run()`. +- **Update client code** to use `streamablehttp_client` instead of SSE client. +- **Implement a message handler** in the client to process notifications. +- **Test for compatibility** with existing tools and workflows. ### Maintaining Compatibility - You can support both SSE and Streamable HTTP by running both transports on different endpoints. @@ -358,70 +389,103 @@ This section provides a step-by-step guide to building, running, and understandi - Ensuring all clients are updated - Handling differences in notification delivery ---- +## Security Considerations -## 9. Progress Notifications & Scenarios +Security should be a top priority when implementing any server, especially when using HTTP-based transports like Streamable HTTP in MCP. -This section explains the concept of progress notifications in MCP, why they matter, and how to implement them using Streamable HTTP. You'll also find a practical assignment to reinforce your understanding. +When implementing MCP servers with HTTP-based transports, security becomes a paramount concern that requires careful attention to multiple attack vectors and protection mechanisms. -### What Are Progress Notifications? -Progress notifications are real-time messages sent from the server to the client during long-running operations. Instead of waiting for the entire process to finish, the server keeps the client updated about the current status. This improves transparency, user experience, and makes debugging easier. +### Overview -**Example:** -- "Processing document 1/10" -- "Processing document 2/10" -- ... -- "Processing complete!" +Security is critical when exposing MCP servers over HTTP. Streamable HTTP introduces new attack surfaces and requires careful configuration. -### Why Use Progress Notifications? -- **Better user experience:** Users see updates as work progresses, not just at the end. -- **Real-time feedback:** Clients can display progress bars or logs, making the app feel responsive. -- **Easier debugging and monitoring:** Developers and users can see where a process might be slow or stuck. +Here are some key security considerations: -### How to Implement Progress Notifications -- **On the server:** Use `ctx.info()` or `ctx.log()` to send notifications as each item is processed. This sends a message to the client before the main result is ready. -- **On the client:** Implement a message handler that listens for and displays notifications as they arrive. This handler distinguishes between notifications and the final result. +- **Origin Header Validation**: Always validate the `Origin` header to prevent DNS rebinding attacks. +- **Localhost Binding**: For local development, bind servers to `localhost` to avoid exposing them to the public internet. +- **Authentication**: Implement authentication (e.g., API keys, OAuth) for production deployments. +- **CORS**: Configure Cross-Origin Resource Sharing (CORS) policies to restrict access. +- **HTTPS**: Use HTTPS in production to encrypt traffic. -**Server Example:** -```python -@mcp.tool(description="A tool that sends progress notifications") -async def process_files(message: str, ctx: Context) -> TextContent: - for i in range(1, 11): - await ctx.info(f"Processing document {i}/10") - await ctx.info("Processing complete!") - return TextContent(type="text", text=f"Done: {message}") -``` +### Best Practices -**Client Example:** -```python -async def message_handler(message): - if isinstance(message, types.ServerNotification): - print("NOTIFICATION:", message) - else: - print("SERVER MESSAGE:", message) -``` +Additionally, here are some best practices to follow when implementing security in your MCP streaming server: +- Never trust incoming requests without validation. +- Log and monitor all access and errors. +- Regularly update dependencies to patch security vulnerabilities. + +### Challenges + +You will face some challenges when implementing security in MCP streaming servers: + +- Balancing security with ease of development +- Ensuring compatibility with various client environments + + +## Upgrading from SSE to Streamable HTTP + +For applications currently using Server-Sent Events (SSE), migrating to Streamable HTTP provides enhanced capabilities and better long-term sustainability for your MCP implementations. + +### Why Upgrade? + +There are two compelling reasons to upgrade from SSE to Streamable HTTP: + +- Streamable HTTP offers better scalability, compatibility, and richer notification support than SSE. +- It is the recommended transport for new MCP applications. + +### Migration Steps + +Here's how you can migrate from SSE to Streamable HTTP in your MCP applications: + +1. **Update server code** to use `transport="streamable-http"` in `mcp.run()`. +2. **Update client code** to use `streamablehttp_client` instead of SSE client. +3. **Implement a message handler** in the client to process notifications. +4. **Test for compatibility** with existing tools and workflows. + +### Maintaining Compatibility + +It's recommended to maintain compatibility with existing SSE clients during the migration process. Here are some strategies: + +- You can support both SSE and Streamable HTTP by running both transports on different endpoints. +- Gradually migrate clients to the new transport. + +### Challenges + +Ensure you address the following challenges during migration: + +- Ensuring all clients are updated +- Handling differences in notification delivery + + +======= +>>>>>>> refs/remotes/origin/copilot/fix-113 ### Assignment: Build Your Own Streaming MCP App **Scenario:** Build an MCP server and client where the server processes a list of items (e.g., files or documents) and sends a notification for each item processed. The client should display each notification as it arrives. **Steps:** + 1. Implement a server tool that processes a list and sends notifications for each item. 2. Implement a client with a message handler to display notifications in real time. 3. Test your implementation by running both server and client, and observe the notifications. -You can use the code samples above as a starting point, or refer to the provided [server.py](./server.py) and [client.py](./client.py) in this chapter for a complete implementation. +[Solution](./solution/README.md) + +## Further Reading & What Next? -## 10. Further Reading & What Next? +To continue your journey with MCP streaming and expand your knowledge, this section provides additional resources and suggested next steps for building more advanced applications. ### Further Reading + - [Microsoft: Introduction to HTTP Streaming](https://learn.microsoft.com/aspnet/core/fundamentals/http-requests?view=aspnetcore-8.0&WT.mc_id=%3Fwt.mc_id%3DMVP_452430#streaming) - [Microsoft: Server-Sent Events (SSE)](https://learn.microsoft.com/azure/application-gateway/for-containers/server-sent-events?tabs=server-sent-events-gateway-api&WT.mc_id=%3Fwt.mc_id%3DMVP_452430) - [Microsoft: CORS in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-8.0&WT.mc_id=%3Fwt.mc_id%3DMVP_452430) - [Python requests: Streaming Requests](https://requests.readthedocs.io/en/latest/user/advanced/#streaming-requests) ### What Next? + - Try building more advanced MCP tools that use streaming for real-time analytics, chat, or collaborative editing. - Explore integrating MCP streaming with frontend frameworks (React, Vue, etc.) for live UI updates. - Next: [Utilising AI Toolkit for VSCode](../07-aitk/README.md) diff --git a/03-GettingStarted/06-http-streaming/solution/README.md b/03-GettingStarted/06-http-streaming/solution/README.md new file mode 100644 index 000000000..57848cd04 --- /dev/null +++ b/03-GettingStarted/06-http-streaming/solution/README.md @@ -0,0 +1,3 @@ +Here's the solutions for each runtime: + +- [Python](./python/README.md) \ No newline at end of file diff --git a/03-GettingStarted/06-http-streaming/solution/python/README.md b/03-GettingStarted/06-http-streaming/solution/python/README.md new file mode 100644 index 000000000..b5efa031e --- /dev/null +++ b/03-GettingStarted/06-http-streaming/solution/python/README.md @@ -0,0 +1,140 @@ +# Running this sample + +Here's how to run the classic HTTP streaming server and client, as well as the MCP streaming server and client using Python. + +### Overview + +- You will set up an MCP server that streams progress notifications to the client as it processes items. +- The client will display each notification in real time. +- This guide covers prerequisites, setup, running, and troubleshooting. + +### Prerequisites + +- Python 3.9 or newer +- The `mcp` Python package (install with `pip install mcp`) + +### Installation & Setup + +1. Clone the repository or download the solution files. + + ```pwsh + git clone https://github.com/microsoft/mcp-for-beginners + ``` + +1. **Create and activate a virtual environment (recommended):** + + ```pwsh + python -m venv venv + .\venv\Scripts\Activate.ps1 # On Windows + # or + source venv/bin/activate # On Linux/macOS + ``` + +1. **Install required dependencies:** + + ```pwsh + pip install "mcp[cli]" + ``` + +### Files + +- **Server:** [server.py](./server.py) +- **Client:** [client.py](./client.py) + +### Running the Classic HTTP Streaming Server + +1. Navigate to the solution directory: + + ```pwsh + cd 03-GettingStarted/06-http-streaming/solution + ``` + +2. Start the classic HTTP streaming server: + + ```pwsh + python server.py + ``` + +3. The server will start and display: + + ``` + Starting FastAPI server for classic HTTP streaming... + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + ``` + +### Running the Classic HTTP Streaming Client + +1. Open a new terminal (activate the same virtual environment and directory): + + ```pwsh + cd 03-GettingStarted/06-http-streaming/solution + python client.py + ``` + +2. You should see streamed messages printed sequentially: + + ```text + Running classic HTTP streaming client... + Connecting to http://localhost:8000/stream with message: hello + --- Streaming Progress --- + Processing file 1/3... + Processing file 2/3... + Processing file 3/3... + Here's the file content: hello + --- Stream Ended --- + ``` + +### Running the MCP Streaming Server + +1. Navigate to the solution directory: + ```pwsh + cd 03-GettingStarted/06-http-streaming/solution + ``` +2. Start the MCP server with the streamable-http transport: + ```pwsh + python server.py mcp + ``` +3. The server will start and display: + ``` + Starting MCP server with streamable-http transport... + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + ``` + +### Running the MCP Streaming Client + +1. Open a new terminal (activate the same virtual environment and directory): + ```pwsh + cd 03-GettingStarted/06-http-streaming/solution + python client.py mcp + ``` +2. You should see notifications printed in real time as the server processes each item: + ``` + Running MCP client... + Starting client... + Session ID before init: None + Session ID after init: a30ab7fca9c84f5fa8f5c54fe56c9612 + Session initialized, ready to call tools. + Received message: root=LoggingMessageNotification(...) + NOTIFICATION: root=LoggingMessageNotification(...) + ... + Tool result: meta=None content=[TextContent(type='text', text='Processed files: file_1.txt, file_2.txt, file_3.txt | Message: hello from client')] + ``` + +### Key Implementation Steps + +1. **Create the MCP server using FastMCP.** +2. **Define a tool that processes a list and sends notifications using `ctx.info()` or `ctx.log()`.** +3. **Run the server with `transport="streamable-http"`.** +4. **Implement a client with a message handler to display notifications as they arrive.** + +### Code Walkthrough +- The server uses async functions and the MCP context to send progress updates. +- The client implements an async message handler to print notifications and the final result. + +### Tips & Troubleshooting + +- Use `async/await` for non-blocking operations. +- Always handle exceptions in both server and client for robustness. +- Test with multiple clients to observe real-time updates. +- If you encounter errors, check your Python version and ensure all dependencies are installed. + diff --git a/03-GettingStarted/06-http-streaming/solution/client.py b/03-GettingStarted/06-http-streaming/solution/python/client.py similarity index 100% rename from 03-GettingStarted/06-http-streaming/solution/client.py rename to 03-GettingStarted/06-http-streaming/solution/python/client.py diff --git a/03-GettingStarted/06-http-streaming/solution/server.py b/03-GettingStarted/06-http-streaming/solution/python/server.py similarity index 100% rename from 03-GettingStarted/06-http-streaming/solution/server.py rename to 03-GettingStarted/06-http-streaming/solution/python/server.py