@contenthero/mcp 0.4.8 → 0.4.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/dist/format.d.ts CHANGED
@@ -56,7 +56,71 @@ export type GeneratedAttachment = {
56
56
  * ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
57
57
  * `mediaBatchResult` already uses: the handler decides, the formatter assembles.
58
58
  */
59
- export declare function completedResult(gen: Generation, attachments?: GeneratedAttachment[]): CallToolResult;
59
+ /**
60
+ * What the generation WIDGET reads.
61
+ *
62
+ * ⭐⭐ `structuredContent` IS THE WIDGET'S ONLY INPUT. It is a separate channel from `content`: the blocks
63
+ * feed the model and any host without app support, this feeds the UI. Both are emitted, so nothing regresses
64
+ * where widgets are unsupported and nothing is duplicated where they are.
65
+ *
66
+ * ⚠️ URLS ONLY, NEVER BYTES. The widget runs in the host's frame and fetches media itself, and our
67
+ * capability urls carry their token in the QUERY STRING, so `<video src>` loads one directly with no header
68
+ * to set. Embedding base64 here would pay the context cost twice over.
69
+ */
70
+ /**
71
+ * Where one output lives in the product.
72
+ *
73
+ * ## ⭐⭐⭐ COMPUTED HERE, NOT IN THE WIDGET, BECAUSE HERE THE INDEX IS ALREADY A NUMBER
74
+ *
75
+ * The widget's first version parsed the slot back out of the output's NAME (`<id>-3` means slot 2), which
76
+ * is a derivation of something this function has in its hand. Parsing a number out of a string we formatted
77
+ * two lines earlier is how an off-by-one gets in, and the symptom would be "Open shows the wrong picture",
78
+ * which reads as a broken link rather than an index bug.
79
+ *
80
+ * ⚠️ **ONE-BASED IN THE NAME, ZERO-BASED IN THE URL.** `<id>-3` is what a person reads as "variation 3",
81
+ * and `variation=2` is the studio's `imageIndex`, which is slot space. Both conventions are correct in
82
+ * their own place and the conversion belongs at exactly one boundary, which is this one.
83
+ *
84
+ * ⚠️ A single-output generation gets NO `variation`. There is no variation to name, and passing 0 would
85
+ * imply there was a choice.
86
+ */
87
+ /** Where a deep link points when no client base url was threaded through. */
88
+ export declare const DEFAULT_APP_URL = "https://app.contenthero.ai";
89
+ export declare function studioUrlFor(baseUrl: string, outputId: string, index: number, total: number): string;
90
+ export declare function generationWidgetData(gen: Generation, posterUrls?: readonly (string | null)[], baseUrl?: string): {
91
+ outputId: string;
92
+ contentType: "image" | "audio" | "video";
93
+ modelId: string;
94
+ /**
95
+ * ⛔⛔ **NULL WHEN THERE IS NOTHING TO NAME, AND THE WIDGET MUST RENDER NO CHIP.**
96
+ *
97
+ * This used to be `displayName ?? gen.modelId`, fed by a catalog fetch in the server with a `catch`
98
+ * that returned the id. The id reads like a label, so a failed fetch showed as a chip flickering
99
+ * between kebab case and title case rather than as a failure. The name now arrives on the row.
100
+ *
101
+ * ⚠️ It is not always this generation's own model: an upload names itself, and a look names the model
102
+ * that produced the image it was assembled from.
103
+ */
104
+ modelName: string | null;
105
+ /** Hex brand accent from the registry, or null. The widget colors its chip with it. */
106
+ modelBrandColor: string | null;
107
+ /** Brand family key (e.g. `"openai"`) for choosing a glyph. NOT the model id. */
108
+ modelIconKey: string | null;
109
+ /** `"W:H"`, or null for audio. Drives the column count and the tile's own shape. */
110
+ displayAspect: string | null;
111
+ /** Verbatim. Some prompts are JSON-shaped because the person authored one; that object IS the prompt. */
112
+ prompt: string | null;
113
+ outputs: {
114
+ url: string;
115
+ posterUrl: string | null;
116
+ name: string;
117
+ /** Where the Open button goes: this asset, in the studio, with its siblings and every action. */
118
+ studioUrl: string;
119
+ }[];
120
+ };
121
+ export declare function completedResult(gen: Generation, attachments?: GeneratedAttachment[], posterUrls?: readonly (string | null)[],
122
+ /** The server this client talks to, so Open deep-links to it rather than always to production. */
123
+ baseUrl?: string): CallToolResult;
60
124
  /** Suggested seconds to wait before re-polling a job, by content type. */
61
125
  export declare function pollAfterSecondsFor(contentType: string): number;
62
126
  /**
@@ -68,8 +132,39 @@ export declare function pollAfterSecondsFor(contentType: string): number;
68
132
  * is what drifted, so these messages now print the call instead, and every site shares this one function.
69
133
  */
70
134
  export declare function getStatusCall(outputIds: readonly string[]): string;
71
- /** A slow job that did not finish within the smart-wait window. */
72
- export declare function pendingResult(outputId: string, pollAfterSeconds?: number): CallToolResult;
135
+ /** What a still-running generation already knows about the shape of its own result. */
136
+ export interface PendingShape {
137
+ contentType: 'image' | 'video' | 'audio';
138
+ modelId: string;
139
+ /** `"W:H"` as requested. Absent for audio and for `auto`/`adaptive`. */
140
+ displayAspect?: string | null;
141
+ /** How many outputs were asked for, so the widget draws that many placeholders. */
142
+ expected?: number;
143
+ }
144
+ /**
145
+ * A slow job that did not finish within the smart-wait window.
146
+ *
147
+ * ## ⭐⭐⭐ THIS IS WHERE THE SKELETONS COME FROM, AND WHY IT IS THE ONLY PLACE THEY COULD
148
+ *
149
+ * A generation returns one of two ways: it finished inside the smart wait, in which case there is nothing
150
+ * to show a spinner for, or it did not, and until now that produced ONE SENTENCE of prose asking the agent
151
+ * to poll. Every video takes that path. So the person who waited longest got the least: a paragraph, while
152
+ * the same job in the studio shows placeholder cards filling in.
153
+ *
154
+ * ⛔ **THE FIX IS NOT TO MAKE `generate_*` RETURN EARLY.** That would hand every host the pending path,
155
+ * including hosts with no MCP Apps support, which would lose the inline image they get today. The pending
156
+ * path already exists and already reaches exactly the people who are waiting.
157
+ *
158
+ * ⚠️ **THE TEXT STAYS, WORD FOR WORD.** It is what a host without app support renders, and it is what the
159
+ * AGENT reads to know it must poll. The widget is added ALONGSIDE it, not instead of it: an agent that
160
+ * stopped polling because the prose was replaced by a payload it cannot see would leave the generation
161
+ * unclaimed.
162
+ *
163
+ * ⚠️ No model NAME here, and that is deliberate. Resolving one would mean a second network call from
164
+ * inside a `catch`, which is the exact shape that produced a chip flickering between kebab case and title
165
+ * case. The widget polls `get_generation_status`, and the name arrives with the first response.
166
+ */
167
+ export declare function pendingResult(outputId: string, pollAfterSeconds?: number, shape?: PendingShape): CallToolResult;
73
168
  /** Synchronous audio result (already complete on submit). */
74
169
  export declare function audioResult(result: GenerateResult | EditAudioResult): CallToolResult;
75
170
  /**
@@ -95,9 +190,9 @@ export declare function costResult(est: CostEstimate): CallToolResult;
95
190
  * caller that stopped at a partial result believing it was complete would silently lose images,
96
191
  * which is the failure this is meant to prevent, not cause.
97
192
  */
98
- export declare function generationStatusResult(gen: Generation): CallToolResult;
193
+ export declare function generationStatusResult(gen: Generation, attachments?: GeneratedAttachment[], baseUrl?: string): CallToolResult;
99
194
  /** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
100
- export declare function generationBatchResult(gens: Generation[]): CallToolResult;
195
+ export declare function generationBatchResult(gens: Generation[], attachmentsByOutputId?: Record<string, GeneratedAttachment[]>, baseUrl?: string): CallToolResult;
101
196
  /** A finished transcription: header line plus the transcript body. */
102
197
  export declare function transcriptResult(t: Transcription): CallToolResult;
103
198
  /** List of avatars, each with the fields an agent needs to drive lip-sync. */
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
package/dist/format.js CHANGED
@@ -3,6 +3,10 @@
3
3
  * Tool errors are returned as `isError` results (not thrown) so the agent sees
4
4
  * a readable message instead of a transport failure.
5
5
  */
6
+ // The spec's own spelling of the key that binds a result to its widget. See `server.ts` for why only the
7
+ // constants come from this package and not its server helpers.
8
+ import { RESOURCE_URI_META_KEY } from '@modelcontextprotocol/ext-apps';
9
+ import { GENERATION_WIDGET_URI } from './widget-uri.js';
6
10
  import { ContentHeroError, InsufficientCreditsError, RateLimitError } from '@contenthero/sdk';
7
11
  export function text(body, isError = false) {
8
12
  return { content: [{ type: 'text', text: body }], isError };
@@ -36,10 +40,101 @@ export function text(body, isError = false) {
36
40
  * ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
37
41
  * `mediaBatchResult` already uses: the handler decides, the formatter assembles.
38
42
  */
39
- export function completedResult(gen, attachments = []) {
43
+ /**
44
+ * What the generation WIDGET reads.
45
+ *
46
+ * ⭐⭐ `structuredContent` IS THE WIDGET'S ONLY INPUT. It is a separate channel from `content`: the blocks
47
+ * feed the model and any host without app support, this feeds the UI. Both are emitted, so nothing regresses
48
+ * where widgets are unsupported and nothing is duplicated where they are.
49
+ *
50
+ * ⚠️ URLS ONLY, NEVER BYTES. The widget runs in the host's frame and fetches media itself, and our
51
+ * capability urls carry their token in the QUERY STRING, so `<video src>` loads one directly with no header
52
+ * to set. Embedding base64 here would pay the context cost twice over.
53
+ */
54
+ /**
55
+ * Where one output lives in the product.
56
+ *
57
+ * ## ⭐⭐⭐ COMPUTED HERE, NOT IN THE WIDGET, BECAUSE HERE THE INDEX IS ALREADY A NUMBER
58
+ *
59
+ * The widget's first version parsed the slot back out of the output's NAME (`<id>-3` means slot 2), which
60
+ * is a derivation of something this function has in its hand. Parsing a number out of a string we formatted
61
+ * two lines earlier is how an off-by-one gets in, and the symptom would be "Open shows the wrong picture",
62
+ * which reads as a broken link rather than an index bug.
63
+ *
64
+ * ⚠️ **ONE-BASED IN THE NAME, ZERO-BASED IN THE URL.** `<id>-3` is what a person reads as "variation 3",
65
+ * and `variation=2` is the studio's `imageIndex`, which is slot space. Both conventions are correct in
66
+ * their own place and the conversion belongs at exactly one boundary, which is this one.
67
+ *
68
+ * ⚠️ A single-output generation gets NO `variation`. There is no variation to name, and passing 0 would
69
+ * imply there was a choice.
70
+ */
71
+ /** Where a deep link points when no client base url was threaded through. */
72
+ export const DEFAULT_APP_URL = 'https://app.contenthero.ai';
73
+ export function studioUrlFor(baseUrl, outputId, index, total) {
74
+ const root = baseUrl.replace(/\/+$/, '');
75
+ const variation = total > 1 ? `&variation=${index}` : '';
76
+ return `${root}/studio?output=${encodeURIComponent(outputId)}${variation}`;
77
+ }
78
+ export function generationWidgetData(gen, posterUrls = [], baseUrl = DEFAULT_APP_URL) {
79
+ const urls = gen.outputUrls ?? [];
80
+ return {
81
+ outputId: gen.outputId,
82
+ contentType: gen.contentType,
83
+ modelId: gen.modelId,
84
+ /**
85
+ * ⛔⛔ **NULL WHEN THERE IS NOTHING TO NAME, AND THE WIDGET MUST RENDER NO CHIP.**
86
+ *
87
+ * This used to be `displayName ?? gen.modelId`, fed by a catalog fetch in the server with a `catch`
88
+ * that returned the id. The id reads like a label, so a failed fetch showed as a chip flickering
89
+ * between kebab case and title case rather than as a failure. The name now arrives on the row.
90
+ *
91
+ * ⚠️ It is not always this generation's own model: an upload names itself, and a look names the model
92
+ * that produced the image it was assembled from.
93
+ */
94
+ modelName: gen.modelDisplayName ?? null,
95
+ /** Hex brand accent from the registry, or null. The widget colors its chip with it. */
96
+ modelBrandColor: gen.modelBrandColor ?? null,
97
+ /** Brand family key (e.g. `"openai"`) for choosing a glyph. NOT the model id. */
98
+ modelIconKey: gen.modelIconKey ?? null,
99
+ /** `"W:H"`, or null for audio. Drives the column count and the tile's own shape. */
100
+ displayAspect: gen.displayAspect ?? null,
101
+ /** Verbatim. Some prompts are JSON-shaped because the person authored one; that object IS the prompt. */
102
+ prompt: gen.prompt ?? null,
103
+ outputs: urls.map((url, i) => ({
104
+ url,
105
+ posterUrl: posterUrls[i] ?? null,
106
+ name: `${gen.outputId}${urls.length > 1 ? `-${i + 1}` : ''}`,
107
+ /** Where the Open button goes: this asset, in the studio, with its siblings and every action. */
108
+ studioUrl: studioUrlFor(baseUrl, gen.outputId, i, urls.length),
109
+ })),
110
+ };
111
+ }
112
+ export function completedResult(gen, attachments = [], posterUrls = [],
113
+ /** The server this client talks to, so Open deep-links to it rather than always to production. */
114
+ baseUrl) {
40
115
  const urls = gen.outputUrls ?? [];
41
116
  const noun = urls.length === 1 ? gen.contentType : `${gen.contentType}s`;
42
- const header = `Done. ${urls.length} ${noun} from ${gen.modelId} (outputId ${gen.outputId}):`;
117
+ /**
118
+ * ⭐⭐ **ONE TEXT LIST, AND NO `resource_link` BLOCKS. THREE REPRESENTATIONS OF ONE URL WAS TWO TOO MANY.**
119
+ *
120
+ * This went through both extremes before landing here. Listing the urls in prose AND attaching a link per
121
+ * output printed every url twice. Suppressing the prose left the links alone, and a host renders those as
122
+ * `name: uri` with NO SEPARATOR BETWEEN THEM, so url 1 ended flush against filename 2 and anything
123
+ * splitting on whitespace read a corrupted token. Measured at all three boundaries of a four-image batch.
124
+ *
125
+ * ⛔ THE SEPARATOR WAS NEVER OURS TO ADD. Our text block ends with a newline; the run-together is the host
126
+ * concatenating sibling blocks, and no content we emit can put a break between two of them.
127
+ *
128
+ * ⭐ So the fallback is the thing we fully control: one newline-delimited list. The widget is the surface
129
+ * that renders, and this is what a host without app support (or a model reading the transcript) gets. It
130
+ * costs less than the links did and it cannot be run together by anybody.
131
+ */
132
+ /**
133
+ * ⭐ **THE ID IS AN ACCEPTABLE FALLBACK HERE AND NOWHERE ELSE.** This block's reader is the model, for
134
+ * whom `gpt-image-2` is a true and directly useful token. The widget's chip has a human reader, for whom
135
+ * the same string is an unexplained failure wearing a label's clothes, so there it renders as nothing.
136
+ */
137
+ const header = `Done. ${urls.length} ${noun} from ${gen.modelDisplayName ?? gen.modelId} (outputId ${gen.outputId}):`;
43
138
  const lines = [header, ...urls.map((u, i) => `${i + 1}. ${u}`)];
44
139
  const p = gen.placement;
45
140
  if (p) {
@@ -52,7 +147,10 @@ export function completedResult(gen, attachments = []) {
52
147
  if (p.warnings?.length)
53
148
  lines.push(`Placement notes: ${p.warnings.join('; ')}`);
54
149
  }
55
- const content = [{ type: 'text', text: lines.join('\n') }];
150
+ // ⚠️ A TRAILING NEWLINE, because a host concatenates blocks without inserting one. Without it the last
151
+ // url ran straight into the next block's rendering, producing `...MBCo_Kkcfe3bafb-...-1.png: https://...`
152
+ // and a token that anything splitting on whitespace would read as part of the filename.
153
+ const content = [{ type: 'text', text: lines.join('\n') + '\n' }];
56
154
  for (const a of attachments) {
57
155
  if (a.kind === 'bytes') {
58
156
  content.push({ type: a.type, data: a.data, mimeType: a.mimeType });
@@ -63,7 +161,20 @@ export function completedResult(gen, attachments = []) {
63
161
  content.push({ type: 'resource_link', uri: a.uri, name: a.name, mimeType: a.mimeType });
64
162
  }
65
163
  }
66
- return { content, isError: false };
164
+ /**
165
+ * ⭐⭐⭐ **THE `_meta` KEY IS WHAT MAKES A WIDGET APPEAR.** Without it the host has an HTML resource it was
166
+ * never told to mount, and the result renders as blocks alone. With it, a host that supports MCP Apps
167
+ * shows the widget and a host that does not ignores the key entirely.
168
+ *
169
+ * ⚠️ Emitted ALONGSIDE the blocks, never instead of them. Two mechanisms, one of which is better where it
170
+ * exists: ChatGPT and Claude get the widget, anything else still gets an image it can draw.
171
+ */
172
+ return {
173
+ content,
174
+ isError: false,
175
+ structuredContent: generationWidgetData(gen, posterUrls, baseUrl),
176
+ _meta: { [RESOURCE_URI_META_KEY]: GENERATION_WIDGET_URI, ui: { resourceUri: GENERATION_WIDGET_URI } },
177
+ };
67
178
  }
68
179
  /** Suggested seconds to wait before re-polling a job, by content type. */
69
180
  export function pollAfterSecondsFor(contentType) {
@@ -80,9 +191,52 @@ export function pollAfterSecondsFor(contentType) {
80
191
  export function getStatusCall(outputIds) {
81
192
  return `get_generation_status { outputIds: [${outputIds.map((id) => `"${id}"`).join(', ')}] }`;
82
193
  }
83
- /** A slow job that did not finish within the smart-wait window. */
84
- export function pendingResult(outputId, pollAfterSeconds = 15) {
85
- return text(`Still rendering (outputId ${outputId}). This is normal for video. Call ${getStatusCall([outputId])} in ~${pollAfterSeconds}s [poll_after_seconds: ${pollAfterSeconds}] to get the final URLs.`);
194
+ /**
195
+ * A slow job that did not finish within the smart-wait window.
196
+ *
197
+ * ## ⭐⭐⭐ THIS IS WHERE THE SKELETONS COME FROM, AND WHY IT IS THE ONLY PLACE THEY COULD
198
+ *
199
+ * A generation returns one of two ways: it finished inside the smart wait, in which case there is nothing
200
+ * to show a spinner for, or it did not, and until now that produced ONE SENTENCE of prose asking the agent
201
+ * to poll. Every video takes that path. So the person who waited longest got the least: a paragraph, while
202
+ * the same job in the studio shows placeholder cards filling in.
203
+ *
204
+ * ⛔ **THE FIX IS NOT TO MAKE `generate_*` RETURN EARLY.** That would hand every host the pending path,
205
+ * including hosts with no MCP Apps support, which would lose the inline image they get today. The pending
206
+ * path already exists and already reaches exactly the people who are waiting.
207
+ *
208
+ * ⚠️ **THE TEXT STAYS, WORD FOR WORD.** It is what a host without app support renders, and it is what the
209
+ * AGENT reads to know it must poll. The widget is added ALONGSIDE it, not instead of it: an agent that
210
+ * stopped polling because the prose was replaced by a payload it cannot see would leave the generation
211
+ * unclaimed.
212
+ *
213
+ * ⚠️ No model NAME here, and that is deliberate. Resolving one would mean a second network call from
214
+ * inside a `catch`, which is the exact shape that produced a chip flickering between kebab case and title
215
+ * case. The widget polls `get_generation_status`, and the name arrives with the first response.
216
+ */
217
+ export function pendingResult(outputId, pollAfterSeconds = 15, shape) {
218
+ const prose = `Still rendering (outputId ${outputId}). This is normal for video. Call ${getStatusCall([outputId])} in ~${pollAfterSeconds}s [poll_after_seconds: ${pollAfterSeconds}] to get the final URLs.`;
219
+ if (!shape)
220
+ return text(prose);
221
+ return {
222
+ content: [{ type: 'text', text: prose }],
223
+ structuredContent: {
224
+ outputId,
225
+ status: 'processing',
226
+ contentType: shape.contentType,
227
+ modelId: shape.modelId,
228
+ modelName: null,
229
+ modelBrandColor: null,
230
+ modelIconKey: null,
231
+ displayAspect: shape.displayAspect ?? null,
232
+ prompt: null,
233
+ /** At least one, or the widget renders a grid with nothing in it and looks broken rather than busy. */
234
+ expected: Math.max(1, shape.expected ?? 1),
235
+ pollAfterSeconds,
236
+ outputs: [],
237
+ },
238
+ _meta: { [RESOURCE_URI_META_KEY]: GENERATION_WIDGET_URI, ui: { resourceUri: GENERATION_WIDGET_URI } },
239
+ };
86
240
  }
87
241
  /** Synchronous audio result (already complete on submit). */
88
242
  export function audioResult(result) {
@@ -135,9 +289,18 @@ export function costResult(est) {
135
289
  * caller that stopped at a partial result believing it was complete would silently lose images,
136
290
  * which is the failure this is meant to prevent, not cause.
137
291
  */
138
- export function generationStatusResult(gen) {
292
+ export function generationStatusResult(gen, attachments = [], baseUrl) {
293
+ /**
294
+ * ⛔⛔ **THIS DROPPED THE ATTACHMENTS AND THEREFORE RENDERED NOTHING.** It called `completedResult(gen)`
295
+ * with no second argument, so POLLING returned text alone even after the generate handlers started
296
+ * attaching blocks. That is the path EVERY async generation takes, which is every video, so the common
297
+ * case stayed blank while the synchronous one worked.
298
+ *
299
+ * ⭐ Found by the `verify:inline` harness in its first run, minutes after it existed. The unit tests could
300
+ * not see it: they call `completedResult` directly and never go through here.
301
+ */
139
302
  if (gen.status === 'completed')
140
- return completedResult(gen);
303
+ return completedResult(gen, attachments, [], baseUrl);
141
304
  if (gen.status === 'failed') {
142
305
  return text(`Generation ${gen.outputId} failed: ${gen.error ?? 'unknown error'}`, true);
143
306
  }
@@ -155,9 +318,12 @@ export function generationStatusResult(gen) {
155
318
  ].join('\n'));
156
319
  }
157
320
  /** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
158
- export function generationBatchResult(gens) {
321
+ export function generationBatchResult(gens, attachmentsByOutputId = {}, baseUrl) {
322
+ // ⚠️ ONLY THE SINGLE FORM ATTACHES. A batch status covering ten generations would embed ten sets of
323
+ // bytes into one result, which is the context blow-up the link design was originally protecting against.
324
+ // The single form is what a caller polling one generation hits, and that is the case worth rendering.
159
325
  if (gens.length === 1)
160
- return generationStatusResult(gens[0]);
326
+ return generationStatusResult(gens[0], attachmentsByOutputId[gens[0].outputId] ?? [], baseUrl);
161
327
  const rows = gens.map((gen) => {
162
328
  if (gen.status === 'completed') {
163
329
  const urls = gen.outputUrls ?? [];