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 9427f4b41..971803df5 100644 --- a/README.md +++ b/README.md @@ -171,10 +171,18 @@ directory contains demo apps showcasing real-world use cases. | | | |:---:|:---| | [![Basic](examples/basic-server-react/grid-cell.png "Starter template")](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) | +| [![Basic](examples/basic-server-react/grid-cell.png "Inlined Java")](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 @@ -208,8 +216,9 @@ use this pattern: ``` For example, to add the map server: `@modelcontextprotocol/server-map`. The -Python examples (`qr-server`, `say-server`) use `uv run` instead — see their -READMEs for details. +Python examples (`qr-server`, `say-server`) use `uv run` instead, and the Java +example (`inlined-server-java`) requires cloning the repo — see their READMEs +for details. #### Local Development 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 fde38a398..a4512c52d 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