Add API/schema version metadata to REST and MCP responses

This commit is contained in:
Rbanh 2026-02-16 22:08:45 -05:00
parent 80f46a70c9
commit 3cb6feeb15
3 changed files with 41 additions and 6 deletions

View File

@ -23,6 +23,11 @@ Open:
- Health: `http://localhost:8787/health` - Health: `http://localhost:8787/health`
- MCP UI bundle descriptor: `http://localhost:8787/mcp/ui-bundle` - 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 ## REST API
### `POST /compile` ### `POST /compile`

View File

@ -1,4 +1,6 @@
import { compile, analyze } from "./compile.js"; import { compile, analyze } from "./compile.js";
const API_VERSION = "0.3.0";
const SCHEMA_VERSION = "1.0.0";
const SERVER_INFO = { const SERVER_INFO = {
name: "schemeta-mcp", name: "schemeta-mcp",
@ -87,10 +89,20 @@ function toolListResult() {
}; };
} }
function withEnvelopeMeta(payload) {
return {
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
...payload
};
}
function uiBundleDescriptor() { function uiBundleDescriptor() {
return { return {
name: "schemeta-workspace", name: "schemeta-workspace",
version: "0.2.0", version: "0.2.0",
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
entry: "/", entry: "/",
title: "Schemeta Workspace", title: "Schemeta Workspace",
transport: "iframe" transport: "iframe"
@ -104,7 +116,7 @@ function handleToolCall(name, args) {
if (name === "schemeta_compile") { if (name === "schemeta_compile") {
const payload = decodePayload(args.payload); const payload = decodePayload(args.payload);
const result = compile(payload, args.options ?? {}); const result = withEnvelopeMeta(compile(payload, args.options ?? {}));
return { return {
content: [{ type: "text", text: JSON.stringify(result) }], content: [{ type: "text", text: JSON.stringify(result) }],
structuredContent: result, structuredContent: result,
@ -114,7 +126,7 @@ function handleToolCall(name, args) {
if (name === "schemeta_analyze") { if (name === "schemeta_analyze") {
const payload = decodePayload(args.payload); const payload = decodePayload(args.payload);
const result = analyze(payload, args.options ?? {}); const result = withEnvelopeMeta(analyze(payload, args.options ?? {}));
return { return {
content: [{ type: "text", text: JSON.stringify(result) }], content: [{ type: "text", text: JSON.stringify(result) }],
structuredContent: result, structuredContent: result,

View File

@ -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 MAX_BODY_BYTES = Number(process.env.MAX_BODY_BYTES ?? 2 * 1024 * 1024);
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? "*"; const CORS_ORIGIN = process.env.CORS_ORIGIN ?? "*";
const FRONTEND_ROOT = join(process.cwd(), "frontend"); const FRONTEND_ROOT = join(process.cwd(), "frontend");
const API_VERSION = "0.3.0";
const SCHEMA_VERSION = "1.0.0";
const MIME_TYPES = { const MIME_TYPES = {
".html": "text/html; charset=utf-8", ".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) => { const server = createServer(async (req, res) => {
if (!req.url || !req.method) { if (!req.url || !req.method) {
return json(res, 400, errorEnvelope("invalid_request", "Invalid request.")); return json(res, 400, errorEnvelope("invalid_request", "Invalid request."));
@ -141,6 +151,8 @@ const server = createServer(async (req, res) => {
return json(res, 200, { return json(res, 200, {
ok: true, ok: true,
service: "schemeta", service: "schemeta",
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
status: "ok", status: "ok",
date: new Date().toISOString() date: new Date().toISOString()
}); });
@ -151,6 +163,8 @@ const server = createServer(async (req, res) => {
ok: true, ok: true,
name: "schemeta-workspace", name: "schemeta-workspace",
version: "0.2.0", version: "0.2.0",
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
entry: "/", entry: "/",
title: "Schemeta Workspace", title: "Schemeta Workspace",
transport: "iframe" transport: "iframe"
@ -161,7 +175,7 @@ const server = createServer(async (req, res) => {
try { try {
const body = await readBody(req); const body = await readBody(req);
const parsed = parsePayloadOptions(body); 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) { } catch (err) {
if (err?.code === "PAYLOAD_TOO_LARGE") { if (err?.code === "PAYLOAD_TOO_LARGE") {
return json(res, 413, errorEnvelope("payload_too_large", `Request body exceeds ${MAX_BODY_BYTES} bytes.`)); 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 { try {
const body = await readBody(req); const body = await readBody(req);
const parsed = parsePayloadOptions(body); 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) { } catch (err) {
if (err?.code === "PAYLOAD_TOO_LARGE") { if (err?.code === "PAYLOAD_TOO_LARGE") {
return json(res, 413, errorEnvelope("payload_too_large", `Request body exceeds ${MAX_BODY_BYTES} bytes.`)); 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 }); const laidOut = applyLayoutToModel(model, { respectLocks: false });
return json(res, 200, { return json(res, 200, {
ok: true, ok: true,
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
model: laidOut, model: laidOut,
compile: compile(laidOut, parsed.options) compile: withEnvelopeMeta(compile(laidOut, parsed.options))
}); });
} catch (err) { } catch (err) {
if (err?.code === "PAYLOAD_TOO_LARGE") { if (err?.code === "PAYLOAD_TOO_LARGE") {
@ -221,8 +237,10 @@ const server = createServer(async (req, res) => {
const laidOut = applyLayoutToModel(model, { respectLocks: true }); const laidOut = applyLayoutToModel(model, { respectLocks: true });
return json(res, 200, { return json(res, 200, {
ok: true, ok: true,
api_version: API_VERSION,
schema_version: SCHEMA_VERSION,
model: laidOut, model: laidOut,
compile: compile(laidOut, parsed.options) compile: withEnvelopeMeta(compile(laidOut, parsed.options))
}); });
} catch (err) { } catch (err) {
if (err?.code === "PAYLOAD_TOO_LARGE") { if (err?.code === "PAYLOAD_TOO_LARGE") {