diff --git a/README.md b/README.md index 2f92598..57075f7 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ Open: - Health: `http://localhost:8787/health` - MCP UI bundle descriptor: `http://localhost:8787/mcp/ui-bundle` +Version metadata: +- REST and MCP tool responses include `api_version` and `schema_version`. +- Current values: `api_version=0.3.0`, `schema_version=1.0.0`. +- Compatibility policy (current): additive, backward-compatible fields may be introduced in the same API minor version. + ## REST API ### `POST /compile` diff --git a/src/mcp-server.js b/src/mcp-server.js index d592de7..5c0444b 100644 --- a/src/mcp-server.js +++ b/src/mcp-server.js @@ -1,4 +1,6 @@ import { compile, analyze } from "./compile.js"; +const API_VERSION = "0.3.0"; +const SCHEMA_VERSION = "1.0.0"; const SERVER_INFO = { name: "schemeta-mcp", @@ -87,10 +89,20 @@ function toolListResult() { }; } +function withEnvelopeMeta(payload) { + return { + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, + ...payload + }; +} + function uiBundleDescriptor() { return { name: "schemeta-workspace", version: "0.2.0", + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, entry: "/", title: "Schemeta Workspace", transport: "iframe" @@ -104,7 +116,7 @@ function handleToolCall(name, args) { if (name === "schemeta_compile") { const payload = decodePayload(args.payload); - const result = compile(payload, args.options ?? {}); + const result = withEnvelopeMeta(compile(payload, args.options ?? {})); return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result, @@ -114,7 +126,7 @@ function handleToolCall(name, args) { if (name === "schemeta_analyze") { const payload = decodePayload(args.payload); - const result = analyze(payload, args.options ?? {}); + const result = withEnvelopeMeta(analyze(payload, args.options ?? {})); return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result, diff --git a/src/server.js b/src/server.js index 63b9314..c383174 100644 --- a/src/server.js +++ b/src/server.js @@ -9,6 +9,8 @@ const PORT = Number(process.env.PORT ?? "8787"); const MAX_BODY_BYTES = Number(process.env.MAX_BODY_BYTES ?? 2 * 1024 * 1024); const CORS_ORIGIN = process.env.CORS_ORIGIN ?? "*"; const FRONTEND_ROOT = join(process.cwd(), "frontend"); +const API_VERSION = "0.3.0"; +const SCHEMA_VERSION = "1.0.0"; const MIME_TYPES = { ".html": "text/html; charset=utf-8", @@ -123,6 +125,14 @@ function parsePayloadOptions(body) { }; } +function withEnvelopeMeta(payload) { + return { + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, + ...payload + }; +} + const server = createServer(async (req, res) => { if (!req.url || !req.method) { return json(res, 400, errorEnvelope("invalid_request", "Invalid request.")); @@ -141,6 +151,8 @@ const server = createServer(async (req, res) => { return json(res, 200, { ok: true, service: "schemeta", + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, status: "ok", date: new Date().toISOString() }); @@ -151,6 +163,8 @@ const server = createServer(async (req, res) => { ok: true, name: "schemeta-workspace", version: "0.2.0", + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, entry: "/", title: "Schemeta Workspace", transport: "iframe" @@ -161,7 +175,7 @@ const server = createServer(async (req, res) => { try { const body = await readBody(req); const parsed = parsePayloadOptions(body); - return json(res, 200, analyze(parsed.payload, parsed.options)); + return json(res, 200, withEnvelopeMeta(analyze(parsed.payload, parsed.options))); } catch (err) { if (err?.code === "PAYLOAD_TOO_LARGE") { return json(res, 413, errorEnvelope("payload_too_large", `Request body exceeds ${MAX_BODY_BYTES} bytes.`)); @@ -177,7 +191,7 @@ const server = createServer(async (req, res) => { try { const body = await readBody(req); const parsed = parsePayloadOptions(body); - return json(res, 200, compile(parsed.payload, parsed.options)); + return json(res, 200, withEnvelopeMeta(compile(parsed.payload, parsed.options))); } catch (err) { if (err?.code === "PAYLOAD_TOO_LARGE") { return json(res, 413, errorEnvelope("payload_too_large", `Request body exceeds ${MAX_BODY_BYTES} bytes.`)); @@ -198,8 +212,10 @@ const server = createServer(async (req, res) => { const laidOut = applyLayoutToModel(model, { respectLocks: false }); return json(res, 200, { ok: true, + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, model: laidOut, - compile: compile(laidOut, parsed.options) + compile: withEnvelopeMeta(compile(laidOut, parsed.options)) }); } catch (err) { if (err?.code === "PAYLOAD_TOO_LARGE") { @@ -221,8 +237,10 @@ const server = createServer(async (req, res) => { const laidOut = applyLayoutToModel(model, { respectLocks: true }); return json(res, 200, { ok: true, + api_version: API_VERSION, + schema_version: SCHEMA_VERSION, model: laidOut, - compile: compile(laidOut, parsed.options) + compile: withEnvelopeMeta(compile(laidOut, parsed.options)) }); } catch (err) { if (err?.code === "PAYLOAD_TOO_LARGE") {