gasp-standard-mcp 1.18.0 → 1.19.0

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/README.md CHANGED
@@ -29,6 +29,16 @@ Add to your project `.mcp.json` or `~/.claude/settings.json`:
29
29
 
30
30
  Restart your MCP client after adding the config.
31
31
 
32
+ ### Hosted endpoint
33
+
34
+ The same server runs over Streamable HTTP for clients that connect to a remote server, such as the Claude connectors directory. Every tool is read-only and the standard is public, so the endpoint takes no authentication.
35
+
36
+ ```bash
37
+ npx gasp-standard-mcp-http # PORT=3000, endpoint /mcp, /health for the version and tool count
38
+ ```
39
+
40
+ `PORT`, `HOST` and `MCP_PATH` set where it listens. `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` (comma-separated) refuse requests whose Host or Origin header is not listed, the DNS-rebinding guard for a server reachable from a browser; set `MCP_ALLOWED_HOSTS` to the public hostname in production. The `Dockerfile` in this directory serves the published package the same way. Every tool carries a `title` and `readOnlyHint: true`, so a client can run them without a per-call prompt. The HTTP server writes one JSON line to stderr per tool call (the tool, the duration, success or error and the client's name and version; never the arguments or the result), so the host's logs count calls per tool.
41
+
32
42
  ## Tools
33
43
 
34
44
  | Tool | Description |
@@ -59,7 +69,7 @@ The traversal tools (`infer_drivers`, `explain_path`, `coverage_gap`) return pat
59
69
 
60
70
  ## Data
61
71
 
62
- Version 1.18.0.
72
+ Version 1.19.0.
63
73
 
64
74
  - 306 metrics across 13 departments
65
75
  - 94 glossary definitions (81 SaaS domain + 13 GASP framework)
@@ -89,7 +99,7 @@ Version 1.18.0.
89
99
  - The drift scorer (`src/ossie/drift.ts`, behind `score_drift`) runs a company's metrics and the canonical ones side by side on the synthetic SaaS dataset (`src/synthetic.ts`, also the test fixture) in PGlite, loaded on first use from the optional dependency `@electric-sql/pglite`; without it the tool says so and runs nothing
90
100
  - The metric compiler (`src/ossie/company.ts`, behind `compile_model`) writes every covered GASP metric into a company's own Ossie model, direct or staged, over their datasets, sources and field expressions. The test suite compiles the fixture adapter and checks every direct and staged value against the reference export on the fixture rows
91
101
  - Adapter schema 1.1 (`standard/gasp-adapter-schema.json`, JSON Schema draft 2020-12, served at https://gaspwiki.com/schemas/gasp-adapter-v1.json): a `datasets` map, per-dataset mappings (a mapping names its `dataset`), informational fields and `$schema`. The schema travels in the bundled data as `adapterSchema` and `validate_adapter` applies it with Ajv, so validation works offline. `npm run derive` writes a ready template to `standard/exports/gasp-adapter-template.json` (served at https://gaspwiki.com/downloads/gasp-adapter-template.json) and the tests validate it against the schema
92
- - GASP Ossie extension schema 1 (`standard/gasp-ossie-extension-v1.json`, JSON Schema draft 2020-12, served at https://gaspwiki.com/schemas/gasp-ossie-extension-v1.json): the parsed `data` of every `custom_extensions` entry GASP writes under `vendor_name: GASP`, one definition per placement (model, dataset, metric, mapping), strict so each key GASP adds outside Ossie is listed. It travels in the bundled data as `extensionSchema` and the cross-document checks (`checkExport` in `src/ossie/check.ts`) validate every GASP payload against it in the export, `validate_mapping`, `compile_model`, `propose_mapping` and `record_decision`
102
+ - GASP Ossie extension schema 1 (`standard/gasp-ossie-extension-v1.json`, JSON Schema draft 2020-12, served at https://gaspwiki.com/schemas/gasp-ossie-extension-v1.json): the parsed `data` of every `custom_extensions` entry GASP writes under `vendor_name: GASP`, one definition per placement (model, dataset, metric, mapping), strict so each key GASP adds outside Ossie is listed. It travels in the bundled data as `extensionSchema` and the cross-document checks (`checkExport` in `src/ossie/check.ts`) validate every GASP payload against it in the export, `validate_mapping`, `compile_model`, `propose_mapping` and `record_decision`. Every tool reads `data` as the JSON string the pinned Ossie schema defines or as an object (the form apache/ossie #522 proposes), under `custom_extensions` or the `nonportable_extensions` key the same proposal renames it to, and writes the pinned form under the pinned key
93
103
  - Sources: the canonical [GASP Standard](https://gaspwiki.com) files
94
104
 
95
105
  ## Examples
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The GASP Standard MCP server over Streamable HTTP, for a hosted endpoint
4
+ * such as https://mcp.gaspwiki.com/mcp that Claude connects to as a remote
5
+ * server. Stateless: every request gets its own transport and server over the
6
+ * data loaded once, so any number of instances can sit behind one hostname.
7
+ * No authentication: the standard is public and every tool is read-only.
8
+ *
9
+ * Run `gasp-standard-mcp-http` (or `node build/http.js`). Environment:
10
+ * `PORT` (default 3000), `HOST` (default 0.0.0.0), `MCP_PATH` (default /mcp),
11
+ * `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` (comma-separated; when either
12
+ * is set, requests whose Host or Origin header is not listed are refused, the
13
+ * DNS-rebinding guard for a server reachable from a browser).
14
+ *
15
+ * Every tool call is written to stderr as one JSON line, `{"event":"tool",
16
+ * "tool":"lookup_metric","ms":12,"ok":true,"client":"claude-ai/1.0"}`, so the
17
+ * host's log stream counts calls per tool. Arguments and results are never
18
+ * logged, which is what the privacy policy at gaspwiki.com/privacy promises.
19
+ */
20
+ import { type Server } from "node:http";
21
+ export interface HttpOptions {
22
+ port?: number;
23
+ host?: string;
24
+ /** The path the MCP endpoint answers on; everything else is the health page or 404. */
25
+ path?: string;
26
+ allowedHosts?: string[];
27
+ allowedOrigins?: string[];
28
+ }
29
+ /** The listening server. `port: 0` picks a free port (tests). */
30
+ export declare function startHttpServer(options?: HttpOptions): Server;
package/build/http.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The GASP Standard MCP server over Streamable HTTP, for a hosted endpoint
4
+ * such as https://mcp.gaspwiki.com/mcp that Claude connects to as a remote
5
+ * server. Stateless: every request gets its own transport and server over the
6
+ * data loaded once, so any number of instances can sit behind one hostname.
7
+ * No authentication: the standard is public and every tool is read-only.
8
+ *
9
+ * Run `gasp-standard-mcp-http` (or `node build/http.js`). Environment:
10
+ * `PORT` (default 3000), `HOST` (default 0.0.0.0), `MCP_PATH` (default /mcp),
11
+ * `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` (comma-separated; when either
12
+ * is set, requests whose Host or Origin header is not listed are refused, the
13
+ * DNS-rebinding guard for a server reachable from a browser).
14
+ *
15
+ * Every tool call is written to stderr as one JSON line, `{"event":"tool",
16
+ * "tool":"lookup_metric","ms":12,"ok":true,"client":"claude-ai/1.0"}`, so the
17
+ * host's log stream counts calls per tool. Arguments and results are never
18
+ * logged, which is what the privacy policy at gaspwiki.com/privacy promises.
19
+ */
20
+ import { createServer as createHttp } from "node:http";
21
+ import { realpathSync } from "node:fs";
22
+ import { pathToFileURL } from "node:url";
23
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24
+ import { PKG_VERSION, TOOL_COUNT, createServer } from "./server.js";
25
+ const CORS = {
26
+ "Access-Control-Allow-Origin": "*",
27
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
28
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Authorization, Mcp-Session-Id, Mcp-Protocol-Version",
29
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, Mcp-Protocol-Version",
30
+ };
31
+ /** The listening server. `port: 0` picks a free port (tests). */
32
+ export function startHttpServer(options = {}) {
33
+ const path = options.path ?? "/mcp";
34
+ const guard = options.allowedHosts?.length || options.allowedOrigins?.length
35
+ ? { enableDnsRebindingProtection: true, allowedHosts: options.allowedHosts, allowedOrigins: options.allowedOrigins }
36
+ : {};
37
+ const handle = async (req, res) => {
38
+ const url = new URL(req.url ?? "/", "http://localhost");
39
+ if (req.method === "OPTIONS") {
40
+ res.writeHead(204, CORS);
41
+ res.end();
42
+ return;
43
+ }
44
+ if (url.pathname === path) {
45
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, ...guard });
46
+ const server = createServer({ onToolCall: (r) => console.error(JSON.stringify(r)) });
47
+ res.on("close", () => { void transport.close(); void server.close(); });
48
+ for (const [k, v] of Object.entries(CORS))
49
+ res.setHeader(k, v);
50
+ await server.connect(transport);
51
+ await transport.handleRequest(req, res);
52
+ return;
53
+ }
54
+ if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/health")) {
55
+ res.writeHead(200, { "Content-Type": "application/json", ...CORS });
56
+ res.end(JSON.stringify({ name: "gasp-standard", version: PKG_VERSION, tools: TOOL_COUNT, endpoint: path, documentation: "https://gaspwiki.com/v1/tools" }));
57
+ return;
58
+ }
59
+ res.writeHead(404, { "Content-Type": "text/plain" });
60
+ res.end(`Not found. The MCP endpoint is ${path}.`);
61
+ };
62
+ const server = createHttp((req, res) => {
63
+ handle(req, res).catch((err) => {
64
+ console.error("[gasp-standard-mcp-http]", err);
65
+ if (!res.headersSent) {
66
+ res.writeHead(500, { "Content-Type": "application/json" });
67
+ }
68
+ if (!res.writableEnded)
69
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null }));
70
+ });
71
+ });
72
+ server.listen(options.port ?? 3000, options.host ?? "0.0.0.0");
73
+ return server;
74
+ }
75
+ const list = (v) => v?.split(",").map((s) => s.trim()).filter(Boolean);
76
+ const invokedDirectly = (() => {
77
+ try {
78
+ return process.argv[1] !== undefined && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ })();
84
+ if (invokedDirectly) {
85
+ const port = Number(process.env.PORT ?? 3000);
86
+ const path = process.env.MCP_PATH ?? "/mcp";
87
+ const server = startHttpServer({ port, host: process.env.HOST, path, allowedHosts: list(process.env.MCP_ALLOWED_HOSTS), allowedOrigins: list(process.env.MCP_ALLOWED_ORIGINS) });
88
+ server.on("listening", () => console.error(`[gasp-standard-mcp-http] ${PKG_VERSION}: ${TOOL_COUNT} tools on port ${port}${path}`));
89
+ const stop = () => server.close(() => process.exit(0));
90
+ process.on("SIGTERM", stop);
91
+ process.on("SIGINT", stop);
92
+ }