@kolbo/mcp 1.87.9 → 1.87.10
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/package.json +1 -1
- package/src/apps/index.js +34 -11
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -48,10 +48,29 @@ const WIDGET_BUILDERS = {
|
|
|
48
48
|
// Widgets are pure functions of source — build once per process.
|
|
49
49
|
const htmlCache = new Map();
|
|
50
50
|
function widgetHtml(uri) {
|
|
51
|
+
uri = String(uri).split('?')[0]; // versioned and bare URIs serve the same page
|
|
51
52
|
if (!htmlCache.has(uri)) htmlCache.set(uri, WIDGET_BUILDERS[uri]());
|
|
52
53
|
return htmlCache.get(uri);
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// ChatGPT treats the widget URI as a permanent cache key: it fetched
|
|
57
|
+
// generation.html once per app link and kept serving that snapshot across every
|
|
58
|
+
// later release (1.87.9's ChatGPT fix never reached a card until the URI
|
|
59
|
+
// changed — verified 2026-09-06 via its widget endpoint, where only
|
|
60
|
+
// force_local=true returned the new HTML). OpenAI's own guidance is "treat the
|
|
61
|
+
// resource URI as a cache key; publish a new URI on every change", so the URI
|
|
62
|
+
// every tool declares and returns carries a content hash of the HTML it points
|
|
63
|
+
// at. The bare URI stays registered for hosts holding an older tools/list.
|
|
64
|
+
const crypto = require('crypto');
|
|
65
|
+
const versionCache = new Map();
|
|
66
|
+
function versionedUri(uri) {
|
|
67
|
+
if (!versionCache.has(uri)) {
|
|
68
|
+
const v = crypto.createHash('sha1').update(widgetHtml(uri)).digest('hex').slice(0, 10);
|
|
69
|
+
versionCache.set(uri, uri + '?v=' + v);
|
|
70
|
+
}
|
|
71
|
+
return versionCache.get(uri);
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
// Hosts apply a deny-by-default CSP to widget iframes — without this
|
|
56
75
|
// declaration EVERY external asset (generated images/videos on the CDN, model
|
|
57
76
|
// icons, Google Fonts) is silently blocked. resourceDomains maps to
|
|
@@ -145,22 +164,25 @@ function registerApps(server) {
|
|
|
145
164
|
[UI.list, 'Kolbo List Widget'],
|
|
146
165
|
[UI.plans, 'Kolbo Plans Widget'],
|
|
147
166
|
]) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
167
|
+
for (const u of [versionedUri(uri), uri]) {
|
|
168
|
+
registerAppResource(
|
|
169
|
+
server, name + (u === uri ? ' (unversioned)' : ''), u,
|
|
170
|
+
{ mimeType: RESOURCE_MIME_TYPE, _meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } } },
|
|
171
|
+
async () => ({
|
|
172
|
+
contents: [{
|
|
173
|
+
uri: u, mimeType: RESOURCE_MIME_TYPE, text: widgetHtml(uri),
|
|
174
|
+
_meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } },
|
|
175
|
+
}],
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
}
|
|
158
179
|
}
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
/** `_meta` for a tool RESULT (and optionally for tool registration). */
|
|
162
183
|
function uiMeta(uri) {
|
|
163
|
-
|
|
184
|
+
const u = versionedUri(uri);
|
|
185
|
+
return { [RESOURCE_URI_META_KEY]: u, ui: { resourceUri: u } };
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
/**
|
|
@@ -740,6 +762,7 @@ module.exports = {
|
|
|
740
762
|
TOOL_WIDGETS,
|
|
741
763
|
registerApps,
|
|
742
764
|
attachToolWidgetMeta,
|
|
765
|
+
versionedUri,
|
|
743
766
|
uiMeta,
|
|
744
767
|
uiResult,
|
|
745
768
|
listResult,
|