Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu öğreticide, Spring Boot ve MCP Java SDK'sını kullanarak görev yönetimi araçlarını kullanıma sunan bir Model Bağlam Protokolü (MCP) sunucusu oluşturacaksınız. Sunucuyu Azure Container Apps'e dağıtır ve VS Code'da GitHub Copilot Sohbeti'nden bu sunucuya bağlanırsınız.
Bu eğitimde, siz:
- MCP araçlarını kullanıma sunan bir Spring Boot uygulaması oluşturma
- MCP sunucusunu GitHub Copilot ile yerel olarak test etme
- Uygulamayı kapsayıcıya alma ve Azure Container Apps'e dağıtma
- GitHub Copilot'ı dağıtılan MCP sunucusuna bağlama
Önkoşullar
- Aktif bir aboneliğe sahip bir Azure hesabı. Ücretsiz bir tane oluşturun.
- Azure CLI sürüm 2.62.0 veya üzeri.
- Java 17 veya üzeri.
- Maven 3.8 veya üzeri.
- GitHub Copilot uzantısına sahip Visual Studio Code.
- Docker Desktop (isteğe bağlı - yalnızca kapsayıcıyı yerel olarak test etmek için gereklidir).
Uygulama iskelesini oluşturma
Bu bölümde, MCP Java SDK'sı ile yeni bir Spring Boot projesi oluşturacaksınız.
Proje dizinini oluşturun:
mkdir tasks-mcp-server && cd tasks-mcp-serverOluştur
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>pom.xmliki temel bağımlılığa sahip bir Spring Boot uygulamasını tanımlar: web çerçevesi içinspring-boot-starter-webve MCP SDK'sı içinmcp-spring-webmvc. Spring Boot Maven eklentisi, uygulamayı yürütülebilir JAR olarak paketler.Uyarı
MCP Java SDK'sı etkin geliştirme aşamasındadır. En son sürüm için MCP Java SDK sürümlerini denetleyin ve uygun şekilde güncelleştirin
<version>.Dizin yapısını oluşturun:
mkdir -p src/main/java/com/example/tasksmcp mkdir -p src/main/resourcesOluştur
src/main/resources/application.properties:server.port=8080
Veri modelini ve depoyu tanımlama
Bu bölümde, görev veri modelini ve bellek içi depoyu tanımlarsınız.
Oluştur
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; } }sınıfı,
TaskItemveri modelini standart alıcılarla ve tamamlanma durumu için bir ayarlayıcıyla tanımlar. Oluşturucu, zaman damgasınıncreatedAtilk değerini otomatik olarak atar.Oluşturma
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); } }TaskStoreSpring bileşeni, örnek verilerle önceden doldurulmuş bir bellek içi listeyi yönetir.AtomicIntegeriş parçacığı güvenli kimlik oluşturma için kullanılır ve standart CRUD işlemleri için yöntemler sağlar.
MCP araçlarını tanımlama
Bu bölümde, Spring Boot uygulamanızda yapay zeka modelinin MCP sunucusunu çağırabileceği ve yapılandırabileceği MCP araçlarını tanımlarsınız.
Oluşturma
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); } }Uyarı
MCP Java SDK API'sinin yüzeyi gelişiyor. Burada gösterilen araç kayıt düzeni, zaman uyumlu araçlar için
SyncToolSpecificationkullanır. Araç kaydını basitleştirebilecek en son deyimler ve ek açıklamalar için MCP Java SDK belgelerine bakın.Oluştur
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; } }Önemli noktalar:
-
WebMvcSseServerTransportProviderSSE aktarımını/mcpyola kaydeder. -
McpServer.sync(transport)araç özelliklerini yapılandırıp her araç belirtimlerini kaydeder. - VS Code'daki GitHub Copilot, MCP sunucularına çıkış noktaları arası isteklerde bulunduğundan CORS etkinleştirilir.
Uyarı
MCP Java SDK henüz kararlı bir akışlı HTTP taşıması sunmadığından, bu öğreticide
WebMvcSseServerTransportProvider(SSE taşıması) kullanılır. Diğer dil öğreticilerinde (.NET, Python, Node.js) akışla aktarılabilir HTTP kullanılır. Java SDK'sı akışla aktarılabilir HTTP desteği eklediğinde, aktarım sağlayıcısını uygun şekilde güncelleştirin. SSE aktarımı VS Code Copilot ve diğer MCP istemcileri ile tam olarak uyumludur.-
Oluştur
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"; } }Ana sınıf, Spring Boot uygulamasını başlatır ve Container Apps sistem durumu yoklamaları için bir
/healthuç noktası sunar. MCP uç noktaları JSON-RPC yanıtları döndürür, bu nedenle sistem durumu denetimleri için ayrı bir sistem durumu uç noktası gerekir.
MCP sunucusunu yerel olarak test edin
Azure'a dağıtmadan önce MCP sunucusunun yerel olarak çalıştırıp GitHub Copilot'tan bağlanarak çalıştığını doğrulayın.
Derleme ve çalıştırma:
mvn spring-boot:runVS Code'ı açın, ardından Copilot Sohbet'i açın ve Aracı modu'nu seçin.
Araçlar düğmesini ve ardından Daha Fazla Araç Ekle... öğesini seçin.>MCP Sunucusu ekleyin.
HTTP (HTTP veya Server-Sent Olayları) öğesini seçin ve aktarım türü sorulduğunda Server-Sent Olaylar'ı (SSE) seçin.
Önemli
Bu öğreticide akışla aktarılabilir HTTP değil SSE aktarımı kullanılır. Bağlantının düzgün çalışması için VS Code'da SSE seçeneğini belirlemeniz gerekir.
Sunucu URL'sini girin:
http://localhost:8080/mcpBir sunucu kimliği girin:
tasks-mcpÇalışma Alanı Ayarları'nı seçin.
Şu şekilde test edin: "Tüm görevleri göster"
Copilot MCP aracı onayı istediğinde Devam'ı seçin.
Bellek içi deponuzdan döndürülen görev listesini görmeniz gerekir.
Tavsiye
"PR'yi gözden geçirmek için bir görev oluştur", "Görev 1'i tamamlanmış olarak işaretle" veya "Görev 2'yi sil" gibi diğer istemleri deneyin.
Uygulamayı kapsayıcılı hale getirme
Uygulamayı Docker kapsayıcısı olarak paketleyerek Azure'a dağıtmadan önce yerel olarak test edebilirsiniz.
Çok aşamalı derleme ile bir
Dockerfileoluşturun: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"]Çok aşamalı oluşturma, Maven derlemesini çalışma zamanı görüntüsünden ayırarak nihai görüntünün boyutunu küçük tutar.
Yerel olarak doğrula:
docker build -t tasks-mcp-server . docker run -p 8080:8080 tasks-mcp-serverSağlık uç noktasının yanıt verdiğini onaylayın:
curl http://localhost:8080/health
Azure Container Apps'a dağıtım
Uygulamayı kapsayıcıya aldıktan sonra Azure CLI kullanarak Azure Container Apps'e dağıtın. komutu az containerapp up kapsayıcı görüntüsünü bulutta oluşturur, bu nedenle bu adım için makinenizde Docker'a ihtiyacınız yoktur.
Ortam değişkenlerini ayarlama:
RESOURCE_GROUP="mcp-tutorial-rg" LOCATION="eastus" ENVIRONMENT_NAME="mcp-env" APP_NAME="tasks-mcp-server-java"Kaynak grubu ve Container Apps ortamı oluşturma:
az group create --name $RESOURCE_GROUP --location $LOCATION az containerapp env create \ --name $ENVIRONMENT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATIONKapsayıcı uygulamasını dağıtma:
az containerapp up \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --environment $ENVIRONMENT_NAME \ --source . \ --ingress external \ --target-port 8080CORS'yi yapılandırma:
az containerapp ingress cors enable \ --name $APP_NAME \ --resource-group $RESOURCE_GROUP \ --allowed-origins "*" \ --allowed-methods "GET,POST,DELETE,OPTIONS" \ --allowed-headers "*"Uyarı
Üretim için joker karakter çıkış noktalarını belirli güvenilir kaynaklarla değiştirin. Bkz . Container Apps'te MCP sunucularının güvenliğini sağlama.
Dağıtımı doğrulayın:
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'ı dağıtılan sunucuya bağlama
MCP sunucusu Azure'da çalıştığına göre VS Code'u GitHub Copilot'ı dağıtılan uç noktaya bağlamak için yapılandırın.
oluşturun veya güncelleştirin
.vscode/mcp.json:{ "servers": { "tasks-mcp-server": { "type": "sse", "url": "https://<your-app-fqdn>/mcp" } } }<your-app-fqdn>değerini dağıtım çıktısından FQDN ile değiştirin.Önemli
"type"olmalıdır çünkü bu öğretici SSE aktarımını kullanmaktadır"sse". ("http"Akışla aktarılabilir HTTP) kullanılması bağlantı hatalarına neden olur.VS Code'da Aracı modunda Copilot Sohbet'i açın.
Doğrula
tasks-mcp-server, Araçlar listesinde görünür. Gerekirse Başlat'ı seçin."Hangi görevlerim var?" gibi bir istemle test edin
Etkileşimli kullanım için ölçeklendirmeyi yapılandırma
Java uygulamalarının daha uzun soğuk başlangıç süreleri vardır. JVM'yi sıcak tutmak için en düşük çoğaltma sayısını ayarlayın:
az containerapp update \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 1
Tavsiye
JVM için uygun kaynak ayarlarını göz önünde bulundurun. Spring Boot uygulamaları için en az 1 vCPU ve 2 GiB bellek önerilir.
Güvenlik konuları
Bu öğreticide basitlik için kimliği doğrulanmamış bir MCP sunucusu kullanılır. Üretimde bir MCP sunucusu çalıştırmadan önce aşağıdaki önerileri gözden geçirin. MCP sunucunuz, büyük dil modelleri (LLM'ler) tarafından desteklenen bir aracı tarafından çağrıldığında, kod ekleme saldırılarına dikkat edin.
- Kimlik doğrulaması ve yetkilendirme: Microsoft Entra Id ile MCP sunucunuzun güvenliğini sağlayın. Bkz . Container Apps'te MCP sunucularının güvenliğini sağlama.
-
Giriş doğrulaması: Araç parametresi doğrulaması için Fasulye Doğrulama (
@Valid,@NotNull,@Size) kullanın. Bkz . Spring Boot'ta Doğrulama. - HTTPS: Azure Container Apps, otomatik TLS sertifikaları ile varsayılan olarak HTTPS'nin zorunlu kılınmasını sağlar.
- En az ayrıcalık: Yalnızca kullanım örneğinizin gerektirdiği araçları kullanıma sunma. Onay olmadan yıkıcı işlemler gerçekleştiren araçlardan kaçının.
- CORS: İzin verilen çıkış noktalarını üretimdeki güvenilen etki alanlarıyla kısıtlayın.
- Günlüğe kaydetme ve izleme: Denetim için SLF4J ve Azure İzleyici kullanarak MCP aracı çağrılarını günlüğe kaydedin.
Kaynakları temizle
Bu uygulamayı kullanmaya devam etmeyecekseniz, bu öğreticide oluşturulan tüm kaynakları kaldırmak için kaynak grubunu silin:
az group delete --resource-group $RESOURCE_GROUP --yes --no-wait