@thehammer/schema-mcp-server 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +35 -7
- package/dist/version-check.d.ts +15 -0
- package/dist/version-check.js +66 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,17 +14,47 @@
|
|
|
14
14
|
* SCHEMA_API_TOKEN - Bearer token for SchemaMcpAuthMiddleware
|
|
15
15
|
* SCHEMA_DEFINITION_ID - ID of the schema being built
|
|
16
16
|
*/
|
|
17
|
-
import { createRequire } from "node:module";
|
|
18
17
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
19
|
import { z } from "zod";
|
|
21
20
|
import * as api from "./api-client.js";
|
|
22
|
-
|
|
23
|
-
const pkg = require("../package.json");
|
|
21
|
+
import { LOADED_VERSION, assertVersionCurrent } from "./version-check.js";
|
|
24
22
|
const server = new McpServer({
|
|
25
23
|
name: "schema-mcp-server",
|
|
26
|
-
version:
|
|
24
|
+
version: LOADED_VERSION,
|
|
27
25
|
});
|
|
26
|
+
/**
|
|
27
|
+
* Prepend the version check to every tool handler — ONE place, all tools.
|
|
28
|
+
*
|
|
29
|
+
* `server.tool(name, desc, schema, handler)` is called 35+ times throughout
|
|
30
|
+
* this file. Rather than editing every call site, we override `server.tool`
|
|
31
|
+
* once right after the server is constructed: the override wraps the handler
|
|
32
|
+
* (always the last argument across all SDK overloads) with assertVersionCurrent,
|
|
33
|
+
* then delegates to the original implementation. New tools added in the future
|
|
34
|
+
* are covered automatically — no per-tool bookkeeping required.
|
|
35
|
+
*
|
|
36
|
+
* Fail-loud invariants:
|
|
37
|
+
* - The handler is always the last argument across every current SDK overload.
|
|
38
|
+
* A runtime `typeof` guard throws immediately if a future SDK overload
|
|
39
|
+
* breaks this assumption — the wrapper never silently wraps the wrong arg.
|
|
40
|
+
* - `assertVersionCurrent()` reads package.json on every call via
|
|
41
|
+
* `fs.readFileSync` (never `require()`, which caches). Sync reads of a
|
|
42
|
+
* ~500-byte file are microsecond-scale — no throttling needed.
|
|
43
|
+
*/
|
|
44
|
+
const originalTool = server.tool.bind(server);
|
|
45
|
+
server.tool = (...args) => {
|
|
46
|
+
const handlerIdx = args.length - 1;
|
|
47
|
+
const handler = args[handlerIdx];
|
|
48
|
+
if (typeof handler !== "function") {
|
|
49
|
+
throw new Error("[schema-mcp-server] server.tool override: expected last argument to be a handler function. SDK overload change detected — rewire the override before continuing.");
|
|
50
|
+
}
|
|
51
|
+
const originalHandler = handler;
|
|
52
|
+
args[handlerIdx] = async (...handlerArgs) => {
|
|
53
|
+
assertVersionCurrent();
|
|
54
|
+
return originalHandler(...handlerArgs);
|
|
55
|
+
};
|
|
56
|
+
return originalTool(...args);
|
|
57
|
+
};
|
|
28
58
|
/**
|
|
29
59
|
* Role-based tool registration.
|
|
30
60
|
*
|
|
@@ -80,7 +110,6 @@ const ROLE_TOOLS = {
|
|
|
80
110
|
"schema_update_model",
|
|
81
111
|
"schema_remove_model",
|
|
82
112
|
"schema_update_root",
|
|
83
|
-
"quality_gate_submit_review",
|
|
84
113
|
]),
|
|
85
114
|
"behavior-builder": new Set([
|
|
86
115
|
...COMMON_TOOLS,
|
|
@@ -88,7 +117,6 @@ const ROLE_TOOLS = {
|
|
|
88
117
|
"directive_create",
|
|
89
118
|
"directive_update",
|
|
90
119
|
"directive_delete",
|
|
91
|
-
"quality_gate_submit_review",
|
|
92
120
|
]),
|
|
93
121
|
"template-builder": new Set([
|
|
94
122
|
...COMMON_TOOLS,
|
|
@@ -97,7 +125,6 @@ const ROLE_TOOLS = {
|
|
|
97
125
|
"template_update",
|
|
98
126
|
"template_patch",
|
|
99
127
|
"template_set_sample_data",
|
|
100
|
-
"quality_gate_submit_review",
|
|
101
128
|
"style_list",
|
|
102
129
|
"annotation_create", // Question annotations for missing fields → orchestrator decides
|
|
103
130
|
]),
|
|
@@ -107,6 +134,7 @@ const ROLE_TOOLS = {
|
|
|
107
134
|
"style_list",
|
|
108
135
|
"context_workflow_inputs",
|
|
109
136
|
"context_workflow_input_pages",
|
|
137
|
+
"quality_gate_submit_review",
|
|
110
138
|
]),
|
|
111
139
|
};
|
|
112
140
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const LOADED_VERSION: string;
|
|
2
|
+
/**
|
|
3
|
+
* Halt the process if the on-disk `package.json` version has drifted from
|
|
4
|
+
* `LOADED_VERSION`.
|
|
5
|
+
*
|
|
6
|
+
* Called from the `server.tool` override in `index.ts` before every tool
|
|
7
|
+
* handler runs. Uses `fs.readFileSync` + `JSON.parse` on every call —
|
|
8
|
+
* `require()` would cache the first read and defeat the check.
|
|
9
|
+
*
|
|
10
|
+
* Side effect: on mismatch (or if the disk read itself fails), writes a
|
|
11
|
+
* clear error line to stderr and calls `process.exit(1)`. There is no graceful
|
|
12
|
+
* shutdown window, no retry, no returned error object. The MCP stdio client
|
|
13
|
+
* sees the disconnect and surfaces it as a dispatch failure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertVersionCurrent(): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime version check — single source of truth for the MCP server's version
|
|
3
|
+
* identity.
|
|
4
|
+
*
|
|
5
|
+
* Owns BOTH:
|
|
6
|
+
* 1. `LOADED_VERSION`: the version captured into memory the first time this
|
|
7
|
+
* module is loaded (one fs read at module load).
|
|
8
|
+
* 2. `assertVersionCurrent()`: compares the on-disk package.json version to
|
|
9
|
+
* `LOADED_VERSION` and halts the process via `process.exit(1)` on
|
|
10
|
+
* mismatch so stale-code runs fail loudly instead of serving old behavior
|
|
11
|
+
* silently.
|
|
12
|
+
*
|
|
13
|
+
* Dist layout assumption: `../package.json` resolves from both
|
|
14
|
+
* `src/version-check.ts` AND `dist/version-check.js` to `mcp-server/package.json`
|
|
15
|
+
* because both files live one level below the package root. If tsconfig ever
|
|
16
|
+
* emits to a nested output dir (e.g. `dist/src/`), this assumption breaks and
|
|
17
|
+
* must be re-pinned here.
|
|
18
|
+
*
|
|
19
|
+
* Wired in once at the top of `index.ts` via a `server.tool` override — every
|
|
20
|
+
* tool handler runs the assertion before doing any work. See that override
|
|
21
|
+
* block for the single injection site.
|
|
22
|
+
*/
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
26
|
+
/**
|
|
27
|
+
* Version frozen into memory at module load. Any later mismatch between this
|
|
28
|
+
* value and the on-disk `package.json` triggers `assertVersionCurrent()` to
|
|
29
|
+
* halt the process — there is no recovery path, no graceful shutdown, no retry.
|
|
30
|
+
*
|
|
31
|
+
* Read at module load via `fs.readFileSync` (not `require()`, which would
|
|
32
|
+
* cache the result and make the runtime check always pass). If `package.json`
|
|
33
|
+
* is missing or malformed at startup, the exception is intentionally
|
|
34
|
+
* uncaught — fail-loud is the correct behavior before the server even begins
|
|
35
|
+
* serving requests.
|
|
36
|
+
*/
|
|
37
|
+
const loadedPkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
38
|
+
export const LOADED_VERSION = loadedPkg.version;
|
|
39
|
+
/**
|
|
40
|
+
* Halt the process if the on-disk `package.json` version has drifted from
|
|
41
|
+
* `LOADED_VERSION`.
|
|
42
|
+
*
|
|
43
|
+
* Called from the `server.tool` override in `index.ts` before every tool
|
|
44
|
+
* handler runs. Uses `fs.readFileSync` + `JSON.parse` on every call —
|
|
45
|
+
* `require()` would cache the first read and defeat the check.
|
|
46
|
+
*
|
|
47
|
+
* Side effect: on mismatch (or if the disk read itself fails), writes a
|
|
48
|
+
* clear error line to stderr and calls `process.exit(1)`. There is no graceful
|
|
49
|
+
* shutdown window, no retry, no returned error object. The MCP stdio client
|
|
50
|
+
* sees the disconnect and surfaces it as a dispatch failure.
|
|
51
|
+
*/
|
|
52
|
+
export function assertVersionCurrent() {
|
|
53
|
+
let diskVersion;
|
|
54
|
+
try {
|
|
55
|
+
const diskPkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
56
|
+
diskVersion = diskPkg.version;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error(`[schema-mcp-server] VERSION CHECK FAILED: unable to read ${pkgPath}. Restart required. Cause:`, err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
if (diskVersion !== LOADED_VERSION) {
|
|
63
|
+
console.error(`[schema-mcp-server] VERSION MISMATCH: loaded v${LOADED_VERSION} in memory, v${diskVersion} on disk. Restart required.`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/schema-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "MCP server for Schema Builder - translates Claude Code tool calls into Laravel API requests",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc",
|
|
24
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
25
25
|
"start": "node dist/index.js",
|
|
26
26
|
"dev": "tsx watch src/index.ts"
|
|
27
27
|
},
|