@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/apps/index.js +34 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.87.9",
3
+ "version": "1.87.10",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- registerAppResource(
149
- server, name, uri,
150
- { mimeType: RESOURCE_MIME_TYPE, _meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } } },
151
- async () => ({
152
- contents: [{
153
- uri, mimeType: RESOURCE_MIME_TYPE, text: widgetHtml(uri),
154
- _meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } },
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
- return { [RESOURCE_URI_META_KEY]: uri, ui: { resourceUri: uri } };
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,