Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In deze zelfstudie bouwt u een MCP-server (Model Context Protocol) die hulpprogramma's voor taakbeheer beschikbaar maakt met behulp van Spring Boot en de MCP Java SDK. U implementeert de server in Azure Container Apps en maakt er verbinding mee vanuit GitHub Copilot Chat in VS Code.
In deze handleiding leert u:
- Een Spring Boot-app maken die MCP-hulpprogramma's beschikbaar maakt
- De MCP-server lokaal testen met GitHub Copilot
- De app containeriseren en implementeren in Azure Container Apps
- GitHub Copilot verbinden met de geïmplementeerde MCP-server
Vereiste voorwaarden
- Een Azure-account met een actief abonnement. Maak gratis een account.
- Azure CLI versie 2.62.0 of hoger.
- Java 17 of hoger.
- Maven 3.8 of hoger.
- Visual Studio Code met de GitHub Copilot-extensie .
- Docker Desktop (optioneel: alleen nodig om de container lokaal te testen).
De scaffold van de app maken
In deze sectie maakt u een nieuw Spring Boot-project met de MCP Java SDK.
Maak de projectmap:
mkdir tasks-mcp-server && cd tasks-mcp-serverMaken
pom.xml:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.0</version> </parent> <groupId>com.example</groupId> <artifactId>tasks-mcp-server</artifactId> <version>1.0.0</version> <name>tasks-mcp-server</name> <description>MCP server for task management on Azure Container Apps</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.modelcontextprotocol.sdk</groupId> <artifactId>mcp-spring-webmvc</artifactId> <version>0.10.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Hiermee
pom.xmldefinieert u een Spring Boot-toepassing met twee belangrijke afhankelijkheden:spring-boot-starter-webvoor het webframework enmcp-spring-webmvcvoor de MCP SDK. De Spring Boot Maven-invoegtoepassing verpakt de app als een uitvoerbaar JAR-bestand.Opmerking
De MCP Java SDK wordt actief ontwikkeld. Controleer de MCP Java SDK-releases voor de nieuwste versie en werk de versie
<version>dienovereenkomstig bij.Maak de mapstructuur:
mkdir -p src/main/java/com/example/tasksmcp mkdir -p src/main/resourcesMaken
src/main/resources/application.properties:server.port=8080
Het gegevensmodel definiëren en opslaan
In deze sectie definieert u het taakgegevensmodel en een in-memory archief.
Maken
src/main/java/com/example/tasksmcp/TaskItem.java:package com.example.tasksmcp; import java.time.Instant; public class TaskItem { private int id; private String title; private String description; private boolean isComplete; private Instant createdAt; public TaskItem(int id, String title, String description, boolean isComplete) { this.id = id; this.title = title; this.description = description; this.isComplete = isComplete; this.createdAt = Instant.now(); } // Getters and setters public int getId() { return id; } public String getTitle() { return title; } public String getDescription() { return description; } public boolean isComplete() { return isComplete; } public void setComplete(boolean complete) { isComplete = complete; } public Instant getCreatedAt() { return createdAt; } }De
TaskItemklasse definieert het gegevensmodel met standaard getters en een setter voor de voltooiingsstatus. De constructor initialiseert decreatedAttijdstempel automatisch.Maken
src/main/java/com/example/tasksmcp/TaskStore.java:package com.example.tasksmcp; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @Component public class TaskStore { private final List<TaskItem> tasks = new ArrayList<>(); private final AtomicInteger nextId = new AtomicInteger(3); public TaskStore() { tasks.add(new TaskItem(1, "Buy groceries", "Milk, eggs, bread", false)); tasks.add(new TaskItem(2, "Write docs", "Draft the MCP tutorial", true)); } public List<TaskItem> getAll() { return List.copyOf(tasks); } public Optional<TaskItem> getById(int id) { return tasks.stream().filter(t -> t.getId() == id).findFirst(); } public TaskItem create(String title, String description) { TaskItem task = new TaskItem(nextId.getAndIncrement(), title, description, false); tasks.add(task); return task; } public Optional<TaskItem> toggleComplete(int id) { return getById(id).map(task -> { task.setComplete(!task.isComplete()); return task; }); } public boolean delete(int id) { return tasks.removeIf(t -> t.getId() == id); } }Het
TaskStoreSpring-onderdeel beheert een in-memory lijst die vooraf is ingevuld met voorbeeldgegevens. Het gebruiktAtomicIntegervoor het genereren van thread-veilige id's en biedt methoden voor standaard CRUD-bewerkingen.
Definieer de MCP-hulpprogramma's
In deze sectie definieert u de MCP-hulpprogramma's die het AI-model kan aanroepen en configureren van de MCP-server in uw Spring Boot-toepassing.
Maken
src/main/java/com/example/tasksmcp/TasksMcpTools.java:package com.example.tasksmcp; import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; import io.modelcontextprotocol.spec.McpSchema.TextContent; import io.modelcontextprotocol.spec.McpSchema.Tool; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; @Component public class TasksMcpTools { private final TaskStore store; private final ObjectMapper objectMapper = new ObjectMapper(); public TasksMcpTools(TaskStore store) { this.store = store; } public List<SyncToolSpecification> getToolSpecifications() { return List.of( listTasksTool(), getTaskTool(), createTaskTool(), toggleTaskCompleteTool(), deleteTaskTool() ); } private SyncToolSpecification listTasksTool() { var tool = new Tool( "list_tasks", "List all tasks with their ID, title, description, and completion status.", emptySchema() ); return new SyncToolSpecification(tool, (exchange, request) -> { try { String json = objectMapper.writeValueAsString(store.getAll()); return new CallToolResult(List.of(new TextContent(json)), false); } catch (Exception e) { return errorResult(e.getMessage()); } }); } private SyncToolSpecification getTaskTool() { var tool = new Tool( "get_task", "Get a single task by its numeric ID.", objectSchema(Map.of( "task_id", propertySchema("integer", "The numeric ID of the task to retrieve") ), List.of("task_id")) ); return new SyncToolSpecification(tool, (exchange, request) -> { int taskId = ((Number) request.arguments().get("task_id")).intValue(); return store.getById(taskId) .map(task -> { try { return new CallToolResult( List.of(new TextContent(objectMapper.writeValueAsString(task))), false); } catch (Exception e) { return errorResult(e.getMessage()); } }) .orElse(textResult("Task with ID " + taskId + " not found.")); }); } private SyncToolSpecification createTaskTool() { var tool = new Tool( "create_task", "Create a new task with the given title and description. Returns the created task.", objectSchema(Map.of( "title", propertySchema("string", "A short title for the task"), "description", propertySchema("string", "A detailed description of what the task involves") ), List.of("title", "description")) ); return new SyncToolSpecification(tool, (exchange, request) -> { String title = (String) request.arguments().get("title"); String description = (String) request.arguments().get("description"); TaskItem task = store.create(title, description); try { return new CallToolResult( List.of(new TextContent(objectMapper.writeValueAsString(task))), false); } catch (Exception e) { return errorResult(e.getMessage()); } }); } private SyncToolSpecification toggleTaskCompleteTool() { var tool = new Tool( "toggle_task_complete", "Toggle a task's completion status between complete and incomplete.", objectSchema(Map.of( "task_id", propertySchema("integer", "The numeric ID of the task to toggle") ), List.of("task_id")) ); return new SyncToolSpecification(tool, (exchange, request) -> { int taskId = ((Number) request.arguments().get("task_id")).intValue(); return store.toggleComplete(taskId) .map(task -> textResult( "Task " + task.getId() + " is now " + (task.isComplete() ? "complete" : "incomplete") + ".")) .orElse(textResult("Task with ID " + taskId + " not found.")); }); } private SyncToolSpecification deleteTaskTool() { var tool = new Tool( "delete_task", "Delete a task by its numeric ID.", objectSchema(Map.of( "task_id", propertySchema("integer", "The numeric ID of the task to delete") ), List.of("task_id")) ); return new SyncToolSpecification(tool, (exchange, request) -> { int taskId = ((Number) request.arguments().get("task_id")).intValue(); boolean deleted = store.delete(taskId); return textResult(deleted ? "Task " + taskId + " deleted." : "Task with ID " + taskId + " not found."); }); } // Helper methods for JSON Schema construction private String emptySchema() { return "{\"type\":\"object\",\"properties\":{}}"; } private String objectSchema(Map<String, String> properties, List<String> required) { ObjectNode schema = objectMapper.createObjectNode(); schema.put("type", "object"); ObjectNode propsNode = objectMapper.createObjectNode(); for (var entry : properties.entrySet()) { try { propsNode.set(entry.getKey(), objectMapper.readTree(entry.getValue())); } catch (Exception e) { propsNode.putObject(entry.getKey()).put("type", "string"); } } schema.set("properties", propsNode); schema.set("required", objectMapper.valueToTree(required)); return schema.toString(); } private String propertySchema(String type, String description) { return "{\"type\":\"" + type + "\",\"description\":\"" + description + "\"}"; } private CallToolResult textResult(String text) { return new CallToolResult(List.of(new TextContent(text)), false); } private CallToolResult errorResult(String message) { return new CallToolResult(List.of(new TextContent("Error: " + message)), true); } }Opmerking
Het MCP Java SDK API-oppervlak ontwikkelt zich. Het hier weergegeven patroon voor hulpprogrammaregistratie gebruikt
SyncToolSpecificationvoor synchrone hulpprogramma's. Raadpleeg de MCP Java SDK-documentatie voor de nieuwste idiomen en aantekeningen die de registratie van hulpprogramma's kunnen vereenvoudigen.Maken
src/main/java/com/example/tasksmcp/McpConfig.java:package com.example.tasksmcp; import io.modelcontextprotocol.server.McpServer; import io.modelcontextprotocol.server.McpSyncServer; import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class McpConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // For production, restrict allowedOrigins to specific trusted domains registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "DELETE", "OPTIONS") .allowedHeaders("*"); } @Bean public WebMvcSseServerTransportProvider mcpTransportProvider() { return new WebMvcSseServerTransportProvider(new com.fasterxml.jackson.databind.ObjectMapper(), "/mcp"); } @Bean public McpSyncServer mcpServer(WebMvcSseServerTransportProvider transport, TasksMcpTools tools) { McpSyncServer server = McpServer.sync(transport) .serverInfo("TasksMCP", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); tools.getToolSpecifications().forEach(server::addTool); return server; } }Belangrijke punten:
-
WebMvcSseServerTransportProviderregistreert het SSE-transport op het/mcppad. -
McpServer.sync(transport)configureert hulpprogrammamogelijkheden en registreert elke hulpprogrammaspecificatie. - CORS is ingeschakeld omdat GitHub Copilot in VS Code cross-origin-aanvragen doet voor MCP-servers.
Opmerking
In deze zelfstudie wordt gebruikgemaakt
WebMvcSseServerTransportProvidervan (SSE-transport) omdat de MCP Java SDK nog geen stabiel streambaar HTTP-transport biedt. De andere tutorials (.NET, Python, Node.js) maken gebruik van HTTP-streaming. Wanneer de Java SDK streambare HTTP-ondersteuning toevoegt, werkt u de transportprovider dienovereenkomstig bij. Het SSE-transport is volledig compatibel met VS Code Copilot en andere MCP-clients.-
Maken
src/main/java/com/example/tasksmcp/TasksMcpApplication.java:package com.example.tasksmcp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class TasksMcpApplication { public static void main(String[] args) { SpringApplication.run(TasksMcpApplication.class, args); } @GetMapping("/health") public String health() { return "healthy"; } }De hoofdklasse bootstrapt de Spring Boot-toepassing en maakt een
/healtheindpunt beschikbaar voor Container Apps-statustests. MCP-eindpunten retourneren JSON-RPC antwoorden, dus er is een afzonderlijk statuseindpunt nodig voor statuscontroles.
De MCP-server lokaal testen
Voordat u implementeert in Azure, controleert u of de MCP-server werkt door deze lokaal uit te voeren en verbinding te maken vanuit GitHub Copilot.
Bouwen en uitvoeren:
mvn spring-boot:runOpen VS Code en open Copilot Chat en selecteer Agentmodus.
Selecteer de knop Extra en voeg meer hulpprogramma's toe...>MCP-server toevoegen.
Selecteer HTTP (HTTP of Server-Sent-gebeurtenissen) en kies Server-Sent gebeurtenissen (SSE) wanneer u om het transporttype wordt gevraagd.
Belangrijk
In deze zelfstudie wordt gebruikgemaakt van het SSE-transport in plaats van HTTP dat niet kan worden gestreamd. U moet de SSE-optie in VS Code selecteren om de verbinding correct te laten werken.
Voer de server-URL in:
http://localhost:8080/mcpVoer een server-id in:
tasks-mcpSelecteer Werkruimte-instellingen.
Test met: 'Toon alle taken'
Selecteer Doorgaan wanneer Copilot de bevestiging van het MCP-hulpprogramma aanvraagt.
U zou de takenlijst moeten zien die wordt geretourneerd uit uw in-memory opslag.
Aanbeveling
Probeer andere prompts zoals 'Creëer een taak om de PR te controleren', 'Taak 1 markeren als voltooid' of 'Taak 2 verwijderen'.
De toepassing in een container plaatsen
Verpak de toepassing als een Docker-container, zodat u deze lokaal kunt testen voordat u implementeert in Azure.
Maak een
Dockerfilemet een build met meerdere fasen:FROM maven:3.9-eclipse-temurin-17-alpine AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src/ src/ RUN mvn package -DskipTests -B FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]De meerfasige build houdt de uiteindelijke afbeelding klein door de Maven-build te scheiden van de runtime-afbeelding.
Lokaal verifiëren:
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-serverControleer of het gezondheidseindpunt reageert:
curl http://localhost:8080/health
Implementeren in Azure Container Apps
Nadat u de toepassing in een container hebt geplaatst, implementeert u deze in Azure Container Apps met behulp van de Azure CLI. De opdracht az containerapp up bouwt de containerafbeelding in de cloud, zodat u Docker niet nodig hebt op uw machine voor deze stap.
Omgevingsvariabelen instellen:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-java"Maak een resourcegroep en een Container Apps-omgeving:
az group create --name $RESOURCE_GROUP --location $LOCATION az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATIONDe container-app implementeren:
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080CORS configureren:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"Opmerking
Vervang wildcards voor productie door specifieke vertrouwde origins. Zie Beveiligde MCP-servers in Container Apps.
Controleer de implementatie:
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
GitHub Copilot verbinden met de geïmplementeerde server
Nu de MCP-server wordt uitgevoerd in Azure, configureert u VS Code om GitHub Copilot te verbinden met het geïmplementeerde eindpunt.
Maken of bijwerken
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "sse", "url": "https://<your-app-fqdn>/mcp" } } }Vervang
<your-app-fqdn>door de FQDN uit de implementatie-uitvoer.Belangrijk
Dit
"type"moet"sse"zijn omdat in deze handleiding gebruik wordt gemaakt van het SSE-transport. Het gebruik van"http"(streambare HTTP) veroorzaakt verbindingsfouten.Open Copilot Chat in Agent modus in VS Code.
Controleer of
tasks-mcp-serverwordt weergegeven in de Hulpmiddelenlijst. Selecteer indien nodig Start.Test met een prompt zoals 'Welke taken heb ik?'
Configureer schaling voor interactief gebruik
Java-toepassingen hebben langere koude starttijden. Stel een minimumaantal replica's in om de JVM warm te houden:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
Aanbeveling
Overweeg resource-instellingen die geschikt zijn voor de JVM. Minimaal 1 vCPU- en 2-GiB-geheugen wordt aanbevolen voor Spring Boot-toepassingen.
Beveiligingsoverwegingen
In deze zelfstudie wordt gebruikgemaakt van een niet-geverifieerde MCP-server voor het gemak. Voordat u een MCP-server in productie draait, bekijk de volgende aanbevelingen. Wanneer uw MCP-server wordt aangeroepen door een agent die wordt aangedreven door grote taalmodellen (LLM's), moet u rekening houden met promptinjectieaanvallen .
- Verificatie en autorisatie: beveilig uw MCP-server met Microsoft Entra-id. Zie Beveiligde MCP-servers in Container Apps.
-
Invoervalidatie: Bean-validatie (
@Valid,@NotNull,@Size) gebruiken voor validatie van hulpprogrammaparameters. Zie Validatie in Spring Boot. - HTTPS: Azure Container Apps dwingt HTTPS standaard af met automatische TLS-certificaten.
- Minimale bevoegdheid: alleen de hulpprogramma's beschikbaar maken die uw use-case vereist. Vermijd hulpprogramma's die destructieve bewerkingen uitvoeren zonder bevestiging.
- CORS: Toegestane oorsprongen beperken tot vertrouwde domeinen in productie.
- Logboekregistratie en bewaking: MCP-hulpprogramma's aanroepen voor controle met behulp van SLF4J en Azure Monitor.
De hulpbronnen opschonen
Als u deze toepassing niet meer gaat gebruiken, verwijdert u de resourcegroep om alle resources te verwijderen die in deze zelfstudie zijn gemaakt:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait