AI Agents → Share this skill with Claude for code generation
Lightweight. 12 dependencies. No containers.
Just annotate and run. See below →
Note: Beta release (v0.3.1-beta) — Multi-class modules, bash tools, telemetry. API stable.
Maven:
<dependency>
<groupId>io.github.terseprompts.fastmcp</groupId>
<artifactId>fastmcp-java</artifactId>
<version>0.3.1-beta</version>
</dependency>Gradle:
dependencies {
implementation 'io.github.terseprompts.fastmcp:fastmcp-java:0.3.1-beta'
}@McpServer(name = "Assistant", version = "1.0")
public class MyAssistant {
@McpTool(description = "Summarize text")
public String summarize(@McpParam(description = "Text") String text) {
return "Summary: " + text.substring(0, Math.min(100, text.length()));
}
public static void main(String[] args) {
FastMCP.server(MyAssistant.class)
.stdio() // or .sse() or .streamable()
.run();
}
}mvn exec:java -Dexec.mainClass="com.example.MyAssistant"That's it. Your MCP server is running.
Working example: EchoServer.java
| You want to... | FastMCP4J |
|---|---|
| Expose Java tools to AI agents | ✅ Perfect fit |
| Build MCP servers quickly | ✅ Annotation-driven, minimal code |
| Add MCP to existing Spring app | ✅ Drop-in, no framework lock-in |
| Lightweight MCP-only solution | ✅ 12 dependencies, not 50+ |
| Fast startup & low memory | ✅ <500ms cold start, ~64MB |
@McpTool(description = "Add two numbers")
public int add(int a, int b) {
return a + b;
}@McpTool(description = "Process data")
@McpAsync // ← just add this
public Mono<String> process(@McpContext Context ctx, String input) {
return Mono.fromCallable(() -> {
ctx.reportProgress(50, "Processing...");
return slowOperation(input);
});
}@McpServer(name = "MyServer", version = "1.0")
@McpMemory // ← just add this
public class MyServer {
// AI now remembers things across sessions
}@McpServer(name = "MyServer", version = "1.0")
@McpMemory // AI remembers
@McpTodo // AI manages tasks
@McpPlanner // AI breaks tasks into steps
@McpFileRead // AI reads your files
@McpFileWrite // AI writes files
public class MyServer {
// All tools enabled, zero implementation needed
}@McpServer(
name = "MyServer",
version = "1.0",
modules = {StringTools.class, MathTools.class} // Explicit modules
)
public class MyServer {
// Tools from StringTools and MathTools are included
}Or use package scanning for auto-discovery:
@McpServer(
name = "MyServer",
version = "1.0",
scanBasePackage = "com.example.tools" // Auto-discover all tools
)
public class MyServer {
// All @McpTool classes in the package are included
}@McpServer(name = "MyServer", version = "1.0")
@McpBash(timeout = 30) // Shell command execution with security guardrails
public class MyServer {
// Provides 'execute_command' tool with OS-aware shell selection
}@McpServer(name = "MyServer", version = "1.0")
@McpTelemetry(enabled = true, exportConsole = true) // Metrics & tracing
public class MyServer {
// Automatic tool invocation tracking with console export
}FastMCP.server(MyServer.class)
.stdio() // For CLI tools, local agents
.sse() // For web clients, long-lived connections
.streamable() // For bidirectional streaming (recommended)
.run();FastMCP.server(MyServer.class)
.port(3000) // HTTP port
.requestTimeout(Duration.ofMinutes(5)) // Request timeout
.keepAliveSeconds(30) // Keep-alive interval
.capabilities(c -> c
.tools(true)
.resources(true, true)
.prompts(true))
.run();Add visual polish to your server, tools, resources, and prompts.
@McpServer(
name = "my-server",
icons = {
"data:image/svg+xml;base64,...:image/svg+xml:64x64:light",
"data:image/svg+xml;base64,...:image/svg+xml:64x64:dark"
}
)
@McpTool(
description = "My tool",
icons = {"https://example.com/icon.png"}
)
public class MyServer { }@McpResource(uri = "config://settings")
public String getSettings() {
return "{\"theme\": \"dark\"}";
}
@McpPrompt(name = "code-review")
public String codeReviewPrompt(@McpParam(description = "Code to review") String code) {
return "Review this code:\n" + code;
}Add ONE annotation, get complete functionality.
| Annotation | Tools You Get |
|---|---|
@McpMemory |
list, read, create, replace, insert, delete, rename |
@McpTodo |
add, list, updateStatus, updateTask, delete, clearCompleted |
@McpPlanner |
createPlan, listPlans, getPlan, addTask, addSubtask |
@McpFileRead |
readLines, readFile, grep, getStats |
@McpFileWrite |
writeFile, appendFile, writeLines, deleteFile, createDirectory |
| Annotation | Target | Purpose |
|---|---|---|
@McpServer |
TYPE | Define your MCP server |
@McpTool |
METHOD | Expose as callable tool |
@McpResource |
METHOD | Expose as resource |
@McpPrompt |
METHOD | Expose as prompt template |
@McpParam |
PARAMETER | Add description, examples, constraints, defaults |
@McpAsync |
METHOD | Make tool async (return Mono<?>) |
@McpContext |
PARAMETER | Inject request context |
@McpPreHook |
METHOD | Run before tool call (params: toolName, order) |
@McpPostHook |
METHOD | Run after tool call (params: toolName, order) |
@McpBash |
TYPE | Enable bash/shell command execution tool |
@McpTelemetry |
TYPE | Enable metrics and tracing (params: enabled, exportConsole, exportOtlp, sampleRate) |
@McpMemory |
TYPE | Enable memory tools |
@McpTodo |
TYPE | Enable todo/task management tools |
@McpPlanner |
TYPE | Enable planning tools |
@McpFileRead |
TYPE | Enable file reading tools |
@McpFileWrite |
TYPE | Enable file writing tools |
@McpParam advanced options:
@McpTool(description = "Create task")
public String createTask(
@McpParam(
description = "Task name",
examples = {"backup", "sync"},
constraints = "Cannot be empty",
defaultValue = "default",
required = false
) String taskName
) { return "Created: " + taskName; }Two hook types supported:
@McpPreHook — Runs before tool is called. Receives Map<String, Object> arguments.
@McpPostHook — Runs after tool completes. Receives Map<String, Object> arguments, Object result.
Use for logging, validation, authentication, audit trails, metrics.
@McpServer(name = "MyServer", version = "1.0")
public class MyServer {
@McpTool(description = "Calculate")
public int calculate(int x, int y) {
return x + y;
}
// Run before ALL tools (*)
@McpPreHook(toolName = "*", order = 1)
void authenticate(Map<String, Object> args) {
String token = (String) args.get("token");
if (!isValid(token)) throw new SecurityException("Unauthorized");
}
// Run after specific tool only
@McpPostHook(toolName = "calculate", order = 1)
void logResult(Map<String, Object> args, Object result) {
System.out.println("Result: " + result);
}
public static void main(String[] args) {
FastMCP.server(MyServer.class).stdio().run();
}
}Hook options:
toolName— Target specific tool name, or"*"for all tools. Empty = inferred from method nameorder— Execution priority (lower = first). Default:0
Hook parameters:
- Pre-hook:
Map<String, Object> arguments— Tool input arguments - Post-hook:
Map<String, Object> arguments, Object result— Input + output
@McpContext — Inject request context into your tool.
Access client info, session data, request metadata.
@McpServer(name = "MyServer", version = "1.0")
public class MyServer {
@McpTool(description = "Get client info")
public String getClientInfo(@McpContext Context context) {
return "Client: " + context.getClientId();
}
@McpTool(description = "Get session ID")
public String getSessionId(@McpContext Context context) {
return "Session: " + context.getSessionId();
}
@McpTool(description = "Read file with context")
public String readFile(@McpContext Context context, String path) {
context.info("Reading file: " + path);
// Access request headers (e.g., for auth)
Map<String, String> headers = context.getRequestHeaders();
String authHeader = headers.get("Authorization");
// ... read file
return "Content";
}
public static void main(String[] args) {
FastMCP.server(MyServer.class).stdio().run();
}
}Context capabilities:
getClientId()— Client identifiergetSessionId()— Session identifiergetToolName()— Current tool namegetRequestHeaders()— Client request headers (e.g., auth tokens, custom headers)info(String)— Log info messagewarning(String)— Log warningerror(String)— Log errorreportProgress(int, String)— Report progress percentagelistResources()— List available resourceslistPrompts()— List available prompts
Execute shell commands with OS-aware shell selection and built-in security guardrails.
@McpServer(name = "MyServer", version = "1.0")
@McpBash(
timeout = 30, // Command timeout in seconds
visibleAfterBasePath = "/sandbox/*", // Whitelist allowed directories
notAllowedPaths = {"/etc", "/root"} // Blacklist dangerous paths
)
public class MyServer { }Security features:
- Directory validation (whitelist/blacklist)
- Dangerous command blocking (rm -rf, wget, curl, ssh, etc.)
- Directory traversal prevention
- Cross-platform path handling (Windows/Unix)
Supported shells:
- Windows:
cmd.exe - macOS:
/bin/zsh - Linux:
/bin/bash
Collect metrics and traces for tool invocations.
@McpServer(name = "MyServer", version = "1.0")
@McpTelemetry(
enabled = true,
exportConsole = true, // Human-readable output
exportOtlp = false, // OpenTelemetry export
sampleRate = 1.0, // 100% sampling
includeArguments = false, // Don't log sensitive args
metricExportIntervalMs = 60_000
)
public class MyServer { }Collected metrics:
- Tool invocation counters
- Execution duration histograms
- Error rates
Split tools across multiple classes for better organization.
Manual modules (fast, explicit):
@McpServer(
name = "MyServer",
version = "1.0",
modules = {StringTools.class, MathTools.class}
)Package scanning (convenient):
@McpServer(
name = "MyServer",
version = "1.0",
scanBasePackage = "com.example.tools"
)Raw MCP SDK: 35+ lines per tool FastMCP4J: ~8 lines per tool
| Framework | Dependencies | Best For |
|---|---|---|
| Spring AI | 50+ jars | Full-stack AI apps |
| LangChain4j | 30+ jars | Enterprise AI pipelines |
| Quarkus AI | 40+ jars | Cloud-native microservices |
| FastMCP4J | 12 jars | MCP servers only |
- Cold start: <500ms
- Tool invocation: <5ms
- Memory: ~64MB
- Purpose-built for MCP — not a general AI framework
- Architecture — How it works
- Roadmap — What's next
- Contributing — PRs welcome
- Changelog — Version history
- Claude Skill — For AI agents
MIT © 2026