MCP (Model Context Protocol) – Was ist das? Erklärung für Java
1. Überblick
MCP (Model Context Protocol) ist ein offener Standard, der es LLMs (Large Language Models) ermöglicht, sich auf einheitliche Weise mit Daten, Tools und externen Systemen zu verbinden – statt dass jede AI-Anwendung ihre eigene Integration für jede Datenquelle schreiben muss. Dieser Artikel erklärt, was MCP ist, wie seine Architektur funktioniert, und zeigt dir, wie du einen einfachen MCP-Server mit Spring Boot (via Spring AI) schreibst, um ein Tool bereitzustellen, das ein LLM aufrufen kann. Nach der Lektüre wirst du verstehen, wann MCP sinnvoll ist und wann es nur Over-Engineering ist.
2. Das Problem, das MCP löst
Bevor es MCP gab, musste jede AI-Anwendung (Chatbot, Agent), die auf eine Datenbank, ein Dateisystem oder interne APIs zugreifen wollte, ihren eigenen Adapter schreiben. Wenn du M AI-Anwendungen und N Datenquellen hast, benötigst du bis zu M × N verschiedene Integration-Codes.
MCP standardisiert diese Kommunikationsschicht: Die AI-Anwendung (genannt Host) muss nur einmal MCP sprechen, und jedes System, das einen MCP-Server bereitstellt, kann mit ihr „kommunizieren”. Das ist wie USB-C für elektronische Geräte – ein einheitlicher Standard statt Dutzender verschiedener Anschlusstypen.
Hinweis: MCP ist kein Framework, das REST oder gRPC für normale Service-zu-Service-Kommunikation ersetzt. Es wurde entwickelt, damit LLMs/Agents Tools aufrufen können – nicht für zwei Backend-Microservices, die miteinander sprechen.
3. MCP-Architektur
MCP hat 3 Hauptrollen:
- Host: Die Anwendung, die das LLM enthält (z. B. Claude Desktop, ein selbst geschriebener Agent).
- Client: Eine Komponente im Host, die eine 1-zu-1-Verbindung mit einem MCP-Server unterhält.
- Server: Ein Prozess, der Capabilities (Tools, Resources, Prompts) nach MCP-Standard bereitstellt.
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Host │ 1-zu-1│ MCP Client │ JSON-RPC │ MCP Server │
│ (mit LLM) │◄──────►│ │◄──────────►│ (DB, API, Datei)│
└──────────────┘ └──────────────┘ └──────────────────┘
Die Kommunikation zwischen Client und Server verwendet JSON-RPC 2.0, übertragen über einen von zwei verbreiteten Transport-Mechanismen:
- stdio: Der Server läuft als Child-Prozess; Client und Server kommunizieren über stdin/stdout. Ideal, wenn der Server lokal läuft.
- SSE/HTTP (Streamable HTTP): Wird verwendet, wenn der Server remote läuft, über das Netzwerk.
3.1. Drei Arten von Capabilities, die ein MCP-Server bereitstellen kann
| Capability | Bedeutung | Beispiel |
|---|---|---|
| Tools | Funktionen, die das LLM aufrufen kann, um Aktionen auszuführen | getWeather(city), createOrder(...) |
| Resources | Daten, die das LLM lesen kann (ähnlich wie GET) | Log-Dateien, Datenbankeinträge |
| Prompts | Prompt-Templates, die der Server vordefiniiert | “Zusammenfassung der Bestellung nach Vorlage X” |
Die meisten praktischen Use Cases drehen sich um Tools, daher konzentriert sich das Beispiel unten darauf.
4. Schreiben eines MCP-Servers mit Spring AI
Spring AI (ab Version 1.0.0-M6) unterstützt MCP über einen eigenen Starter, mit dem du ein Java-Bean in ein MCP-Tool verwandeln kannst – nur mit einer Annotation, ohne JSON-RPC selbst zu implementieren.
4.1. Abhängigkeit hinzufügen
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
4.2. Tool definieren
@Service
public class WeatherService {
@Tool(description = "Aktuelle Temperatur einer Stadt abrufen")
public String getTemperature(String city) {
return switch (city.toLowerCase()) {
case "ha noi" -> "28°C, bewölkt";
case "sai gon" -> "32°C, sonnig";
default -> "Keine Daten für Stadt: " + city;
};
}
}
@Toolmarkiert diese Methode als MCP-Tool. Das Attributdescriptionist sehr wichtig – das LLM nutzt diese Beschreibung, um zu entscheiden, ob es das Tool aufrufen soll und mit welchen Parametern.getTemperatureakzeptiert einenString cityund gibt eine Zeichenkette mit der Wetterbeschreibung zurück. Der Parameter und der Rückgabetyp werden vom MCP-Server automatisch in ein JSON-Schema konvertiert, das das LLM versteht.
4.3. Tool beim MCP-Server registrieren
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
MethodToolCallbackProvider scannt Methoden mit @Tool in weatherService und registriert sie automatisch beim MCP-Server, wenn der Application Context startet.
4.4. Server konfigurieren
spring.ai.mcp.server.name=weather-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.stdio=true
Mit stdio=true läuft der Server als eigenständiger Prozess und kommuniziert via stdin/stdout – ideal zum schnellen Testen mit Claude Desktop oder einem selbst geschriebenen MCP-Client. Falls du den Server als unabhängigen Service remote bereitstellen möchtest, wechsle zum Transport SSE und exponiere ihn über einen HTTP-Endpoint, statt stdio.
Server starten:
mvn spring-boot:run
Started WeatherMcpApplication in 1.124 seconds
Registered MCP tool: getTemperature
5. Aufrufen des MCP-Servers vom Client
Angenommen, du schreibst eine Host-Anwendung (z. B. einen internen Agent), die das Tool getTemperature oben aufrufen muss.
5.1. Client-Abhängigkeit
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
5.2. Tool aufrufen
@Component
public class WeatherAgent {
private final McpSyncClient mcpClient;
public WeatherAgent(McpSyncClient mcpClient) {
this.mcpClient = mcpClient;
}
public String askTemperature(String city) {
CallToolRequest request = new CallToolRequest(
"getTemperature",
Map.of("city", city));
CallToolResult result = mcpClient.callTool(request);
return result.content().get(0).text();
}
}
McpSyncClientist ein Client, den Spring AI automatisch als Bean erstellt, basierend auf der Transport-Konfiguration (stdio oder SSE), die inapplication.ymldeklariert ist.CallToolRequestfasst den Tool-Namen (getTemperature) und Parameter (city) in einen JSON-RPC-Call zusammen.result.content()gibt eine Liste von Inhalten der Antwort zurück; hier nehmen wir den erstentext.
Client-Konfiguration (Beispiel: Verbindung zum Server via stdio, indem die Server-JAR als Unterprozess gestartet wird):
spring:
ai:
mcp:
client:
stdio:
connections:
weather-server:
command: java
args: -jar weather-mcp-server.jar
6. Testen von Tools mit JUnit 5 + AssertJ
Da @Tool nur eine Annotation auf einer normalen Java-Methode ist, testst du die Business-Logik unabhängig vom MCP – du musst keinen ganzen Server hochfahren:
class WeatherServiceTest {
private final WeatherService weatherService = new WeatherService();
@Test
void shouldReturnTemperatureForKnownCity() {
String result = weatherService.getTemperature("Ha Noi");
assertThat(result).contains("28°C");
}
@Test
void shouldReturnFallbackMessageForUnknownCity() {
String result = weatherService.getTemperature("Tokyo");
assertThat(result)
.startsWith("Keine Daten für Stadt:")
.contains("Tokyo");
}
}
Falls du die MCP-Integration selbst testen möchtest (wird das Tool mit dem richtigen Namen und richtigem Schema registriert?), kannst du einen Test auf @SpringBootTest-Ebene schreiben und ToolCallbackProvider überprüfen:
@SpringBootTest
class McpServerConfigTest {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
@Test
void shouldExposeTemperatureTool() {
var toolNames = toolCallbackProvider.getToolCallbacks().stream()
.map(callback -> callback.getToolDefinition().name())
.toList();
assertThat(toolNames).contains("getTemperature");
}
}
Dieser Test stellt sicher, dass das Tool mit dem richtigen Namen exponiert wird, auch wenn du WeatherService refaktorierst – verhindert, dass du den Methodennamen änderst und vergisst, diese Änderung zu aktualisieren, wo das LLM das Tool referenziert.
7. Häufige Fehler und wann man MCP nicht verwenden sollte
Tool-Beschreibung ist zu vage. Wenn die description nur “Daten abrufen” sagt, wird das LLM sehr leicht das falsche Tool wählen, wenn es mehrere ähnliche Tools gibt. Schreibe eine spezifische Beschreibung, die Ein- und Ausgaben klar macht.
MCP zur Ersetzung von REST/gRPC intern nutzen. MCP fügt eine JSON-RPC-Schicht und einen eigenen Transport hinzu – optimiert für den Fall, dass ein LLM dynamisch Tools aufruft, nicht für zwei Backend-Services mit hohem Durchsatz und niedriger Latenz. MCP hier zu nutzen ist Over-Engineering.
Keine Zugriffskontrolle auf Tools. Ein MCP-Server läuft typischerweise mit den Berechtigungen des Prozesses, der ihn hostet. Wenn du ein Tool exponierst, das Dateien löschen oder Shell-Befehle ausführen kann – ohne die Eingabeparameter zu validieren – könnte ein LLM (oder Prompt Injection) das Tool missbrauchen. Validiere immer die Parameter im Tool, vertrau nicht blind auf Eingaben vom LLM.
stdio-Server in serverloser Umgebung starten. Transport stdio muss einen Child-Prozess spawnen – funktioniert nicht gut in serverlosen/kurzlebigen Container-Umgebungen. In solchen Fällen nutze Transport SSE/HTTP.
Zwischen Client und Server nicht synchronisierte Versionen. MCP entwickelt sich schnell weiter (viele Breaking Changes zwischen Spec-Versionen). Bevor du das SDK aktualisierst, überprüfe gründlich das Changelog, bevor du in Production gehst.
8. Fazit
MCP standardisiert, wie LLMs Tools aufrufen und externe Daten lesen – es löst das M × N-Integrationsproblem zwischen AI-Anwendungen und Datenquellen. Mit Spring AI kannst du einen Java-Service mit nur einer @Tool-Annotation als MCP-Tool exponieren, ohne JSON-RPC selbst zu implementieren. Nutze MCP, wenn du einen Agent/Chatbot baust, der Tools dynamisch aufrufen soll, basierend auf LLM-Entscheidungen – aber vermeide es für normale Service-zu-Service-Kommunikation, wo REST oder gRPC die bessere Wahl sind.