@hartl-services/medusa-contracts 1.5.0 → 1.7.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.
- package/README.md +14 -0
- package/dist/email/schemas.d.ts +7 -0
- package/dist/email/schemas.js +1 -0
- package/dist/esm/email/schemas.mjs +1 -0
- package/dist/esm/order-assistant/paths.mjs +33 -0
- package/dist/esm/order-assistant/schemas.mjs +385 -0
- package/dist/esm/order-assistant/types.mjs +1 -0
- package/dist/order-assistant/paths.d.ts +33 -0
- package/dist/order-assistant/paths.js +36 -0
- package/dist/order-assistant/schemas.d.ts +391 -0
- package/dist/order-assistant/schemas.js +389 -0
- package/dist/order-assistant/types.d.ts +31 -0
- package/dist/order-assistant/types.js +2 -0
- package/dist/plugin-features/schemas.d.ts +36 -36
- package/package.json +16 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Consumer subpaths:
|
|
|
28
28
|
- `@hartl-services/medusa-contracts/types/plugin-features-store`
|
|
29
29
|
- `@hartl-services/medusa-contracts/types/plugin-features-admin`
|
|
30
30
|
- `@hartl-services/medusa-contracts/types/tax-compliance-store`
|
|
31
|
+
- `@hartl-services/medusa-contracts/types/order-assistant`
|
|
31
32
|
- `@hartl-services/medusa-contracts/schemas/affiliate`
|
|
32
33
|
- `@hartl-services/medusa-contracts/schemas/batch-management`
|
|
33
34
|
- `@hartl-services/medusa-contracts/schemas/documents`
|
|
@@ -39,6 +40,7 @@ Consumer subpaths:
|
|
|
39
40
|
- `@hartl-services/medusa-contracts/schemas/checkout`
|
|
40
41
|
- `@hartl-services/medusa-contracts/schemas/plugin-features`
|
|
41
42
|
- `@hartl-services/medusa-contracts/schemas/tax-compliance`
|
|
43
|
+
- `@hartl-services/medusa-contracts/schemas/order-assistant`
|
|
42
44
|
- `@hartl-services/medusa-contracts/paths/affiliate`
|
|
43
45
|
- `@hartl-services/medusa-contracts/paths/batch-management`
|
|
44
46
|
- `@hartl-services/medusa-contracts/paths/documents`
|
|
@@ -49,6 +51,7 @@ Consumer subpaths:
|
|
|
49
51
|
- `@hartl-services/medusa-contracts/paths/checkout`
|
|
50
52
|
- `@hartl-services/medusa-contracts/paths/plugin-features`
|
|
51
53
|
- `@hartl-services/medusa-contracts/paths/tax-compliance`
|
|
54
|
+
- `@hartl-services/medusa-contracts/paths/order-assistant`
|
|
52
55
|
|
|
53
56
|
Server-only event subpaths (see "Server-only event contracts" below):
|
|
54
57
|
|
|
@@ -70,6 +73,17 @@ Packing-list DTOs are exported only from `types/documents-admin`. Use
|
|
|
70
73
|
`packingListsPaths` from `paths/documents` with the consuming Admin's configured
|
|
71
74
|
Medusa SDK; there is deliberately no packing-list Store contract or client.
|
|
72
75
|
|
|
76
|
+
The order assistant (`order-assistant`) is not a JSON REST route: `paths` holds
|
|
77
|
+
the MCP endpoint (`/mcp`), `schemas` the input and output schemas of its four
|
|
78
|
+
tools, the tool names and the order metadata keys the assistant writes, and
|
|
79
|
+
`types` their inferred types. A storefront never calls it; the consumer is an
|
|
80
|
+
MCP client (an AI chatbot) and the base's own tests. Since 1.7.0 the same
|
|
81
|
+
subpaths also hold the assistant's OAuth sign-in: `paths.oauth` (discovery,
|
|
82
|
+
registration, authorization, token), `paths.admin` and `paths.adminPages`
|
|
83
|
+
(consent page and connected chatbots in the Admin), the request and response
|
|
84
|
+
schemas of those endpoints, `ORDER_ASSISTANT_FEATURES` and
|
|
85
|
+
`ORDER_ASSISTANT_OAUTH` (scope and lifetimes).
|
|
86
|
+
|
|
73
87
|
File Gateway responses are binary and have no JSON response DTO. Public paths
|
|
74
88
|
come from `paths/files`. Private gateway URLs are opaque, signed server results
|
|
75
89
|
and must not be assembled by a consumer.
|
package/dist/email/schemas.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare const EmailSafeStatusCodeSchema: z.ZodEnum<{
|
|
|
40
40
|
connection_failed: "connection_failed";
|
|
41
41
|
invalid_content: "invalid_content";
|
|
42
42
|
lease_expired: "lease_expired";
|
|
43
|
+
no_recipient: "no_recipient";
|
|
43
44
|
not_configured: "not_configured";
|
|
44
45
|
notifications_disabled: "notifications_disabled";
|
|
45
46
|
outcome_unknown: "outcome_unknown";
|
|
@@ -179,6 +180,7 @@ export declare const AdminEmailMessageSchema: z.ZodObject<{
|
|
|
179
180
|
connection_failed: "connection_failed";
|
|
180
181
|
invalid_content: "invalid_content";
|
|
181
182
|
lease_expired: "lease_expired";
|
|
183
|
+
no_recipient: "no_recipient";
|
|
182
184
|
not_configured: "not_configured";
|
|
183
185
|
notifications_disabled: "notifications_disabled";
|
|
184
186
|
outcome_unknown: "outcome_unknown";
|
|
@@ -217,6 +219,7 @@ export declare const AdminEmailAttemptSchema: z.ZodObject<{
|
|
|
217
219
|
connection_failed: "connection_failed";
|
|
218
220
|
invalid_content: "invalid_content";
|
|
219
221
|
lease_expired: "lease_expired";
|
|
222
|
+
no_recipient: "no_recipient";
|
|
220
223
|
not_configured: "not_configured";
|
|
221
224
|
notifications_disabled: "notifications_disabled";
|
|
222
225
|
outcome_unknown: "outcome_unknown";
|
|
@@ -312,6 +315,7 @@ export declare const ListAdminEmailMessagesResponseSchema: z.ZodObject<{
|
|
|
312
315
|
connection_failed: "connection_failed";
|
|
313
316
|
invalid_content: "invalid_content";
|
|
314
317
|
lease_expired: "lease_expired";
|
|
318
|
+
no_recipient: "no_recipient";
|
|
315
319
|
not_configured: "not_configured";
|
|
316
320
|
notifications_disabled: "notifications_disabled";
|
|
317
321
|
outcome_unknown: "outcome_unknown";
|
|
@@ -382,6 +386,7 @@ export declare const GetAdminEmailMessageResponseSchema: z.ZodObject<{
|
|
|
382
386
|
connection_failed: "connection_failed";
|
|
383
387
|
invalid_content: "invalid_content";
|
|
384
388
|
lease_expired: "lease_expired";
|
|
389
|
+
no_recipient: "no_recipient";
|
|
385
390
|
not_configured: "not_configured";
|
|
386
391
|
notifications_disabled: "notifications_disabled";
|
|
387
392
|
outcome_unknown: "outcome_unknown";
|
|
@@ -420,6 +425,7 @@ export declare const GetAdminEmailMessageResponseSchema: z.ZodObject<{
|
|
|
420
425
|
connection_failed: "connection_failed";
|
|
421
426
|
invalid_content: "invalid_content";
|
|
422
427
|
lease_expired: "lease_expired";
|
|
428
|
+
no_recipient: "no_recipient";
|
|
423
429
|
not_configured: "not_configured";
|
|
424
430
|
notifications_disabled: "notifications_disabled";
|
|
425
431
|
outcome_unknown: "outcome_unknown";
|
|
@@ -486,6 +492,7 @@ export declare const ResendAdminEmailMessageResponseSchema: z.ZodObject<{
|
|
|
486
492
|
connection_failed: "connection_failed";
|
|
487
493
|
invalid_content: "invalid_content";
|
|
488
494
|
lease_expired: "lease_expired";
|
|
495
|
+
no_recipient: "no_recipient";
|
|
489
496
|
not_configured: "not_configured";
|
|
490
497
|
notifications_disabled: "notifications_disabled";
|
|
491
498
|
outcome_unknown: "outcome_unknown";
|
package/dist/email/schemas.js
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shop's MCP endpoint for the order assistant (Streamable HTTP,
|
|
3
|
+
* stateless). Outside `/admin` and `/store`: it authenticates on its own.
|
|
4
|
+
*
|
|
5
|
+
* `oauth`: the OAuth 2.1 endpoints an MCP client (claude.ai, ChatGPT) uses
|
|
6
|
+
* to sign an Admin user in; the well-known paths are fixed by RFC 9728 and
|
|
7
|
+
* RFC 8414. `admin`: the Admin API behind the consent page and the list of
|
|
8
|
+
* connected chatbots. `adminPages`: Admin pages, relative to the Admin root.
|
|
9
|
+
*/
|
|
10
|
+
export const orderAssistantPaths = {
|
|
11
|
+
mcp: "/mcp",
|
|
12
|
+
oauth: {
|
|
13
|
+
protectedResourceMetadata: "/.well-known/oauth-protected-resource",
|
|
14
|
+
/** Same document; clients probe the path of the MCP endpoint first. */
|
|
15
|
+
protectedResourceMetadataForMcp: "/.well-known/oauth-protected-resource/mcp",
|
|
16
|
+
authorizationServerMetadata: "/.well-known/oauth-authorization-server",
|
|
17
|
+
register: "/mcp/oauth/register",
|
|
18
|
+
authorize: "/mcp/oauth/authorize",
|
|
19
|
+
token: "/mcp/oauth/token",
|
|
20
|
+
},
|
|
21
|
+
admin: {
|
|
22
|
+
authorization: (authorizationId) => `/admin/order-assistant/authorizations/${encodeURIComponent(authorizationId)}`,
|
|
23
|
+
connections: "/admin/order-assistant/connections",
|
|
24
|
+
connection: (connectionId) => `/admin/order-assistant/connections/${encodeURIComponent(connectionId)}`,
|
|
25
|
+
},
|
|
26
|
+
adminPages: {
|
|
27
|
+
/**
|
|
28
|
+
* The request id is a path segment on purpose: the Admin login returns
|
|
29
|
+
* to the path it came from and drops the query string.
|
|
30
|
+
*/
|
|
31
|
+
authorize: (authorizationId) => `/order-assistant/authorize/${encodeURIComponent(authorizationId)}`,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { z } from "@medusajs/framework/zod";
|
|
2
|
+
import { NonEmptyStringSchema, WireDateSchema } from "../shared/primitives.mjs";
|
|
3
|
+
/** Plugin key and container key of the order assistant in the base. */
|
|
4
|
+
export const ORDER_ASSISTANT_PLUGIN_KEY = "order_assistant";
|
|
5
|
+
/** Tool names as the MCP client sees them. */
|
|
6
|
+
export const ORDER_ASSISTANT_TOOLS = {
|
|
7
|
+
searchCustomers: "kunden_suchen",
|
|
8
|
+
searchProducts: "produkte_suchen",
|
|
9
|
+
listShippingOptions: "versandarten_auflisten",
|
|
10
|
+
createDraftOrder: "bestellentwurf_anlegen",
|
|
11
|
+
};
|
|
12
|
+
/** How the order reached the shop; stored in the order metadata. */
|
|
13
|
+
export const OrderAssistantSourceSchema = z.enum(["fax", "telefon"]);
|
|
14
|
+
/** Order metadata keys the assistant writes. */
|
|
15
|
+
export const ORDER_ASSISTANT_METADATA = {
|
|
16
|
+
source: "order_source",
|
|
17
|
+
note: "order_note",
|
|
18
|
+
};
|
|
19
|
+
export const ORDER_ASSISTANT_LIMITS = {
|
|
20
|
+
customers: 10,
|
|
21
|
+
variants: 20,
|
|
22
|
+
items: 50,
|
|
23
|
+
noteLength: 2000,
|
|
24
|
+
};
|
|
25
|
+
const CountryCodeSchema = z
|
|
26
|
+
.string()
|
|
27
|
+
.trim()
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.regex(/^[a-z]{2}$/)
|
|
30
|
+
.describe("Land als ISO-3166-1-alpha-2-Code, z. B. at oder de.");
|
|
31
|
+
const OptionalTextSchema = z.string().trim().min(1).max(255).optional();
|
|
32
|
+
export const OrderAssistantAddressSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
first_name: NonEmptyStringSchema.max(255),
|
|
35
|
+
last_name: NonEmptyStringSchema.max(255),
|
|
36
|
+
company: OptionalTextSchema.describe("Firma oder Praxis, falls angegeben."),
|
|
37
|
+
address_1: NonEmptyStringSchema.max(255).describe("Straße und Hausnummer."),
|
|
38
|
+
address_2: OptionalTextSchema.describe("Adresszusatz."),
|
|
39
|
+
postal_code: NonEmptyStringSchema.max(20),
|
|
40
|
+
city: NonEmptyStringSchema.max(255),
|
|
41
|
+
country_code: CountryCodeSchema,
|
|
42
|
+
phone: OptionalTextSchema,
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
export const SearchCustomersInputSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
query: OptionalTextSchema.describe("Name, Firma, Telefonnummer oder E-Mail-Adresse (auch Teile davon)."),
|
|
48
|
+
postal_code: OptionalTextSchema.describe("PLZ einer Adresse der Person."),
|
|
49
|
+
city: OptionalTextSchema.describe("Ort einer Adresse der Person."),
|
|
50
|
+
})
|
|
51
|
+
.strict()
|
|
52
|
+
.refine((value) => Boolean(value.query ?? value.postal_code ?? value.city), { message: "Mindestens ein Suchfeld ist nötig." });
|
|
53
|
+
export const OrderAssistantCustomerAddressSchema = z.object({
|
|
54
|
+
first_name: z.string().nullable(),
|
|
55
|
+
last_name: z.string().nullable(),
|
|
56
|
+
company: z.string().nullable(),
|
|
57
|
+
address_1: z.string().nullable(),
|
|
58
|
+
address_2: z.string().nullable(),
|
|
59
|
+
postal_code: z.string().nullable(),
|
|
60
|
+
city: z.string().nullable(),
|
|
61
|
+
country_code: z.string().nullable(),
|
|
62
|
+
phone: z.string().nullable(),
|
|
63
|
+
});
|
|
64
|
+
export const OrderAssistantCustomerSchema = z.object({
|
|
65
|
+
id: z.string(),
|
|
66
|
+
first_name: z.string().nullable(),
|
|
67
|
+
last_name: z.string().nullable(),
|
|
68
|
+
company_name: z.string().nullable(),
|
|
69
|
+
email: z.string().nullable(),
|
|
70
|
+
phone: z.string().nullable(),
|
|
71
|
+
/** Registered shop account (true) or a customer record without login. */
|
|
72
|
+
has_account: z.boolean(),
|
|
73
|
+
addresses: z.array(OrderAssistantCustomerAddressSchema),
|
|
74
|
+
});
|
|
75
|
+
export const SearchCustomersOutputSchema = z.object({
|
|
76
|
+
customers: z.array(OrderAssistantCustomerSchema),
|
|
77
|
+
});
|
|
78
|
+
export const SearchProductsInputSchema = z
|
|
79
|
+
.object({
|
|
80
|
+
query: NonEmptyStringSchema.max(255).describe("Produktname oder Artikelnummer (auch Teile davon)."),
|
|
81
|
+
country_code: CountryCodeSchema.describe("Lieferland als ISO-Code; bestimmt Preis und Währung."),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
export const OrderAssistantVariantSchema = z.object({
|
|
85
|
+
variant_id: z.string(),
|
|
86
|
+
product_title: z.string(),
|
|
87
|
+
variant_title: z.string().nullable(),
|
|
88
|
+
sku: z.string().nullable(),
|
|
89
|
+
/** Price per unit in major currency units (33.5 = 33,50); null without a price. */
|
|
90
|
+
unit_price: z.number().nullable(),
|
|
91
|
+
currency_code: z.string(),
|
|
92
|
+
/** Available quantity; null when the variant's stock is not managed. */
|
|
93
|
+
available_quantity: z.number().nullable(),
|
|
94
|
+
/** False when the variant cannot be ordered (no price or no stock). */
|
|
95
|
+
orderable: z.boolean(),
|
|
96
|
+
});
|
|
97
|
+
export const SearchProductsOutputSchema = z.object({
|
|
98
|
+
variants: z.array(OrderAssistantVariantSchema),
|
|
99
|
+
});
|
|
100
|
+
export const ListShippingOptionsInputSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
country_code: CountryCodeSchema.describe("Lieferland als ISO-Code."),
|
|
103
|
+
})
|
|
104
|
+
.strict();
|
|
105
|
+
export const OrderAssistantShippingOptionSchema = z.object({
|
|
106
|
+
id: z.string(),
|
|
107
|
+
name: z.string(),
|
|
108
|
+
/** Flat price in major currency units; null when none is set for the currency. */
|
|
109
|
+
amount: z.number().nullable(),
|
|
110
|
+
currency_code: z.string(),
|
|
111
|
+
});
|
|
112
|
+
export const ListShippingOptionsOutputSchema = z.object({
|
|
113
|
+
shipping_options: z.array(OrderAssistantShippingOptionSchema),
|
|
114
|
+
});
|
|
115
|
+
export const OrderAssistantNewCustomerSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
first_name: NonEmptyStringSchema.max(255),
|
|
118
|
+
last_name: NonEmptyStringSchema.max(255),
|
|
119
|
+
company_name: OptionalTextSchema,
|
|
120
|
+
email: z
|
|
121
|
+
.string()
|
|
122
|
+
.trim()
|
|
123
|
+
.toLowerCase()
|
|
124
|
+
.email()
|
|
125
|
+
.max(255)
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Nur angeben, wenn eine E-Mail-Adresse bekannt ist."),
|
|
128
|
+
phone: OptionalTextSchema,
|
|
129
|
+
})
|
|
130
|
+
.strict();
|
|
131
|
+
export const CreateDraftOrderInputSchema = z
|
|
132
|
+
.object({
|
|
133
|
+
customer_id: z
|
|
134
|
+
.string()
|
|
135
|
+
.trim()
|
|
136
|
+
.min(1)
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("ID einer bestehenden Kund:in aus kunden_suchen."),
|
|
139
|
+
new_customer: OrderAssistantNewCustomerSchema.optional().describe("Neue Kund:in ohne Konto; nur wenn kunden_suchen niemanden findet."),
|
|
140
|
+
shipping_address: OrderAssistantAddressSchema,
|
|
141
|
+
billing_address: OrderAssistantAddressSchema.optional().describe("Rechnungsadresse; ohne Angabe gilt die Lieferadresse."),
|
|
142
|
+
items: z
|
|
143
|
+
.array(z
|
|
144
|
+
.object({
|
|
145
|
+
variant_id: NonEmptyStringSchema.describe("ID der Variante aus produkte_suchen."),
|
|
146
|
+
quantity: z.number().int().positive().max(9999),
|
|
147
|
+
})
|
|
148
|
+
.strict())
|
|
149
|
+
.min(1)
|
|
150
|
+
.max(ORDER_ASSISTANT_LIMITS.items),
|
|
151
|
+
shipping_option_id: NonEmptyStringSchema.describe("ID der Versandart aus versandarten_auflisten."),
|
|
152
|
+
source: OrderAssistantSourceSchema.describe("Wie die Bestellung eingegangen ist."),
|
|
153
|
+
note: z
|
|
154
|
+
.string()
|
|
155
|
+
.trim()
|
|
156
|
+
.min(1)
|
|
157
|
+
.max(ORDER_ASSISTANT_LIMITS.noteLength)
|
|
158
|
+
.optional()
|
|
159
|
+
.describe("Interne Notiz, z. B. Unklarheiten auf dem Fax."),
|
|
160
|
+
})
|
|
161
|
+
.strict()
|
|
162
|
+
.refine((value) => Boolean(value.customer_id) !== Boolean(value.new_customer), { message: "Genau eines von customer_id und new_customer ist nötig." });
|
|
163
|
+
export const OrderAssistantDraftItemSchema = z.object({
|
|
164
|
+
title: z.string(),
|
|
165
|
+
sku: z.string().nullable(),
|
|
166
|
+
quantity: z.number(),
|
|
167
|
+
unit_price: z.number(),
|
|
168
|
+
total: z.number(),
|
|
169
|
+
});
|
|
170
|
+
export const CreateDraftOrderOutputSchema = z.object({
|
|
171
|
+
draft_order_id: z.string(),
|
|
172
|
+
display_id: z.number().nullable(),
|
|
173
|
+
customer_id: z.string(),
|
|
174
|
+
/** True when the assistant created the customer record for this draft. */
|
|
175
|
+
customer_created: z.boolean(),
|
|
176
|
+
currency_code: z.string(),
|
|
177
|
+
items: z.array(OrderAssistantDraftItemSchema),
|
|
178
|
+
shipping_total: z.number(),
|
|
179
|
+
tax_total: z.number(),
|
|
180
|
+
total: z.number(),
|
|
181
|
+
/** Path of the draft in the Admin, relative to the backend URL. */
|
|
182
|
+
admin_path: z.string(),
|
|
183
|
+
});
|
|
184
|
+
// --- Anmeldung per OAuth (MCP authorization, OAuth 2.1) --------------------
|
|
185
|
+
/** Feature keys of the order assistant's plugin toggles. */
|
|
186
|
+
export const ORDER_ASSISTANT_FEATURES = {
|
|
187
|
+
oauth: "oauth",
|
|
188
|
+
};
|
|
189
|
+
export const ORDER_ASSISTANT_OAUTH = {
|
|
190
|
+
/** The one scope: use the assistant's tools as the signed-in Admin user. */
|
|
191
|
+
scope: "order_assistant",
|
|
192
|
+
accessTokenTtlSeconds: 3600,
|
|
193
|
+
/** A connection ends when its refresh token stays unused this long. */
|
|
194
|
+
connectionIdleDays: 30,
|
|
195
|
+
/** How long the consent page may stay open. */
|
|
196
|
+
authorizationRequestTtlSeconds: 600,
|
|
197
|
+
/** How long the client has to redeem the code after the consent. */
|
|
198
|
+
authorizationCodeTtlSeconds: 120,
|
|
199
|
+
maxRedirectUris: 10,
|
|
200
|
+
maxUriLength: 2000,
|
|
201
|
+
maxClientNameLength: 120,
|
|
202
|
+
maxStateLength: 2000,
|
|
203
|
+
};
|
|
204
|
+
const OAUTH_LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
205
|
+
/** `http://localhost`, `http://127.0.0.1` or `http://[::1]`, any port. */
|
|
206
|
+
export const isOAuthLoopbackRedirectUri = (value) => {
|
|
207
|
+
try {
|
|
208
|
+
const url = new URL(value);
|
|
209
|
+
return (url.protocol === "http:" && OAUTH_LOOPBACK_HOSTS.includes(url.hostname));
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* A redirect URI a client may register: https, or http on a loopback address
|
|
217
|
+
* (native clients such as Claude Code), without credentials or fragment.
|
|
218
|
+
*/
|
|
219
|
+
export const OAuthRedirectUriSchema = z
|
|
220
|
+
.string()
|
|
221
|
+
.max(ORDER_ASSISTANT_OAUTH.maxUriLength)
|
|
222
|
+
.refine((value) => {
|
|
223
|
+
let url;
|
|
224
|
+
try {
|
|
225
|
+
url = new URL(value);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (url.hash || url.username || url.password) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return url.protocol === "https:" || isOAuthLoopbackRedirectUri(value);
|
|
234
|
+
}, {
|
|
235
|
+
message: "A redirect URI must use https, or http on a loopback address, and carry no fragment.",
|
|
236
|
+
});
|
|
237
|
+
/**
|
|
238
|
+
* Dynamic client registration request (RFC 7591). Unknown metadata is
|
|
239
|
+
* ignored; the requested authentication method, grant and response types are
|
|
240
|
+
* accepted as sent and answered with what the server actually supports.
|
|
241
|
+
*/
|
|
242
|
+
export const OAuthClientRegistrationRequestSchema = z.object({
|
|
243
|
+
redirect_uris: z
|
|
244
|
+
.array(OAuthRedirectUriSchema)
|
|
245
|
+
.min(1)
|
|
246
|
+
.max(ORDER_ASSISTANT_OAUTH.maxRedirectUris),
|
|
247
|
+
client_name: z
|
|
248
|
+
.string()
|
|
249
|
+
.trim()
|
|
250
|
+
.min(1)
|
|
251
|
+
.max(ORDER_ASSISTANT_OAUTH.maxClientNameLength)
|
|
252
|
+
.optional(),
|
|
253
|
+
});
|
|
254
|
+
export const OAuthClientRegistrationResponseSchema = z.object({
|
|
255
|
+
client_id: z.string(),
|
|
256
|
+
/** Seconds since the epoch. */
|
|
257
|
+
client_id_issued_at: z.number(),
|
|
258
|
+
client_name: z.string().optional(),
|
|
259
|
+
redirect_uris: z.array(z.string()),
|
|
260
|
+
/** Public clients only: PKCE instead of a client secret. */
|
|
261
|
+
token_endpoint_auth_method: z.literal("none"),
|
|
262
|
+
grant_types: z.array(z.string()),
|
|
263
|
+
response_types: z.array(z.string()),
|
|
264
|
+
scope: z.string(),
|
|
265
|
+
});
|
|
266
|
+
/** Query of the authorization endpoint, before the client is looked up. */
|
|
267
|
+
export const OAuthAuthorizeQuerySchema = z.object({
|
|
268
|
+
client_id: z.string().min(1).max(200),
|
|
269
|
+
redirect_uri: z.string().min(1).max(ORDER_ASSISTANT_OAUTH.maxUriLength),
|
|
270
|
+
response_type: z.string().max(100).optional(),
|
|
271
|
+
code_challenge: z.string().max(200).optional(),
|
|
272
|
+
code_challenge_method: z.string().max(20).optional(),
|
|
273
|
+
state: z.string().max(ORDER_ASSISTANT_OAUTH.maxStateLength).optional(),
|
|
274
|
+
scope: z.string().max(1000).optional(),
|
|
275
|
+
resource: z.string().max(ORDER_ASSISTANT_OAUTH.maxUriLength).optional(),
|
|
276
|
+
});
|
|
277
|
+
/** Body of the token endpoint (`application/x-www-form-urlencoded`). */
|
|
278
|
+
export const OAuthTokenRequestSchema = z.discriminatedUnion("grant_type", [
|
|
279
|
+
z.object({
|
|
280
|
+
grant_type: z.literal("authorization_code"),
|
|
281
|
+
code: z.string().min(1).max(200),
|
|
282
|
+
/** RFC 7636: 43 to 128 unreserved characters. */
|
|
283
|
+
code_verifier: z.string().regex(/^[A-Za-z0-9\-._~]{43,128}$/),
|
|
284
|
+
client_id: z.string().min(1).max(200),
|
|
285
|
+
redirect_uri: z.string().max(ORDER_ASSISTANT_OAUTH.maxUriLength).optional(),
|
|
286
|
+
resource: z.string().max(ORDER_ASSISTANT_OAUTH.maxUriLength).optional(),
|
|
287
|
+
}),
|
|
288
|
+
z.object({
|
|
289
|
+
grant_type: z.literal("refresh_token"),
|
|
290
|
+
refresh_token: z.string().min(1).max(200),
|
|
291
|
+
client_id: z.string().min(1).max(200),
|
|
292
|
+
resource: z.string().max(ORDER_ASSISTANT_OAUTH.maxUriLength).optional(),
|
|
293
|
+
}),
|
|
294
|
+
]);
|
|
295
|
+
export const OAuthTokenResponseSchema = z.object({
|
|
296
|
+
access_token: z.string(),
|
|
297
|
+
token_type: z.literal("Bearer"),
|
|
298
|
+
/** Seconds until the access token expires. */
|
|
299
|
+
expires_in: z.number(),
|
|
300
|
+
/** Replaces the refresh token that was sent; the old one stops working. */
|
|
301
|
+
refresh_token: z.string(),
|
|
302
|
+
scope: z.string(),
|
|
303
|
+
});
|
|
304
|
+
export const OAuthErrorCodeSchema = z.enum([
|
|
305
|
+
"invalid_request",
|
|
306
|
+
"invalid_client",
|
|
307
|
+
"invalid_grant",
|
|
308
|
+
"invalid_target",
|
|
309
|
+
"invalid_client_metadata",
|
|
310
|
+
"invalid_redirect_uri",
|
|
311
|
+
"unsupported_grant_type",
|
|
312
|
+
"unsupported_response_type",
|
|
313
|
+
"access_denied",
|
|
314
|
+
"temporarily_unavailable",
|
|
315
|
+
]);
|
|
316
|
+
/** Error body of the registration and token endpoints (RFC 6749, RFC 7591). */
|
|
317
|
+
export const OAuthErrorResponseSchema = z.object({
|
|
318
|
+
error: OAuthErrorCodeSchema,
|
|
319
|
+
error_description: z.string(),
|
|
320
|
+
});
|
|
321
|
+
/** RFC 9728: where the MCP endpoint's authorization server is. */
|
|
322
|
+
export const OAuthProtectedResourceMetadataSchema = z.object({
|
|
323
|
+
resource: z.string(),
|
|
324
|
+
authorization_servers: z.array(z.string()),
|
|
325
|
+
bearer_methods_supported: z.array(z.string()),
|
|
326
|
+
scopes_supported: z.array(z.string()),
|
|
327
|
+
resource_name: z.string(),
|
|
328
|
+
});
|
|
329
|
+
/** RFC 8414: the authorization server's endpoints and capabilities. */
|
|
330
|
+
export const OAuthAuthorizationServerMetadataSchema = z.object({
|
|
331
|
+
issuer: z.string(),
|
|
332
|
+
authorization_endpoint: z.string(),
|
|
333
|
+
token_endpoint: z.string(),
|
|
334
|
+
registration_endpoint: z.string(),
|
|
335
|
+
response_types_supported: z.array(z.string()),
|
|
336
|
+
grant_types_supported: z.array(z.string()),
|
|
337
|
+
code_challenge_methods_supported: z.array(z.string()),
|
|
338
|
+
token_endpoint_auth_methods_supported: z.array(z.string()),
|
|
339
|
+
scopes_supported: z.array(z.string()),
|
|
340
|
+
/** RFC 9207: every authorization response carries `iss`. */
|
|
341
|
+
authorization_response_iss_parameter_supported: z.boolean(),
|
|
342
|
+
});
|
|
343
|
+
/** What the consent page shows about a pending sign-in request. */
|
|
344
|
+
export const AdminOrderAssistantAuthorizationSchema = z.object({
|
|
345
|
+
id: z.string(),
|
|
346
|
+
/** Name the chatbot registered with; `null` when it gave none. */
|
|
347
|
+
client_name: z.string().nullable(),
|
|
348
|
+
/** Host the browser is sent back to after the decision. */
|
|
349
|
+
redirect_host: z.string(),
|
|
350
|
+
/** True when the chatbot runs on this computer (loopback redirect). */
|
|
351
|
+
redirect_is_loopback: z.boolean(),
|
|
352
|
+
/** True when the host belongs to a chatbot service the shop knows. */
|
|
353
|
+
redirect_is_known: z.boolean(),
|
|
354
|
+
expires_at: WireDateSchema,
|
|
355
|
+
});
|
|
356
|
+
export const AdminOrderAssistantAuthorizationResponseSchema = z.object({
|
|
357
|
+
authorization: AdminOrderAssistantAuthorizationSchema,
|
|
358
|
+
});
|
|
359
|
+
export const DecideOrderAssistantAuthorizationSchema = z
|
|
360
|
+
.object({ approve: z.boolean() })
|
|
361
|
+
.strict();
|
|
362
|
+
export const DecideOrderAssistantAuthorizationResponseSchema = z.object({
|
|
363
|
+
/** Where the browser must go next: the chatbot's redirect URI. */
|
|
364
|
+
redirect_to: z.string(),
|
|
365
|
+
});
|
|
366
|
+
/** One chatbot an Admin user connected through OAuth. */
|
|
367
|
+
export const AdminOrderAssistantConnectionSchema = z.object({
|
|
368
|
+
id: z.string(),
|
|
369
|
+
client_name: z.string().nullable(),
|
|
370
|
+
redirect_host: z.string(),
|
|
371
|
+
user_id: z.string(),
|
|
372
|
+
/** `null` when the Admin user no longer exists. */
|
|
373
|
+
user_email: z.string().nullable(),
|
|
374
|
+
created_at: WireDateSchema,
|
|
375
|
+
last_used_at: WireDateSchema.nullable(),
|
|
376
|
+
/** When the connection ends unless the chatbot uses it before. */
|
|
377
|
+
expires_at: WireDateSchema,
|
|
378
|
+
});
|
|
379
|
+
export const AdminOrderAssistantConnectionsResponseSchema = z.object({
|
|
380
|
+
connections: z.array(AdminOrderAssistantConnectionSchema),
|
|
381
|
+
});
|
|
382
|
+
export const DeleteOrderAssistantConnectionResponseSchema = z.object({
|
|
383
|
+
id: z.string(),
|
|
384
|
+
deleted: z.literal(true),
|
|
385
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shop's MCP endpoint for the order assistant (Streamable HTTP,
|
|
3
|
+
* stateless). Outside `/admin` and `/store`: it authenticates on its own.
|
|
4
|
+
*
|
|
5
|
+
* `oauth`: the OAuth 2.1 endpoints an MCP client (claude.ai, ChatGPT) uses
|
|
6
|
+
* to sign an Admin user in; the well-known paths are fixed by RFC 9728 and
|
|
7
|
+
* RFC 8414. `admin`: the Admin API behind the consent page and the list of
|
|
8
|
+
* connected chatbots. `adminPages`: Admin pages, relative to the Admin root.
|
|
9
|
+
*/
|
|
10
|
+
export declare const orderAssistantPaths: {
|
|
11
|
+
readonly mcp: "/mcp";
|
|
12
|
+
readonly oauth: {
|
|
13
|
+
readonly protectedResourceMetadata: "/.well-known/oauth-protected-resource";
|
|
14
|
+
/** Same document; clients probe the path of the MCP endpoint first. */
|
|
15
|
+
readonly protectedResourceMetadataForMcp: "/.well-known/oauth-protected-resource/mcp";
|
|
16
|
+
readonly authorizationServerMetadata: "/.well-known/oauth-authorization-server";
|
|
17
|
+
readonly register: "/mcp/oauth/register";
|
|
18
|
+
readonly authorize: "/mcp/oauth/authorize";
|
|
19
|
+
readonly token: "/mcp/oauth/token";
|
|
20
|
+
};
|
|
21
|
+
readonly admin: {
|
|
22
|
+
readonly authorization: (authorizationId: string) => string;
|
|
23
|
+
readonly connections: "/admin/order-assistant/connections";
|
|
24
|
+
readonly connection: (connectionId: string) => string;
|
|
25
|
+
};
|
|
26
|
+
readonly adminPages: {
|
|
27
|
+
/**
|
|
28
|
+
* The request id is a path segment on purpose: the Admin login returns
|
|
29
|
+
* to the path it came from and drops the query string.
|
|
30
|
+
*/
|
|
31
|
+
readonly authorize: (authorizationId: string) => string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.orderAssistantPaths = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The shop's MCP endpoint for the order assistant (Streamable HTTP,
|
|
6
|
+
* stateless). Outside `/admin` and `/store`: it authenticates on its own.
|
|
7
|
+
*
|
|
8
|
+
* `oauth`: the OAuth 2.1 endpoints an MCP client (claude.ai, ChatGPT) uses
|
|
9
|
+
* to sign an Admin user in; the well-known paths are fixed by RFC 9728 and
|
|
10
|
+
* RFC 8414. `admin`: the Admin API behind the consent page and the list of
|
|
11
|
+
* connected chatbots. `adminPages`: Admin pages, relative to the Admin root.
|
|
12
|
+
*/
|
|
13
|
+
exports.orderAssistantPaths = {
|
|
14
|
+
mcp: "/mcp",
|
|
15
|
+
oauth: {
|
|
16
|
+
protectedResourceMetadata: "/.well-known/oauth-protected-resource",
|
|
17
|
+
/** Same document; clients probe the path of the MCP endpoint first. */
|
|
18
|
+
protectedResourceMetadataForMcp: "/.well-known/oauth-protected-resource/mcp",
|
|
19
|
+
authorizationServerMetadata: "/.well-known/oauth-authorization-server",
|
|
20
|
+
register: "/mcp/oauth/register",
|
|
21
|
+
authorize: "/mcp/oauth/authorize",
|
|
22
|
+
token: "/mcp/oauth/token",
|
|
23
|
+
},
|
|
24
|
+
admin: {
|
|
25
|
+
authorization: (authorizationId) => `/admin/order-assistant/authorizations/${encodeURIComponent(authorizationId)}`,
|
|
26
|
+
connections: "/admin/order-assistant/connections",
|
|
27
|
+
connection: (connectionId) => `/admin/order-assistant/connections/${encodeURIComponent(connectionId)}`,
|
|
28
|
+
},
|
|
29
|
+
adminPages: {
|
|
30
|
+
/**
|
|
31
|
+
* The request id is a path segment on purpose: the Admin login returns
|
|
32
|
+
* to the path it came from and drops the query string.
|
|
33
|
+
*/
|
|
34
|
+
authorize: (authorizationId) => `/order-assistant/authorize/${encodeURIComponent(authorizationId)}`,
|
|
35
|
+
},
|
|
36
|
+
};
|