diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 671b7a31e..d1845b125 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,6 +44,11 @@ jobs:
with:
node-version: "20"
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "21"
+
- run: npm install
- run: npm run build
@@ -109,12 +114,12 @@ jobs:
with:
distribution: Ubuntu-24.04
- - name: Install Node.js in WSL
+ - name: Install Node.js and Java in WSL
shell: wsl-bash {0}
run: |
sudo apt-get update
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
- sudo apt-get install -y nodejs
+ sudo apt-get install -y nodejs openjdk-17-jdk-headless maven
- name: Build and test in WSL
shell: wsl-bash {0}
diff --git a/README.md b/README.md
index e1429330c..5a3f06b78 100644
--- a/README.md
+++ b/README.md
@@ -89,10 +89,18 @@ The [`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/exa
| | |
|:---:|:---|
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) | The same app built with different frameworks — pick your favorite!
[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) · [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) · [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) · [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) · [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) · [Vanilla JS](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) |
+| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/inlined-server-java) | [**Inlined Java**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/inlined-server-java) — MCP App server in Java with the UI inlined as an HTML string (no frontend build step). Loads the SDK from CDN. |
### Running the Examples
+#### Prerequisites
+
+Most examples require only Node.js 18+. A few have additional requirements:
+
+- **Python examples** (`qr-server`, `say-server`): [uv](https://docs.astral.sh/uv/getting-started/installation/)
+- **Java example** (`inlined-server-java`): Java 17+ and Maven 3.6+
+
#### With basic-host
To run all examples locally using [basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) (the reference host implementation included in this repo):
@@ -306,6 +314,13 @@ To use these examples with MCP clients that support the stdio transport (such as
"--stdio"
]
},
+ "basic-java": {
+ "command": "bash",
+ "args": [
+ "-c",
+ "cd /path/to/ext-apps/examples/inlined-server-java && mvn -B package -DskipTests -q >&2 && java -jar target/inlined-server-java-1.0.0.jar --stdio"
+ ]
+ },
"qr": {
"command": "uv",
"args": [
@@ -331,7 +346,7 @@ To use these examples with MCP clients that support the stdio transport (such as
> [!NOTE]
-> The `qr` server requires cloning the repository first. See [qr-server README](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) for details.
+> The `qr` and `basic-java` servers require cloning the repository first. See their README files for details.
#### Local Development
@@ -484,6 +499,13 @@ Then configure your MCP client to build and run the local server. Replace `~/cod
"cd ~/code/ext-apps/examples/wiki-explorer-server && npm run build >&2 && node dist/index.js --stdio"
]
},
+ "basic-java": {
+ "command": "bash",
+ "args": [
+ "-c",
+ "cd ~/code/ext-apps/examples/inlined-server-java && mvn -B package -DskipTests -q >&2 && java -jar target/inlined-server-java-1.0.0.jar --stdio"
+ ]
+ },
"qr": {
"command": "bash",
"args": [
diff --git a/examples/inlined-server-java/package.json b/examples/inlined-server-java/package.json
new file mode 100644
index 000000000..d571cba51
--- /dev/null
+++ b/examples/inlined-server-java/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@modelcontextprotocol/server-inlined-java",
+ "version": "1.0.0",
+ "private": true,
+ "description": "MCP App server in Java with inlined HTML UI (no frontend build step)",
+ "scripts": {
+ "build": "mvn -B package -DskipTests -q",
+ "start": "mvn -B package -DskipTests -q && java -jar target/inlined-server-java-1.0.0.jar",
+ "dev": "mvn -B package -DskipTests -q && java -jar target/inlined-server-java-1.0.0.jar"
+ }
+}
diff --git a/examples/inlined-server-java/pom.xml b/examples/inlined-server-java/pom.xml
new file mode 100644
index 000000000..994bf20c6
--- /dev/null
+++ b/examples/inlined-server-java/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+ io.modelcontextprotocol.examples
+ inlined-server-java
+ 1.0.0
+ jar
+
+
+ 21
+ ${java.version}
+ ${java.version}
+ UTF-8
+
+
+
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+ 0.17.2
+
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ 12.0.16
+
+
+ org.eclipse.jetty.ee10
+ jetty-ee10-servlet
+ 12.0.16
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.16
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ package
+ shade
+
+ false
+
+
+ io.modelcontextprotocol.examples.Main
+
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Main.java b/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Main.java
new file mode 100644
index 000000000..ca79d88d6
--- /dev/null
+++ b/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Main.java
@@ -0,0 +1,54 @@
+package io.modelcontextprotocol.examples;
+
+import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapperSupplier;
+import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
+import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.ee10.servlet.FilterHolder;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
+
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Entry point for the inlined-server-java MCP App example.
+ *
+ * HTTP (default): java -jar inlined-server-java.jar
+ * Stdio: java -jar inlined-server-java.jar --stdio
+ */
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+ var json = new JacksonMcpJsonMapperSupplier().get();
+
+ if (List.of(args).contains("--stdio")) {
+ Server.create(new StdioServerTransportProvider(json));
+ return;
+ }
+
+ int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "3001"));
+ var transport = HttpServletStatelessServerTransport.builder().jsonMapper(json).build();
+ Server.create(transport);
+
+ var context = new ServletContextHandler();
+ context.addFilter(new FilterHolder((Filter) (req, res, chain) -> {
+ ((HttpServletResponse) res).setHeader("Access-Control-Allow-Origin", "*");
+ ((HttpServletResponse) res).setHeader("Access-Control-Allow-Headers", "*");
+ if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
+ ((HttpServletResponse) res).setStatus(200);
+ return;
+ }
+ chain.doFilter(req, res);
+ }), "/*", EnumSet.of(DispatcherType.REQUEST));
+ context.addServlet(new ServletHolder(transport), "/mcp");
+
+ var server = new org.eclipse.jetty.server.Server(port);
+ server.setHandler(context);
+ server.start();
+ System.out.println("MCP server listening on http://localhost:" + port + "/mcp");
+ server.join();
+ }
+}
diff --git a/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Server.java b/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Server.java
new file mode 100644
index 000000000..7adf267c3
--- /dev/null
+++ b/examples/inlined-server-java/src/main/java/io/modelcontextprotocol/examples/Server.java
@@ -0,0 +1,86 @@
+package io.modelcontextprotocol.examples;
+
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.server.McpStatelessServerFeatures;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpServerTransportProvider;
+import io.modelcontextprotocol.spec.McpStatelessServerTransport;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MCP server definition: registers a "get-time" tool with an inline HTML UI resource.
+ */
+public class Server {
+
+ static final String RESOURCE_URI = "ui://get-time/index.html";
+ static final String RESOURCE_MIME = "text/html;profile=mcp-app";
+
+ static final String UI_HTML = """
+
+
+
+
+
+ Get Time
+
+
+ Server time: —
+
+
+
+ """;
+
+ static final McpSchema.Tool TOOL = McpSchema.Tool.builder()
+ .name("get-time")
+ .description("Returns the current server time as an ISO 8601 string")
+ .inputSchema(new McpSchema.JsonSchema("object", null, null, null, null, null))
+ .meta(Map.of("ui", Map.of("resourceUri", RESOURCE_URI)))
+ .build();
+
+ static final McpSchema.Resource RESOURCE = McpSchema.Resource.builder()
+ .uri(RESOURCE_URI).name("Get Time UI").mimeType(RESOURCE_MIME).build();
+
+ static McpSchema.CallToolResult getTime() {
+ var time = Instant.now().toString();
+ return McpSchema.CallToolResult.builder()
+ .content(List.of(new McpSchema.TextContent(time)))
+ .structuredContent(Map.of("time", time))
+ .build();
+ }
+
+ static McpSchema.ReadResourceResult readResource() {
+ return new McpSchema.ReadResourceResult(List.of(new McpSchema.TextResourceContents(
+ RESOURCE_URI, RESOURCE_MIME, UI_HTML,
+ Map.of("ui", Map.of("csp", Map.of("resourceDomains", List.of("https://unpkg.com")))))));
+ }
+
+ /** Stateful server (stdio). */
+ static void create(McpServerTransportProvider transport) {
+ McpServer.sync(transport)
+ .serverInfo("inlined-server-java", "1.0.0")
+ .tools(new McpServerFeatures.SyncToolSpecification(TOOL, (ex, a) -> getTime()))
+ .resources(new McpServerFeatures.SyncResourceSpecification(RESOURCE, (ex, r) -> readResource()))
+ .build();
+ }
+
+ /** Stateless server (HTTP, matches JS examples). */
+ static void create(McpStatelessServerTransport transport) {
+ McpServer.sync(transport)
+ .serverInfo("inlined-server-java", "1.0.0")
+ .tools(new McpStatelessServerFeatures.SyncToolSpecification(TOOL, (ctx, r) -> getTime()))
+ .resources(new McpStatelessServerFeatures.SyncResourceSpecification(RESOURCE, (ctx, r) -> readResource()))
+ .build();
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 7aa9ef932..ffc2df8a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -557,6 +557,10 @@
"dev": true,
"license": "MIT"
},
+ "examples/inlined-server-java": {
+ "name": "@modelcontextprotocol/server-inlined-java",
+ "version": "1.0.0"
+ },
"examples/integration-server": {
"version": "1.0.0",
"dependencies": {
@@ -2657,6 +2661,10 @@
"resolved": "examples/debug-server",
"link": true
},
+ "node_modules/@modelcontextprotocol/server-inlined-java": {
+ "resolved": "examples/inlined-server-java",
+ "link": true
+ },
"node_modules/@modelcontextprotocol/server-map": {
"resolved": "examples/map-server",
"link": true