@curless/hotel-storefront 0.23.2 → 0.24.1

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 (107) hide show
  1. package/README.md +35 -11
  2. package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +2 -0
  3. package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -0
  4. package/dist/__tests__/__fixtures__/huazhu-fake.js +3 -0
  5. package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -0
  6. package/dist/__tests__/huazhu-backend.test.d.ts +2 -0
  7. package/dist/__tests__/huazhu-backend.test.d.ts.map +1 -0
  8. package/dist/__tests__/huazhu-backend.test.js +434 -0
  9. package/dist/__tests__/huazhu-backend.test.js.map +1 -0
  10. package/dist/__tests__/huazhu-cancel-policy.test.d.ts +2 -0
  11. package/dist/__tests__/huazhu-cancel-policy.test.d.ts.map +1 -0
  12. package/dist/__tests__/huazhu-cancel-policy.test.js +82 -0
  13. package/dist/__tests__/huazhu-cancel-policy.test.js.map +1 -0
  14. package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
  15. package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
  16. package/dist/__tests__/huazhu-sandbox.test.js +172 -0
  17. package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
  18. package/dist/__tests__/huazhu-sellable.test.d.ts +2 -0
  19. package/dist/__tests__/huazhu-sellable.test.d.ts.map +1 -0
  20. package/dist/__tests__/huazhu-sellable.test.js +192 -0
  21. package/dist/__tests__/huazhu-sellable.test.js.map +1 -0
  22. package/dist/__tests__/server.test.js +129 -4
  23. package/dist/__tests__/server.test.js.map +1 -1
  24. package/dist/__tests__/tool-descriptions.test.js +85 -7
  25. package/dist/__tests__/tool-descriptions.test.js.map +1 -1
  26. package/dist/backend.d.ts +27 -0
  27. package/dist/backend.d.ts.map +1 -1
  28. package/dist/backend.js +7 -0
  29. package/dist/backend.js.map +1 -1
  30. package/dist/card.generated.d.ts +1 -1
  31. package/dist/card.generated.js +2 -2
  32. package/dist/card.generated.js.map +1 -1
  33. package/dist/config.d.ts +64 -4
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js.map +1 -1
  36. package/dist/facilities.d.ts.map +1 -1
  37. package/dist/facilities.js +7 -5
  38. package/dist/facilities.js.map +1 -1
  39. package/dist/huazhu/backend.d.ts +40 -0
  40. package/dist/huazhu/backend.d.ts.map +1 -0
  41. package/dist/huazhu/backend.js +1318 -0
  42. package/dist/huazhu/backend.js.map +1 -0
  43. package/dist/huazhu/bookings.d.ts +81 -0
  44. package/dist/huazhu/bookings.d.ts.map +1 -0
  45. package/dist/huazhu/bookings.js +189 -0
  46. package/dist/huazhu/bookings.js.map +1 -0
  47. package/dist/huazhu/cancel-policy.d.ts +69 -0
  48. package/dist/huazhu/cancel-policy.d.ts.map +1 -0
  49. package/dist/huazhu/cancel-policy.js +201 -0
  50. package/dist/huazhu/cancel-policy.js.map +1 -0
  51. package/dist/huazhu/catalog.d.ts +75 -0
  52. package/dist/huazhu/catalog.d.ts.map +1 -0
  53. package/dist/huazhu/catalog.js +251 -0
  54. package/dist/huazhu/catalog.js.map +1 -0
  55. package/dist/huazhu/check.d.ts +5 -0
  56. package/dist/huazhu/check.d.ts.map +1 -0
  57. package/dist/huazhu/check.js +170 -0
  58. package/dist/huazhu/check.js.map +1 -0
  59. package/dist/huazhu/client.d.ts +100 -0
  60. package/dist/huazhu/client.d.ts.map +1 -0
  61. package/dist/huazhu/client.js +232 -0
  62. package/dist/huazhu/client.js.map +1 -0
  63. package/dist/huazhu/fake.d.ts +64 -0
  64. package/dist/huazhu/fake.d.ts.map +1 -0
  65. package/dist/huazhu/fake.js +365 -0
  66. package/dist/huazhu/fake.js.map +1 -0
  67. package/dist/huazhu/sandbox.d.ts +26 -0
  68. package/dist/huazhu/sandbox.d.ts.map +1 -0
  69. package/dist/huazhu/sandbox.js +289 -0
  70. package/dist/huazhu/sandbox.js.map +1 -0
  71. package/dist/huazhu/sellable.d.ts +81 -0
  72. package/dist/huazhu/sellable.d.ts.map +1 -0
  73. package/dist/huazhu/sellable.js +219 -0
  74. package/dist/huazhu/sellable.js.map +1 -0
  75. package/dist/huazhu/wire.d.ts +389 -0
  76. package/dist/huazhu/wire.d.ts.map +1 -0
  77. package/dist/huazhu/wire.js +72 -0
  78. package/dist/huazhu/wire.js.map +1 -0
  79. package/dist/index.js +18 -4
  80. package/dist/index.js.map +1 -1
  81. package/dist/legal.d.ts +4 -3
  82. package/dist/legal.d.ts.map +1 -1
  83. package/dist/legal.js +71 -30
  84. package/dist/legal.js.map +1 -1
  85. package/dist/lib.d.ts +8 -3
  86. package/dist/lib.d.ts.map +1 -1
  87. package/dist/lib.js +8 -1
  88. package/dist/lib.js.map +1 -1
  89. package/dist/server.d.ts +20 -8
  90. package/dist/server.d.ts.map +1 -1
  91. package/dist/server.js +132 -28
  92. package/dist/server.js.map +1 -1
  93. package/dist/surface.d.ts +2 -1
  94. package/dist/surface.d.ts.map +1 -1
  95. package/dist/surface.js +42 -10
  96. package/dist/surface.js.map +1 -1
  97. package/dist/tools.d.ts +33 -20
  98. package/dist/tools.d.ts.map +1 -1
  99. package/dist/tools.js +122 -11
  100. package/dist/tools.js.map +1 -1
  101. package/dist/types.d.ts +21 -2
  102. package/dist/types.d.ts.map +1 -1
  103. package/dist/vocab.d.ts +5 -0
  104. package/dist/vocab.d.ts.map +1 -1
  105. package/dist/vocab.js +2 -1
  106. package/dist/vocab.js.map +1 -1
  107. package/package.json +1 -1
package/dist/legal.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { FULL_CAPABILITIES } from './backend.js';
1
2
  // The pages a directory review asks for, and a guest may actually read.
2
3
  //
3
4
  // Written from the CONFIG rather than fixed text, for the same reason the tool
@@ -34,10 +35,22 @@ const footer = (config) => {
34
35
  <br><a href="./docs">What this does</a> · <a href="./privacy">Privacy</a> · <a href="./terms">Terms</a>
35
36
  </div>`;
36
37
  };
37
- /** True while the rooms come from a demonstration file. Said on every page, first. */
38
- const demoBanner = (config) => config.data.source === 'api'
38
+ /**
39
+ * Where the rooms come from: a demonstration file, a simulated copy of the
40
+ * group's system (a test deployment), or the group's own system. Every page
41
+ * that says who holds a booking asks this, never `data.source` alone - an api
42
+ * source can be the simulation.
43
+ */
44
+ const sourceOf = (c) => c.data.source !== 'api' ? 'demo' : c.data.api?.sandbox ? 'sandbox' : 'live';
45
+ /** Said on every page, first, unless the rooms are the group's own. */
46
+ const demoBanner = (config) => sourceOf(config) === 'live'
39
47
  ? ''
40
- : `<p><strong>This is a demonstration.</strong> The ${esc(config.brand)} properties named here are real, but
48
+ : sourceOf(config) === 'sandbox'
49
+ ? `<p><strong>This is a test environment.</strong> It answers from a simulated copy of
50
+ ${esc(config.brand)}'s booking system, not from ${esc(config.brand)}: the hotels, rates, rooms and fees
51
+ were invented for testing, and nothing booked through it is a ${esc(config.brand)} reservation.
52
+ No room is held and no money changes hands.</p>`
53
+ : `<p><strong>This is a demonstration.</strong> The ${esc(config.brand)} properties named here are real, but
41
54
  every rate, room count, add-on price and cancellation fee it answers with was made up for the demo.
42
55
  Nothing booked through it reserves a real room, and no money changes hands.</p>`;
43
56
  /**
@@ -48,22 +61,40 @@ const demoBanner = (config) => config.data.source === 'api'
48
61
  * that say what this connector is are where that belongs - not in a comment in
49
62
  * a data file. `operator` is who built it; the brand is not them.
50
63
  */
51
- const marksNotice = (config) => `<h2>Names and pictures</h2>
64
+ const marksNotice = (config) => sourceOf(config) === 'sandbox'
65
+ ? `<h2>Names</h2>
66
+ <p>${esc(config.brand)} and the names of its brands belong to ${esc(config.brand)}. They are used here to
67
+ test a booking connector for ${esc(config.brand)} against a simulated copy of its distribution system,
68
+ before it is connected to the real one; the hotels shown are invented. This connector is operated by
69
+ ${esc(config.legal.operator ?? 'Curless')}; it is not ${esc(config.brand)} and does not speak for it.</p>`
70
+ : sourceOf(config) === 'live'
71
+ ? `<h2>Names and pictures</h2>
72
+ <p>${esc(config.brand)}, the names of its properties and the photographs shown on the card belong to
73
+ ${esc(config.brand)}, and come from its own distribution system. This connector is operated by
74
+ ${esc(config.legal.operator ?? 'Curless')}, which sells ${esc(config.brand)}'s rooms as a distributor;
75
+ it is not ${esc(config.brand)}.</p>`
76
+ : `<h2>Names and pictures</h2>
52
77
  <p>${esc(config.brand)}, the names of its properties and the photographs shown on the card belong to
53
78
  ${esc(config.brand)}. They are used here to demonstrate a booking connector against the group's real
54
79
  property list. This connector is operated by ${esc(config.legal.operator ?? 'Curless')} and is not
55
80
  ${esc(config.brand)}, not affiliated with it, and not endorsed by it.</p>`;
56
- export const docsPage = (config) => shell('What this connector does', config.brand, `<h1>${esc(config.brand)} ${esc(config.naming?.nounPlural ?? 'hotels')}</h1>
81
+ export const docsPage = (config, caps = FULL_CAPABILITIES) => shell('What this connector does', config.brand, `<h1>${esc(config.brand)} ${esc(config.naming?.nounPlural ?? 'hotels')}</h1>
57
82
  <p>An MCP connector that lets an AI assistant look up ${esc(config.brand)} properties, price a stay on
58
- given nights, put rooms and add-ons into a trip, and ask for those rooms to be held.</p>
83
+ given nights, put ${caps.extras ? 'rooms and add-ons' : 'rooms'} into a trip, and ${caps.booking === 'pay-at-hotel'
84
+ ? 'book those rooms, to be paid at the hotel'
85
+ : 'ask for those rooms to be held'}.</p>
59
86
  ${demoBanner(config)}
60
87
  <h2>What it can do</h2>
61
88
  <ul>
62
89
  <li>List the ${esc(config.naming?.nounPlural ?? 'hotels')} in a city — all of them, not a pick.</li>
63
90
  <li>Show one property's rooms for specific nights, with the stay total including taxes and fees,
64
91
  the cancellation terms, and how many rooms are left.</li>
65
- <li>Build a trip of room nights and add-ons, and price it.</li>
66
- <li>Request a booking: the rooms are held and a confirmation code is issued.</li>
92
+ <li>Build a trip of room nights${caps.extras ? ' and add-ons' : ''}, and price it.</li>
93
+ <li>${sourceOf(config) === 'sandbox'
94
+ ? 'Book: each stay becomes a reservation in the simulated system, with its own confirmation code.'
95
+ : caps.booking === 'pay-at-hotel'
96
+ ? `Book: each stay becomes ${esc(config.brand)}'s own reservation, with its own confirmation code, paid at the hotel.`
97
+ : 'Request a booking: the rooms are held and a confirmation code is issued.'}</li>
67
98
  <li>Show what cancelling would cost before anything is cancelled, then cancel.</li>
68
99
  <li>Answer from the property's own published policies.</li>
69
100
  </ul>
@@ -77,7 +108,7 @@ export const docsPage = (config) => shell('What this connector does', config.bra
77
108
  cancellation fee is shown with the rule it came from.</li>
78
109
  </ul>
79
110
  ${footer(config)}`);
80
- export const privacyPage = (config, imageHosts = []) => shell('Privacy', config.brand, `<h1>Privacy</h1>
111
+ export const privacyPage = (config, imageHosts = [], caps = FULL_CAPABILITIES) => shell('Privacy', config.brand, `<h1>Privacy</h1>
81
112
  ${demoBanner(config)}
82
113
  <h2>What this connector holds</h2>
83
114
  <ul>
@@ -87,18 +118,21 @@ export const privacyPage = (config, imageHosts = []) => shell('Privacy', config.
87
118
  <li><strong>A booking you request</strong> — the name and email you gave, a phone number if you
88
119
  gave one, the room requests (a bed type, a floor, a quiet, connecting or accessible room, an
89
120
  early check-in or a late arrival), your arrival time and any note, the dates, rooms and price,
90
- and the reason if you give one for cancelling. ${config.data.source === 'api'
91
- ? `It is passed to ${esc(config.brand)}, who keep the booking.`
92
- : 'In this demonstration it is kept in memory for as long as this process runs, and nothing is written to disk; once there are many, bookings that are over are forgotten first.'}</li>
121
+ and the reason if you give one for cancelling. ${sourceOf(config) === 'sandbox'
122
+ ? `In this test environment it is sent to a simulated copy of ${esc(config.brand)}'s system that ${esc(config.legal.operator ?? 'Curless')} runs on the same server - never to ${esc(config.brand)} - and both keep it until 90 days after the check-out date, so that the booking can be found and cancelled from the conversation.`
123
+ : sourceOf(config) === 'live'
124
+ ? `It is sent to ${esc(config.brand)}, who make and keep the reservation - the name, the email, the phone number, the dates, the rooms and the requests, which reach the hotel's desk. This connector also keeps a copy on its own server, until 90 days after the check-out date, so that the booking can be found and cancelled from the conversation: ${esc(config.brand)}'s system does not give back the email a booking was made with.`
125
+ : 'In this demonstration it is kept in memory for as long as this process runs, and nothing is written to disk; once there are many, bookings that are over are forgotten first.'}</li>
93
126
  <li><strong>Who can open a booking.</strong> The conversation it was made in — and anyone who
94
127
  gives its confirmation code together with the email it was made with, as a front desk would
95
- ask. That pair lets the booking be read, changed or cancelled, from another conversation too,
128
+ ask. That pair lets the booking be read, ${caps.changes ? 'changed ' : ''}or cancelled, from another conversation too,
96
129
  so keep them as you would any booking confirmation. The email alone opens nothing.</li>
97
130
  <li><strong>A session id</strong>, issued by this connector when the assistant connects. The
98
- conversation's trip, bookings and sign-in are filed under it; it identifies a conversation,
131
+ conversation's trip, bookings${caps.members ? ' and sign-in' : ''} are filed under it; it identifies a conversation,
99
132
  not a person, and is never shown in an answer. The card gets a separate random id for the
100
133
  trip it is showing, so the card and the conversation reach the same one.</li>
101
- <li><strong>A membership sign-in</strong> — the membership number and surname you gave, and the
134
+ ${caps.members
135
+ ? `<li><strong>A membership sign-in</strong> — the membership number and surname you gave, and the
102
136
  name, tier and points they match — for this conversation, and forgotten two hours after it
103
137
  was last used.</li>
104
138
  <li><strong>Room preferences you asked to have remembered</strong> — only if you are signed in as a
@@ -106,19 +140,20 @@ export const privacyPage = (config, imageHosts = []) => shell('Privacy', config.
106
140
  the kind of room you prefer — a bed type, a floor, a quiet or connecting room — against the
107
141
  membership number, and nothing else: never an accessible room (that is asked for on each
108
142
  stay that needs it), a note, a name, an email, an arrival time or a card. It comes back the
109
- next time that membership signs in, and "forget my preferences" removes it. ${config.data.source === 'api'
110
- ? `It is kept on the membership's own profile with ${esc(config.brand)}.`
111
- : 'In this demonstration it is kept in memory and does not survive a restart.'}</li>
112
- <li><strong>Wrong guesses</strong> — a confirmation code and email, or a membership number and
113
- surname, that did not match — counted for a quarter of an hour, so that neither pair can be
143
+ next time that membership signs in, and "forget my preferences" removes it. ${sourceOf(config) === 'live'
144
+ ? `It is kept on the membership's own profile with ${esc(config.brand)}.`
145
+ : 'In this demonstration it is kept in memory and does not survive a restart.'}</li>`
146
+ : ''}
147
+ <li><strong>Wrong guesses</strong> — a confirmation code and email${caps.members ? ', or a membership number and surname,' : ''} that did not match — counted for a quarter of an hour, so that neither pair can be
114
148
  guessed at. After a few, that kind of lookup waits.</li>
115
149
  </ul>
116
150
  <h2>What it does not hold</h2>
117
151
  <ul>
118
152
  <li>No card numbers, and no payment details of any kind — it has nowhere to put them
119
153
  and never asks.</li>
120
- <li>No account and no password. The only thing kept about you from one conversation to the next
121
- is the room preferences above, and only when you asked.</li>
154
+ <li>No account and no password. ${caps.members
155
+ ? 'The only thing kept about you from one conversation to the next is the room preferences above, and only when you asked.'
156
+ : 'Nothing is kept about you from one conversation to the next except a booking you made, as above.'}</li>
122
157
  <li>No advertising or analytics identifiers, and nothing is sold or shared.</li>
123
158
  </ul>
124
159
  ${imageHosts.length
@@ -138,19 +173,25 @@ export const privacyPage = (config, imageHosts = []) => shell('Privacy', config.
138
173
  : ''}
139
174
  ${marksNotice(config)}
140
175
  ${footer(config)}`);
141
- export const termsPage = (config) => shell('Terms', config.brand, `<h1>Terms</h1>
176
+ export const termsPage = (config, caps = FULL_CAPABILITIES) => shell('Terms', config.brand, `<h1>Terms</h1>
142
177
  ${demoBanner(config)}
143
178
  <h2>What this is</h2>
144
179
  <p>A connector operated by ${esc(config.legal.operator ?? 'Curless')} that an AI assistant uses on
145
- your behalf. ${config.data.source === 'api'
146
- ? `The rooms, rates and policies come from ${esc(config.brand)}; the booking is between you and them.`
147
- : 'The rates and availability it shows are demonstration values and do not reserve anything.'}</p>
180
+ your behalf. ${sourceOf(config) === 'sandbox'
181
+ ? 'The hotels, rates and availability it shows come from a simulated booking system, for testing, and do not reserve anything.'
182
+ : sourceOf(config) === 'live'
183
+ ? `The rooms, rates and policies come from ${esc(config.brand)}; the booking is between you and them.`
184
+ : 'The rates and availability it shows are demonstration values and do not reserve anything.'}</p>
148
185
  <h2>Bookings</h2>
149
- <p>${config.payment?.mode === 'wallet'
150
- ? 'A booking is confirmed once payment completes. Until then the rooms are held only.'
151
- : '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>
186
+ <p>${sourceOf(config) === 'sandbox'
187
+ ? `In this test environment a booking is made in a simulated copy of ${esc(config.brand)}'s system: it reserves no room, and nothing is paid.`
188
+ : config.payment?.mode === 'wallet'
189
+ ? 'A booking is confirmed once payment completes. Until then the rooms are held only.'
190
+ : caps.booking === 'pay-at-hotel'
191
+ ? `A booking is ${esc(config.brand)}'s own reservation, made in your name and paid at the hotel under its terms; this connector takes no payment. A booking whose confirmation has not come back from ${esc(config.brand)} is said to be waiting until it has.`
192
+ : '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>
152
193
  <h2>No warranty</h2>
153
- <p>This connector is provided as is. ${config.data.source === 'api'
194
+ <p>This connector is provided as is. ${sourceOf(config) === 'live'
154
195
  ? 'Rates and availability can change between a quote and a booking.'
155
196
  : 'Nothing it shows is a rate anyone can book.'}</p>
156
197
  ${marksNotice(config)}
package/dist/legal.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"legal.js","sourceRoot":"","sources":["../src/legal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"legal.js","sourceRoot":"","sources":["../src/legal.ts"],"names":[],"mappings":"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"}
package/dist/lib.d.ts CHANGED
@@ -16,10 +16,15 @@ export { loadHotelData, parseHotelData } from './data.js';
16
16
  export { loadConfig, buildServer, widgetMeta, imageDomainsOf, CARD_URI, CARD_MIME, VERSION, } from './server.js';
17
17
  export { createDemoHotels } from './demo-hotels.js';
18
18
  export { toolNames } from './config.js';
19
+ export { buildTools, withheldTools } from './tools.js';
19
20
  export { resolveBrand, DEMO_NOTE } from './vocab.js';
20
- export { MAX_PAGE } from './backend.js';
21
- export type { HotelBackend } from './backend.js';
22
- export type { HotelConfig, ToolNames } from './config.js';
21
+ export { FULL_CAPABILITIES, MAX_PAGE } from './backend.js';
22
+ export type { BackendCapabilities, HotelBackend } from './backend.js';
23
+ export type { ApiSource, HotelConfig, ToolNames } from './config.js';
24
+ export { HUAZHU_CAPABILITIES, createHuazhuHotels } from './huazhu/backend.js';
25
+ export { checkHuazhu } from './huazhu/check.js';
26
+ export { HuazhuClient, HuazhuRefusal, HuazhuUnreachable } from './huazhu/client.js';
27
+ export { describePolicy, parseCancelPolicy } from './huazhu/cancel-policy.js';
23
28
  export type { ResolvedBrand, Vocabulary } from './vocab.js';
24
29
  export type { Booking, Extra, Hotel, HotelDetail, HotelPolicy, Identity, Member, Money, Quote, Room, Stay, TripLine, } from './types.js';
25
30
  //# sourceMappingURL=lib.d.ts.map
package/dist/lib.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,YAAY,EACV,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAW,EACX,WAAW,EACX,QAAQ,EACR,MAAM,EACN,KAAK,EACL,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACtE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,YAAY,EACV,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAW,EACX,WAAW,EACX,QAAQ,EACR,MAAM,EACN,KAAK,EACL,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,MAAM,YAAY,CAAC"}
package/dist/lib.js CHANGED
@@ -16,6 +16,13 @@ export { loadHotelData, parseHotelData } from './data.js';
16
16
  export { loadConfig, buildServer, widgetMeta, imageDomainsOf, CARD_URI, CARD_MIME, VERSION, } from './server.js';
17
17
  export { createDemoHotels } from './demo-hotels.js';
18
18
  export { toolNames } from './config.js';
19
+ // What a deployment offers, for a brand repository checking its own config.
20
+ export { buildTools, withheldTools } from './tools.js';
19
21
  export { resolveBrand, DEMO_NOTE } from './vocab.js';
20
- export { MAX_PAGE } from './backend.js';
22
+ export { FULL_CAPABILITIES, MAX_PAGE } from './backend.js';
23
+ // A group's own booking system, for a brand repository's own checks.
24
+ export { HUAZHU_CAPABILITIES, createHuazhuHotels } from './huazhu/backend.js';
25
+ export { checkHuazhu } from './huazhu/check.js';
26
+ export { HuazhuClient, HuazhuRefusal, HuazhuUnreachable } from './huazhu/client.js';
27
+ export { describePolicy, parseCancelPolicy } from './huazhu/cancel-policy.js';
21
28
  //# sourceMappingURL=lib.js.map
package/dist/lib.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,QAAQ,EACR,SAAS,EACT,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,4EAA4E;AAC5E,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG3D,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/server.d.ts CHANGED
@@ -16,7 +16,7 @@ export declare const PROTOCOL_VERSION = "2025-06-18";
16
16
  * number. Found by reading the boot banner of a container that had just been
17
17
  * deployed (2026-09-22): seventeen tools listed, and "0.7.0" above them.
18
18
  */
19
- export declare const VERSION = "0.23.2";
19
+ export declare const VERSION = "0.24.1";
20
20
  /**
21
21
  * The card's resource URI.
22
22
  *
@@ -25,7 +25,7 @@ export declare const VERSION = "0.23.2";
25
25
  * connector went storefront-v35 → v40 in a single day of UI work for exactly
26
26
  * this reason.
27
27
  */
28
- export declare const CARD_URI = "ui://curless/hotel/booking-v39.html";
28
+ export declare const CARD_URI = "ui://curless/hotel/booking-v40.html";
29
29
  /**
30
30
  * ⚠️ `text/html;profile=mcp-app`, not `text/html+skybridge`. The MIME is how a
31
31
  * host recognises this resource as a card at all; the plausible near-miss made
@@ -41,10 +41,10 @@ export declare const CARD_MIME = "text/html;profile=mcp-app";
41
41
  */
42
42
  export declare const UI_META: {
43
43
  readonly ui: {
44
- readonly resourceUri: "ui://curless/hotel/booking-v39.html";
44
+ readonly resourceUri: "ui://curless/hotel/booking-v40.html";
45
45
  };
46
- readonly 'ui/resourceUri': "ui://curless/hotel/booking-v39.html";
47
- readonly 'openai/outputTemplate': "ui://curless/hotel/booking-v39.html";
46
+ readonly 'ui/resourceUri': "ui://curless/hotel/booking-v40.html";
47
+ readonly 'openai/outputTemplate': "ui://curless/hotel/booking-v40.html";
48
48
  readonly 'openai/widgetAccessible': true;
49
49
  };
50
50
  /**
@@ -63,20 +63,22 @@ export declare const widgetMeta: (imageDomains: string[]) => {
63
63
  csp: {
64
64
  resourceDomains: string[];
65
65
  };
66
- resourceUri: "ui://curless/hotel/booking-v39.html";
66
+ resourceUri: "ui://curless/hotel/booking-v40.html";
67
67
  };
68
68
  'openai/widgetCSP': {
69
69
  connect_domains: never[];
70
70
  resource_domains: string[];
71
71
  };
72
- 'ui/resourceUri': "ui://curless/hotel/booking-v39.html";
73
- 'openai/outputTemplate': "ui://curless/hotel/booking-v39.html";
72
+ 'ui/resourceUri': "ui://curless/hotel/booking-v40.html";
73
+ 'openai/outputTemplate': "ui://curless/hotel/booking-v40.html";
74
74
  'openai/widgetAccessible': true;
75
75
  };
76
76
  /** The trip snapshot that rides beside every answer, for the card's header. */
77
77
  export declare const TRIP_META_KEY = "curless/trip";
78
78
  export type BuiltServer = {
79
79
  app: Hono;
80
+ /** Settled once there is something to answer with - see `Made.ready`. */
81
+ ready: () => Promise<void>;
80
82
  brand: ResolvedBrand;
81
83
  deps: ToolDeps;
82
84
  trips: TripStore;
@@ -89,6 +91,16 @@ export type ServerOptions = {
89
91
  configPath?: string;
90
92
  publicUrl?: string;
91
93
  now?: () => number;
94
+ /** How the group's own API is reached - a test's fake, or the platform's fetch. */
95
+ fetch?: typeof fetch;
96
+ /** Where the group's API credentials are read from. Default `process.env`. */
97
+ env?: Record<string, string | undefined>;
98
+ /**
99
+ * The work an API backend does on its own clock: keeping the hotel list
100
+ * current, and following up bookings whose outcome is not known yet. Off in
101
+ * tests. Default on.
102
+ */
103
+ background?: boolean;
92
104
  };
93
105
  export declare const buildServer: (opts: ServerOptions) => BuiltServer;
94
106
  /**
@@ -1 +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,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,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;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,wCAAwC,CAAC;AAC9D;;;;;GAKG;AACH,eAAO,MAAM,SAAS,8BAA8B,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,OAAO;;;;;;;CAKV,CAAC;AAEX;;;;;;;;;;GAUG;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,WAAW,CAAC;IACrB,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,WAoQjD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,EAAE,KAC9D,MAAM,EAIR,CAAC;AAWF,yFAAyF;AACzF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAGlF,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS/C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EACL,KAAK,QAAQ,EAKd,MAAM,YAAY,CAAC;AACpB,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;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,wCAAwC,CAAC;AAC9D;;;;;GAKG;AACH,eAAO,MAAM,SAAS,8BAA8B,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,OAAO;;;;;;;CAKV,CAAC;AAEX;;;;;;;;;;GAUG;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,yEAAyE;IACzE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,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;IACnB,mFAAmF;IACnF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAkHF,eAAO,MAAM,WAAW,GAAI,MAAM,aAAa,KAAG,WAsQjD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,EAAE,KAC9D,MAAM,EAIR,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 CHANGED
@@ -2,14 +2,19 @@ import { randomUUID } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  import { Hono } from 'hono';
5
+ import { FULL_CAPABILITIES } from './backend.js';
5
6
  import { HOTEL_CARD_HTML } from './card.generated.js';
6
7
  import { loadHotelData } from './data.js';
7
8
  import { createDemoHotels } from './demo-hotels.js';
9
+ import { createHuazhuHotels } from './huazhu/backend.js';
10
+ import { BookingStore, Reconciler } from './huazhu/bookings.js';
11
+ import { Catalog } from './huazhu/catalog.js';
12
+ import { HuazhuClient } from './huazhu/client.js';
8
13
  import { docsPage, privacyPage, termsPage } from './legal.js';
9
14
  import { Misses } from './limits.js';
10
15
  import { MemberStore } from './members.js';
11
16
  import { buildInstructions, listTools, tripToolNames } from './surface.js';
12
- import { buildOutputSchemas, buildTools, handleTool } from './tools.js';
17
+ import { buildOutputSchemas, buildTools, handleTool, withheldTools, } from './tools.js';
13
18
  import { TripIds } from './trip-ids.js';
14
19
  import { TripStore, tripKey } from './trip.js';
15
20
  import { resolveBrand } from './vocab.js';
@@ -30,7 +35,7 @@ export const PROTOCOL_VERSION = '2025-06-18';
30
35
  * number. Found by reading the boot banner of a container that had just been
31
36
  * deployed (2026-09-22): seventeen tools listed, and "0.7.0" above them.
32
37
  */
33
- export const VERSION = '0.23.2';
38
+ export const VERSION = '0.24.1';
34
39
  /**
35
40
  * The card's resource URI.
36
41
  *
@@ -39,7 +44,7 @@ export const VERSION = '0.23.2';
39
44
  * connector went storefront-v35 → v40 in a single day of UI work for exactly
40
45
  * this reason.
41
46
  */
42
- export const CARD_URI = 'ui://curless/hotel/booking-v39.html';
47
+ export const CARD_URI = 'ui://curless/hotel/booking-v40.html';
43
48
  /**
44
49
  * ⚠️ `text/html;profile=mcp-app`, not `text/html+skybridge`. The MIME is how a
45
50
  * host recognises this resource as a card at all; the plausible near-miss made
@@ -77,22 +82,108 @@ export const widgetMeta = (imageDomains) => ({
77
82
  });
78
83
  /** The trip snapshot that rides beside every answer, for the card's header. */
79
84
  export const TRIP_META_KEY = 'curless/trip';
85
+ const makeBackend = (config, opts, now) => {
86
+ if (config.data.source === 'demo') {
87
+ const data = loadHotelData(config.data.file ?? 'data/hotels.json', opts.configPath);
88
+ const backend = createDemoHotels({
89
+ data,
90
+ now,
91
+ ...(config.data.holdMinutes ? { holdMs: config.data.holdMinutes * 60_000 } : {}),
92
+ });
93
+ const domains = imageDomainsOf(data.hotels);
94
+ return {
95
+ backend,
96
+ imageDomains: () => domains,
97
+ hotelCount: () => data.hotels.length,
98
+ health: () => ({}),
99
+ ready: () => Promise.resolve(),
100
+ };
101
+ }
102
+ const api = config.data.api;
103
+ if (!api)
104
+ throw new Error('data.source "api" needs data.api: which system, and where it answers');
105
+ if (api.provider !== 'huazhu') {
106
+ throw new Error(`hotel-storefront ${VERSION} does not know the "${String(api.provider)}" booking system`);
107
+ }
108
+ // ⚠️ THE NAMES OF WHAT IS MISSING, NEVER THE VALUES OF WHAT IS THERE.
109
+ const env = opts.env ?? process.env;
110
+ const prefix = api.envPrefix ?? 'HUAZHU';
111
+ const names = {
112
+ clientId: `${prefix}_CLIENT_ID`,
113
+ secret: `${prefix}_CLIENT_SECRET`,
114
+ distributorId: `${prefix}_DISTRIBUTOR_ID`,
115
+ };
116
+ const missing = Object.values(names).filter((n) => !env[n]);
117
+ if (missing.length) {
118
+ throw new Error(`${config.brand}: ${missing.join(', ')} ${missing.length === 1 ? 'is' : 'are'} not set - the credentials Huazhu issued go in the environment, never in the config`);
119
+ }
120
+ const client = new HuazhuClient({
121
+ baseUrl: api.baseUrl,
122
+ authUrl: api.authUrl,
123
+ credentials: {
124
+ clientId: String(env[names.clientId]),
125
+ secret: String(env[names.secret]),
126
+ distributorId: String(env[names.distributorId]),
127
+ },
128
+ ...(opts.fetch ? { fetch: opts.fetch } : {}),
129
+ now,
130
+ });
131
+ // ⚠️ THE BRAND'S REPOSITORY IS MOUNTED READ-ONLY on the box, beside this
132
+ // config - so where state is written can be moved out of it by the
133
+ // deployment (`HOTEL_STATE_DIR`) without the brand's config naming a path
134
+ // that only exists inside our container.
135
+ const stateDir = resolve(opts.configPath ? dirname(opts.configPath) : '.', env.HOTEL_STATE_DIR || api.stateDir);
136
+ const catalog = new Catalog({
137
+ client,
138
+ stateDir,
139
+ ...(api.hotels ? { only: api.hotels } : {}),
140
+ now,
141
+ });
142
+ const store = new BookingStore(stateDir, now);
143
+ const reconciler = new Reconciler(store, client, now);
144
+ const backend = createHuazhuHotels({
145
+ brand: config.brand,
146
+ client,
147
+ catalog,
148
+ store,
149
+ reconciler,
150
+ paymentType: api.paymentType ?? 'NGPL',
151
+ language: api.language ?? 'zh',
152
+ tz: api.timezone ?? 'Asia/Shanghai',
153
+ ...(api.allowTags ? { allowTags: api.allowTags } : {}),
154
+ now,
155
+ });
156
+ if (opts.background !== false) {
157
+ reconciler.start();
158
+ const keepCurrent = () => {
159
+ if (catalog.stale())
160
+ void catalog.sync();
161
+ };
162
+ keepCurrent();
163
+ setInterval(keepCurrent, 60 * 60_000).unref?.();
164
+ }
165
+ // The photographs' hosts, worked out again only when the list changed.
166
+ let domains;
167
+ return {
168
+ backend,
169
+ imageDomains: () => {
170
+ if (!domains || domains.size !== catalog.size) {
171
+ domains = { size: catalog.size, hosts: backend.imageDomains() };
172
+ }
173
+ return domains.hosts;
174
+ },
175
+ hotelCount: () => catalog.size,
176
+ health: () => backend.health(),
177
+ ready: () => (catalog.size > 0 && !catalog.stale() ? Promise.resolve() : catalog.sync()),
178
+ };
179
+ };
80
180
  export const buildServer = (opts) => {
81
181
  const config = opts.config;
82
182
  const brand = resolveBrand(config);
83
183
  const now = opts.now ?? (() => Date.now());
84
- if (config.data.source !== 'demo') {
85
- // Named in the config type so the shape does not have to change when the
86
- // group's API lands - but a deployment that claims it today would answer
87
- // with nothing, and silently.
88
- throw new Error(`hotel-storefront ${VERSION} only implements data.source "demo"; the group's own API is the next step`);
89
- }
90
- const data = loadHotelData(config.data.file ?? 'data/hotels.json', opts.configPath);
91
- const backend = createDemoHotels({
92
- data,
93
- now,
94
- ...(config.data.holdMinutes ? { holdMs: config.data.holdMinutes * 60_000 } : {}),
95
- });
184
+ const made = makeBackend(config, opts, now);
185
+ const { backend } = made;
186
+ const caps = backend.capabilities ?? FULL_CAPABILITIES;
96
187
  const trips = new TripStore(now);
97
188
  const members = new MemberStore(now);
98
189
  const tripIds = new TripIds(now);
@@ -108,15 +199,19 @@ export const buildServer = (opts) => {
108
199
  misses,
109
200
  ...(config.server.logoFile ? { logoUrl: `${publicUrl}/logo.png` } : {}),
110
201
  };
111
- const TOOLS = buildTools(brand.tools, brand.vocab);
202
+ const TOOLS = buildTools(brand.tools, brand.vocab, caps);
203
+ const OFFERED = new Set(TOOLS.map((t) => t.name));
204
+ const WITHHELD = withheldTools(brand.tools, caps);
205
+ deps.offered = OFFERED;
112
206
  const OUTPUT_SCHEMAS = buildOutputSchemas(brand.tools);
113
207
  const TRIP_TOOLS = tripToolNames(brand);
114
208
  // A policy answer is TEXT for the model, not something to look at: drawing a
115
209
  // booking card over "can I bring a dog" puts a storefront in front of someone
116
210
  // who asked a question.
117
211
  const CARDLESS = new Set([brand.tools.policies, brand.tools.lookUp]);
118
- const imageDomains = imageDomainsOf(data.hotels);
119
- const META = widgetMeta(imageDomains);
212
+ // Worked out per answer: an API backend's photographs arrive with its hotel
213
+ // list, which is still filling in on a first boot.
214
+ const meta = () => widgetMeta(made.imageDomains());
120
215
  const app = new Hono();
121
216
  const file = (name) => {
122
217
  if (!name || !opts.configPath)
@@ -156,17 +251,18 @@ export const buildServer = (opts) => {
156
251
  })
157
252
  : c.notFound();
158
253
  });
159
- app.get('/docs', (c) => c.html(docsPage(config)));
160
- app.get('/privacy', (c) => c.html(privacyPage(config, imageDomains)));
161
- app.get('/terms', (c) => c.html(termsPage(config)));
254
+ app.get('/docs', (c) => c.html(docsPage(config, caps)));
255
+ app.get('/privacy', (c) => c.html(privacyPage(config, made.imageDomains(), caps)));
256
+ app.get('/terms', (c) => c.html(termsPage(config, caps)));
162
257
  app.get('/healthz', (c) => c.json({
163
258
  ok: true,
164
259
  brand: config.brand,
165
260
  version: VERSION,
166
- hotels: data.hotels.length,
261
+ hotels: made.hotelCount(),
167
262
  demo: brand.vocab.demo,
168
263
  trips: trips.size,
169
264
  signedIn: members.size,
265
+ ...made.health(),
170
266
  }));
171
267
  app.get('/', (c) => c.redirect('./docs', 302));
172
268
  app.get('/mcp', (c) => c.json({ name: config.server.name, transport: 'streamable-http', version: VERSION }));
@@ -212,7 +308,7 @@ export const buildServer = (opts) => {
212
308
  : {}),
213
309
  },
214
310
  // How to BEHAVE, not just what the tools are - see surface.ts.
215
- instructions: buildInstructions(brand),
311
+ instructions: buildInstructions(brand, caps),
216
312
  });
217
313
  }
218
314
  // ⚠️ The spec says a receiver MUST answer ping promptly. The shopping
@@ -224,7 +320,7 @@ export const buildServer = (opts) => {
224
320
  return c.body(null, 202);
225
321
  if (method === 'tools/list') {
226
322
  return ok(c, id, {
227
- tools: listTools(TOOLS, OUTPUT_SCHEMAS, (name) => (CARDLESS.has(name) ? {} : META), TRIP_TOOLS),
323
+ tools: listTools(TOOLS, OUTPUT_SCHEMAS, (name) => (CARDLESS.has(name) ? {} : meta()), TRIP_TOOLS),
228
324
  });
229
325
  }
230
326
  if (method === 'resources/list') {
@@ -235,7 +331,7 @@ export const buildServer = (opts) => {
235
331
  name: `${config.brand} booking`,
236
332
  description: `Find ${brand.vocab.nounPlural}, price a stay, build a trip and request the rooms`,
237
333
  mimeType: CARD_MIME,
238
- _meta: META,
334
+ _meta: meta(),
239
335
  },
240
336
  ],
241
337
  });
@@ -252,12 +348,20 @@ export const buildServer = (opts) => {
252
348
  if (!uri.startsWith('ui://curless/hotel/'))
253
349
  return fail(c, id, -32602, `unknown resource: ${uri}`);
254
350
  return ok(c, id, {
255
- contents: [{ uri, mimeType: CARD_MIME, text: HOTEL_CARD_HTML, _meta: META }],
351
+ contents: [{ uri, mimeType: CARD_MIME, text: HOTEL_CARD_HTML, _meta: meta() }],
256
352
  });
257
353
  }
258
354
  if (method === 'tools/call') {
259
355
  const name = String(params?.name ?? '');
260
356
  const args = (params?.arguments ?? {});
357
+ // A tool this deployment does not offer is not listed, and is not
358
+ // answered either - a host holding an old tool list gets told so.
359
+ if (WITHHELD.has(name)) {
360
+ return ok(c, id, {
361
+ content: [{ type: 'text', text: `${name} is not offered by this connector.` }],
362
+ isError: true,
363
+ });
364
+ }
261
365
  const session = c.req.header('mcp-session-id') ?? `anon-${randomUUID()}`;
262
366
  // The card names its trip; the conversation is known by its session. See
263
367
  // trip-ids.ts for why both exist.
@@ -296,7 +400,7 @@ export const buildServer = (opts) => {
296
400
  quantity: l.quantity,
297
401
  }),
298
402
  },
299
- ...(CARDLESS.has(name) ? {} : META),
403
+ ...(CARDLESS.has(name) ? {} : meta()),
300
404
  },
301
405
  });
302
406
  }
@@ -306,7 +410,7 @@ export const buildServer = (opts) => {
306
410
  }
307
411
  return fail(c, id, -32601, `unknown method: ${method}`);
308
412
  });
309
- return { app, brand, deps, trips, members, tripIds };
413
+ return { app, ready: made.ready, brand, deps, trips, members, tripIds };
310
414
  };
311
415
  /**
312
416
  * Every origin the card will try to load a picture from.