Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
166591fc09 | ||
|
|
f64728774d | ||
|
|
631df2ff46 | ||
|
|
8f934a8f8e | ||
|
|
086a028879 |
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
COPY src ./src
|
||||||
|
USER node
|
||||||
|
CMD ["node", "src/http.js"]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Runs on the existing green-tea-mcp app. Deploy: cd havre && fly deploy (needs FLY_API_TOKEN).
|
||||||
|
app = "green-tea-mcp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
|
||||||
|
[env]
|
||||||
|
PORT = "8080"
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 8080
|
||||||
|
force_https = true
|
||||||
|
auto_stop_machines = "stop" # stateless, so idle machines can stop: no cost while unused
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 0
|
||||||
|
|
||||||
|
[[http_service.checks]]
|
||||||
|
method = "GET"
|
||||||
|
path = "/health"
|
||||||
|
interval = "30s"
|
||||||
|
timeout = "5s"
|
||||||
|
grace_period = "10s"
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
size = "shared-cpu-1x"
|
||||||
|
memory = "256mb"
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"description": "Havre MCP server. Stateless: no disk, no db, no stored secrets.",
|
"description": "Havre MCP server. Stateless: no disk, no db, no stored secrets.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
|
"start:http": "node src/http.js",
|
||||||
"test": "node --test"
|
"test": "node --test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { createServer as createHttp } from "node:http";
|
||||||
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
|
import { createServer } from "./server.js";
|
||||||
|
|
||||||
|
// Stateless streamable HTTP: a fresh MCP server + transport per request, nothing kept between requests.
|
||||||
|
export function createHttpServer() {
|
||||||
|
return createHttp(async (req, res) => {
|
||||||
|
const path = new URL(req.url, "http://x").pathname;
|
||||||
|
if (path === "/health") return res.writeHead(200, { "content-type": "text/plain" }).end("ok");
|
||||||
|
if (path !== "/mcp") return res.writeHead(404).end();
|
||||||
|
if (req.method !== "POST") return res.writeHead(405, { allow: "POST" }).end();
|
||||||
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
||||||
|
const server = createServer();
|
||||||
|
res.on("close", () => { transport.close(); server.close(); });
|
||||||
|
try {
|
||||||
|
await server.connect(transport);
|
||||||
|
await transport.handleRequest(req, res);
|
||||||
|
} catch {
|
||||||
|
if (!res.headersSent) res.writeHead(500).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
createHttpServer().listen(Number(process.env.PORT ?? 8080), "0.0.0.0");
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { test } from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
|
import { createHttpServer } from "../src/http.js";
|
||||||
|
|
||||||
|
test("health check and MCP over streamable HTTP", async () => {
|
||||||
|
const http = createHttpServer().listen(0);
|
||||||
|
await new Promise((r) => http.once("listening", r));
|
||||||
|
const base = `http://127.0.0.1:${http.address().port}`;
|
||||||
|
const client = new Client({ name: "test", version: "0.0.0" });
|
||||||
|
try {
|
||||||
|
assert.equal((await fetch(`${base}/health`)).status, 200);
|
||||||
|
assert.equal((await fetch(`${base}/nope`)).status, 404);
|
||||||
|
await client.connect(new StreamableHTTPClientTransport(new URL(`${base}/mcp`)));
|
||||||
|
assert.equal(client.getServerVersion().name, "havre");
|
||||||
|
assert.equal((await client.callTool({ name: "havre_ping", arguments: { message: "hi" } })).content[0].text, "hi");
|
||||||
|
} finally {
|
||||||
|
await client.close();
|
||||||
|
http.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user