fastMCP4J

mcp
Security Audit
Pass
Health Pass
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 11 GitHub stars
Code Pass
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Annotate a Java class → production MCP server. Tools, memory, prompts + a sandboxed bash terminal where the host is unreachable by construction. Java 17+, 12 deps, no containers.

README.md

FastMCP4J

Maven Central
Java
CI
License
Tests
MCP Marketplace

Annotate a Java class. Ship an MCP server.

Tools, resources, prompts, memory — and a sandboxed bash terminal that can't
touch your machine. No boilerplate, no containers, no 50-jar framework.

AI Agents → Share this skill with Claude for code generation

An AI agent driving the sandboxed bash tool: virtual identity, read-only project mounts, network denied, destructive commands blocked — writes stay in the sandbox.

FastMCP4J is an annotation-driven SDK for the
Model Context Protocol (spec 2.0.1) on
Java 17+. One annotation turns a class into an MCP server; one more gives your
AI agent a real terminal — a bash sandbox where the host is unreachable by
construction
. Cold start in under 500 ms, twelve dependencies, everything
testable in-process.

Status: beta (v0.5.0-beta) — API stable, 235 tests passing, published to
Maven Central.


Quick start (2 minutes)

Maven

<dependency>
    <groupId>io.github.terseprompts.fastmcp</groupId>
    <artifactId>fastmcp-java</artifactId>
    <version>0.5.0-beta</version>
</dependency>

Gradle

implementation 'io.github.terseprompts.fastmcp:fastmcp-java:0.5.0-beta'

That's the whole install story: Java 17+, one artifact on Maven Central,
MCP Java SDK 2.0.1 underneath. No annotation processor, no codegen step, no
container runtime.

Create your server

@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


Why this exists

Every Java team wiring AI agents to tools faces the same choice, and every
option hurts:

Raw MCP SDK Spring AI / LangChain4j FastMCP4J
Lines per tool 35+ varies, plus framework glue ~8
Dependencies 1 + your patience 30–50+ jars 12 jars
Startup fast framework-sized <500 ms, ~64 MB
Sandboxed bash roll your own not included ✅ built-in (bashkit4j)
Built-in memory / todo / planner / file tools no no ✅ one annotation each
Lock-in none framework none — MCP in, MCP out

Before: a day of JSON-schema plumbing per tool, and shell access means a
rogue prompt is a node down.
After: annotations on Tuesday, agents in production Wednesday — and the
terminal they use can't touch your disk.


The API tour

1 · Make a tool — sync or async

@McpTool(description = "Add two numbers")
public int add(int a, int b) {
    return a + b;
}

@McpTool(description = "Process data")
@McpAsync  // ← just add this; return Mono<?>
public Mono<String> process(@McpContext Context ctx, String input) {
    return Mono.fromCallable(() -> {
        ctx.reportProgress(50, "Processing...");
        return slowOperation(input);
    });
}

2 · Built-in brains — one annotation each

@McpServer(name = "MyServer", version = "1.0")
@McpMemory     // AI remembers things across sessions
@McpTodo       // AI manages tasks
@McpPlanner    // AI breaks work into plans
@McpFileRead   // AI reads your files
@McpFileWrite  // AI writes files
public class MyServer {
    // complete tool sets enabled, zero implementation
}
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

3 · Sandboxed bash — give the agent a terminal, not your machine

@McpServer(name = "Reviewer", version = "1.0")
@McpBash(
    allowMountsUnder = "C:/dev",           // opt in: all it may ever see
    mounts = {"/project=C:/dev/my-app"},   // mount the project — read-only
    timeout = 30, maxCommands = 10_000     // bounds runaway scripts
)
public class Reviewer { }

Scripts run in a bashkit4j
in-memory sandbox: POSIX-style bash with 160+ commands re-implemented
natively, a virtual filesystem, network denied by default. No real bash is
ever spawned.
Then the agent calls the bash tool:

grep -rn TODO /project/src | head -5      # real files, zero risk
echo "findings..." > /notes.md            # writes stay in the sandbox
Host shell (ProcessBuilder, Docker) Sandbox mode
Real bash on the host ✅ runs — full attack surface ❌ never — bash re-implemented natively
Host filesystem visible ✅ all of it ❌ invisible until you mount, read-only by default
OS processes per command ✅ one per call ❌ zero — in-process
Network access ✅ open ❌ denied by default
One rogue script node down sandbox reset

State persists across calls (cwd, env, files) — multi-step agent workflows
work. Mounts are enforced inside the native library: canonicalized,
symlink-safe, and impossible without allowMountsUnder. Need the real shell
for trusted automation? mode = BashMode.HOST keeps the legacy tool with its
path guardrails.

Sandbox mode requires the optional
io.github.terseprompts:bashkit4j:0.2.0 dependency — native libs for
Windows/Linux/macOS (x86-64 + ARM64) are bundled and auto-detected.

4 · Pick a transport

FastMCP.server(MyServer.class)
    .stdio()       // CLI tools, local agents
    .sse()         // web clients, long-lived connections
    .streamable()  // 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();

5 · Resources & prompts

@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;
}

6 · Hooks — before/after every tool call

// 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 a specific tool only
@McpPostHook(toolName = "calculate", order = 1)
void logResult(Map<String, Object> args, Object result) {
    System.out.println("Result: " + result);
}
  • toolName — target tool, or "*" for all (empty = inferred from method name)
  • order — execution priority, lower runs first (default 0)
  • Pre-hooks receive the arguments; post-hooks receive arguments + result

7 · Request context

@McpTool(description = "Read file with auth")
public String readFile(@McpContext Context context, String path) {
    context.info("Reading file: " + path);
    String auth = context.getRequestHeaders().get("Authorization");
    // ...
}

Context gives you getClientId(), getSessionId(), getToolName(),
getRequestHeaders(), info/warning/error logging, reportProgress,
listResources(), listPrompts().

8 · Organize at scale

// explicit modules
@McpServer(name = "MyServer", version = "1.0",
    modules = {StringTools.class, MathTools.class})

// or package scanning
@McpServer(name = "MyServer", version = "1.0",
    scanBasePackage = "com.example.tools")

9 · Icons & telemetry

@McpServer(
    name = "my-server",
    icons = {"data:image/svg+xml;base64,...:image/svg+xml:64x64:light"}
)
@McpTelemetry(enabled = true, exportConsole = true, sampleRate = 1.0)
public class MyServer { }

Telemetry collects tool invocation counters, duration histograms, and error
rates — console or OpenTelemetry export.


Annotations reference

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 Description, examples, constraints, defaults
@McpAsync METHOD Make tool async (return Mono<?>)
@McpContext PARAMETER Inject request context
@McpPreHook / @McpPostHook METHOD Run code before/after tool calls
@McpBash TYPE Bash tool — sandboxed (default) or host shell via mode
@McpTelemetry TYPE Metrics and tracing
@McpMemory / @McpTodo / @McpPlanner TYPE Built-in tool sets
@McpFileRead / @McpFileWrite TYPE Built-in file 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; }

What the sandbox actually does (measured, not claimed)

FastMCP4J ships 235 tests (mvn test) — the sandbox suite runs against
the real native library on Windows and Linux CI, including deliberate escape
probes:

Probe Result
ls / inside the sandbox virtual root only — pom.xml, target, host paths absent
whoami / hostname agent@sandbox — virtual identity, not your OS user
Write to a read-only mount fails; host file provably never appears
Read-write mount round-trips to the host through the Java API
cd + file across tool calls persists inside a server; sealed between servers
Script exceeding timeout caller gets TIMEOUT; sandbox replaced fresh for next call
4 concurrent tool calls all complete — calls serialize on the sandbox
Sandbox without bashkit4j on classpath clear startup error naming the dependency

Who it's for

  • AI/LLM engineers — expose Java services to Claude, Cursor, or any MCP
    client with annotation-level effort.
  • Teams shipping agent tools — memory, todo, planning, file access, and a
    sandboxed terminal out of the box; hooks and telemetry for production.
  • Security-conscious platforms — agent terminal access with the host
    unreachable by construction, not by prompt-engineering.
  • Existing Spring/DI codebases — drop-in server, no framework lock-in;
    your beans become tools with an annotation.

Requirements & performance

Just Java 17+ and Maven 3.8+. MCP spec 2.0.1 via the official Java SDK
(mcp-core + mcp-json-jackson2).

  • Cold start: <500 ms
  • Tool invocation: <5 ms
  • Memory: ~64 MB
  • Purpose-built for MCP — not a general AI framework

CI/CD

Branch What runs
development (staging) Full test suite + MCP integration tests (STDIO / SSE / Streamable / Sandboxed Bash) on every push and PR
main Same suite + publish to Maven Central (staged; manual approval in Sonatype Central)

Flow: feature branch → developmentmain (release).


Documentation


License

MIT © 2026


Less boilerplate. More shipping.

Get startedExamplesDocs

Made with ❤️ for the Java community

Reviews (0)

No results found