What is an MCP server and why is it different from wiring a tool directly?
When wiring a tool directly into an application, you are responsible for defining the tool’s schema and its functionality. Both live in that application’s code. If three different applications need access to the same external service, each one maintains its own integration. Model Context Protocol, or MCP, separates tool definitions from individual applications and turns them into a process called a server.
An MCP server is a process that exposes tools, resources, and prompts that MCP clients can use.
The practical difference: a tool is the model reaching out to get something when it decides it needs it. A resource is the client pulling known data in before the model even starts. A prompt is packaging expert-level instructions so every developer on your team gets the same quality output without having to know how to write that instruction themselves.
Three things an MCP server exposes — you already know tools, here are the other two.
Resources — read-only data fetched directly into context
A resource is data the server exposes at an address. The client fetches it and places it into context directly — no tool call, no model decision involved. Think of it as a GET request to a known endpoint.
Two forms:
- Direct resource — fixed address, no parameters. The data is always at that address.
- Templated resource — address contains a parameter, resolved at fetch time.
When to use it over a tool: When you know upfront what data you need in context before the turn starts. A tool call is dynamic — the model decides when and whether to call it. A resource is deterministic — the client fetches it directly. Cheaper and more predictable when the data is known.
Practical examples:
# Direct resource — fixed address
mcp://java-tools/conventions/spring-boot-standards
→ Returns your team's Spring Boot coding standards
→ Client fetches this at session start, always in context
# Templated resource — parameterized address
mcp://java-tools/schema/{tableName}
→ mcp://java-tools/schema/orders
→ Returns the DDL for the orders table
→ Client fetches by passing the table name as parameterOther real-world examples:
mcp://jira/project/OMS/open-tickets— current sprint tickets loaded into contextmcp://confluence/runbooks/{serviceName}— runbook for a specific servicemcp://github/repo/accenture-oms/pull-requests/open— open PRs fetched directly
Prompts — pre-written instruction templates invoked by name
A prompt is a vetted instruction template the server exposes so any client can invoke it by name instead of writing their own version. The server maintains it in one place, every connected client gets the same quality.
When a prompt is worth it over just asking Claude: When specific wording materially changes the output quality. For routine questions the user's own words are fine. For specialized tasks — a security review, a database migration audit, an API contract check — a carefully constructed prompt produces consistently better results than whatever each developer would type on the fly.
Practical examples for your Spring Boot context:
# Invoked by name in the client
/prompt: review-db-migration
# What the server actually sends to the model
"Review this Flyway migration file against these criteria:
1. Does it have a matching rollback script in db/rollbacks/?
2. Are all foreign key columns indexed?
3. Does it use CREATE OR REPLACE anywhere (not allowed)?
4. Are all VARCHAR columns explicitly sized?
5. Is the version number in the correct format V{date}_{seq}?
Return findings as: PASS / WARN / FAIL with specific line references."# Another example
/prompt: api-contract-review
# Server sends
"Review this REST controller against our API standards:
1. Are all endpoints using method-specific annotations (@GetMapping etc)?
2. Are all response bodies Java records, never raw entities?
3. Is business logic absent from the controller?
4. Are all error responses using ProblemDetail (RFC 9457)?
5. Does every endpoint have @Operation and @ApiResponse annotations?
Return a checklist with PASS/FAIL per item."
How all three fit together
Mechanism What it is When to reach for it Tool Action the model calls dynamically When the model needs to decide whether and when to fetch or act Resource Read-only data fetched directly into context When you know upfront what data you need, before the turn starts Prompt Pre-written instruction template invoked by name When specific wording produces materially better results than freeform input
Three transports, each for a different deployment topology.
The MCP transport (stdio, HTTP, SSE) just carries JSON-RPC envelopes
stdio — server runs as a child process on the same machine
The client launches the server as a subprocess and communicates via stdin/stdout. One line in, one line out — JSON-RPC over pipes. This is exactly what your ClaudeMCPServer.java uses. No network, no port, no authentication needed. The process lives and dies with the client session.
Use it when: the server runs locally alongside Claude Code on the same machine.
HTTP — server runs as a standalone process, local or remote
The client sends HTTP POST requests to a fixed URL. The server is a normal HTTP service — could be on localhost, could be on a remote host. Stateless request/response, same as any REST API. Each request is independent.
Use it when: the server needs to be shared across multiple clients, runs on a remote machine, or needs to persist beyond a single session.
SSE (Server-Sent Events) — HTTP with a persistent stream back
An extension of HTTP transport. The client POSTs requests normally, but the server holds the connection open and streams responses back as events rather than closing after each response. Useful when the server needs to push notifications or stream partial results.
Use it when: the server needs to send unsolicited updates to the client, or results arrive incrementally.
The practical decision
Server on same machine, local dev → stdio
Server shared across team / remote → HTTP
Server needs to push events → SSE
Your ClaudeMCPServer.java is stdio — that's why System.out is the protocol wire and System.err is the only safe place to log. Switching to HTTP would mean replacing stdin/stdout with an HTTP listener, but the JSON-RPC message structure stays identical.