Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ jobs:
with:
node-version: "20"

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- run: npm install

- run: npm run build
Expand Down Expand Up @@ -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}
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,18 @@ The [`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/exa
| | |
|:---:|:---|
| [![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!<br><br>[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. |
<!-- prettier-ignore-end -->

### 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):
Expand Down Expand Up @@ -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": [
Expand All @@ -331,7 +346,7 @@ To use these examples with MCP clients that support the stdio transport (such as
</details>

> [!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

Expand Down Expand Up @@ -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": [
Expand Down
11 changes: 11 additions & 0 deletions examples/inlined-server-java/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
81 changes: 81 additions & 0 deletions examples/inlined-server-java/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.modelcontextprotocol.examples</groupId>
<artifactId>inlined-server-java</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- MCP Java SDK: bundles mcp-core + Jackson 2 JSON mapper -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.17.2</version>
</dependency>

<!-- Embedded Jetty for serving the HTTP transport -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>12.0.16</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
<version>12.0.16</version>
</dependency>

<!-- Simple logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.16</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.modelcontextprotocol.examples.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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 = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="light dark">
<title>Get Time</title>
</head>
<body>
<p><strong>Server time:</strong> <code id="time">—</code></p>
<script type="module">
import { App } from 'https://unpkg.com/@modelcontextprotocol/ext-apps@1.0.1/dist/src/app-with-deps.js';
const app = new App({ name: 'get-time-app', version: '1.0.0' });
app.ontoolresult = (result) => {
document.getElementById('time').textContent =
result.structuredContent?.time ?? result.content?.[0]?.text ?? '?';
};
await app.connect();
</script>
</body>
</html>
""";

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();
}
}
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading