diff --git a/examples/basic-server-vanillajs-without-libs/.gitignore b/examples/basic-server-vanillajs-without-libs/.gitignore
new file mode 100644
index 00000000..7310e736
--- /dev/null
+++ b/examples/basic-server-vanillajs-without-libs/.gitignore
@@ -0,0 +1 @@
+.wrangler
\ No newline at end of file
diff --git a/examples/basic-server-vanillajs-without-libs/package.json b/examples/basic-server-vanillajs-without-libs/package.json
new file mode 100644
index 00000000..7622a36a
--- /dev/null
+++ b/examples/basic-server-vanillajs-without-libs/package.json
@@ -0,0 +1,5 @@
+{
+ "scripts": {
+ "dev": "bun run worker.js"
+ }
+}
diff --git a/examples/basic-server-vanillajs-without-libs/worker.js b/examples/basic-server-vanillajs-without-libs/worker.js
new file mode 100644
index 00000000..448ae6d1
--- /dev/null
+++ b/examples/basic-server-vanillajs-without-libs/worker.js
@@ -0,0 +1,632 @@
+/**
+ * Single Cloudflare Worker that is:
+ * 1. A fully functional MCP server (Streamable HTTP transport)
+ * 2. Serves an embedded MCP App view (HTML) as a ui:// resource
+ *
+ * No libraries used — raw JSON-RPC 2.0 over HTTP + postMessage.
+ */
+
+// ── The HTML view embedded as a string ─────────────────────────────
+const VIEW_HTML = /*html*/ `
+
+
+
+
+Counter App
+
+
+
+
+
+—
+
+
+
+
+Connecting…
+
+
+
+`;
+
+// ── Constants ──────────────────────────────────────────────────────
+
+const MCP_PROTOCOL_VERSION = "2025-03-26";
+const UI_MIME_TYPE = "text/html;profile=mcp-app";
+const RESOURCE_URI = "ui://counter/view.html";
+
+// ── Server state (in-memory, per-isolate) ──────────────────────────
+// Note: In production you'd use Durable Objects for per-session state.
+let counter = 0;
+
+// ── JSON-RPC helpers ───────────────────────────────────────────────
+
+function jsonRpcResponse(id, result) {
+ return { jsonrpc: "2.0", id, result };
+}
+
+function jsonRpcError(id, code, message) {
+ return { jsonrpc: "2.0", id, error: { code, message } };
+}
+
+// ── MCP request handler ────────────────────────────────────────────
+
+function handleMcpRequest(method, params, id) {
+ switch (method) {
+ // ─── Initialize ────────────────────────────────────────────
+ case "initialize":
+ return jsonRpcResponse(id, {
+ protocolVersion: MCP_PROTOCOL_VERSION,
+ capabilities: {
+ tools: { listChanged: false },
+ resources: { listChanged: false },
+ },
+ serverInfo: {
+ name: "counter-worker",
+ version: "1.0.0",
+ },
+ });
+
+ // ─── Ping ──────────────────────────────────────────────────
+ case "ping":
+ return jsonRpcResponse(id, {});
+
+ // ─── Tools ─────────────────────────────────────────────────
+ case "tools/list":
+ return jsonRpcResponse(id, {
+ tools: [
+ {
+ name: "counter",
+ description:
+ "Interactive counter app. Shows a UI with +/- buttons.",
+ inputSchema: {
+ type: "object",
+ properties: {
+ initialValue: {
+ type: "number",
+ description: "Starting count value",
+ },
+ },
+ },
+ _meta: {
+ ui: { resourceUri: RESOURCE_URI },
+ "ui/resourceUri": RESOURCE_URI, // legacy compat
+ },
+ },
+ {
+ name: "increment",
+ description: "Increment the counter by a given amount",
+ inputSchema: {
+ type: "object",
+ properties: {
+ amount: {
+ type: "number",
+ description: "Amount to add (negative to subtract)",
+ },
+ },
+ },
+ _meta: {
+ ui: {
+ resourceUri: RESOURCE_URI,
+ visibility: ["app"], // hidden from model, only callable by the view
+ },
+ "ui/resourceUri": RESOURCE_URI,
+ },
+ },
+ ],
+ });
+
+ case "tools/call": {
+ const { name, arguments: args } = params || {};
+
+ if (name === "counter") {
+ const initial = args?.initialValue ?? 0;
+ counter = Number(initial);
+ return jsonRpcResponse(id, {
+ content: [{ type: "text", text: JSON.stringify({ count: counter }) }],
+ });
+ }
+
+ if (name === "increment") {
+ const amount = Number(args?.amount ?? 1);
+ counter += amount;
+ return jsonRpcResponse(id, {
+ content: [{ type: "text", text: JSON.stringify({ count: counter }) }],
+ });
+ }
+
+ return jsonRpcError(id, -32602, `Unknown tool: ${name}`);
+ }
+
+ // ─── Resources ─────────────────────────────────────────────
+ case "resources/list":
+ return jsonRpcResponse(id, {
+ resources: [
+ {
+ uri: RESOURCE_URI,
+ name: "Counter View",
+ description: "Interactive counter UI",
+ mimeType: UI_MIME_TYPE,
+ },
+ ],
+ });
+
+ case "resources/read": {
+ const uri = params?.uri;
+ if (uri === RESOURCE_URI) {
+ return jsonRpcResponse(id, {
+ contents: [
+ {
+ uri: RESOURCE_URI,
+ mimeType: UI_MIME_TYPE,
+ text: VIEW_HTML,
+ _meta: {
+ ui: {
+ prefersBorder: true,
+ },
+ },
+ },
+ ],
+ });
+ }
+ return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);
+ }
+
+ case "resources/templates/list":
+ return jsonRpcResponse(id, { resourceTemplates: [] });
+
+ // ─── Prompts ───────────────────────────────────────────────
+ case "prompts/list":
+ return jsonRpcResponse(id, { prompts: [] });
+
+ default:
+ return jsonRpcError(id, -32601, `Method not found: ${method}`);
+ }
+}
+
+// ── HTTP handler ───────────────────────────────────────────────────
+
+export default {
+ async fetch(request) {
+ const url = new URL(request.url);
+
+ // ── CORS preflight ─────────────────────────────────────────
+ if (request.method === "OPTIONS") {
+ return new Response(null, {
+ status: 204,
+ headers: {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type",
+ },
+ });
+ }
+
+ const corsHeaders = {
+ "Access-Control-Allow-Origin": "*",
+ };
+
+ // ── MCP endpoint (Streamable HTTP) ─────────────────────────
+ if (url.pathname === "/mcp" || url.pathname === "/") {
+ if (request.method === "GET") {
+ // SSE endpoint — for this simple example we just return
+ // a keep-alive stream. A real implementation would push
+ // server-initiated notifications here.
+ const { readable, writable } = new TransformStream();
+ const writer = writable.getWriter();
+ const encoder = new TextEncoder();
+
+ // Send a comment to keep connection alive
+ writer.write(encoder.encode(": connected\n\n"));
+
+ // Keep alive every 30s
+ const interval = setInterval(() => {
+ writer.write(encoder.encode(": ping\n\n")).catch(() => {
+ clearInterval(interval);
+ });
+ }, 30000);
+
+ // Clean up when client disconnects
+ request.signal?.addEventListener("abort", () => {
+ clearInterval(interval);
+ writer.close().catch(() => {});
+ });
+
+ return new Response(readable, {
+ headers: {
+ ...corsHeaders,
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+ }
+
+ if (request.method === "POST") {
+ let body;
+ try {
+ body = await request.json();
+ } catch {
+ return new Response(
+ JSON.stringify(jsonRpcError(null, -32700, "Parse error")),
+ {
+ status: 400,
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
+ },
+ );
+ }
+
+ // Handle batch or single request
+ const isBatch = Array.isArray(body);
+ const messages = isBatch ? body : [body];
+ const responses = [];
+
+ for (const msg of messages) {
+ if (msg.jsonrpc !== "2.0") continue;
+
+ // Notification (no id) — just acknowledge
+ if (msg.id === undefined || msg.id === null) {
+ // notifications/initialized, etc. — no response needed
+ continue;
+ }
+
+ // Request
+ const result = handleMcpRequest(msg.method, msg.params, msg.id);
+ responses.push(result);
+ }
+
+ // If all were notifications, return 204
+ if (responses.length === 0) {
+ return new Response(null, {
+ status: 204,
+ headers: corsHeaders,
+ });
+ }
+
+ const responseBody = isBatch ? responses : responses[0];
+ return new Response(JSON.stringify(responseBody), {
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
+ });
+ }
+
+ return new Response("Method not allowed", {
+ status: 405,
+ headers: corsHeaders,
+ });
+ }
+
+ // ── Fallback: 404 ──────────────────────────────────────────
+ return new Response("Not found. MCP endpoint is at /mcp", {
+ status: 404,
+ headers: corsHeaders,
+ });
+ },
+};
diff --git a/examples/basic-server-vanillajs-without-libs/wrangler.json b/examples/basic-server-vanillajs-without-libs/wrangler.json
new file mode 100644
index 00000000..38b4cdee
--- /dev/null
+++ b/examples/basic-server-vanillajs-without-libs/wrangler.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://unpkg.com/wrangler@latest/config-schema.json",
+ "name": "basic-server-vanillajs-without-libs",
+ "compatibility_date": "2026-02-24",
+ "main": "worker.js"
+}