@curless/hotel-storefront 0.1.0

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 (98) hide show
  1. package/README.md +63 -0
  2. package/dist/__tests__/card-contract.test.d.ts +2 -0
  3. package/dist/__tests__/card-contract.test.d.ts.map +1 -0
  4. package/dist/__tests__/card-contract.test.js +64 -0
  5. package/dist/__tests__/card-contract.test.js.map +1 -0
  6. package/dist/__tests__/data.test.d.ts +2 -0
  7. package/dist/__tests__/data.test.d.ts.map +1 -0
  8. package/dist/__tests__/data.test.js +89 -0
  9. package/dist/__tests__/data.test.js.map +1 -0
  10. package/dist/__tests__/dates.test.d.ts +2 -0
  11. package/dist/__tests__/dates.test.d.ts.map +1 -0
  12. package/dist/__tests__/dates.test.js +71 -0
  13. package/dist/__tests__/dates.test.js.map +1 -0
  14. package/dist/__tests__/demo-hotels.test.d.ts +2 -0
  15. package/dist/__tests__/demo-hotels.test.d.ts.map +1 -0
  16. package/dist/__tests__/demo-hotels.test.js +240 -0
  17. package/dist/__tests__/demo-hotels.test.js.map +1 -0
  18. package/dist/__tests__/server.test.d.ts +2 -0
  19. package/dist/__tests__/server.test.d.ts.map +1 -0
  20. package/dist/__tests__/server.test.js +209 -0
  21. package/dist/__tests__/server.test.js.map +1 -0
  22. package/dist/__tests__/tool-descriptions.test.d.ts +2 -0
  23. package/dist/__tests__/tool-descriptions.test.d.ts.map +1 -0
  24. package/dist/__tests__/tool-descriptions.test.js +139 -0
  25. package/dist/__tests__/tool-descriptions.test.js.map +1 -0
  26. package/dist/__tests__/tools.test.d.ts +2 -0
  27. package/dist/__tests__/tools.test.d.ts.map +1 -0
  28. package/dist/__tests__/tools.test.js +226 -0
  29. package/dist/__tests__/tools.test.js.map +1 -0
  30. package/dist/backend.d.ts +43 -0
  31. package/dist/backend.d.ts.map +1 -0
  32. package/dist/backend.js +2 -0
  33. package/dist/backend.js.map +1 -0
  34. package/dist/card.generated.d.ts +4 -0
  35. package/dist/card.generated.d.ts.map +1 -0
  36. package/dist/card.generated.js +8 -0
  37. package/dist/card.generated.js.map +1 -0
  38. package/dist/config.d.ts +122 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/config.js +42 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/data.d.ts +5 -0
  43. package/dist/data.d.ts.map +1 -0
  44. package/dist/data.js +183 -0
  45. package/dist/data.js.map +1 -0
  46. package/dist/dates.d.ts +32 -0
  47. package/dist/dates.d.ts.map +1 -0
  48. package/dist/dates.js +75 -0
  49. package/dist/dates.js.map +1 -0
  50. package/dist/demo-hotels.d.ts +96 -0
  51. package/dist/demo-hotels.d.ts.map +1 -0
  52. package/dist/demo-hotels.js +446 -0
  53. package/dist/demo-hotels.js.map +1 -0
  54. package/dist/fence.d.ts +24 -0
  55. package/dist/fence.d.ts.map +1 -0
  56. package/dist/fence.js +0 -0
  57. package/dist/fence.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +23 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/legal.d.ts +5 -0
  63. package/dist/legal.d.ts.map +1 -0
  64. package/dist/legal.js +111 -0
  65. package/dist/legal.js.map +1 -0
  66. package/dist/money.d.ts +13 -0
  67. package/dist/money.d.ts.map +1 -0
  68. package/dist/money.js +46 -0
  69. package/dist/money.js.map +1 -0
  70. package/dist/server.d.ts +85 -0
  71. package/dist/server.d.ts.map +1 -0
  72. package/dist/server.js +275 -0
  73. package/dist/server.js.map +1 -0
  74. package/dist/surface.d.ts +52 -0
  75. package/dist/surface.d.ts.map +1 -0
  76. package/dist/surface.js +99 -0
  77. package/dist/surface.js.map +1 -0
  78. package/dist/tools.d.ts +336 -0
  79. package/dist/tools.d.ts.map +1 -0
  80. package/dist/tools.js +862 -0
  81. package/dist/tools.js.map +1 -0
  82. package/dist/trip-ids.d.ts +16 -0
  83. package/dist/trip-ids.d.ts.map +1 -0
  84. package/dist/trip-ids.js +77 -0
  85. package/dist/trip-ids.js.map +1 -0
  86. package/dist/trip.d.ts +59 -0
  87. package/dist/trip.d.ts.map +1 -0
  88. package/dist/trip.js +149 -0
  89. package/dist/trip.js.map +1 -0
  90. package/dist/types.d.ts +193 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +13 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/vocab.d.ts +33 -0
  95. package/dist/vocab.d.ts.map +1 -0
  96. package/dist/vocab.js +15 -0
  97. package/dist/vocab.js.map +1 -0
  98. package/package.json +44 -0
package/dist/legal.js ADDED
@@ -0,0 +1,111 @@
1
+ // The pages a directory review asks for, and a guest may actually read.
2
+ //
3
+ // Written from the CONFIG rather than fixed text, for the same reason the tool
4
+ // names are: the second hotel group must not have to fork these. And written
5
+ // honestly - a privacy page that claims we hold nothing while an in-memory trip
6
+ // exists is the kind of small lie that costs a listing.
7
+ const shell = (title, brand, body) => `<!doctype html>
8
+ <html lang="en"><head>
9
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
10
+ <title>${esc(title)} · ${esc(brand)}</title>
11
+ <style>
12
+ :root { color-scheme: light dark; --fg:#1a1f2b; --fg-soft:#5b6472; --bg:#fff; --line:#e4e8ee; }
13
+ @media (prefers-color-scheme: dark) { :root { --fg:#eceef1; --fg-soft:#a4abb6; --bg:#16181c; --line:#2c3138; } }
14
+ body { margin:0; background:var(--bg); color:var(--fg);
15
+ font:15px/1.65 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; }
16
+ main { max-width: 680px; margin: 0 auto; padding: 48px 20px 72px; }
17
+ h1 { font-size: 26px; letter-spacing:-0.02em; margin: 0 0 6px; }
18
+ h2 { font-size: 16px; margin: 30px 0 6px; }
19
+ p, li { color: var(--fg-soft); }
20
+ ul { padding-inline-start: 20px; }
21
+ .meta { font-size: 13px; color: var(--fg-soft); border-top: 1px solid var(--line);
22
+ margin-top: 36px; padding-top: 14px; }
23
+ a { color: inherit; }
24
+ </style></head><body><main>${body}</main></body></html>
25
+ `;
26
+ const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => c === '&' ? '&amp;' : c === '<' ? '&lt;' : c === '>' ? '&gt;' : c === '"' ? '&quot;' : '&#39;');
27
+ const footer = (config) => {
28
+ const operator = config.legal.operator ?? 'Curless';
29
+ const contact = config.legal.operatorContact;
30
+ return `<div class="meta">
31
+ Operated by ${esc(operator)}${contact ? ` · <a href="mailto:${esc(contact)}">${esc(contact)}</a>` : ''}
32
+ ${config.legal.region ? ` · ${esc(config.legal.region)}` : ''}
33
+ ${config.legal.updated ? ` · Last updated ${esc(config.legal.updated)}` : ''}
34
+ <br><a href="./docs">What this does</a> · <a href="./privacy">Privacy</a> · <a href="./terms">Terms</a>
35
+ </div>`;
36
+ };
37
+ /** True while the rooms come from a demonstration file. Said on every page, first. */
38
+ const demoBanner = (config) => config.data.source === 'api'
39
+ ? ''
40
+ : `<p><strong>This is a demonstration.</strong> The ${esc(config.brand)} properties named here are real, but
41
+ every rate, room count, add-on price and cancellation fee it answers with was made up for the demo.
42
+ Nothing booked through it reserves a real room, and no money changes hands.</p>`;
43
+ export const docsPage = (config) => shell('What this connector does', config.brand, `<h1>${esc(config.brand)} ${esc(config.naming?.nounPlural ?? 'hotels')}</h1>
44
+ <p>An MCP connector that lets an AI assistant look up ${esc(config.brand)} properties, price a stay on
45
+ given nights, put rooms and add-ons into a trip, and ask for those rooms to be held.</p>
46
+ ${demoBanner(config)}
47
+ <h2>What it can do</h2>
48
+ <ul>
49
+ <li>List the ${esc(config.naming?.nounPlural ?? 'hotels')} in a city — all of them, not a pick.</li>
50
+ <li>Show one property's rooms for specific nights, with the stay total including taxes and fees,
51
+ the cancellation terms, and how many rooms are left.</li>
52
+ <li>Build a trip of room nights and add-ons, and price it.</li>
53
+ <li>Request a booking: the rooms are held and a confirmation code is issued.</li>
54
+ <li>Show what cancelling would cost before anything is cancelled, then cancel.</li>
55
+ <li>Answer from the property's own published policies.</li>
56
+ </ul>
57
+ <h2>What it will not do</h2>
58
+ <ul>
59
+ <li><strong>${config.payment?.mode === 'wallet' ? 'Charge without the guest acting' : 'Take payment'}.</strong> ${config.payment?.mode === 'wallet'
60
+ ? 'A charge follows the guest pressing pay, never an assistant deciding to.'
61
+ : 'This connector collects no card details and charges nothing. A card is presented at check-in.'}</li>
62
+ <li><strong>Guess dates.</strong> No nights, no prices — it asks instead.</li>
63
+ <li><strong>Invent availability or fees.</strong> A held room comes off the count, and a
64
+ cancellation fee is shown with the rule it came from.</li>
65
+ </ul>
66
+ ${footer(config)}`);
67
+ export const privacyPage = (config) => shell('Privacy', config.brand, `<h1>Privacy</h1>
68
+ ${demoBanner(config)}
69
+ <h2>What this connector holds</h2>
70
+ <ul>
71
+ <li><strong>The trip you are building</strong> — hotel ids, room ids, your dates and how many
72
+ rooms — in memory, for as long as the conversation is active and up to four hours after its
73
+ last change. It is not written to disk and does not survive a restart.</li>
74
+ <li><strong>A booking you request</strong> — the name, email and any note you typed, with the
75
+ dates and rooms — for as long as this process runs. Nothing is written to disk.</li>
76
+ <li><strong>A session id</strong>, so the card you are looking at and the conversation reach the
77
+ same trip. It identifies a conversation, not a person.</li>
78
+ </ul>
79
+ <h2>What it does not hold</h2>
80
+ <ul>
81
+ <li>No card numbers, and no payment details of any kind — it has nowhere to put them
82
+ and never asks.</li>
83
+ <li>No account, no password, no profile.</li>
84
+ <li>No advertising or analytics identifiers, and nothing is sold or shared.</li>
85
+ </ul>
86
+ <h2>Your name and email</h2>
87
+ <p>They reach this connector only when you type them into the booking form or say them in the
88
+ conversation. The assistant is told not to fill them in from an account or profile, and the
89
+ connector has no way to read either.</p>
90
+ ${config.legal.brandContact
91
+ ? `<h2>The hotel group</h2><p>Questions about a real booking go to ${esc(config.brand)} at
92
+ <a href="mailto:${esc(config.legal.brandContact)}">${esc(config.legal.brandContact)}</a>.</p>`
93
+ : ''}
94
+ ${footer(config)}`);
95
+ export const termsPage = (config) => shell('Terms', config.brand, `<h1>Terms</h1>
96
+ ${demoBanner(config)}
97
+ <h2>What this is</h2>
98
+ <p>A connector operated by ${esc(config.legal.operator ?? 'Curless')} that an AI assistant uses on
99
+ your behalf. ${config.data.source === 'api'
100
+ ? `The rooms, rates and policies come from ${esc(config.brand)}; the booking is between you and them.`
101
+ : 'The rates and availability it shows are demonstration values and do not reserve anything.'}</p>
102
+ <h2>Bookings</h2>
103
+ <p>${config.payment?.mode === 'wallet'
104
+ ? 'A booking is confirmed once payment completes. Until then the rooms are held only.'
105
+ : 'A booking request holds rooms; it takes no payment and is not a confirmed reservation. The hold lapses at the time stated on the confirmation.'} Cancellation terms are the property's own, and are shown in full before anything is cancelled.</p>
106
+ <h2>No warranty</h2>
107
+ <p>This connector is provided as is. ${config.data.source === 'api'
108
+ ? 'Rates and availability can change between a quote and a booking.'
109
+ : 'Nothing it shows is a rate anyone can book.'}</p>
110
+ ${footer(config)}`);
111
+ //# sourceMappingURL=legal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal.js","sourceRoot":"","sources":["../src/legal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Money } from './types.js';
2
+ export declare const minorFactor: (currency: string) => number;
3
+ /** A price written the way a human writes it (3800) in this currency's minor units. */
4
+ export declare const toMinor: (major: number, currency: string) => number;
5
+ export declare const money: (minor: number, currency: string) => Money;
6
+ export declare const addMoney: (a: Money, b: Money) => Money;
7
+ export declare const sumMoney: (parts: Money[], currency: string) => Money;
8
+ export declare const multiplyMoney: (m: Money, by: number) => Money;
9
+ /** Tax at a rate, rounded once, at the end - never per line and then summed. */
10
+ export declare const taxOn: (m: Money, rate: number) => Money;
11
+ /** For the text a model reads back to a guest. The card formats its own. */
12
+ export declare const formatMoney: (m: Money) => string;
13
+ //# sourceMappingURL=money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAYxC,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,KAAG,MACK,CAAC;AAErD,uFAAuF;AACvF,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,EAAE,UAAU,MAAM,KAAG,MACf,CAAC;AAE5C,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,UAAU,MAAM,KAAG,KAGtD,CAAC;AAEH,eAAO,MAAM,QAAQ,GAAI,GAAG,KAAK,EAAE,GAAG,KAAK,KAAG,KAG7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,EAAE,EAAE,UAAU,MAAM,KAAG,KACI,CAAC;AAEjE,eAAO,MAAM,aAAa,GAAI,GAAG,KAAK,EAAE,IAAI,MAAM,KAAG,KAAwC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,KAAK,GAAI,GAAG,KAAK,EAAE,MAAM,MAAM,KAAG,KAA0C,CAAC;AAY1F,4EAA4E;AAC5E,eAAO,MAAM,WAAW,GAAI,GAAG,KAAK,KAAG,MAUtC,CAAC"}
package/dist/money.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Money is integer minor units plus a currency, everywhere, including inside
3
+ * the arithmetic. A rate of 3800 HKD held as a float and multiplied by a tax
4
+ * rate lands on 4293.999999999999 often enough to print a wrong total on a
5
+ * card, and there is no way to notice that in review.
6
+ */
7
+ /** Currencies with no minor unit at all - ¥1 is 1, not 100. */
8
+ const ZERO_DECIMAL = new Set(['JPY', 'KRW', 'VND', 'IDR', 'CLP', 'ISK']);
9
+ export const minorFactor = (currency) => ZERO_DECIMAL.has(currency.toUpperCase()) ? 1 : 100;
10
+ /** A price written the way a human writes it (3800) in this currency's minor units. */
11
+ export const toMinor = (major, currency) => Math.round(major * minorFactor(currency));
12
+ export const money = (minor, currency) => ({
13
+ minor: Math.round(minor),
14
+ currency,
15
+ });
16
+ export const addMoney = (a, b) => {
17
+ if (a.currency !== b.currency)
18
+ throw new Error(`cannot add ${a.currency} to ${b.currency}`);
19
+ return money(a.minor + b.minor, a.currency);
20
+ };
21
+ export const sumMoney = (parts, currency) => parts.reduce((acc, p) => addMoney(acc, p), money(0, currency));
22
+ export const multiplyMoney = (m, by) => money(m.minor * by, m.currency);
23
+ /** Tax at a rate, rounded once, at the end - never per line and then summed. */
24
+ export const taxOn = (m, rate) => money(m.minor * rate, m.currency);
25
+ const SYMBOLS = {
26
+ HKD: 'HK$',
27
+ CNY: '¥',
28
+ SGD: 'S$',
29
+ USD: '$',
30
+ JPY: '¥',
31
+ EUR: '€',
32
+ GBP: '£',
33
+ };
34
+ /** For the text a model reads back to a guest. The card formats its own. */
35
+ export const formatMoney = (m) => {
36
+ const factor = minorFactor(m.currency);
37
+ const sign = m.minor < 0 ? '-' : '';
38
+ const amount = factor === 1 ? String(Math.abs(m.minor)) : (Math.abs(m.minor) / factor).toFixed(2);
39
+ const parts = amount.split('.');
40
+ const grouped = (parts[0] ?? '0').replace(/\B(?=(\d{3})+$)/g, ',');
41
+ const cents = parts[1] ?? '';
42
+ const body = cents && cents !== '00' ? `${grouped}.${cents}` : grouped;
43
+ const symbol = SYMBOLS[m.currency.toUpperCase()];
44
+ return symbol ? `${sign}${symbol}${body}` : `${sign}${body} ${m.currency}`;
45
+ };
46
+ //# sourceMappingURL=money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"money.js","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { Hono } from 'hono';
2
+ import type { HotelConfig } from './config.js';
3
+ import { type ToolDeps } from './tools.js';
4
+ import { TripIds } from './trip-ids.js';
5
+ import { TripStore } from './trip.js';
6
+ import { type ResolvedBrand } from './vocab.js';
7
+ export declare const PROTOCOL_VERSION = "2025-06-18";
8
+ export declare const VERSION = "0.1.0";
9
+ /**
10
+ * The card's resource URI.
11
+ *
12
+ * ⚠️ VERSIONED, AND BUMPED ON EVERY CARD CHANGE. Hosts cache by this URI, and a
13
+ * stale card is worse than no card because it looks current. The shopping
14
+ * connector went storefront-v35 → v40 in a single day of UI work for exactly
15
+ * this reason.
16
+ */
17
+ export declare const CARD_URI = "ui://curless/hotel/booking-v2.html";
18
+ /**
19
+ * ⚠️ `text/html;profile=mcp-app`, not `text/html+skybridge`. The MIME is how a
20
+ * host recognises this resource as a card at all; the plausible near-miss made
21
+ * Claude answer "There was a problem displaying content" and the card never
22
+ * rendered.
23
+ */
24
+ export declare const CARD_MIME = "text/html;profile=mcp-app";
25
+ /**
26
+ * One card, two hosts. Claude (MCP Apps) reads `ui.resourceUri`; ChatGPT (Apps
27
+ * SDK) reads `openai/outputTemplate`, and `openai/widgetAccessible` is what
28
+ * lets the rendered card call back into these tools - which is every button on
29
+ * it, so without that flag the card is a picture.
30
+ */
31
+ export declare const UI_META: {
32
+ readonly ui: {
33
+ readonly resourceUri: "ui://curless/hotel/booking-v2.html";
34
+ };
35
+ readonly 'ui/resourceUri': "ui://curless/hotel/booking-v2.html";
36
+ readonly 'openai/outputTemplate': "ui://curless/hotel/booking-v2.html";
37
+ readonly 'openai/widgetAccessible': true;
38
+ };
39
+ /**
40
+ * WHERE THE CARD MAY LOAD IMAGES FROM.
41
+ *
42
+ * An MCP App runs in a sandboxed iframe and the spec is explicit: resource
43
+ * domains "empty or omitted → no network resources". The shopping connector
44
+ * lost three rounds of "the pictures still aren't showing" to this, having set
45
+ * only ChatGPT's key. This card draws its own artwork and needs nothing - but a
46
+ * deployment whose data carries `imageUrl`s has to list their host here.
47
+ */
48
+ export declare const widgetMeta: (imageDomains: string[]) => {
49
+ ui: {
50
+ csp: {
51
+ resourceDomains: string[];
52
+ };
53
+ resourceUri: "ui://curless/hotel/booking-v2.html";
54
+ };
55
+ 'openai/widgetCSP': {
56
+ connect_domains: never[];
57
+ resource_domains: string[];
58
+ };
59
+ 'ui/resourceUri': "ui://curless/hotel/booking-v2.html";
60
+ 'openai/outputTemplate': "ui://curless/hotel/booking-v2.html";
61
+ 'openai/widgetAccessible': true;
62
+ };
63
+ /** The trip snapshot that rides beside every answer, for the card's header. */
64
+ export declare const TRIP_META_KEY = "curless/trip";
65
+ export type BuiltServer = {
66
+ app: Hono;
67
+ brand: ResolvedBrand;
68
+ deps: ToolDeps;
69
+ trips: TripStore;
70
+ tripIds: TripIds;
71
+ };
72
+ export type ServerOptions = {
73
+ config: HotelConfig;
74
+ /** Where the config was read from, so `data.file` resolves beside it. */
75
+ configPath?: string;
76
+ publicUrl?: string;
77
+ now?: () => number;
78
+ };
79
+ export declare const buildServer: (opts: ServerOptions) => BuiltServer;
80
+ /** Read a brand's config from disk. The path is the one `data.file` resolves against. */
81
+ export declare const loadConfig: (path: string) => {
82
+ config: HotelConfig;
83
+ configPath: string;
84
+ };
85
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,OAAO,EAAE,KAAK,QAAQ,EAA8C,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAW,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,KAAK,aAAa,EAAgB,MAAM,YAAY,CAAC;AAS9D,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAC7C,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,uCAAuC,CAAC;AAC7D;;;;;GAKG;AACH,eAAO,MAAM,SAAS,8BAA8B,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,OAAO;;;;;;;CAKV,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GAAI,cAAc,MAAM,EAAE;;;;;;;;;;;;;;CAI/C,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,IAAI,CAAC;IACV,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,MAAM,aAAa,KAAG,WA6NjD,CAAC;AAWF,yFAAyF;AACzF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAGlF,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,275 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { Hono } from 'hono';
5
+ import { HOTEL_CARD_HTML } from './card.generated.js';
6
+ import { loadHotelData } from './data.js';
7
+ import { createDemoHotels } from './demo-hotels.js';
8
+ import { docsPage, privacyPage, termsPage } from './legal.js';
9
+ import { buildInstructions, listTools, tripToolNames } from './surface.js';
10
+ import { buildOutputSchemas, buildTools, handleTool } from './tools.js';
11
+ import { TripIds } from './trip-ids.js';
12
+ import { TripStore, tripKey } from './trip.js';
13
+ import { resolveBrand } from './vocab.js';
14
+ // The connector itself: one config in, one HTTP app out.
15
+ //
16
+ // Everything interesting is elsewhere - the rooms in a HotelBackend, the
17
+ // answers in tools.ts, the behaviour in surface.ts. This file is the wiring
18
+ // that a host actually talks to, and the few protocol facts that were each
19
+ // learned by getting them wrong somewhere else in this repo.
20
+ export const PROTOCOL_VERSION = '2025-06-18';
21
+ export const VERSION = '0.1.0';
22
+ /**
23
+ * The card's resource URI.
24
+ *
25
+ * ⚠️ VERSIONED, AND BUMPED ON EVERY CARD CHANGE. Hosts cache by this URI, and a
26
+ * stale card is worse than no card because it looks current. The shopping
27
+ * connector went storefront-v35 → v40 in a single day of UI work for exactly
28
+ * this reason.
29
+ */
30
+ export const CARD_URI = 'ui://curless/hotel/booking-v2.html';
31
+ /**
32
+ * ⚠️ `text/html;profile=mcp-app`, not `text/html+skybridge`. The MIME is how a
33
+ * host recognises this resource as a card at all; the plausible near-miss made
34
+ * Claude answer "There was a problem displaying content" and the card never
35
+ * rendered.
36
+ */
37
+ export const CARD_MIME = 'text/html;profile=mcp-app';
38
+ /**
39
+ * One card, two hosts. Claude (MCP Apps) reads `ui.resourceUri`; ChatGPT (Apps
40
+ * SDK) reads `openai/outputTemplate`, and `openai/widgetAccessible` is what
41
+ * lets the rendered card call back into these tools - which is every button on
42
+ * it, so without that flag the card is a picture.
43
+ */
44
+ export const UI_META = {
45
+ ui: { resourceUri: CARD_URI },
46
+ 'ui/resourceUri': CARD_URI,
47
+ 'openai/outputTemplate': CARD_URI,
48
+ 'openai/widgetAccessible': true,
49
+ };
50
+ /**
51
+ * WHERE THE CARD MAY LOAD IMAGES FROM.
52
+ *
53
+ * An MCP App runs in a sandboxed iframe and the spec is explicit: resource
54
+ * domains "empty or omitted → no network resources". The shopping connector
55
+ * lost three rounds of "the pictures still aren't showing" to this, having set
56
+ * only ChatGPT's key. This card draws its own artwork and needs nothing - but a
57
+ * deployment whose data carries `imageUrl`s has to list their host here.
58
+ */
59
+ export const widgetMeta = (imageDomains) => ({
60
+ ...UI_META,
61
+ ui: { ...UI_META.ui, csp: { resourceDomains: imageDomains } },
62
+ 'openai/widgetCSP': { connect_domains: [], resource_domains: imageDomains },
63
+ });
64
+ /** The trip snapshot that rides beside every answer, for the card's header. */
65
+ export const TRIP_META_KEY = 'curless/trip';
66
+ export const buildServer = (opts) => {
67
+ const config = opts.config;
68
+ const brand = resolveBrand(config);
69
+ const now = opts.now ?? (() => Date.now());
70
+ if (config.data.source !== 'demo') {
71
+ // Named in the config type so the shape does not have to change when the
72
+ // group's API lands - but a deployment that claims it today would answer
73
+ // with nothing, and silently.
74
+ throw new Error(`hotel-storefront ${VERSION} only implements data.source "demo"; the group's own API is the next step`);
75
+ }
76
+ const data = loadHotelData(config.data.file ?? 'data/hotels.json', opts.configPath);
77
+ const backend = createDemoHotels({ data, now });
78
+ const trips = new TripStore(now);
79
+ const tripIds = new TripIds(now);
80
+ const publicUrl = (opts.publicUrl ?? config.urls.publicUrl ?? config.urls.appUrl).replace(/\/$/, '');
81
+ const deps = {
82
+ backend,
83
+ trips,
84
+ tools: brand.tools,
85
+ vocab: brand.vocab,
86
+ websiteUrl: config.server.websiteUrl,
87
+ ...(config.server.logoFile ? { logoUrl: `${publicUrl}/logo.png` } : {}),
88
+ };
89
+ const TOOLS = buildTools(brand.tools, brand.vocab);
90
+ const OUTPUT_SCHEMAS = buildOutputSchemas(brand.tools);
91
+ const TRIP_TOOLS = tripToolNames(brand);
92
+ // A policy answer is TEXT for the model, not something to look at: drawing a
93
+ // booking card over "can I bring a dog" puts a storefront in front of someone
94
+ // who asked a question.
95
+ const CARDLESS = new Set([brand.tools.policies]);
96
+ const imageDomains = [...new Set(data.hotels.flatMap((h) => hostOf(h.imageUrl)))];
97
+ const META = widgetMeta(imageDomains);
98
+ const app = new Hono();
99
+ const file = (name) => {
100
+ if (!name || !opts.configPath)
101
+ return null;
102
+ try {
103
+ const raw = readFileSync(resolve(dirname(opts.configPath), name));
104
+ // Copied into its own ArrayBuffer: node's Buffer is
105
+ // Uint8Array<ArrayBufferLike>, which since TS 5.7 is not the
106
+ // Uint8Array<ArrayBuffer> a response body wants.
107
+ const bytes = new Uint8Array(new ArrayBuffer(raw.byteLength));
108
+ bytes.set(raw);
109
+ return bytes;
110
+ }
111
+ catch (err) {
112
+ console.error(`[brand] ${name} could not be read: ${err.message}`);
113
+ return null;
114
+ }
115
+ };
116
+ // The group's own artwork, or a 404. Never another brand's: the shopping
117
+ // connector once served the first merchant's logo to a merchant who had
118
+ // supplied none, which names the wrong party on someone else's connector.
119
+ app.get('/icon.png', (c) => {
120
+ const bytes = file(config.server.iconFile);
121
+ return bytes
122
+ ? c.body(bytes, 200, {
123
+ 'content-type': 'image/png',
124
+ 'cache-control': 'public, max-age=86400',
125
+ })
126
+ : c.notFound();
127
+ });
128
+ app.get('/logo.png', (c) => {
129
+ const bytes = file(config.server.logoFile);
130
+ return bytes
131
+ ? c.body(bytes, 200, {
132
+ 'content-type': 'image/png',
133
+ 'cache-control': 'public, max-age=86400',
134
+ })
135
+ : c.notFound();
136
+ });
137
+ app.get('/docs', (c) => c.html(docsPage(config)));
138
+ app.get('/privacy', (c) => c.html(privacyPage(config)));
139
+ app.get('/terms', (c) => c.html(termsPage(config)));
140
+ app.get('/healthz', (c) => c.json({
141
+ ok: true,
142
+ brand: config.brand,
143
+ version: VERSION,
144
+ hotels: data.hotels.length,
145
+ demo: brand.vocab.demo,
146
+ trips: trips.size,
147
+ }));
148
+ app.get('/', (c) => c.redirect('./docs', 302));
149
+ app.get('/mcp', (c) => c.json({ name: config.server.name, transport: 'streamable-http', version: VERSION }));
150
+ // JSON-RPC, both halves. `id ?? null` because a malformed request has no id
151
+ // and the spec still wants the field present.
152
+ const ok = (c, id, result) => c.json({ jsonrpc: '2.0', id: id ?? null, result });
153
+ const fail = (c, id, code, message) => c.json({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
154
+ app.post('/mcp', async (c) => {
155
+ let body;
156
+ try {
157
+ body = await c.req.json();
158
+ }
159
+ catch {
160
+ return fail(c, null, -32700, 'parse error');
161
+ }
162
+ const { id, method, params } = body;
163
+ if (method === 'initialize') {
164
+ // ⚠️ WE ISSUE THE SESSION ID. A host that is sent none has none to send
165
+ // back, and then every call is its own conversation - which, for a trip,
166
+ // means the guest's rooms vanish between turns.
167
+ const sessionId = c.req.header('mcp-session-id') ?? randomUUID();
168
+ c.header('Mcp-Session-Id', sessionId);
169
+ return ok(c, id, {
170
+ protocolVersion: PROTOCOL_VERSION,
171
+ capabilities: { tools: {}, resources: {} },
172
+ serverInfo: {
173
+ name: config.server.name,
174
+ title: config.server.title,
175
+ version: VERSION,
176
+ websiteUrl: config.server.websiteUrl ?? publicUrl,
177
+ ...(config.server.iconFile
178
+ ? {
179
+ icons: [
180
+ { src: `${publicUrl}/icon.png`, mimeType: 'image/png', sizes: ['256x256'] },
181
+ ],
182
+ }
183
+ : {}),
184
+ },
185
+ // How to BEHAVE, not just what the tools are - see surface.ts.
186
+ instructions: buildInstructions(brand),
187
+ });
188
+ }
189
+ // ⚠️ The spec says a receiver MUST answer ping promptly. The shopping
190
+ // connector answered -32601 and Claude told the buyer it could not reach
191
+ // the shop - over a connection on which every tool call was succeeding.
192
+ if (method === 'ping')
193
+ return ok(c, id, {});
194
+ if (method === 'notifications/initialized')
195
+ return c.body(null, 202);
196
+ if (method === 'tools/list') {
197
+ return ok(c, id, {
198
+ tools: listTools(TOOLS, OUTPUT_SCHEMAS, (name) => (CARDLESS.has(name) ? {} : META), TRIP_TOOLS),
199
+ });
200
+ }
201
+ if (method === 'resources/list') {
202
+ return ok(c, id, {
203
+ resources: [
204
+ {
205
+ uri: CARD_URI,
206
+ name: `${config.brand} booking`,
207
+ description: `Find ${brand.vocab.nounPlural}, price a stay, build a trip and request the rooms`,
208
+ mimeType: CARD_MIME,
209
+ _meta: META,
210
+ },
211
+ ],
212
+ });
213
+ }
214
+ if (method === 'resources/read') {
215
+ const uri = String(params?.uri ?? '');
216
+ // ANY card URI in our namespace gets the CURRENT card, and the answer
217
+ // echoes the URI that was asked for. A host still holding an old URI
218
+ // otherwise asks for a resource we refuse to serve, and ChatGPT showed
219
+ // the buyer "Failed to fetch template" until the connector was deleted
220
+ // and re-added - nothing broken except our own refusal. Echoing rather
221
+ // than correcting: a host that asked for v1 and got v2 back has an answer
222
+ // to a question it did not ask, and some discard it.
223
+ if (!uri.startsWith('ui://curless/hotel/'))
224
+ return fail(c, id, -32602, `unknown resource: ${uri}`);
225
+ return ok(c, id, {
226
+ contents: [{ uri, mimeType: CARD_MIME, text: HOTEL_CARD_HTML, _meta: META }],
227
+ });
228
+ }
229
+ if (method === 'tools/call') {
230
+ const name = String(params?.name ?? '');
231
+ const args = (params?.arguments ?? {});
232
+ const session = c.req.header('mcp-session-id') ?? `anon-${randomUUID()}`;
233
+ // The card names its trip; the conversation is known by its session. See
234
+ // trip-ids.ts for why both exist.
235
+ const named = typeof args.trip === 'string' ? tripIds.keyFor(args.trip) : undefined;
236
+ const key = named ?? tripKey(session, config.brand);
237
+ try {
238
+ const answer = await handleTool(deps, name, args, { session, tripKey: key });
239
+ const lines = trips.read(key);
240
+ return ok(c, id, {
241
+ content: [{ type: 'text', text: answer.text }],
242
+ structuredContent: answer.structured,
243
+ // Beside the answer, not inside it: the card needs the trip to badge
244
+ // its header, and the model needs no such list - handing it one is
245
+ // how a model starts retyping a trip the guest can see.
246
+ _meta: {
247
+ [TRIP_META_KEY]: { tripId: tripIds.idFor(key), count: lines.length },
248
+ ...(CARDLESS.has(name) ? {} : META),
249
+ },
250
+ });
251
+ }
252
+ catch (err) {
253
+ return fail(c, id, -32603, err.message);
254
+ }
255
+ }
256
+ return fail(c, id, -32601, `unknown method: ${method}`);
257
+ });
258
+ return { app, brand, deps, trips, tripIds };
259
+ };
260
+ const hostOf = (url) => {
261
+ if (!url)
262
+ return [];
263
+ try {
264
+ return [new URL(url).origin];
265
+ }
266
+ catch {
267
+ return [];
268
+ }
269
+ };
270
+ /** Read a brand's config from disk. The path is the one `data.file` resolves against. */
271
+ export const loadConfig = (path) => {
272
+ const configPath = resolve(path);
273
+ return { config: JSON.parse(readFileSync(configPath, 'utf8')), configPath };
274
+ };
275
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { type ResolvedBrand } from './vocab.js';
2
+ /**
3
+ * How to BEHAVE, not just what the tools are.
4
+ *
5
+ * Most of these lines exist because the opposite behaviour is the natural one.
6
+ * An assistant handed hotel tools will: interview the guest about budget and
7
+ * view before showing anything; pick the "best" property in a city and quietly
8
+ * drop the other three; fill in "this weekend" for dates nobody gave; call a
9
+ * held room booked; and cancel when asked to cancel, discovering the fee
10
+ * afterwards. Each of those is one line below.
11
+ */
12
+ export declare const buildInstructions: (brand: ResolvedBrand) => string;
13
+ /**
14
+ * The `tools/list` answer: each tool with its card metadata and the shape of
15
+ * what it returns.
16
+ *
17
+ * The schemas are merged on here rather than kept in the tool literals - that
18
+ * list is the contract a guest's assistant reads, and it stays legible without
19
+ * them inline.
20
+ */
21
+ export declare const listTools: <T extends {
22
+ name: string;
23
+ inputSchema?: unknown;
24
+ }>(tools: readonly T[], outputSchemas: Record<string, unknown>, metaFor: (name: string) => Record<string, unknown>, tripTools?: ReadonlySet<string>) => ((T & {
25
+ outputSchema?: {} | undefined;
26
+ _meta: Record<string, unknown>;
27
+ inputSchema: {
28
+ properties: {
29
+ trip: {
30
+ readonly type: "string";
31
+ readonly description: "Set by the booking card: the id of the trip it is showing. Leave it out.";
32
+ };
33
+ };
34
+ };
35
+ }) | (T & {
36
+ outputSchema?: {} | undefined;
37
+ _meta: Record<string, unknown>;
38
+ inputSchema?: undefined;
39
+ }))[];
40
+ /**
41
+ * What the card fills in and the assistant leaves out: the id of the trip the
42
+ * card is showing. The card's calls reach the server on a different connection
43
+ * from the conversation's, so it names the trip instead of relying on the
44
+ * session.
45
+ */
46
+ export declare const TRIP_PARAM: {
47
+ readonly type: "string";
48
+ readonly description: "Set by the booking card: the id of the trip it is showing. Leave it out.";
49
+ };
50
+ /** Every tool the card calls about the trip or a booking. */
51
+ export declare const tripToolNames: (brand: ResolvedBrand) => ReadonlySet<string>;
52
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAY3D;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,aAAa,KAAG,MAoCxD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EACzE,OAAO,SAAS,CAAC,EAAE,EACnB,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,YAAW,WAAW,CAAC,MAAM,CAAa;;;;;;;;;;;;;;;KAexC,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,6DAA6D;AAC7D,eAAO,MAAM,aAAa,GAAI,OAAO,aAAa,KAAG,WAAW,CAAC,MAAM,CAQnE,CAAC"}