@plitzi/sdk-server 0.32.24 → 0.32.25
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/modules/mcp/proxy/config.js +24 -7
- package/dist/modules/mcp/proxy/grant.js +4 -2
- package/dist/modules/mcp/proxy/handler.js +31 -0
- package/dist/modules/mcp/proxy/payload.js +29 -0
- package/dist/modules/mcp/proxy/rewrite.js +75 -9
- package/dist/modules/mcp/server.js +1 -1
- package/dist/src/modules/mcp/proxy/config.d.ts +6 -4
- package/dist/src/modules/mcp/proxy/grant.d.ts +3 -1
- package/dist/src/modules/mcp/proxy/index.d.ts +3 -2
- package/dist/src/modules/mcp/proxy/payload.d.ts +8 -0
- package/dist/src/modules/mcp/proxy/rewrite.d.ts +7 -2
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -22,25 +22,42 @@ var proxySettings = (config) => {
|
|
|
22
22
|
};
|
|
23
23
|
/** The endpoint as a widget must address it: absolute, because the page runs on the host's origin and a relative
|
|
24
24
|
* URL there points at the host. Falls back to the origin this request came in on, which is the right one whenever
|
|
25
|
-
* the MCP server owns its sub-domain; a deployment reached under a different public name sets `proxy.baseUrl`.
|
|
26
|
-
|
|
25
|
+
* the MCP server owns its sub-domain; a deployment reached under a different public name sets `proxy.baseUrl`. */
|
|
26
|
+
var endpointOf = (settings, req) => {
|
|
27
|
+
const base = (settings.baseUrl ?? requestOrigin(req)).replace(/\/$/, "");
|
|
28
|
+
return base ? `${base}${settings.path}` : void 0;
|
|
29
|
+
};
|
|
30
|
+
/** The proxy a tool rewrites through. The grants it mints carry the fingerprint of THIS request's credential, so
|
|
31
|
+
* they belong to this connection. */
|
|
27
32
|
var requestProxy = (config, req) => {
|
|
28
33
|
const settings = proxySettings(config);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!base) return;
|
|
34
|
+
const endpoint = settings && endpointOf(settings, req);
|
|
35
|
+
if (!settings || !endpoint) return;
|
|
32
36
|
return {
|
|
33
|
-
endpoint
|
|
37
|
+
endpoint,
|
|
34
38
|
secret: settings.secret,
|
|
35
39
|
identity: connectionId(req.headers.authorization, settings.secret),
|
|
36
40
|
ttl: settings.ttl,
|
|
37
41
|
tools: settings.tools
|
|
38
42
|
};
|
|
39
43
|
};
|
|
44
|
+
/** The proxy the ENDPOINT itself mints with, while serving an API answer that carries URLs of its own. The request
|
|
45
|
+
* it is serving comes from a sandboxed iframe and presents no credential, so the connection is the one signed into
|
|
46
|
+
* the grant being served — the widget's own — rather than one read off a header. */
|
|
47
|
+
var grantingProxy = (settings, req, identity) => {
|
|
48
|
+
const endpoint = endpointOf(settings, req);
|
|
49
|
+
return endpoint ? {
|
|
50
|
+
endpoint,
|
|
51
|
+
secret: settings.secret,
|
|
52
|
+
identity,
|
|
53
|
+
ttl: settings.ttl,
|
|
54
|
+
tools: settings.tools
|
|
55
|
+
} : void 0;
|
|
56
|
+
};
|
|
40
57
|
/** The proxy THIS tool may use, or undefined — the single place that decides it, so a tool cannot reach the
|
|
41
58
|
* endpoint by being wired to it later. A render authors a throwaway widget, so rewriting its URLs is invisible
|
|
42
59
|
* and lasts as long as the widget does; a tool that WRITES a space (plitzi_apply) would persist those URLs into
|
|
43
60
|
* content the user owns, which is why it is not on the list unless a deployment puts it there deliberately. */
|
|
44
61
|
var proxyForTool = (proxy, tool) => proxy?.tools.includes(tool) ? proxy : void 0;
|
|
45
62
|
//#endregion
|
|
46
|
-
export { PROXY_PATH, proxyForTool, proxySettings, requestProxy };
|
|
63
|
+
export { PROXY_PATH, grantingProxy, proxyForTool, proxySettings, requestProxy };
|
|
@@ -49,12 +49,14 @@ var readGrant = (param, secret) => {
|
|
|
49
49
|
const payload = payloadOf(kind, expiresAt, identity, target);
|
|
50
50
|
if (verify(payload, signature, secret)) return {
|
|
51
51
|
kind,
|
|
52
|
-
target
|
|
52
|
+
target,
|
|
53
|
+
identity
|
|
53
54
|
};
|
|
54
55
|
const origin = originOf(target);
|
|
55
56
|
return origin && verify(payloadOf(kind, expiresAt, identity, origin), signature, secret) ? {
|
|
56
57
|
kind,
|
|
57
|
-
target
|
|
58
|
+
target,
|
|
59
|
+
identity
|
|
58
60
|
} : void 0;
|
|
59
61
|
};
|
|
60
62
|
/** Is this URL one the widget can already reach? Absolute http(s) URLs are the ones a sandbox CSP blocks;
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
+
import { grantingProxy } from "./config.js";
|
|
1
2
|
import { readGrant } from "./grant.js";
|
|
2
3
|
import { fetchResource } from "./fetch.js";
|
|
4
|
+
import { rewritablePayload, rewritePayload } from "./payload.js";
|
|
3
5
|
//#region src/modules/mcp/proxy/handler.ts
|
|
4
6
|
var CACHE_CONTROL = {
|
|
5
7
|
asset: "public, max-age=86400, immutable",
|
|
6
8
|
data: "no-store"
|
|
7
9
|
};
|
|
10
|
+
/** Read a response whole, or undefined once it is past the limit. Only a rewritable answer is read this way: it has
|
|
11
|
+
* to be complete before a URL inside it can be swapped, and an API answer is small — which is exactly why the
|
|
12
|
+
* streaming path below stays the rule for everything else. */
|
|
13
|
+
var readAll = async (body, maxBytes) => {
|
|
14
|
+
const reader = body.getReader();
|
|
15
|
+
const chunks = [];
|
|
16
|
+
let written = 0;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const { done, value } = await reader.read();
|
|
19
|
+
if (done) break;
|
|
20
|
+
written += value.byteLength;
|
|
21
|
+
if (written > maxBytes) {
|
|
22
|
+
await reader.cancel();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
chunks.push(Buffer.from(value));
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks);
|
|
28
|
+
};
|
|
8
29
|
var fail = (res, status, reason) => {
|
|
9
30
|
res.setStatus(status);
|
|
10
31
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
@@ -47,6 +68,16 @@ var handleProxyRequest = async (req, res, settings) => {
|
|
|
47
68
|
res.end();
|
|
48
69
|
return;
|
|
49
70
|
}
|
|
71
|
+
const payloadProxy = grant.kind === "data" && rewritablePayload(result.contentType) ? grantingProxy(settings, req, grant.identity) : void 0;
|
|
72
|
+
if (payloadProxy) {
|
|
73
|
+
const body = await readAll(result.body, settings.maxBytes);
|
|
74
|
+
if (!body) {
|
|
75
|
+
fail(res, 413, "That response is too large to load in a widget.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
res.send(rewritePayload(body.toString("utf8"), payloadProxy));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
50
81
|
const reader = result.body.getReader();
|
|
51
82
|
let written = 0;
|
|
52
83
|
try {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { grantUrl, isGranted, isRemote } from "./grant.js";
|
|
2
|
+
import { looksLikeAsset } from "./rewrite.js";
|
|
3
|
+
//#region src/modules/mcp/proxy/payload.ts
|
|
4
|
+
var JSON_TYPES = ["application/json", "application/ld+json"];
|
|
5
|
+
var ASSET_FIELD = /(image|img|thumb|photo|picture|avatar|icon|logo|cover|banner|poster|artwork|media|src)/i;
|
|
6
|
+
/** Is this what the endpoint fetched a body it can rewrite? Only JSON: it is what an apiContainer binds against,
|
|
7
|
+
* and re-serializing anything else would risk changing a document to fix a URL that may not even be loaded. */
|
|
8
|
+
var rewritablePayload = (contentType) => JSON_TYPES.some((type) => contentType.startsWith(type));
|
|
9
|
+
var rewriteNode = (node, field, proxy) => {
|
|
10
|
+
if (typeof node === "string") return isRemote(node) && !isGranted(node, proxy) && (looksLikeAsset(node) || ASSET_FIELD.test(field)) ? grantUrl(node, proxy) : node;
|
|
11
|
+
if (Array.isArray(node)) return node.map((item) => rewriteNode(item, field, proxy));
|
|
12
|
+
if (node !== null && typeof node === "object") {
|
|
13
|
+
const entries = Object.entries(node);
|
|
14
|
+
return Object.fromEntries(entries.map(([key, value]) => [key, rewriteNode(value, key, proxy)]));
|
|
15
|
+
}
|
|
16
|
+
return node;
|
|
17
|
+
};
|
|
18
|
+
/** Rewrite the asset URLs an API answer carries so the widget loads them through this endpoint too. Returns the
|
|
19
|
+
* body untouched when it is not the JSON it claimed to be — the widget asked for that response, and serving it
|
|
20
|
+
* as it came is better than failing the fetch over a rewrite. */
|
|
21
|
+
var rewritePayload = (body, proxy) => {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(rewriteNode(JSON.parse(body), "", proxy));
|
|
24
|
+
} catch {
|
|
25
|
+
return body;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { rewritablePayload, rewritePayload };
|
|
@@ -3,11 +3,25 @@ import { grantUrl, isGranted, isRemote } from "./grant.js";
|
|
|
3
3
|
var URL_ATTRIBUTES = ["src", "poster"];
|
|
4
4
|
var MARKUP_ATTRIBUTES = ["content", "html"];
|
|
5
5
|
var FETCHING_TYPE = "apiContainer";
|
|
6
|
+
var SET_STATE_ACTION = "setState";
|
|
7
|
+
var NAVIGATION_ACTION = "navigate";
|
|
8
|
+
var FETCHING_UTILITY = "webHook";
|
|
9
|
+
var ASSET_EXTENSION = /\.(?:apng|avif|bmp|gif|ico|jpe?g|png|svg|webp|aac|flac|m4a|mp3|oga|ogg|opus|wav|mp4|m4v|mov|ogv|webm|otf|ttf|woff2?)$/i;
|
|
6
10
|
var CSS_URL = /url\(\s*(["']?)(https?:\/\/[^"')\s]+)\1\s*\)/gi;
|
|
7
11
|
var HTML_SRC = /(<[^>]+?\ssrc\s*=\s*)(["'])(https?:\/\/[^"']+)\2/gi;
|
|
8
12
|
var MARKDOWN_IMAGE = /(!\[[^\]]*\]\(\s*)(https?:\/\/[^\s)]+)/gi;
|
|
9
13
|
var unescapeHtml = (url) => url.replace(/&/g, "&");
|
|
10
14
|
var toProxy = (url, proxy) => isGranted(url, proxy) ? url : grantUrl(unescapeHtml(url), proxy);
|
|
15
|
+
/** Does this URL name a file the widget loads, judged by its path alone? Used where nothing else says what a URL is
|
|
16
|
+
* for — an interaction param, a field of an API answer — so the extension is the evidence and its absence is a
|
|
17
|
+
* reason to leave the URL alone. */
|
|
18
|
+
var looksLikeAsset = (url) => {
|
|
19
|
+
try {
|
|
20
|
+
return ASSET_EXTENSION.test(new URL(url).pathname);
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
11
25
|
/** Rewrite every remote URL embedded in a string: `url(…)` in CSS, `src="…"` in markup, `` in markdown. */
|
|
12
26
|
var rewriteText = (text, proxy) => text.replace(CSS_URL, (_match, quote, url) => `url(${quote}${toProxy(url, proxy)}${quote})`).replace(HTML_SRC, (_match, prefix, quote, url) => `${prefix}${quote}${toProxy(url, proxy)}${quote}`).replace(MARKDOWN_IMAGE, (_match, prefix, url) => `${prefix}${toProxy(url, proxy)}`);
|
|
13
27
|
var rewriteStyleObject = (styleObject, proxy) => {
|
|
@@ -17,17 +31,68 @@ var rewriteStyleBlock = (block, proxy) => {
|
|
|
17
31
|
if (block.default) rewriteStyleObject(block.default, proxy);
|
|
18
32
|
for (const state of Object.values(block.states ?? {})) rewriteStyleObject(state, proxy);
|
|
19
33
|
};
|
|
20
|
-
/** Why a
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
/** Why a request was left as authored, or undefined when it can be granted. Each case is one this hop cannot
|
|
35
|
+
* reproduce faithfully, and getting it wrong silently would be worse than the request being blocked: a write sent
|
|
36
|
+
* through a GET, or an API answering 401 because its credential was dropped on the way. Reads both vocabularies —
|
|
37
|
+
* an apiContainer's attributes and a webHook's params name the same things differently. */
|
|
38
|
+
var unproxyableFetch = (source) => {
|
|
39
|
+
const { method, headers, accessToken, authorizationToken } = source;
|
|
25
40
|
if (typeof method === "string" && method.toLowerCase() !== "get") return `it is a ${method.toUpperCase()} request`;
|
|
26
41
|
if (typeof accessToken === "string" && accessToken !== "") return "it sends its own credentials";
|
|
42
|
+
if (typeof authorizationToken === "string" && authorizationToken !== "") return "it sends its own credentials";
|
|
27
43
|
return headers && Object.keys(headers).length > 0 ? "it sends its own headers" : void 0;
|
|
28
44
|
};
|
|
45
|
+
var rewriteSetState = (params, proxy) => {
|
|
46
|
+
const { key, value } = params;
|
|
47
|
+
if (typeof key !== "string" || typeof value !== "string") return false;
|
|
48
|
+
if (URL_ATTRIBUTES.includes(key) && isRemote(value)) {
|
|
49
|
+
params.value = toProxy(value, proxy);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (MARKUP_ATTRIBUTES.includes(key) && value.includes("http")) {
|
|
53
|
+
params.value = rewriteText(value, proxy);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
58
|
+
var rewriteParams = (params, proxy) => {
|
|
59
|
+
for (const [name, value] of Object.entries(params)) {
|
|
60
|
+
if (typeof value !== "string") continue;
|
|
61
|
+
if (URL_ATTRIBUTES.includes(name) && isRemote(value)) params[name] = toProxy(value, proxy);
|
|
62
|
+
else if (MARKUP_ATTRIBUTES.includes(name) && value.includes("http")) params[name] = rewriteText(value, proxy);
|
|
63
|
+
else if (isRemote(value) && looksLikeAsset(value)) params[name] = toProxy(value, proxy);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var rewriteWebHook = (node, proxy, warnings) => {
|
|
67
|
+
const params = node.params;
|
|
68
|
+
const url = params.url;
|
|
69
|
+
if (typeof url !== "string" || !isRemote(url) || isGranted(url, proxy)) return;
|
|
70
|
+
const blocked = unproxyableFetch(params);
|
|
71
|
+
if (blocked) {
|
|
72
|
+
warnings.push(`The ${FETCHING_UTILITY} step "${node.title}" calls its endpoint directly because ${blocked}, so that request is subject to the network policy of the surface it renders in, and to the CORS headers of the API, and may not run. A plain GET with no credentials is fetched by the widget server instead, which always works.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
params.url = grantUrl(url, proxy, "data");
|
|
76
|
+
};
|
|
77
|
+
var rewriteInteractions = (interactions, proxy, warnings) => {
|
|
78
|
+
for (const node of Object.values(interactions ?? {})) {
|
|
79
|
+
if (node.type === "globalCallback" && node.action === NAVIGATION_ACTION) continue;
|
|
80
|
+
if (node.type === "utility" && node.action === FETCHING_UTILITY) {
|
|
81
|
+
rewriteWebHook(node, proxy, warnings);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const params = node.params;
|
|
85
|
+
if (node.action === SET_STATE_ACTION && rewriteSetState(params, proxy)) continue;
|
|
86
|
+
rewriteParams(params, proxy);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var rewriteBindings = (bindings, proxy) => {
|
|
90
|
+
for (const category of Object.values(bindings ?? {})) for (const binding of category) for (const transformer of binding.transformers ?? []) rewriteParams(transformer.params, proxy);
|
|
91
|
+
};
|
|
29
92
|
var rewriteSchema = (schema, proxy, warnings) => {
|
|
30
93
|
for (const element of Object.values(schema.flat)) {
|
|
94
|
+
rewriteInteractions(element.definition.interactions, proxy, warnings);
|
|
95
|
+
rewriteBindings(element.definition.bindings, proxy);
|
|
31
96
|
const attributes = element.attributes;
|
|
32
97
|
for (const name of URL_ATTRIBUTES) {
|
|
33
98
|
const value = attributes[name];
|
|
@@ -39,7 +104,7 @@ var rewriteSchema = (schema, proxy, warnings) => {
|
|
|
39
104
|
}
|
|
40
105
|
const query = attributes.query;
|
|
41
106
|
if (element.definition.type !== FETCHING_TYPE || typeof query !== "string" || !isRemote(query)) continue;
|
|
42
|
-
const blocked = unproxyableFetch(
|
|
107
|
+
const blocked = unproxyableFetch(attributes);
|
|
43
108
|
if (blocked) {
|
|
44
109
|
warnings.push(`The ${FETCHING_TYPE} "${element.idRef ?? element.id}" calls its endpoint directly because ${blocked}, so that request is subject to the network policy of the surface it renders in, and to the CORS headers of the API, and may not run. A plain GET with no headers is fetched by the widget server instead, which always works.`);
|
|
45
110
|
continue;
|
|
@@ -57,8 +122,9 @@ var rewriteStyle = (style, proxy) => {
|
|
|
57
122
|
}
|
|
58
123
|
if (style.cache.includes("url(")) style.cache = rewriteText(style.cache, proxy);
|
|
59
124
|
};
|
|
60
|
-
/** Point everything an authored widget loads from outside — images, media, fonts,
|
|
61
|
-
*
|
|
125
|
+
/** Point everything an authored widget loads from outside — images, media, fonts, the data an apiContainer fetches
|
|
126
|
+
* and the URLs its interactions swap in later — at this server's endpoint, in place. The AGENT never sees this: it
|
|
127
|
+
* authors the real URL and this runs
|
|
62
128
|
* afterwards, on the throwaway space a render builds (so nothing shared is mutated) and before the global style
|
|
63
129
|
* cache is compiled (so the concatenated CSS comes out already rewritten). Returns what could not be rewritten,
|
|
64
130
|
* which is the only part the model hears about. */
|
|
@@ -69,4 +135,4 @@ var proxifyResources = (space, proxy) => {
|
|
|
69
135
|
return warnings;
|
|
70
136
|
};
|
|
71
137
|
//#endregion
|
|
72
|
-
export { proxifyResources, rewriteText };
|
|
138
|
+
export { looksLikeAsset, proxifyResources, rewriteText };
|
|
@@ -45,7 +45,7 @@ var createMcpServer = async ({ adapters, getSpaceId, preview, screenshot, logger
|
|
|
45
45
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
46
46
|
const server = new McpServer({
|
|
47
47
|
name: "plitzi-mcp",
|
|
48
|
-
version: "0.32.
|
|
48
|
+
version: "0.32.25"
|
|
49
49
|
}, { instructions: hasSpace ? serverInstructions : widgetsOnlyInstructions });
|
|
50
50
|
registerResources(server, getSpace, MCP_ENV, log, hasSpace);
|
|
51
51
|
registerApps(server, {
|
|
@@ -5,11 +5,13 @@ export declare const PROXY_PATH = "/__proxy";
|
|
|
5
5
|
* on: without one the endpoint could not tell its own widgets' URLs from anyone else's, and an unsigned fetcher
|
|
6
6
|
* on a public origin is an open proxy. */
|
|
7
7
|
export declare const proxySettings: (config: SSRServerConfig) => ResourceProxySettings | undefined;
|
|
8
|
-
/** The
|
|
9
|
-
*
|
|
10
|
-
* the MCP server owns its sub-domain; a deployment reached under a different public name sets `proxy.baseUrl`.
|
|
11
|
-
* The grants it mints carry the fingerprint of THIS request's credential, so they belong to this connection. */
|
|
8
|
+
/** The proxy a tool rewrites through. The grants it mints carry the fingerprint of THIS request's credential, so
|
|
9
|
+
* they belong to this connection. */
|
|
12
10
|
export declare const requestProxy: (config: SSRServerConfig, req: SSRRequest) => ResourceProxy | undefined;
|
|
11
|
+
/** The proxy the ENDPOINT itself mints with, while serving an API answer that carries URLs of its own. The request
|
|
12
|
+
* it is serving comes from a sandboxed iframe and presents no credential, so the connection is the one signed into
|
|
13
|
+
* the grant being served — the widget's own — rather than one read off a header. */
|
|
14
|
+
export declare const grantingProxy: (settings: ResourceProxySettings, req: SSRRequest, identity: string) => ResourceProxy | undefined;
|
|
13
15
|
/** The proxy THIS tool may use, or undefined — the single place that decides it, so a tool cannot reach the
|
|
14
16
|
* endpoint by being wired to it later. A render authors a throwaway widget, so rewriting its URLs is invisible
|
|
15
17
|
* and lasts as long as the widget does; a tool that WRITES a space (plitzi_apply) would persist those URLs into
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ProxyKind, ResourceProxy } from './types';
|
|
2
2
|
/** What a widget was granted. The kind is signed in too, so a URL minted for an image cannot be replayed as an
|
|
3
|
-
* API call (they answer with different content types, cache rules and request headers).
|
|
3
|
+
* API call (they answer with different content types, cache rules and request headers). The connection comes back
|
|
4
|
+
* out because a data answer may carry URLs of its own, and the grants minted for those belong to the same one. */
|
|
4
5
|
export type Grant = {
|
|
5
6
|
kind: ProxyKind;
|
|
6
7
|
target: string;
|
|
8
|
+
identity: string;
|
|
7
9
|
};
|
|
8
10
|
/** Mint the URL a widget will load this target from: this server's endpoint, carrying the target, the kind it was
|
|
9
11
|
* granted for, when the grant stops working and which connection minted it — all covered by one signature. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { PROXY_PATH, proxyForTool, proxySettings, requestProxy } from './config';
|
|
1
|
+
export { grantingProxy, PROXY_PATH, proxyForTool, proxySettings, requestProxy } from './config';
|
|
2
2
|
export { grantUrl, isGranted, isRemote, PROXY_PARAM, readGrant } from './grant';
|
|
3
3
|
export { handleProxyRequest } from './handler';
|
|
4
|
-
export {
|
|
4
|
+
export { rewritablePayload, rewritePayload } from './payload';
|
|
5
|
+
export { looksLikeAsset, proxifyResources, rewriteText } from './rewrite';
|
|
5
6
|
export { connectionId, sign, verify } from './sign';
|
|
6
7
|
export { DEFAULT_PROXY_TOOLS } from './types';
|
|
7
8
|
export type { ProxyKind, ResourceProxy, ResourceProxySettings } from './types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ResourceProxy } from './types';
|
|
2
|
+
/** Is this what the endpoint fetched a body it can rewrite? Only JSON: it is what an apiContainer binds against,
|
|
3
|
+
* and re-serializing anything else would risk changing a document to fix a URL that may not even be loaded. */
|
|
4
|
+
export declare const rewritablePayload: (contentType: string) => boolean;
|
|
5
|
+
/** Rewrite the asset URLs an API answer carries so the widget loads them through this endpoint too. Returns the
|
|
6
|
+
* body untouched when it is not the JSON it claimed to be — the widget asked for that response, and serving it
|
|
7
|
+
* as it came is better than failing the fetch over a rewrite. */
|
|
8
|
+
export declare const rewritePayload: (body: string, proxy: ResourceProxy) => string;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { ResourceProxy } from './types';
|
|
2
2
|
import { Schema, Style } from '@plitzi/sdk-shared';
|
|
3
|
+
/** Does this URL name a file the widget loads, judged by its path alone? Used where nothing else says what a URL is
|
|
4
|
+
* for — an interaction param, a field of an API answer — so the extension is the evidence and its absence is a
|
|
5
|
+
* reason to leave the URL alone. */
|
|
6
|
+
export declare const looksLikeAsset: (url: string) => boolean;
|
|
3
7
|
/** Rewrite every remote URL embedded in a string: `url(…)` in CSS, `src="…"` in markup, `` in markdown. */
|
|
4
8
|
export declare const rewriteText: (text: string, proxy: ResourceProxy) => string;
|
|
5
|
-
/** Point everything an authored widget loads from outside — images, media, fonts,
|
|
6
|
-
*
|
|
9
|
+
/** Point everything an authored widget loads from outside — images, media, fonts, the data an apiContainer fetches
|
|
10
|
+
* and the URLs its interactions swap in later — at this server's endpoint, in place. The AGENT never sees this: it
|
|
11
|
+
* authors the real URL and this runs
|
|
7
12
|
* afterwards, on the throwaway space a render builds (so nothing shared is mutated) and before the global style
|
|
8
13
|
* cache is compiled (so the concatenated CSS comes out already rewritten). Returns what could not be rewritten,
|
|
9
14
|
* which is the only part the model hears about. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-server",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.25",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
33
|
-
"@plitzi/plitzi-sdk": "0.32.
|
|
34
|
-
"@plitzi/sdk-schema": "0.32.
|
|
35
|
-
"@plitzi/sdk-shared": "0.32.
|
|
33
|
+
"@plitzi/plitzi-sdk": "0.32.25",
|
|
34
|
+
"@plitzi/sdk-schema": "0.32.25",
|
|
35
|
+
"@plitzi/sdk-shared": "0.32.25",
|
|
36
36
|
"ejs": "^6.0.1",
|
|
37
37
|
"esbuild": "^0.28.1",
|
|
38
38
|
"zod": "^4.4.3"
|