easypost-mcp 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/audit/AuditLogger.js +20 -0
- package/src/constants/toolRisk.js +36 -0
- package/src/elicitation/fallback.js +37 -0
- package/src/elicitation/fields/catalog.js +171 -0
- package/src/pipeline/ExecutionPipeline.js +226 -0
- package/src/resources/ResourceManager.js +244 -0
- package/src/resources/cache.js +41 -0
- package/src/resources/fuzzy.js +57 -0
- package/src/resources/staticResources.js +89 -0
- package/src/schemas/addressSchemas.js +3 -1
- package/src/schemas/batchSchemas.js +2 -1
- package/src/schemas/commonSchemas.js +5 -1
- package/src/schemas/pickupSchemas.js +4 -1
- package/src/schemas/resourceSchemas.js +16 -0
- package/src/schemas/returnSchemas.js +5 -1
- package/src/schemas/shipmentSchemas.js +8 -6
- package/src/server/createServer.js +25 -1
- package/src/services/AddressService.js +53 -3
- package/src/services/BatchService.js +8 -1
- package/src/services/ConfirmationService.js +45 -0
- package/src/services/ElicitationService.js +230 -0
- package/src/services/IdempotencyStore.js +40 -0
- package/src/services/PickupService.js +47 -20
- package/src/services/ReturnService.js +37 -7
- package/src/services/ShipmentService.js +149 -17
- package/src/services/WorkflowStateStore.js +70 -0
- package/src/services/index.js +20 -5
- package/src/tools/ToolDefinition.js +13 -2
- package/src/tools/definitions/pickups.js +2 -2
- package/src/tools/definitions/resources.js +59 -0
- package/src/tools/definitions/shipments.js +3 -3
- package/src/tools/index.js +2 -0
- package/src/validators/antiHallucination.js +109 -8
- package/src/validators/validate.js +2 -2
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ResourceCache } from "./cache.js";
|
|
2
|
+
import { CARRIERS, COUNTRIES, PACKAGE_TYPES, PAYMENT_METHODS, SERVICES_BY_CARRIER, STATES_BY_COUNTRY } from "./staticResources.js";
|
|
3
|
+
import { fuzzySuggest, normalizeCandidate } from "./fuzzy.js";
|
|
4
|
+
|
|
5
|
+
const CACHE_KEYS = Object.freeze({
|
|
6
|
+
carriers: "carriers",
|
|
7
|
+
services: "services",
|
|
8
|
+
packageTypes: "packageTypes",
|
|
9
|
+
countries: "countries",
|
|
10
|
+
carrierAccounts: "carrierAccounts",
|
|
11
|
+
warehouses: "warehouses",
|
|
12
|
+
states: "states",
|
|
13
|
+
paymentMethods: "paymentMethods",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function validationResult({ valid, confidence = 1, issues = [], suggestions = [], value = null }) {
|
|
17
|
+
return { valid, confidence, issues, suggestions, value };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class ResourceManager {
|
|
21
|
+
constructor(easypostClient, { ttlMs = 15 * 60 * 1000 } = {}) {
|
|
22
|
+
this.easypost = easypostClient;
|
|
23
|
+
this.cache = new ResourceCache({ ttlMs });
|
|
24
|
+
this.initialized = false;
|
|
25
|
+
this.initializePromise = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async initialize(context = {}) {
|
|
29
|
+
if (this.initialized) return;
|
|
30
|
+
if (this.initializePromise) return this.initializePromise;
|
|
31
|
+
|
|
32
|
+
this.initializePromise = this.refresh(context).finally(() => {
|
|
33
|
+
this.initialized = true;
|
|
34
|
+
this.initializePromise = null;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return this.initializePromise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async refresh(context = {}) {
|
|
41
|
+
this.cache.set(CACHE_KEYS.carriers, CARRIERS);
|
|
42
|
+
this.cache.set(CACHE_KEYS.services, SERVICES_BY_CARRIER);
|
|
43
|
+
this.cache.set(CACHE_KEYS.packageTypes, PACKAGE_TYPES);
|
|
44
|
+
this.cache.set(CACHE_KEYS.countries, COUNTRIES);
|
|
45
|
+
this.cache.set(CACHE_KEYS.states, STATES_BY_COUNTRY);
|
|
46
|
+
this.cache.set(CACHE_KEYS.paymentMethods, PAYMENT_METHODS);
|
|
47
|
+
this.cache.set(CACHE_KEYS.warehouses, []);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const carrierAccounts = await this.easypost.execute(
|
|
51
|
+
"carrier_accounts.list",
|
|
52
|
+
(client) => client.CarrierAccount.all(),
|
|
53
|
+
context
|
|
54
|
+
);
|
|
55
|
+
this.cache.set(CACHE_KEYS.carrierAccounts, Array.isArray(carrierAccounts) ? carrierAccounts : []);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
context.logger?.warn(
|
|
58
|
+
{ err: { name: error.name, code: error.code, message: error.message } },
|
|
59
|
+
"Carrier account resource refresh failed; continuing with static resources"
|
|
60
|
+
);
|
|
61
|
+
this.cache.set(CACHE_KEYS.carrierAccounts, []);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async ensureFresh(context = {}) {
|
|
66
|
+
if (!this.initialized || !this.cache.hasFresh(CACHE_KEYS.carriers)) {
|
|
67
|
+
await this.initialize(context);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getCarriers() {
|
|
72
|
+
return this.cache.get(CACHE_KEYS.carriers) || CARRIERS;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getServices(carrierCode) {
|
|
76
|
+
const services = this.cache.get(CACHE_KEYS.services) || SERVICES_BY_CARRIER;
|
|
77
|
+
return carrierCode ? services[carrierCode] || [] : services;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getCarrierAccounts() {
|
|
81
|
+
return this.cache.get(CACHE_KEYS.carrierAccounts) || [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getPackageTypes() {
|
|
85
|
+
return this.cache.get(CACHE_KEYS.packageTypes) || PACKAGE_TYPES;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getCountries() {
|
|
89
|
+
return this.cache.get(CACHE_KEYS.countries) || COUNTRIES;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getStates(countryCode) {
|
|
93
|
+
const states = this.cache.get(CACHE_KEYS.states) || STATES_BY_COUNTRY;
|
|
94
|
+
return countryCode ? states[countryCode] || [] : states;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getPaymentMethods() {
|
|
98
|
+
return this.cache.get(CACHE_KEYS.paymentMethods) || PAYMENT_METHODS;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getOperationalContext() {
|
|
102
|
+
return {
|
|
103
|
+
resources: {
|
|
104
|
+
carriers_count: this.getCarriers().length,
|
|
105
|
+
package_types_count: (this.cache.get(CACHE_KEYS.packageTypes) || []).length,
|
|
106
|
+
countries_count: (this.cache.get(CACHE_KEYS.countries) || []).length,
|
|
107
|
+
user_shipping_accounts_count: this.getCarrierAccounts().length,
|
|
108
|
+
cache: Object.fromEntries(
|
|
109
|
+
Object.values(CACHE_KEYS).map((key) => [key, this.cache.metadata(key)])
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
validateCarrier(input) {
|
|
116
|
+
const carriers = this.getCarriers();
|
|
117
|
+
const normalized = normalizeCandidate(input);
|
|
118
|
+
const exact = carriers.find((carrier) => {
|
|
119
|
+
const labels = [carrier.code, carrier.name, ...(carrier.aliases || [])];
|
|
120
|
+
return labels.some((label) => normalizeCandidate(label) === normalized);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (exact) {
|
|
124
|
+
return validationResult({ valid: true, confidence: 1, value: exact.code });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return validationResult({
|
|
128
|
+
valid: false,
|
|
129
|
+
confidence: 0,
|
|
130
|
+
issues: [{ code: "UNKNOWN_CARRIER", message: `Unsupported carrier: ${input}` }],
|
|
131
|
+
suggestions: fuzzySuggest(input, carriers),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
validateService(carrierInput, serviceInput) {
|
|
136
|
+
const carrier = this.validateCarrier(carrierInput);
|
|
137
|
+
if (!carrier.valid) return carrier;
|
|
138
|
+
|
|
139
|
+
const services = this.getServices(carrier.value);
|
|
140
|
+
const exact = services.find((service) => normalizeCandidate(service) === normalizeCandidate(serviceInput));
|
|
141
|
+
if (exact) {
|
|
142
|
+
return validationResult({ valid: true, confidence: 1, value: exact });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return validationResult({
|
|
146
|
+
valid: false,
|
|
147
|
+
confidence: 0,
|
|
148
|
+
issues: [{
|
|
149
|
+
code: "UNSUPPORTED_SERVICE_FOR_CARRIER",
|
|
150
|
+
message: `Unsupported service for ${carrier.value}: ${serviceInput}`,
|
|
151
|
+
}],
|
|
152
|
+
suggestions: fuzzySuggest(
|
|
153
|
+
serviceInput,
|
|
154
|
+
services.map((service) => ({ code: service, name: service })),
|
|
155
|
+
{ threshold: 0.7 }
|
|
156
|
+
),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
validateCarrierAccount(input) {
|
|
161
|
+
const accounts = this.getCarrierAccounts();
|
|
162
|
+
if (!accounts.length) return validationResult({ valid: true, confidence: 0.6, value: input });
|
|
163
|
+
const match = accounts.find((account) => account.id === input);
|
|
164
|
+
if (match) return validationResult({ valid: true, confidence: 1, value: input });
|
|
165
|
+
return validationResult({
|
|
166
|
+
valid: false,
|
|
167
|
+
confidence: 0,
|
|
168
|
+
issues: [{ code: "UNKNOWN_CARRIER_ACCOUNT", message: `Unknown carrier account: ${input}` }],
|
|
169
|
+
suggestions: accounts.slice(0, 5).map((account) => ({
|
|
170
|
+
id: account.id,
|
|
171
|
+
carrier: account.carrier,
|
|
172
|
+
description: account.description,
|
|
173
|
+
})),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
validatePackageType(input) {
|
|
178
|
+
const packageTypes = this.getPackageTypes();
|
|
179
|
+
const exact = packageTypes.find((packageType) => normalizeCandidate(packageType) === normalizeCandidate(input));
|
|
180
|
+
if (exact) return validationResult({ valid: true, confidence: 1, value: exact });
|
|
181
|
+
return validationResult({
|
|
182
|
+
valid: false,
|
|
183
|
+
confidence: 0,
|
|
184
|
+
issues: [{ code: "UNKNOWN_PACKAGE_TYPE", message: `Unknown package type: ${input}` }],
|
|
185
|
+
suggestions: fuzzySuggest(
|
|
186
|
+
input,
|
|
187
|
+
packageTypes.map((packageType) => ({ code: packageType, name: packageType })),
|
|
188
|
+
{ threshold: 0.72 }
|
|
189
|
+
),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
validateCountry(input) {
|
|
194
|
+
const countries = this.getCountries();
|
|
195
|
+
const exact = countries.find((country) => {
|
|
196
|
+
const labels = [country.code, country.name];
|
|
197
|
+
return labels.some((label) => normalizeCandidate(label) === normalizeCandidate(input));
|
|
198
|
+
});
|
|
199
|
+
if (exact) return validationResult({ valid: true, confidence: 1, value: exact.code });
|
|
200
|
+
return validationResult({
|
|
201
|
+
valid: false,
|
|
202
|
+
confidence: 0,
|
|
203
|
+
issues: [{ code: "UNKNOWN_COUNTRY", message: `Unknown country: ${input}` }],
|
|
204
|
+
suggestions: fuzzySuggest(input, countries, { threshold: 0.72 }),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
validateState(countryInput, stateInput) {
|
|
209
|
+
const country = this.validateCountry(countryInput);
|
|
210
|
+
if (!country.valid) return country;
|
|
211
|
+
const states = this.getStates(country.value);
|
|
212
|
+
if (!states.length) return validationResult({ valid: true, confidence: 0.6, value: stateInput });
|
|
213
|
+
const exact = states.find((state) => normalizeCandidate(state) === normalizeCandidate(stateInput));
|
|
214
|
+
if (exact) return validationResult({ valid: true, confidence: 1, value: exact });
|
|
215
|
+
return validationResult({
|
|
216
|
+
valid: false,
|
|
217
|
+
confidence: 0,
|
|
218
|
+
issues: [{ code: "UNKNOWN_STATE", message: `Unknown state/province for ${country.value}: ${stateInput}` }],
|
|
219
|
+
suggestions: fuzzySuggest(
|
|
220
|
+
stateInput,
|
|
221
|
+
states.map((state) => ({ code: state, name: state })),
|
|
222
|
+
{ threshold: 0.7 }
|
|
223
|
+
),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
validatePaymentMethod(input) {
|
|
228
|
+
const methods = this.getPaymentMethods();
|
|
229
|
+
const exact = methods.find((method) => normalizeCandidate(method) === normalizeCandidate(input));
|
|
230
|
+
if (exact) return validationResult({ valid: true, confidence: 1, value: exact });
|
|
231
|
+
return validationResult({
|
|
232
|
+
valid: false,
|
|
233
|
+
confidence: 0,
|
|
234
|
+
issues: [{ code: "UNKNOWN_PAYMENT_METHOD", message: `Unknown payment method: ${input}` }],
|
|
235
|
+
suggestions: fuzzySuggest(
|
|
236
|
+
input,
|
|
237
|
+
methods.map((method) => ({ code: method, name: method })),
|
|
238
|
+
{ threshold: 0.7 }
|
|
239
|
+
),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { ResourceManager };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class ResourceCache {
|
|
2
|
+
constructor({ ttlMs = 15 * 60 * 1000 } = {}) {
|
|
3
|
+
this.ttlMs = ttlMs;
|
|
4
|
+
this.entries = new Map();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get(key) {
|
|
8
|
+
const entry = this.entries.get(key);
|
|
9
|
+
if (!entry) return null;
|
|
10
|
+
if (Date.now() >= entry.expiresAt) {
|
|
11
|
+
this.entries.delete(key);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return entry.value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set(key, value, ttlMs = this.ttlMs) {
|
|
18
|
+
this.entries.set(key, {
|
|
19
|
+
value,
|
|
20
|
+
expiresAt: Date.now() + ttlMs,
|
|
21
|
+
loadedAt: new Date().toISOString(),
|
|
22
|
+
});
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
hasFresh(key) {
|
|
27
|
+
return this.get(key) !== null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
metadata(key) {
|
|
31
|
+
const entry = this.entries.get(key);
|
|
32
|
+
if (!entry) return null;
|
|
33
|
+
return {
|
|
34
|
+
loaded_at: entry.loadedAt,
|
|
35
|
+
expires_at: new Date(entry.expiresAt).toISOString(),
|
|
36
|
+
stale: Date.now() >= entry.expiresAt,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { ResourceCache };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
function normalizeCandidate(value) {
|
|
2
|
+
return String(value || "")
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function levenshtein(a, b) {
|
|
9
|
+
const left = normalizeCandidate(a);
|
|
10
|
+
const right = normalizeCandidate(b);
|
|
11
|
+
if (left === right) return 0;
|
|
12
|
+
if (!left.length) return right.length;
|
|
13
|
+
if (!right.length) return left.length;
|
|
14
|
+
|
|
15
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
16
|
+
const current = new Array(right.length + 1);
|
|
17
|
+
|
|
18
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
19
|
+
current[0] = i;
|
|
20
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
21
|
+
const cost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
22
|
+
current[j] = Math.min(
|
|
23
|
+
current[j - 1] + 1,
|
|
24
|
+
previous[j] + 1,
|
|
25
|
+
previous[j - 1] + cost
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
previous.splice(0, previous.length, ...current);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return previous[right.length];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function similarity(a, b) {
|
|
35
|
+
const left = normalizeCandidate(a);
|
|
36
|
+
const right = normalizeCandidate(b);
|
|
37
|
+
const maxLength = Math.max(left.length, right.length);
|
|
38
|
+
if (!maxLength) return 1;
|
|
39
|
+
return 1 - levenshtein(left, right) / maxLength;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fuzzySuggest(input, candidates, { threshold = 0.72, limit = 5 } = {}) {
|
|
43
|
+
const normalizedInput = normalizeCandidate(input);
|
|
44
|
+
if (!normalizedInput) return [];
|
|
45
|
+
|
|
46
|
+
return candidates
|
|
47
|
+
.map((candidate) => {
|
|
48
|
+
const labels = [candidate.code, candidate.name, ...(candidate.aliases || [])].filter(Boolean);
|
|
49
|
+
const score = Math.max(...labels.map((label) => similarity(normalizedInput, label)));
|
|
50
|
+
return { ...candidate, score: Number(score.toFixed(3)) };
|
|
51
|
+
})
|
|
52
|
+
.filter((candidate) => candidate.score >= threshold)
|
|
53
|
+
.sort((a, b) => b.score - a.score || String(a.code).localeCompare(String(b.code)))
|
|
54
|
+
.slice(0, limit);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { normalizeCandidate, similarity, fuzzySuggest };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const CARRIERS = [
|
|
2
|
+
{ code: "USPS", name: "USPS", aliases: ["united states postal service"] },
|
|
3
|
+
{ code: "FedEx", name: "FedEx", aliases: ["federal express"] },
|
|
4
|
+
{ code: "UPS", name: "UPS", aliases: ["united parcel service"] },
|
|
5
|
+
{ code: "DHLExpress", name: "DHL Express", aliases: ["dhl"] },
|
|
6
|
+
{ code: "CanadaPost", name: "Canada Post", aliases: ["canadapost"] },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const SERVICES_BY_CARRIER = {
|
|
10
|
+
USPS: [
|
|
11
|
+
"GroundAdvantage",
|
|
12
|
+
"First",
|
|
13
|
+
"Priority",
|
|
14
|
+
"Express",
|
|
15
|
+
"ParcelSelect",
|
|
16
|
+
"MediaMail",
|
|
17
|
+
"LibraryMail",
|
|
18
|
+
],
|
|
19
|
+
FedEx: [
|
|
20
|
+
"FEDEX_GROUND",
|
|
21
|
+
"FEDEX_2_DAY",
|
|
22
|
+
"FEDEX_2_DAY_AM",
|
|
23
|
+
"STANDARD_OVERNIGHT",
|
|
24
|
+
"PRIORITY_OVERNIGHT",
|
|
25
|
+
"FIRST_OVERNIGHT",
|
|
26
|
+
"GROUND_HOME_DELIVERY",
|
|
27
|
+
"INTERNATIONAL_PRIORITY",
|
|
28
|
+
"INTERNATIONAL_ECONOMY",
|
|
29
|
+
],
|
|
30
|
+
UPS: [
|
|
31
|
+
"Ground",
|
|
32
|
+
"UPSStandard",
|
|
33
|
+
"UPSSaver",
|
|
34
|
+
"NextDayAir",
|
|
35
|
+
"NextDayAirSaver",
|
|
36
|
+
"2ndDayAir",
|
|
37
|
+
"3DaySelect",
|
|
38
|
+
"WorldwideExpress",
|
|
39
|
+
"WorldwideExpedited",
|
|
40
|
+
],
|
|
41
|
+
DHLExpress: [
|
|
42
|
+
"ExpressWorldwide",
|
|
43
|
+
"ExpressEasy",
|
|
44
|
+
"ExpressEnvelope",
|
|
45
|
+
"MedicalExpress",
|
|
46
|
+
],
|
|
47
|
+
CanadaPost: [
|
|
48
|
+
"RegularParcel",
|
|
49
|
+
"ExpeditedParcel",
|
|
50
|
+
"Xpresspost",
|
|
51
|
+
"Priority",
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const PACKAGE_TYPES = [
|
|
56
|
+
"Parcel",
|
|
57
|
+
"FlatRateEnvelope",
|
|
58
|
+
"FlatRateLegalEnvelope",
|
|
59
|
+
"FlatRatePaddedEnvelope",
|
|
60
|
+
"FlatRateSmallBox",
|
|
61
|
+
"FlatRateMediumBox",
|
|
62
|
+
"FlatRateLargeBox",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const COUNTRIES = [
|
|
66
|
+
{ code: "US", name: "United States" },
|
|
67
|
+
{ code: "CA", name: "Canada" },
|
|
68
|
+
{ code: "GB", name: "United Kingdom" },
|
|
69
|
+
{ code: "AU", name: "Australia" },
|
|
70
|
+
{ code: "DE", name: "Germany" },
|
|
71
|
+
{ code: "FR", name: "France" },
|
|
72
|
+
{ code: "IN", name: "India" },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const STATES_BY_COUNTRY = {
|
|
76
|
+
US: [
|
|
77
|
+
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA",
|
|
78
|
+
"HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD",
|
|
79
|
+
"MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ",
|
|
80
|
+
"NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC",
|
|
81
|
+
"SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY",
|
|
82
|
+
"DC",
|
|
83
|
+
],
|
|
84
|
+
CA: ["AB", "BC", "MB", "NB", "NL", "NS", "NT", "NU", "ON", "PE", "QC", "SK", "YT"],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const PAYMENT_METHODS = ["SENDER", "THIRD_PARTY", "RECEIVER"];
|
|
88
|
+
|
|
89
|
+
export { CARRIERS, SERVICES_BY_CARRIER, PACKAGE_TYPES, COUNTRIES, STATES_BY_COUNTRY, PAYMENT_METHODS };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, nonEmptyString, optionalNonEmptyString } from "./commonSchemas.js";
|
|
1
|
+
import { z, nonEmptyString, optionalNonEmptyString, confirmationFields } from "./commonSchemas.js";
|
|
2
2
|
|
|
3
3
|
const addressSchema = z.object({
|
|
4
4
|
name: optionalNonEmptyString,
|
|
@@ -17,11 +17,13 @@ const addressSchema = z.object({
|
|
|
17
17
|
const verifyAddressSchema = z.object({
|
|
18
18
|
address: addressSchema,
|
|
19
19
|
verifications: z.array(z.enum(["delivery", "zip4"])).default(["delivery"]),
|
|
20
|
+
...confirmationFields,
|
|
20
21
|
}).strict();
|
|
21
22
|
|
|
22
23
|
const createAddressSchema = z.object({
|
|
23
24
|
address: addressSchema,
|
|
24
25
|
verify: z.boolean().default(false),
|
|
26
|
+
...confirmationFields,
|
|
25
27
|
}).strict();
|
|
26
28
|
|
|
27
29
|
export { addressSchema, verifyAddressSchema, createAddressSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, easypostId } from "./commonSchemas.js";
|
|
1
|
+
import { z, easypostId, confirmationFields } from "./commonSchemas.js";
|
|
2
2
|
import { createShipmentSchema } from "./shipmentSchemas.js";
|
|
3
3
|
|
|
4
4
|
const batchShipmentCreateSchema = createShipmentSchema.extend({
|
|
@@ -17,6 +17,7 @@ const createBatchSchema = z.object({
|
|
|
17
17
|
|
|
18
18
|
const buyBatchSchema = z.object({
|
|
19
19
|
batch_id: easypostId,
|
|
20
|
+
...confirmationFields,
|
|
20
21
|
}).strict();
|
|
21
22
|
|
|
22
23
|
const batchStatusSchema = buyBatchSchema;
|
|
@@ -5,6 +5,10 @@ const optionalNonEmptyString = nonEmptyString.optional();
|
|
|
5
5
|
const easypostId = z.string().trim().regex(/^[a-z]+_[A-Za-z0-9]+$/, "Invalid EasyPost id format");
|
|
6
6
|
|
|
7
7
|
const currencyAmount = z.union([z.string().trim().regex(/^\d+(\.\d{1,2})?$/), z.number().nonnegative()]);
|
|
8
|
+
const idempotencyKey = z.string().trim().min(8).max(128).regex(/^[A-Za-z0-9_.:-]+$/, "Invalid idempotency key");
|
|
9
|
+
const confirmationFields = {
|
|
10
|
+
confirm: z.literal(true).optional(),
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
const paginationSchema = z.object({
|
|
10
14
|
page_size: z.number().int().min(1).max(100).optional(),
|
|
@@ -12,4 +16,4 @@ const paginationSchema = z.object({
|
|
|
12
16
|
after_id: z.string().trim().optional(),
|
|
13
17
|
}).strict();
|
|
14
18
|
|
|
15
|
-
export { z, nonEmptyString, optionalNonEmptyString, easypostId, currencyAmount, paginationSchema };
|
|
19
|
+
export { z, nonEmptyString, optionalNonEmptyString, easypostId, currencyAmount, idempotencyKey, confirmationFields, paginationSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, easypostId, nonEmptyString } from "./commonSchemas.js";
|
|
1
|
+
import { z, easypostId, nonEmptyString, idempotencyKey, confirmationFields } from "./commonSchemas.js";
|
|
2
2
|
import { addressSchema } from "./addressSchemas.js";
|
|
3
3
|
|
|
4
4
|
const schedulePickupSchema = z.object({
|
|
@@ -8,10 +8,13 @@ const schedulePickupSchema = z.object({
|
|
|
8
8
|
max_datetime: nonEmptyString,
|
|
9
9
|
instructions: z.string().trim().max(500).optional(),
|
|
10
10
|
carrier_accounts: z.array(nonEmptyString).optional(),
|
|
11
|
+
idempotency_key: idempotencyKey.optional(),
|
|
12
|
+
...confirmationFields,
|
|
11
13
|
}).strict();
|
|
12
14
|
|
|
13
15
|
const cancelPickupSchema = z.object({
|
|
14
16
|
pickup_id: easypostId,
|
|
17
|
+
...confirmationFields,
|
|
15
18
|
}).strict();
|
|
16
19
|
|
|
17
20
|
export { schedulePickupSchema, cancelPickupSchema };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z, nonEmptyString } from "./commonSchemas.js";
|
|
2
|
+
|
|
3
|
+
const listResourcesSchema = z.object({
|
|
4
|
+
refresh: z.boolean().optional(),
|
|
5
|
+
}).strict();
|
|
6
|
+
|
|
7
|
+
const validateCarrierSchema = z.object({
|
|
8
|
+
carrier: nonEmptyString,
|
|
9
|
+
}).strict();
|
|
10
|
+
|
|
11
|
+
const validateServiceSchema = z.object({
|
|
12
|
+
carrier: nonEmptyString,
|
|
13
|
+
service: nonEmptyString,
|
|
14
|
+
}).strict();
|
|
15
|
+
|
|
16
|
+
export { listResourcesSchema, validateCarrierSchema, validateServiceSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, easypostId } from "./commonSchemas.js";
|
|
1
|
+
import { z, easypostId, confirmationFields, idempotencyKey } from "./commonSchemas.js";
|
|
2
2
|
import { addressSchema } from "./addressSchemas.js";
|
|
3
3
|
import { parcelSchema } from "./parcelSchemas.js";
|
|
4
4
|
|
|
@@ -9,6 +9,10 @@ const createReturnLabelSchema = z.object({
|
|
|
9
9
|
parcel: parcelSchema,
|
|
10
10
|
carrier: z.string().trim().optional(),
|
|
11
11
|
service: z.string().trim().optional(),
|
|
12
|
+
rate_id: easypostId.optional(),
|
|
13
|
+
rate_option: z.number().int().min(1).optional(),
|
|
14
|
+
idempotency_key: idempotencyKey.optional(),
|
|
15
|
+
...confirmationFields,
|
|
12
16
|
}).strict();
|
|
13
17
|
|
|
14
18
|
export { createReturnLabelSchema };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, easypostId, nonEmptyString, paginationSchema, currencyAmount } from "./commonSchemas.js";
|
|
1
|
+
import { z, easypostId, nonEmptyString, paginationSchema, currencyAmount, idempotencyKey, confirmationFields } from "./commonSchemas.js";
|
|
2
2
|
import { addressSchema } from "./addressSchemas.js";
|
|
3
3
|
import { parcelSchema } from "./parcelSchemas.js";
|
|
4
4
|
|
|
@@ -19,17 +19,17 @@ const createShipmentSchema = z.object({
|
|
|
19
19
|
customs_info: customsInfoSchema,
|
|
20
20
|
options: shipmentOptionsSchema,
|
|
21
21
|
reference: z.string().trim().max(255).optional(),
|
|
22
|
+
idempotency_key: idempotencyKey.optional(),
|
|
22
23
|
}).strict();
|
|
23
24
|
|
|
24
25
|
const buyShippingLabelSchema = z.object({
|
|
25
26
|
shipment_id: easypostId,
|
|
26
27
|
rate_id: easypostId.optional(),
|
|
27
|
-
|
|
28
|
-
service: nonEmptyString.optional(),
|
|
28
|
+
rate_option: z.number().int().min(1).optional(),
|
|
29
29
|
insurance: currencyAmount.optional(),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
30
|
+
idempotency_key: idempotencyKey,
|
|
31
|
+
...confirmationFields,
|
|
32
|
+
}).strict();
|
|
33
33
|
|
|
34
34
|
const getShipmentSchema = z.object({
|
|
35
35
|
shipment_id: easypostId,
|
|
@@ -44,6 +44,7 @@ const estimateRatesSchema = createShipmentSchema;
|
|
|
44
44
|
|
|
45
45
|
const refundShipmentSchema = z.object({
|
|
46
46
|
shipment_id: easypostId,
|
|
47
|
+
...confirmationFields,
|
|
47
48
|
}).strict();
|
|
48
49
|
|
|
49
50
|
const cancelShipmentSchema = refundShipmentSchema;
|
|
@@ -51,6 +52,7 @@ const cancelShipmentSchema = refundShipmentSchema;
|
|
|
51
52
|
const insureShipmentSchema = z.object({
|
|
52
53
|
shipment_id: easypostId,
|
|
53
54
|
amount: currencyAmount,
|
|
55
|
+
...confirmationFields,
|
|
54
56
|
}).strict();
|
|
55
57
|
|
|
56
58
|
export { createShipmentSchema, buyShippingLabelSchema, getShipmentSchema, listShipmentsSchema, estimateRatesSchema, refundShipmentSchema, cancelShipmentSchema, insureShipmentSchema };
|
|
@@ -11,6 +11,8 @@ import { createInMemoryRateLimiter } from "../middleware/rateLimiter.js";
|
|
|
11
11
|
import { createServices } from "../services/index.js";
|
|
12
12
|
import { createToolRegistry } from "../tools/index.js";
|
|
13
13
|
import { redactForLogs } from "../utils/sanitize.js";
|
|
14
|
+
import { AuditLogger } from "../audit/AuditLogger.js";
|
|
15
|
+
import { ExecutionPipeline } from "../pipeline/ExecutionPipeline.js";
|
|
14
16
|
|
|
15
17
|
function readPackageVersion() {
|
|
16
18
|
const pkgPath = path.resolve(
|
|
@@ -35,6 +37,13 @@ export function createMcpServer() {
|
|
|
35
37
|
const config = getConfig();
|
|
36
38
|
const logger = getLoggerSync();
|
|
37
39
|
const services = createServices();
|
|
40
|
+
const auditLogger = new AuditLogger(logger);
|
|
41
|
+
const pipeline = new ExecutionPipeline({
|
|
42
|
+
resourceManager: services.resources,
|
|
43
|
+
elicitationService: services.elicitation,
|
|
44
|
+
workflowState: services.workflowState,
|
|
45
|
+
auditLogger,
|
|
46
|
+
});
|
|
38
47
|
const registry = createToolRegistry(services);
|
|
39
48
|
const limiter = createInMemoryRateLimiter({
|
|
40
49
|
limit: config.rateLimit.perMinute,
|
|
@@ -53,12 +62,23 @@ export function createMcpServer() {
|
|
|
53
62
|
}
|
|
54
63
|
);
|
|
55
64
|
|
|
65
|
+
services.resources.initialize({ logger: logger.child({ operation: "resources.initialize" }) })
|
|
66
|
+
.then(() => {
|
|
67
|
+
logger.info(services.resources.getOperationalContext(), "Resource grounding initialized");
|
|
68
|
+
})
|
|
69
|
+
.catch((error) => {
|
|
70
|
+
logger.warn(
|
|
71
|
+
{ err: { name: error.name, code: error.code, message: error.message } },
|
|
72
|
+
"Resource grounding initialization failed; retrying lazily on first tool call"
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
56
76
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
57
77
|
logger.debug({ toolCount: registry.list().length }, "Listing MCP tools");
|
|
58
78
|
return { tools: registry.list() };
|
|
59
79
|
});
|
|
60
80
|
|
|
61
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
81
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
62
82
|
const correlationId = createCorrelationId();
|
|
63
83
|
const toolName = request.params.name;
|
|
64
84
|
const childLogger = logger.child({ correlationId, toolName });
|
|
@@ -75,6 +95,10 @@ export function createMcpServer() {
|
|
|
75
95
|
const result = await tool.execute(request.params.arguments, {
|
|
76
96
|
correlationId,
|
|
77
97
|
logger: childLogger,
|
|
98
|
+
pipeline,
|
|
99
|
+
resources: services.resources,
|
|
100
|
+
server,
|
|
101
|
+
requestExtra: extra,
|
|
78
102
|
});
|
|
79
103
|
|
|
80
104
|
childLogger.info({ result: redactForLogs(result) }, "MCP tool completed");
|