@plitzi/sdk-server 0.32.10 → 0.32.11
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/CHANGELOG.md +10 -0
- package/dist/core/http/dispatcher.js +45 -9
- package/dist/core/services/mcp.js +4 -4
- package/dist/helpers/serverLog.js +33 -0
- package/dist/index.js +2 -1
- package/dist/mcp.js +2 -1
- package/dist/modules/mcp/handler.js +5 -0
- package/dist/modules/mcp/helpers/log.js +20 -10
- package/dist/modules/mcp/resources/register.js +10 -4
- package/dist/modules/mcp/resources/renderGuide.js +17 -7
- package/dist/modules/mcp/server.js +1 -1
- package/dist/plugins/manager.js +2 -0
- package/dist/plugins/validate.js +23 -0
- package/dist/src/core/http/dispatcher.test.d.ts +1 -0
- package/dist/src/core/http/types.d.ts +3 -0
- package/dist/src/helpers/buildResponseHelpers.d.ts +2 -0
- package/dist/src/helpers/serverLog.d.ts +10 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/mcp.d.ts +1 -0
- package/dist/src/modules/mcp/handler.d.ts +4 -4
- package/dist/src/modules/mcp/helpers/log.d.ts +3 -3
- package/dist/src/modules/mcp/resources/renderGuide.d.ts +2 -1
- package/dist/src/modules/mcp/server.d.ts +2 -2
- package/dist/src/plugins/validate.d.ts +9 -0
- package/dist/src/plugins/validate.test.d.ts +1 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,21 +2,57 @@ import { applySecurityHeaders } from "./securityHeaders.js";
|
|
|
2
2
|
import { buildResponseHelpers } from "../../helpers/buildResponseHelpers.js";
|
|
3
3
|
import { parseRequest } from "../requestParser.js";
|
|
4
4
|
//#region src/core/http/dispatcher.ts
|
|
5
|
-
var
|
|
5
|
+
var safePath = (url) => {
|
|
6
|
+
const [path = "/", query] = url.split("?");
|
|
7
|
+
if (!query) return path;
|
|
8
|
+
const keys = [...new Set(new URLSearchParams(query).keys())];
|
|
9
|
+
return keys.length > 0 ? `${path}?${keys.join("&")}` : path;
|
|
10
|
+
};
|
|
11
|
+
var errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
12
|
+
var statusOf = (rawRes, res) => rawRes.headersSent ? rawRes.statusCode : res.status;
|
|
13
|
+
var runPipeline = async (raw, rawRes, buildContext, stages, server) => {
|
|
14
|
+
const startedAt = Date.now();
|
|
6
15
|
const req = parseRequest(raw);
|
|
7
16
|
const res = buildResponseHelpers(rawRes, req.headers["accept-encoding"]);
|
|
8
|
-
if (req.path === "\0") {
|
|
9
|
-
res.setStatus(400);
|
|
10
|
-
res.end();
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
17
|
const ctx = buildContext(raw, rawRes, req, res);
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const logger = ctx.config.logger;
|
|
19
|
+
const logRequest = (error) => {
|
|
20
|
+
if (!logger) return;
|
|
21
|
+
const status = statusOf(rawRes, res);
|
|
22
|
+
logger({
|
|
23
|
+
kind: "request",
|
|
24
|
+
server,
|
|
25
|
+
method: req.method,
|
|
26
|
+
path: safePath(req.url),
|
|
27
|
+
...ctx.operation === void 0 ? {} : { operation: ctx.operation },
|
|
28
|
+
status,
|
|
29
|
+
durationMs: Date.now() - startedAt,
|
|
30
|
+
ok: !error && status < 400,
|
|
31
|
+
...error ? { error: errorText(error) } : {},
|
|
32
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
if (req.path === "\0") {
|
|
37
|
+
res.setStatus(400);
|
|
38
|
+
res.end();
|
|
39
|
+
logRequest();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
applySecurityHeaders(res, ctx.config, ctx.port);
|
|
43
|
+
for (const stage of stages) if (await stage(ctx)) {
|
|
44
|
+
logRequest();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
logRequest();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logRequest(error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
16
52
|
};
|
|
17
53
|
var makeHandler = (label, buildContext, stages) => {
|
|
18
54
|
return (raw, rawRes) => {
|
|
19
|
-
runPipeline(raw, rawRes, buildContext, stages).catch((err) => {
|
|
55
|
+
runPipeline(raw, rawRes, buildContext, stages, label).catch((err) => {
|
|
20
56
|
console.error(`[${label}] Unhandled error:`, err);
|
|
21
57
|
try {
|
|
22
58
|
if (!rawRes.headersSent) rawRes.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -3,12 +3,12 @@ import { createHttpPreviewClient } from "../../modules/mcp/previewClient.js";
|
|
|
3
3
|
import { createHttpScreenshotClient } from "../../modules/mcp/screenshotClient.js";
|
|
4
4
|
//#region src/core/services/mcp.ts
|
|
5
5
|
var mcpPathOf = (config) => config.mcpAi?.path ?? config.mcp?.path ?? "/mcp";
|
|
6
|
-
var serveMcp = (ctx) => {
|
|
7
|
-
const { previewClient, screenshot, adapters,
|
|
8
|
-
|
|
6
|
+
var serveMcp = async (ctx) => {
|
|
7
|
+
const { previewClient, screenshot, adapters, logger } = ctx.config;
|
|
8
|
+
ctx.operation = await handleMcp(ctx.raw, ctx.rawRes, ctx.req, adapters, {
|
|
9
9
|
preview: previewClient ? createHttpPreviewClient(previewClient) : void 0,
|
|
10
10
|
screenshot: screenshot ? createHttpScreenshotClient(screenshot) : void 0,
|
|
11
|
-
logger
|
|
11
|
+
logger
|
|
12
12
|
});
|
|
13
13
|
};
|
|
14
14
|
var mcpStage = async (ctx) => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/helpers/serverLog.ts
|
|
2
|
+
var outcomeOf = (event) => event.ok ? "ok" : `ERROR ${event.error ?? ""}`.trim();
|
|
3
|
+
var renderRequest = (event) => {
|
|
4
|
+
const operation = event.operation ? ` ${event.operation}` : "";
|
|
5
|
+
const timing = `${Math.round(event.durationMs)}ms`;
|
|
6
|
+
return `[${event.server}] ${event.method} ${event.path}${operation} ${event.status} ${timing} ${outcomeOf(event)}`;
|
|
7
|
+
};
|
|
8
|
+
var renderTool = (event) => {
|
|
9
|
+
const args = event.argsSummary ? ` ${event.argsSummary}` : "";
|
|
10
|
+
return `[mcp] tools/call ${event.name}${args} ${Math.round(event.durationMs)}ms ${outcomeOf(event)}`;
|
|
11
|
+
};
|
|
12
|
+
var renderResource = (event) => `[mcp] resources/read ${event.name} ${Math.round(event.durationMs)}ms ${outcomeOf(event)}`;
|
|
13
|
+
/** One line for any {@link ServerLogEvent}: an HTTP request reads as an access-log line
|
|
14
|
+
* (`[SSR] GET /pricing 200 12ms ok`), the MCP events as what happened inside one
|
|
15
|
+
* (`[mcp] tools/call plitzi_apply {operations:[3]} 41ms ok`). Events arrive PII-free — the dispatcher strips
|
|
16
|
+
* query values and collects no headers, cookies or IPs, and tool args are summarised by shape — so rendering is
|
|
17
|
+
* a pure format. */
|
|
18
|
+
var renderLogEvent = (event) => {
|
|
19
|
+
switch (event.kind) {
|
|
20
|
+
case "request": return renderRequest(event);
|
|
21
|
+
case "tool": return renderTool(event);
|
|
22
|
+
case "resource": return renderResource(event);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** A drop-in `SSRServerConfig.logger` for consumers that just want the log on the console. Consumers with their
|
|
26
|
+
* own logging stack should pass their own sink instead and read the structured event. */
|
|
27
|
+
var consoleLogger = (event) => {
|
|
28
|
+
const line = renderLogEvent(event);
|
|
29
|
+
if (event.ok) console.log(line);
|
|
30
|
+
else console.error(line);
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { consoleLogger, renderLogEvent };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildAgentGuide } from "./modules/mcp/helpers/agentPrompt.js";
|
|
2
|
+
import { consoleLogger, renderLogEvent } from "./helpers/serverLog.js";
|
|
2
3
|
import { operation } from "./modules/mcp/tools/operations/index.js";
|
|
3
4
|
import { apply, applyShape } from "./modules/mcp/tools/apply/index.js";
|
|
4
5
|
import { read, readShape } from "./modules/mcp/tools/read.js";
|
|
@@ -19,4 +20,4 @@ import { createSSRServer } from "./core/server/ssrServer.js";
|
|
|
19
20
|
import { createServer } from "./core/createServer.js";
|
|
20
21
|
import { createJsonAdapters } from "./adapters/jsonAdapters.js";
|
|
21
22
|
import AIEngine from "./modules/ai/AIEngine.js";
|
|
22
|
-
export { AIEngine, PREVIEW_TOKEN_PARAM, apply, applyShape, bindTools, buildAgentGuide, buildHealthPayload, createHttpPreviewClient, createHttpScreenshotClient, createJsonAdapters, createMCPServer, createMcpServer, createMemoryDraftStore, createPreview, createSSRServer, createServer, getAllowedModes, handleMcp, isCallToolResult, isToolActive, operation, read, readMcpBody, readShape, registerHealthCheck, resolveServices, resolveToolHandler, search, searchShape, serveMcp, toolResponseErr, toolResponseFromResult, toolResponseOk, tools, validate, validateShape, zodToJsonSchema };
|
|
23
|
+
export { AIEngine, PREVIEW_TOKEN_PARAM, apply, applyShape, bindTools, buildAgentGuide, buildHealthPayload, consoleLogger, createHttpPreviewClient, createHttpScreenshotClient, createJsonAdapters, createMCPServer, createMcpServer, createMemoryDraftStore, createPreview, createSSRServer, createServer, getAllowedModes, handleMcp, isCallToolResult, isToolActive, operation, read, readMcpBody, readShape, registerHealthCheck, renderLogEvent, resolveServices, resolveToolHandler, search, searchShape, serveMcp, toolResponseErr, toolResponseFromResult, toolResponseOk, tools, validate, validateShape, zodToJsonSchema };
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { consoleLogger, renderLogEvent } from "./helpers/serverLog.js";
|
|
1
2
|
import { createMcpServer } from "./modules/mcp/server.js";
|
|
2
3
|
import { handleMcp, readMcpBody, serveMcp } from "./modules/mcp/handler.js";
|
|
3
4
|
import { createHttpPreviewClient } from "./modules/mcp/previewClient.js";
|
|
4
5
|
import { createHttpScreenshotClient } from "./modules/mcp/screenshotClient.js";
|
|
5
6
|
import { createMCPServer } from "./core/server/mcpServer.js";
|
|
6
|
-
export { createHttpPreviewClient, createHttpScreenshotClient, createMCPServer, createMcpServer, handleMcp, readMcpBody, serveMcp };
|
|
7
|
+
export { consoleLogger, createHttpPreviewClient, createHttpScreenshotClient, createMCPServer, createMcpServer, handleMcp, readMcpBody, renderLogEvent, serveMcp };
|
|
@@ -15,6 +15,10 @@ var readMcpBody = (req) => new Promise((resolve, reject) => {
|
|
|
15
15
|
});
|
|
16
16
|
req.on("error", reject);
|
|
17
17
|
});
|
|
18
|
+
var operationOf = (body) => {
|
|
19
|
+
if (Array.isArray(body)) return `batch(${body.length})`;
|
|
20
|
+
if (typeof body === "object" && body !== null && "method" in body && typeof body.method === "string") return body.method;
|
|
21
|
+
};
|
|
18
22
|
var serveMcp = async (raw, res, server) => {
|
|
19
23
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
20
24
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -60,6 +64,7 @@ var serveMcp = async (raw, res, server) => {
|
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
66
|
await transport.handleRequest(raw, res, body);
|
|
67
|
+
return operationOf(body);
|
|
63
68
|
} finally {
|
|
64
69
|
await transport.close();
|
|
65
70
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
import { renderLogEvent } from "../../../helpers/serverLog.js";
|
|
1
2
|
//#region src/modules/mcp/helpers/log.ts
|
|
2
3
|
var MCP_DEBUG = process.env.MCP_DEBUG === "1";
|
|
3
|
-
var
|
|
4
|
-
|
|
4
|
+
var MCP_LOG_ARGS = process.env.MCP_LOG_ARGS === "1";
|
|
5
|
+
var shapeOf = (value, depth = 0) => {
|
|
6
|
+
if (value === null) return "null";
|
|
7
|
+
if (Array.isArray(value)) return `[${value.length}]`;
|
|
8
|
+
if (typeof value === "object") {
|
|
9
|
+
if (depth >= 2) return "{…}";
|
|
10
|
+
const entries = Object.entries(value);
|
|
11
|
+
const shown = entries.slice(0, 8).map(([key, item]) => `${key}:${shapeOf(item, depth + 1)}`);
|
|
12
|
+
return `{${[...shown, ...entries.length > shown.length ? ["…"] : []].join(",")}}`;
|
|
13
|
+
}
|
|
14
|
+
return typeof value;
|
|
15
|
+
};
|
|
16
|
+
var rawSummary = (value, max = 300) => {
|
|
5
17
|
let json;
|
|
6
18
|
try {
|
|
7
19
|
json = JSON.stringify(value);
|
|
@@ -10,25 +22,23 @@ var summarize = (value, max = 300) => {
|
|
|
10
22
|
}
|
|
11
23
|
return json.length > max ? `${json.slice(0, max)}…` : json;
|
|
12
24
|
};
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const detail = event.argsSummary ? ` ${event.argsSummary}` : "";
|
|
17
|
-
const status = event.ok ? "ok" : `ERROR ${event.error ?? ""}`;
|
|
18
|
-
console.log(`[mcp] ${kind} ${event.name}${detail} ${Math.round(event.durationMs)}ms ${status}`);
|
|
25
|
+
var summarize = (value) => {
|
|
26
|
+
if (value === void 0) return;
|
|
27
|
+
return MCP_LOG_ARGS ? rawSummary(value) : shapeOf(value);
|
|
19
28
|
};
|
|
29
|
+
var errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
20
30
|
var noop = () => void 0;
|
|
21
31
|
var inertLog = {
|
|
22
32
|
toolCall: noop,
|
|
23
33
|
resourceRead: noop
|
|
24
34
|
};
|
|
25
|
-
/** Build the
|
|
35
|
+
/** Build the protocol-log sink for one MCP server. Dispatches structured events to the consumer's `logger` when
|
|
26
36
|
* provided; otherwise renders to the console when MCP_DEBUG=1; otherwise a no-op. */
|
|
27
37
|
var createMcpLog = (logger) => {
|
|
28
38
|
if (!logger && !MCP_DEBUG) return inertLog;
|
|
29
39
|
const emit = (event) => {
|
|
30
40
|
if (logger) logger(event);
|
|
31
|
-
else
|
|
41
|
+
else console.log(renderLogEvent(event));
|
|
32
42
|
};
|
|
33
43
|
return {
|
|
34
44
|
toolCall: (name, args, ms, error) => emit({
|
|
@@ -21,19 +21,25 @@ var registerResources = (server, getSpace, env, log) => {
|
|
|
21
21
|
throw error;
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
+
const emitStatic = (uri, read) => {
|
|
25
|
+
const start = performance.now();
|
|
26
|
+
const result = read();
|
|
27
|
+
log.resourceRead(uri, performance.now() - start);
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
24
30
|
server.registerResource("Guide", "plitzi://guide", {
|
|
25
31
|
description: "How to read and write this space with mcp-ai",
|
|
26
32
|
mimeType: "text/markdown"
|
|
27
|
-
}, () => ({ contents: [{
|
|
33
|
+
}, () => emitStatic("plitzi://guide", () => ({ contents: [{
|
|
28
34
|
uri: "plitzi://guide",
|
|
29
35
|
mimeType: "text/markdown",
|
|
30
36
|
text: guideText
|
|
31
|
-
}] }));
|
|
37
|
+
}] })));
|
|
32
38
|
server.registerResource("CSS properties", "plitzi://css-properties", {
|
|
33
39
|
description: "Valid kebab-case CSS property keys",
|
|
34
40
|
mimeType: "application/json"
|
|
35
|
-
}, () => jsonContents("plitzi://css-properties", envelope(cssProperties)));
|
|
36
|
-
registerRenderResources(server);
|
|
41
|
+
}, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope(cssProperties))));
|
|
42
|
+
registerRenderResources(server, log);
|
|
37
43
|
const fixed = [
|
|
38
44
|
[
|
|
39
45
|
"Primer",
|
|
@@ -168,19 +168,29 @@ connects that source to a descendant's field.
|
|
|
168
168
|
The tool returns \`rendered: false\` with \`errors: [{ path, message, hint }]\`. Read the hint, fix that one op, and
|
|
169
169
|
retry — you never lose the rest of the batch.
|
|
170
170
|
`;
|
|
171
|
-
var registerRenderResources = (server) => {
|
|
171
|
+
var registerRenderResources = (server, log) => {
|
|
172
172
|
server.registerResource("Render guide", RENDER_GUIDE_URI, {
|
|
173
173
|
description: "How to author a plitzi_render widget: operations, element types, styling, examples.",
|
|
174
174
|
mimeType: "text/markdown"
|
|
175
|
-
}, () =>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
}, () => {
|
|
176
|
+
const start = performance.now();
|
|
177
|
+
const contents = { contents: [{
|
|
178
|
+
uri: RENDER_GUIDE_URI,
|
|
179
|
+
mimeType: "text/markdown",
|
|
180
|
+
text: renderGuideText
|
|
181
|
+
}] };
|
|
182
|
+
log.resourceRead(RENDER_GUIDE_URI, performance.now() - start);
|
|
183
|
+
return contents;
|
|
184
|
+
});
|
|
180
185
|
server.registerResource("Render element types", RENDER_TYPES_URI, {
|
|
181
186
|
description: "Built-in element types a plitzi_render widget can use, with descriptions.",
|
|
182
187
|
mimeType: "application/json"
|
|
183
|
-
}, () =>
|
|
188
|
+
}, () => {
|
|
189
|
+
const start = performance.now();
|
|
190
|
+
const contents = jsonContents(RENDER_TYPES_URI, envelope(renderTypes()));
|
|
191
|
+
log.resourceRead(RENDER_TYPES_URI, performance.now() - start);
|
|
192
|
+
return contents;
|
|
193
|
+
});
|
|
184
194
|
};
|
|
185
195
|
//#endregion
|
|
186
196
|
export { RENDER_GUIDE_URI, RENDER_TYPES_URI, registerRenderResources };
|
|
@@ -43,7 +43,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) =>
|
|
|
43
43
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
44
44
|
const server = new McpServer({
|
|
45
45
|
name: "plitzi-mcp",
|
|
46
|
-
version: "0.32.
|
|
46
|
+
version: "0.32.11"
|
|
47
47
|
}, { instructions: serverInstructions });
|
|
48
48
|
registerResources(server, getSpace, MCP_ENV, log);
|
|
49
49
|
registerApps(server);
|
package/dist/plugins/manager.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compilePlugin } from "./compile.js";
|
|
2
2
|
import { copyPlugin } from "./copy.js";
|
|
3
3
|
import { detectAction, isComponentSource } from "./detect.js";
|
|
4
|
+
import { assertPluginSources } from "./validate.js";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import fs from "node:fs/promises";
|
|
6
7
|
//#region src/plugins/manager.ts
|
|
@@ -19,6 +20,7 @@ var PluginManager = class {
|
|
|
19
20
|
nameIndex = /* @__PURE__ */ new Map();
|
|
20
21
|
devMode = false;
|
|
21
22
|
constructor(plugins, cacheDir, ttlMs, devMode = false) {
|
|
23
|
+
assertPluginSources(plugins);
|
|
22
24
|
this.plugins = plugins;
|
|
23
25
|
this.outputDir = path.resolve(process.cwd(), cacheDir ?? DEFAULT_CACHE_DIR);
|
|
24
26
|
this.ttlMs = ttlMs ?? DEFAULT_TTL_MS;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isComponentSource } from "./detect.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
//#region src/plugins/validate.ts
|
|
4
|
+
var isRemote = (js) => js.startsWith("http://") || js.startsWith("https://");
|
|
5
|
+
/** The file a plugin source needs on disk, or null when nothing local is involved — a remote URL (fetched and
|
|
6
|
+
* cached at request time) or a component handed in directly. */
|
|
7
|
+
var localSourcePath = (source) => {
|
|
8
|
+
if (isComponentSource(source) || isRemote(source.js)) return null;
|
|
9
|
+
return source.js;
|
|
10
|
+
};
|
|
11
|
+
/** Fail at boot on a plugin whose entry file is missing. A plugin entry is a plain string path in the server
|
|
12
|
+
* config, so nothing — not tsc, not eslint — notices when that file is moved or deleted; without this check the
|
|
13
|
+
* server starts happily and only dies on the first render that needs the plugin, with an esbuild resolve error
|
|
14
|
+
* far from its cause. */
|
|
15
|
+
var assertPluginSources = (plugins) => {
|
|
16
|
+
const missing = Object.entries(plugins).map(([name, source]) => ({
|
|
17
|
+
name,
|
|
18
|
+
file: localSourcePath(source)
|
|
19
|
+
})).filter((entry) => entry.file !== null && !fs.existsSync(entry.file)).map((entry) => ` ${entry.name}: ${entry.file ?? ""}`);
|
|
20
|
+
if (missing.length > 0) throw new Error(`Plugin entry file not found (declared in the server config):\n${missing.join("\n")}`);
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { assertPluginSources, localSourcePath };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -10,6 +10,9 @@ export interface BaseContext {
|
|
|
10
10
|
res: SSRResponseHelpers;
|
|
11
11
|
config: SSRServerConfig;
|
|
12
12
|
port: number;
|
|
13
|
+
/** Set by the stage that answers when the path alone does not identify the work (the MCP endpoint serves every
|
|
14
|
+
* JSON-RPC method on the same URL). The dispatcher folds it into the access log. */
|
|
15
|
+
operation?: string;
|
|
13
16
|
}
|
|
14
17
|
export interface SSRContext extends BaseContext {
|
|
15
18
|
renderFn: SSRTemplateFn;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SSRResponseHelpers } from '@plitzi/sdk-shared';
|
|
2
2
|
export type RawResponse = {
|
|
3
3
|
headersSent: boolean;
|
|
4
|
+
/** The status actually written to the wire — the access log reads it once the response is out. */
|
|
5
|
+
statusCode: number;
|
|
4
6
|
setHeader(name: string, value: string | number | readonly string[]): unknown;
|
|
5
7
|
getHeaders(): Record<string, string | number | readonly string[]>;
|
|
6
8
|
writeHead(statusCode: number, headers?: Record<string, string | number | readonly string[]>): unknown;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ServerLogEvent, ServerLogger } from '@plitzi/sdk-shared';
|
|
2
|
+
/** One line for any {@link ServerLogEvent}: an HTTP request reads as an access-log line
|
|
3
|
+
* (`[SSR] GET /pricing 200 12ms ok`), the MCP events as what happened inside one
|
|
4
|
+
* (`[mcp] tools/call plitzi_apply {operations:[3]} 41ms ok`). Events arrive PII-free — the dispatcher strips
|
|
5
|
+
* query values and collects no headers, cookies or IPs, and tool args are summarised by shape — so rendering is
|
|
6
|
+
* a pure format. */
|
|
7
|
+
export declare const renderLogEvent: (event: ServerLogEvent) => string;
|
|
8
|
+
/** A drop-in `SSRServerConfig.logger` for consumers that just want the log on the console. Consumers with their
|
|
9
|
+
* own logging stack should pass their own sink instead and read the structured event. */
|
|
10
|
+
export declare const consoleLogger: ServerLogger;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createServer, createSSRServer, createMCPServer, resolveServices } from './core/createServer';
|
|
2
2
|
export { registerHealthCheck, buildHealthPayload } from './core/health';
|
|
3
|
+
export { consoleLogger, renderLogEvent } from './helpers/serverLog';
|
|
3
4
|
export type { HealthCheckApp, HealthIdentity } from './core/health';
|
|
4
5
|
export { createJsonAdapters } from './adapters/jsonAdapters';
|
|
5
6
|
export { AIEngine, toolResponseOk, toolResponseErr, zodToJsonSchema, getAllowedModes, bindTools, isToolActive, resolveToolHandler, isCallToolResult, toolResponseFromResult } from './modules/ai';
|
package/dist/src/mcp.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* AI engine…), so pulling `createMCPServer` from there makes the process load that whole graph — including the
|
|
5
5
|
* React element packages its types come from — where this entry loads only the MCP server. */
|
|
6
6
|
export { createMCPServer } from './core/server/mcpServer';
|
|
7
|
+
export { consoleLogger, renderLogEvent } from './helpers/serverLog';
|
|
7
8
|
export { createMcpServer, handleMcp, serveMcp, readMcpBody } from './modules/mcp/handler';
|
|
8
9
|
export { createHttpPreviewClient } from './modules/mcp/previewClient';
|
|
9
10
|
export { createHttpScreenshotClient } from './modules/mcp/screenshotClient';
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { createMcpServer } from './server';
|
|
2
2
|
import { PreviewClient, ScreenshotClient } from './types';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { SSRAdapters, SSRRequest,
|
|
4
|
+
import { SSRAdapters, SSRRequest, ServerLogger } from '@plitzi/sdk-shared';
|
|
5
5
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
6
|
/** Per-request wiring the MCP service does not resolve itself: the renderer clients and the log sink. Everything
|
|
7
7
|
* here is optional — the service degrades feature by feature. */
|
|
8
8
|
export type McpRequestOptions = {
|
|
9
9
|
preview?: PreviewClient;
|
|
10
10
|
screenshot?: ScreenshotClient;
|
|
11
|
-
logger?:
|
|
11
|
+
logger?: ServerLogger;
|
|
12
12
|
};
|
|
13
13
|
export declare const readMcpBody: (req: IncomingMessage) => Promise<unknown>;
|
|
14
|
-
export declare const serveMcp: (raw: IncomingMessage, res: ServerResponse, server: McpServer) => Promise<
|
|
15
|
-
export declare const handleMcp: (raw: IncomingMessage, res: ServerResponse, req: SSRRequest, adapters: SSRAdapters, options?: McpRequestOptions) => Promise<
|
|
14
|
+
export declare const serveMcp: (raw: IncomingMessage, res: ServerResponse, server: McpServer) => Promise<string | undefined>;
|
|
15
|
+
export declare const handleMcp: (raw: IncomingMessage, res: ServerResponse, req: SSRRequest, adapters: SSRAdapters, options?: McpRequestOptions) => Promise<string | undefined>;
|
|
16
16
|
export { createMcpServer };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ServerLogger } from '@plitzi/sdk-shared';
|
|
2
2
|
export interface McpLog {
|
|
3
3
|
toolCall(name: string, args: unknown, ms: number, error?: unknown): void;
|
|
4
4
|
resourceRead(uri: string, ms: number, error?: unknown): void;
|
|
5
5
|
}
|
|
6
|
-
/** Build the
|
|
6
|
+
/** Build the protocol-log sink for one MCP server. Dispatches structured events to the consumer's `logger` when
|
|
7
7
|
* provided; otherwise renders to the console when MCP_DEBUG=1; otherwise a no-op. */
|
|
8
|
-
export declare const createMcpLog: (logger?:
|
|
8
|
+
export declare const createMcpLog: (logger?: ServerLogger) => McpLog;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { McpLog } from '../helpers';
|
|
1
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
export declare const RENDER_GUIDE_URI = "plitzi://render/guide";
|
|
3
4
|
export declare const RENDER_TYPES_URI = "plitzi://render/types";
|
|
4
|
-
export declare const registerRenderResources: (server: McpServer) => void;
|
|
5
|
+
export declare const registerRenderResources: (server: McpServer, log: McpLog) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { PreviewClient, ScreenshotClient } from './types';
|
|
3
|
-
import { SSRAdapters,
|
|
3
|
+
import { SSRAdapters, ServerLogger } from '@plitzi/sdk-shared';
|
|
4
4
|
/** The MCP service is stateless: every request resolves its own `spaceId` (from the request JWT) and reads the
|
|
5
5
|
* space fresh through the adapters — schema and style are two documents, read/written independently. Both the
|
|
6
6
|
* spaceId and the space itself resolve lazily, so the public surface (handshake, tools-list, resources-list,
|
|
@@ -17,6 +17,6 @@ export interface McpServerContext {
|
|
|
17
17
|
screenshot?: ScreenshotClient;
|
|
18
18
|
/** Structured request-log sink. When set, every tool call and resource read emits an McpLogEvent to it (the
|
|
19
19
|
* consumer renders them); otherwise logging falls back to the console when MCP_DEBUG=1. */
|
|
20
|
-
logger?:
|
|
20
|
+
logger?: ServerLogger;
|
|
21
21
|
}
|
|
22
22
|
export declare const createMcpServer: ({ adapters, getSpaceId, preview, screenshot, logger }: McpServerContext) => McpServer;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PluginSource } from '@plitzi/sdk-shared';
|
|
2
|
+
/** The file a plugin source needs on disk, or null when nothing local is involved — a remote URL (fetched and
|
|
3
|
+
* cached at request time) or a component handed in directly. */
|
|
4
|
+
export declare const localSourcePath: (source: PluginSource) => string | null;
|
|
5
|
+
/** Fail at boot on a plugin whose entry file is missing. A plugin entry is a plain string path in the server
|
|
6
|
+
* config, so nothing — not tsc, not eslint — notices when that file is moved or deleted; without this check the
|
|
7
|
+
* server starts happily and only dies on the first render that needs the plugin, with an esbuild resolve error
|
|
8
|
+
* far from its cause. */
|
|
9
|
+
export declare const assertPluginSources: (plugins: Record<string, PluginSource>) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-server",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.11",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
-
"@plitzi/plitzi-sdk": "0.32.
|
|
33
|
-
"@plitzi/sdk-schema": "0.32.
|
|
34
|
-
"@plitzi/sdk-shared": "0.32.
|
|
32
|
+
"@plitzi/plitzi-sdk": "0.32.11",
|
|
33
|
+
"@plitzi/sdk-schema": "0.32.11",
|
|
34
|
+
"@plitzi/sdk-shared": "0.32.11",
|
|
35
35
|
"ejs": "^6.0.1",
|
|
36
36
|
"esbuild": "^0.28.1",
|
|
37
37
|
"zod": "^4.4.3"
|