@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.
- package/README.md +35 -11
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +2 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js +3 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -0
- package/dist/__tests__/huazhu-backend.test.d.ts +2 -0
- package/dist/__tests__/huazhu-backend.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-backend.test.js +434 -0
- package/dist/__tests__/huazhu-backend.test.js.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts +2 -0
- package/dist/__tests__/huazhu-cancel-policy.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js +82 -0
- package/dist/__tests__/huazhu-cancel-policy.test.js.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.js +172 -0
- package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sellable.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sellable.test.js +192 -0
- package/dist/__tests__/huazhu-sellable.test.js.map +1 -0
- package/dist/__tests__/server.test.js +129 -4
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/tool-descriptions.test.js +85 -7
- package/dist/__tests__/tool-descriptions.test.js.map +1 -1
- package/dist/backend.d.ts +27 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +7 -0
- package/dist/backend.js.map +1 -1
- package/dist/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/config.d.ts +64 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/facilities.d.ts.map +1 -1
- package/dist/facilities.js +7 -5
- package/dist/facilities.js.map +1 -1
- package/dist/huazhu/backend.d.ts +40 -0
- package/dist/huazhu/backend.d.ts.map +1 -0
- package/dist/huazhu/backend.js +1318 -0
- package/dist/huazhu/backend.js.map +1 -0
- package/dist/huazhu/bookings.d.ts +81 -0
- package/dist/huazhu/bookings.d.ts.map +1 -0
- package/dist/huazhu/bookings.js +189 -0
- package/dist/huazhu/bookings.js.map +1 -0
- package/dist/huazhu/cancel-policy.d.ts +69 -0
- package/dist/huazhu/cancel-policy.d.ts.map +1 -0
- package/dist/huazhu/cancel-policy.js +201 -0
- package/dist/huazhu/cancel-policy.js.map +1 -0
- package/dist/huazhu/catalog.d.ts +75 -0
- package/dist/huazhu/catalog.d.ts.map +1 -0
- package/dist/huazhu/catalog.js +251 -0
- package/dist/huazhu/catalog.js.map +1 -0
- package/dist/huazhu/check.d.ts +5 -0
- package/dist/huazhu/check.d.ts.map +1 -0
- package/dist/huazhu/check.js +170 -0
- package/dist/huazhu/check.js.map +1 -0
- package/dist/huazhu/client.d.ts +100 -0
- package/dist/huazhu/client.d.ts.map +1 -0
- package/dist/huazhu/client.js +232 -0
- package/dist/huazhu/client.js.map +1 -0
- package/dist/huazhu/fake.d.ts +64 -0
- package/dist/huazhu/fake.d.ts.map +1 -0
- package/dist/huazhu/fake.js +365 -0
- package/dist/huazhu/fake.js.map +1 -0
- package/dist/huazhu/sandbox.d.ts +26 -0
- package/dist/huazhu/sandbox.d.ts.map +1 -0
- package/dist/huazhu/sandbox.js +289 -0
- package/dist/huazhu/sandbox.js.map +1 -0
- package/dist/huazhu/sellable.d.ts +81 -0
- package/dist/huazhu/sellable.d.ts.map +1 -0
- package/dist/huazhu/sellable.js +219 -0
- package/dist/huazhu/sellable.js.map +1 -0
- package/dist/huazhu/wire.d.ts +389 -0
- package/dist/huazhu/wire.d.ts.map +1 -0
- package/dist/huazhu/wire.js +72 -0
- package/dist/huazhu/wire.js.map +1 -0
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/legal.d.ts +4 -3
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +71 -30
- package/dist/legal.js.map +1 -1
- package/dist/lib.d.ts +8 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +8 -1
- package/dist/lib.js.map +1 -1
- package/dist/server.d.ts +20 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +132 -28
- package/dist/server.js.map +1 -1
- package/dist/surface.d.ts +2 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +42 -10
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts +33 -20
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +122 -11
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +21 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vocab.d.ts +5 -0
- package/dist/vocab.d.ts.map +1 -1
- package/dist/vocab.js +2 -1
- package/dist/vocab.js.map +1 -1
- 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
|
-
/**
|
|
38
|
-
|
|
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
|
-
:
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
91
|
-
? `
|
|
92
|
-
:
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
121
|
-
|
|
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
|
|
146
|
-
?
|
|
147
|
-
:
|
|
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
|
|
150
|
-
?
|
|
151
|
-
:
|
|
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
|
|
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":"
|
|
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;
|
|
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.
|
|
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-
|
|
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-
|
|
44
|
+
readonly resourceUri: "ui://curless/hotel/booking-v40.html";
|
|
45
45
|
};
|
|
46
|
-
readonly 'ui/resourceUri': "ui://curless/hotel/booking-
|
|
47
|
-
readonly 'openai/outputTemplate': "ui://curless/hotel/booking-
|
|
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-
|
|
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-
|
|
73
|
-
'openai/outputTemplate': "ui://curless/hotel/booking-
|
|
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
|
/**
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,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.
|
|
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-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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:
|
|
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) ? {} :
|
|
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:
|
|
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:
|
|
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) ? {} :
|
|
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.
|