Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ebben az oktatóanyagban létrehoz egy Model Context Protocol (MCP) kiszolgálót, amely a Spring Boot és az MCP Java SDK használatával teszi elérhetővé a feladatkezelő eszközöket. Üzembe helyezheti a kiszolgálót az Azure Container Appsben, és csatlakozhat hozzá a GitHub Copilot Chatből a VS Code-ban.
Ebben az útmutatóban Ön:
- McP-eszközöket elérhetővé tevő Spring Boot-alkalmazás létrehozása
- Az MCP-kiszolgáló helyi tesztelése a GitHub Copilottal
- Az alkalmazás tárolóba helyezése és üzembe helyezése az Azure Container Appsben
- A GitHub Copilot csatlakoztatása az üzembe helyezett MCP-kiszolgálóhoz
Előfeltételek
- Egy Azure-fiók, aktív előfizetéssel. Hozzon létre egyet ingyen.
- Az Azure CLI 2.62.0-s vagy újabb verziója.
- Java 17 vagy újabb verzió.
- Maven 3.8 vagy újabb.
- Visual Studio Code a GitHub Copilot bővítményével.
- Docker Desktop (nem kötelező – csak a tároló helyi teszteléséhez szükséges).
Az alkalmazásállvány létrehozása
Ebben a szakaszban egy új Spring Boot-projektet hoz létre az MCP Java SDK-val.
Hozza létre a projektkönyvtárat:
mkdir tasks-mcp-server && cd tasks-mcp-serverLétrehoz
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>A
pom.xmlrendszer két fő függőséggel rendelkező Spring Boot-alkalmazást határoz meg:spring-boot-starter-weba webes keretrendszerhez ésmcp-spring-webmvcaz MCP SDK-hoz. A Spring Boot Maven beépülő modul végrehajtható JAR-ként csomagolja be az alkalmazást.Megjegyzés:
Az MCP Java SDK aktív fejlesztés alatt áll. Ellenőrizze az MCP Java SDK legújabb verzióját, és frissítse ennek
<version>megfelelően.Hozza létre a címtárstruktúrát:
mkdir -p src/main/java/com/example/tasksmcp mkdir -p src/main/resourcesLétrehoz
src/main/resources/application.properties:server.port=8080
Az adatmodell és az adattár definiálása
Ebben a szakaszban definiálja a tevékenység adatmodellt és egy memórián belüli tárolót.
Létrehoz
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; } }Az
TaskItemosztály az adatmodellt standard getterekkel és egy beállítóval határozza meg a befejezési állapothoz. A konstruktor automatikusan inicializálja azcreatedAtidőbélyeget.Létrehoz
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); } }A
TaskStoreSpring-összetevő a mintaadatokkal előre feltöltött memóriabeli listát kezeli. Szálbiztos azonosítók létrehozására szolgálAtomicInteger, és szabványos CRUD-műveletekhez biztosít metódusokat.
Az MCP-eszközök definiálása
Ebben a szakaszban definiálja azokat az MCP-eszközöket, amelyeket az AI-modell meghívhat és konfigurálhat az MCP-kiszolgálót a Spring Boot-alkalmazásban.
Létrehoz
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); } }Megjegyzés:
Az MCP Java SDK API felülete folyamatosan fejlődik. Az itt látható eszközregisztrációs minta szinkron eszközökhöz használható
SyncToolSpecification. Tekintse meg az MCP Java SDK dokumentációjában azokat a legújabb kifejezéseket és széljegyzeteket, amelyek egyszerűsíthetik az eszközregisztrációt.Létrehoz
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; } }Összefoglalás:
-
WebMvcSseServerTransportProviderregisztrálja az SSE-átvitelt az/mcpútvonalon. -
McpServer.sync(transport)konfigurálja az eszköz képességeit, és regisztrálja az egyes eszközspecifikációkat. - A CORS engedélyezve van, mert a VS Code-ban a GitHub Copilot kereszt-forrás kéréseket küld az MCP-kiszolgálóknak.
Megjegyzés:
Ez az oktatóanyag azért használ
WebMvcSseServerTransportProvider(SSE-átvitelt), mert az MCP Java SDK még nem kínál stabil streamelhető HTTP-átvitelt. A többi nyelvi oktatóanyag (.NET, Python, Node.js) streamelhető HTTP-t használ. Amikor a Java SDK streamelhető HTTP-támogatást ad hozzá, ennek megfelelően frissítse az átviteli szolgáltatót. Az SSE-átvitel teljes mértékben kompatibilis a VS Code Copilot és más MCP-ügyfelekkel.-
Létrehoz
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"; } }A főosztály elindítja a Spring Boot-alkalmazást, és elérhetővé tesz egy
/healthvégpontot a Container Apps állapottesztjeihez. Az MCP-végpontok JSON-RPC válaszokat ad vissza, ezért az állapot-ellenőrzésekhez külön állapotvégpontra van szükség.
Az MCP-kiszolgáló helyi tesztelése
Az Azure-ban való üzembe helyezés előtt ellenőrizze, hogy az MCP-kiszolgáló működik-e helyileg futtatva és a GitHub Copilotból való csatlakozással.
Építés és futtatás:
mvn spring-boot:runNyissa meg a VS Code-ot, majd nyissa meg a Copilot-csevegést , és válassza az Ügynök módot.
Válassza az Eszközök gombot, majd a További eszközök hozzáadása...>Adja hozzá az MCP-kiszolgálót.
Válassza az HTTP (HTTP vagy Server-Sent Események) lehetőséget, és válassza a Server-Sent Események (SSE) lehetőséget, amikor a rendszer kéri az átviteli típust.
Fontos
Ez az oktatóanyag az SSE-átvitelt használja, nem streamelhető HTTP-t. Ahhoz, hogy a kapcsolat megfelelően működjön, ki kell választania az SSE lehetőséget a VS Code-ban.
Adja meg a kiszolgáló URL-címét:
http://localhost:8080/mcpAdjon meg egy kiszolgálóazonosítót:
tasks-mcpVálassza a Munkaterület beállításai lehetőséget.
Tesztelés a következővel: "Az összes feladat megjelenítése"
Válassza a Folytatás lehetőséget, amikor a Copilot az MCP eszköz megerősítését kéri.
Látnia kell a memóriabeli tárolóból visszaadott feladatlistát.
Jótanács
Próbálkozzon más kérdésekkel, például a "Feladat létrehozása a kérelem áttekintéséhez", az "1. tevékenység megjelölése befejezettként" vagy a "2. tevékenység törlése" kérdésekkel.
Az alkalmazás tárolóba helyezése
Csomagolja be az alkalmazást Docker-tárolóként, hogy helyileg tesztelhesse, mielőtt üzembe helyezené az Azure-ban.
Hozzon létre egy
Dockerfiletöbbfázisú buildet: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"]A többfázisú build a Maven-buildnek a futásidejű képtől való elválasztásával kis méretűvé teszi a végső képet.
Helyi ellenőrzés:
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-serverEllenőrizze, hogy az állapotvégpont válaszol-e:
curl http://localhost:8080/health
Üzembe helyezés az Azure Container Appsben
Az alkalmazás tárolóba helyezése után helyezze üzembe az Azure Container Appsben az Azure CLI használatával. A az containerapp up parancs létrehozza a tárolórendszerképet a felhőben, így ehhez a lépéshez nincs szükség a Dockerre a gépen.
Környezeti változók beállítása:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-java"Erőforráscsoport és Container Apps-környezet létrehozása:
az group create --name $RESOURCE_GROUP --location $LOCATION az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATIONA tárolóalkalmazás üzembe helyezése:
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080CORS konfigurálása:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"Megjegyzés:
A gyártásban cserélje le a helyettesítő eredeteket meghatározott, megbízható eredetekre. Lásd: Biztonságos MCP-kiszolgálók a Container Appsben.
Ellenőrizze az üzembe helyezést:
APP_URL=$(az containerapp show \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --query "properties.configuration.ingress.fqdn" -o tsv) curl https://$APP_URL/health
A GitHub Copilot csatlakoztatása az üzembe helyezett kiszolgálóhoz
Most, hogy az MCP-kiszolgáló az Azure-ban fut, konfigurálja a VS Code-ot a GitHub Copilot üzembe helyezett végponthoz való csatlakoztatásához.
Létrehozás vagy frissítés
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "sse", "url": "https://<your-app-fqdn>/mcp" } } }Cserélje le
<your-app-fqdn>a központi telepítési kimenet teljes tartománynevét.Fontos
"type"-nek"sse"kell lennie, mert ez az oktatóanyag az SSE-átvitelt használja. A"http"(streamelhető HTTP) használata csatlakozási hibákat okoz.A VS Code-ban nyissa meg a Copilot-csevegést ügynök módban.
Az Ellenőrzés
tasks-mcp-servermegjelenik az Eszközök listában. Szükség esetén válassza a Start lehetőséget.Tesztelje a következőt: "Milyen feladatokkal rendelkezem?"
Skálázás konfigurálása interaktív használatra
A Java-alkalmazások hidegindítási ideje hosszabb. Állítsa be a minimális replikaszámot a JVM melegen tartásához:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
Jótanács
Fontolja meg a JVM-hez megfelelő erőforrás-beállításokat. Spring Boot-alkalmazásokhoz legalább 1 vCPU és 2 GiB memória ajánlott.
Biztonsági szempontok
Ez az oktatóanyag egy nem hitelesített MCP-kiszolgálót használ az egyszerűség kedvéért. Mielőtt éles környezetben futtat egy MCP-kiszolgálót, tekintse át az alábbi javaslatokat. Ha az MCP-kiszolgálót nagy nyelvi modellek (LLM-ek) által működtetett ügynök hívja meg, vegye figyelembe a gyors injektálási támadásokat.
- Hitelesítés és engedélyezés: Az MCP-kiszolgáló védelme a Microsoft Entra-azonosítóval. Lásd: Biztonságos MCP-kiszolgálók a Container Appsben.
-
Bemeneti ellenőrzés: Az eszközparaméter-ellenőrzéshez használja a Bean Validation (
@Valid,@NotNull, )@Sizeparancsot. Lásd : Érvényesítés a Spring Bootban. - HTTPS: Az Azure Container Apps alapértelmezés szerint automatikus TLS-tanúsítványokkal kényszeríti a HTTPS-t.
- Minimális jogosultság: Csak azokat az eszközöket tegye elérhetővé, amelyet a használati eset igényel. Kerülje azokat az eszközöket, amelyek megerősítés nélkül végeznek romboló műveleteket.
- CORS: Az engedélyezett források korlátozása az éles környezetben megbízható tartományokra.
- Naplózás és figyelés: Az MCP-eszközök meghívásainak naplózása az SLF4J és az Azure Monitor használatával.
Erőforrások tisztítása
Ha nem folytatja az alkalmazás használatát, törölje az erőforráscsoportot az oktatóanyagban létrehozott összes erőforrás eltávolításához:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait
Következő lépés
Kapcsolódó tartalom
- McP-kiszolgálók az Azure Container Appsben – áttekintés
- MCP-kiszolgáló üzembe helyezése a Container Appsben (.NET)
- MCP-kiszolgáló üzembe helyezése a Container Appsben (Python)
- MCP-kiszolgáló üzembe helyezése a Container Appsben (Node.js)
- McP-kiszolgálók hibaelhárítása a Container Appsben
- MCP Java SDK