@curless/mcp-server 0.1.46 → 0.1.49
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/dist/index.js +646 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2348,6 +2348,651 @@ const TOOLS = [
|
|
|
2348
2348
|
}
|
|
2349
2349
|
},
|
|
2350
2350
|
},
|
|
2351
|
+
// ─── CURLESS PUTIEN (putien.curless.ai) ───────────────────────────────
|
|
2352
|
+
{
|
|
2353
|
+
name: 'find_putien_outlets',
|
|
2354
|
+
description: '[curless-mcp-server v0.1.46] List Curless Putien (putien.curless.ai) restaurant outlets for a reservation. Optional city filter (Singapore/Hong Kong/Kuala Lumpur/Jakarta/Manila).',
|
|
2355
|
+
inputSchema: {
|
|
2356
|
+
type: 'object',
|
|
2357
|
+
properties: {
|
|
2358
|
+
city: { type: 'string', description: 'Optional city filter (Singapore, Hong Kong, Kuala Lumpur, Jakarta, Manila).' },
|
|
2359
|
+
},
|
|
2360
|
+
},
|
|
2361
|
+
handler: async (args) => {
|
|
2362
|
+
try {
|
|
2363
|
+
const city = typeof args.city === 'string' ? args.city : undefined;
|
|
2364
|
+
const data = unwrap(await client.get('/api/putien/outlets', { city }));
|
|
2365
|
+
return { outlets: data?.outlets ?? [] };
|
|
2366
|
+
}
|
|
2367
|
+
catch (e) {
|
|
2368
|
+
return toErrorResult(e);
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
},
|
|
2372
|
+
{
|
|
2373
|
+
name: 'reserve_table',
|
|
2374
|
+
description: '[curless-mcp-server v0.1.46] Reserve a Curless Putien table with the user\'s VCC. Required outlet_id + reservation_date + reservation_time + party_size + customer_name; optional room_type (standard|private_room|banquet). Charges a per-seat deposit. Approval-gated ≥ $200.',
|
|
2375
|
+
inputSchema: {
|
|
2376
|
+
type: 'object',
|
|
2377
|
+
required: ['reservation_date', 'reservation_time', 'party_size', 'customer_name'],
|
|
2378
|
+
properties: {
|
|
2379
|
+
outlet_id: { type: 'string', description: 'Outlet id from find_putien_outlets.' },
|
|
2380
|
+
reservation_date: { type: 'string', description: 'Date for the reservation (e.g. "2026-06-15").' },
|
|
2381
|
+
reservation_time: { type: 'string', description: 'Time for the reservation (e.g. "19:00").' },
|
|
2382
|
+
party_size: { type: 'integer', minimum: 1, description: 'Number of guests.' },
|
|
2383
|
+
room_type: { type: 'string', enum: ['standard', 'private_room', 'banquet'], description: 'Optional room type. Defaults to standard.' },
|
|
2384
|
+
customer_name: { type: 'string', description: 'Guest full name.' },
|
|
2385
|
+
vcc_id: { type: 'string', description: 'Optional. Defaults to session\'s last-created VCC, then most recent install-stamped active VCC with putien-compatible merchant_lock.' },
|
|
2386
|
+
approval_id: { type: 'string', description: 'When retrying a previously-gated order (≥ $200), pass the approval_id from the first call. Other args ignored on replay.' },
|
|
2387
|
+
},
|
|
2388
|
+
},
|
|
2389
|
+
handler: async (args) => {
|
|
2390
|
+
// ── Replay branch ──────────────────────────────────────────
|
|
2391
|
+
const approvalId = typeof args.approval_id === 'string' ? args.approval_id.trim() : '';
|
|
2392
|
+
if (approvalId) {
|
|
2393
|
+
try {
|
|
2394
|
+
const result = unwrap(await client.post('/api/putien/checkout', { approval_id: approvalId }));
|
|
2395
|
+
if (result?.status === 'pending_approval') {
|
|
2396
|
+
return {
|
|
2397
|
+
status: 'pending_approval',
|
|
2398
|
+
approval_id: result.approval_id,
|
|
2399
|
+
approval_url: result.approval_url,
|
|
2400
|
+
amount_usd: result.amount_usd,
|
|
2401
|
+
expires_at: result.expires_at,
|
|
2402
|
+
hint: result.hint ?? 'Approval still pending. Open the URL and decide before retrying.',
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
return stripIframe({
|
|
2406
|
+
order_id: result.order_id ?? result.id,
|
|
2407
|
+
confirmation_code: result.confirmation_code,
|
|
2408
|
+
outlet: result.outlet,
|
|
2409
|
+
reservation_date: result.reservation_date,
|
|
2410
|
+
reservation_time: result.reservation_time,
|
|
2411
|
+
party_size: result.party_size,
|
|
2412
|
+
room_type: result.room_type,
|
|
2413
|
+
total_amount_usd: result.total_amount_usd,
|
|
2414
|
+
status: result.status,
|
|
2415
|
+
payment_id: result.payment_id,
|
|
2416
|
+
approval_id: approvalId,
|
|
2417
|
+
view_url: result.view_url,
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
catch (e) {
|
|
2421
|
+
return toErrorResult(e);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
// ── Fresh order branch ──────────────────────────────────────
|
|
2425
|
+
const outletId = typeof args.outlet_id === 'string' ? args.outlet_id.trim() : undefined;
|
|
2426
|
+
const reservationDate = String(args.reservation_date || '').trim();
|
|
2427
|
+
const reservationTime = String(args.reservation_time || '').trim();
|
|
2428
|
+
const partySize = Number(args.party_size);
|
|
2429
|
+
const customerName = String(args.customer_name || '').trim();
|
|
2430
|
+
const roomType = typeof args.room_type === 'string' ? args.room_type.trim() : undefined;
|
|
2431
|
+
if (!reservationDate || !reservationTime || !partySize || !customerName) {
|
|
2432
|
+
return { error: 'missing_fields', hint: 'reservation_date, reservation_time, party_size, and customer_name are required.' };
|
|
2433
|
+
}
|
|
2434
|
+
const vccId = (typeof args.vcc_id === 'string' && args.vcc_id) ||
|
|
2435
|
+
(await resolveDefaultVcc({ merchantLock: 'putien' }));
|
|
2436
|
+
if (!vccId) {
|
|
2437
|
+
return {
|
|
2438
|
+
error: 'no_vcc',
|
|
2439
|
+
hint: 'No suitable install-stamped VCC found with merchant_lock allowing putien. Run create_vcc(merchant_lock="any") + top_up_vcc, then retry.',
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
try {
|
|
2443
|
+
const result = unwrap(await client.post('/api/putien/checkout', {
|
|
2444
|
+
outlet_id: outletId,
|
|
2445
|
+
reservation_date: reservationDate,
|
|
2446
|
+
reservation_time: reservationTime,
|
|
2447
|
+
party_size: partySize,
|
|
2448
|
+
room_type: roomType,
|
|
2449
|
+
customer_name: customerName,
|
|
2450
|
+
vcc_id: vccId,
|
|
2451
|
+
agent_id: stampAgent('default'),
|
|
2452
|
+
caller_agent_id: callerAgentId(),
|
|
2453
|
+
}));
|
|
2454
|
+
if (result?.status === 'pending_approval') {
|
|
2455
|
+
return {
|
|
2456
|
+
status: 'pending_approval',
|
|
2457
|
+
approval_id: result.approval_id,
|
|
2458
|
+
approval_url: result.approval_url,
|
|
2459
|
+
amount_usd: result.amount_usd,
|
|
2460
|
+
threshold_usd: result.threshold_usd,
|
|
2461
|
+
expires_at: result.expires_at,
|
|
2462
|
+
hint: result.hint ?? `Order requires human approval. Open ${result.approval_url}, decide, then re-run reserve_table with approval_id="${result.approval_id}" (other args ignored on replay).`,
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
return stripIframe({
|
|
2466
|
+
order_id: result.order_id ?? result.id,
|
|
2467
|
+
confirmation_code: result.confirmation_code,
|
|
2468
|
+
outlet: result.outlet,
|
|
2469
|
+
reservation_date: result.reservation_date,
|
|
2470
|
+
reservation_time: result.reservation_time,
|
|
2471
|
+
party_size: result.party_size,
|
|
2472
|
+
room_type: result.room_type,
|
|
2473
|
+
total_amount_usd: result.total_amount_usd,
|
|
2474
|
+
status: result.status,
|
|
2475
|
+
payment_id: result.payment_id,
|
|
2476
|
+
vcc_id: vccId,
|
|
2477
|
+
view_url: result.view_url,
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
catch (e) {
|
|
2481
|
+
return toErrorResult(e);
|
|
2482
|
+
}
|
|
2483
|
+
},
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
name: 'putien_menu',
|
|
2487
|
+
description: '[curless-mcp-server v0.1.46] Browse the Curless Putien menu (signatures/set_menus/festive/dtc/membership). Pass category for one section or omit for all.',
|
|
2488
|
+
inputSchema: {
|
|
2489
|
+
type: 'object',
|
|
2490
|
+
properties: {
|
|
2491
|
+
category: { type: 'string', description: 'signatures | set_menus | festive | dtc | membership. Omit for the full menu.' },
|
|
2492
|
+
},
|
|
2493
|
+
},
|
|
2494
|
+
handler: async (args) => {
|
|
2495
|
+
try {
|
|
2496
|
+
const category = typeof args.category === 'string' ? args.category.trim() : '';
|
|
2497
|
+
if (!category) {
|
|
2498
|
+
const data = unwrap(await client.get('/api/putien/menu'));
|
|
2499
|
+
return { service: data?.service, sections: data?.sections ?? [] };
|
|
2500
|
+
}
|
|
2501
|
+
try {
|
|
2502
|
+
const data = unwrap(await client.get(`/api/putien/menu/${encodeURIComponent(category)}`));
|
|
2503
|
+
return { category: data?.section?.category, section: data?.section };
|
|
2504
|
+
}
|
|
2505
|
+
catch (e) {
|
|
2506
|
+
if (e?.status === 404 || /unknown_category/.test(String(e?.message ?? ''))) {
|
|
2507
|
+
return {
|
|
2508
|
+
error: 'unknown_category',
|
|
2509
|
+
valid_categories: ['signatures', 'set_menus', 'festive', 'dtc', 'membership'],
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
throw e;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
catch (e) {
|
|
2516
|
+
return toErrorResult(e);
|
|
2517
|
+
}
|
|
2518
|
+
},
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
name: 'order_putien',
|
|
2522
|
+
description: '[curless-mcp-server v0.1.46] Pay for a Curless Putien menu item with the user\'s VCC. Required item_id + customer_name. Approval-gated ≥ $200.',
|
|
2523
|
+
inputSchema: {
|
|
2524
|
+
type: 'object',
|
|
2525
|
+
required: ['item_id', 'customer_name'],
|
|
2526
|
+
properties: {
|
|
2527
|
+
item_id: { type: 'string', description: 'Menu item id from putien_menu.' },
|
|
2528
|
+
customer_name: { type: 'string', description: 'Customer full name.' },
|
|
2529
|
+
quantity: { type: 'integer', minimum: 1, maximum: 20, default: 1 },
|
|
2530
|
+
vcc_id: { type: 'string', description: 'Optional. Defaults to session\'s last-created VCC, then most recent install-stamped active VCC with putien-compatible merchant_lock.' },
|
|
2531
|
+
approval_id: { type: 'string', description: 'When retrying a previously-gated order (≥ $200), pass the approval_id from the first call. Other args ignored on replay.' },
|
|
2532
|
+
},
|
|
2533
|
+
},
|
|
2534
|
+
handler: async (args) => {
|
|
2535
|
+
// ── Replay branch ──────────────────────────────────────────
|
|
2536
|
+
const approvalId = typeof args.approval_id === 'string' ? args.approval_id.trim() : '';
|
|
2537
|
+
if (approvalId) {
|
|
2538
|
+
try {
|
|
2539
|
+
const result = unwrap(await client.post('/api/putien/checkout', { approval_id: approvalId }));
|
|
2540
|
+
if (result?.status === 'pending_approval') {
|
|
2541
|
+
return {
|
|
2542
|
+
status: 'pending_approval',
|
|
2543
|
+
approval_id: result.approval_id,
|
|
2544
|
+
approval_url: result.approval_url,
|
|
2545
|
+
amount_usd: result.amount_usd,
|
|
2546
|
+
expires_at: result.expires_at,
|
|
2547
|
+
hint: result.hint ?? 'Approval still pending. Open the URL and decide before retrying.',
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
return stripIframe({
|
|
2551
|
+
order_id: result.order_id ?? result.id,
|
|
2552
|
+
confirmation_code: result.confirmation_code,
|
|
2553
|
+
item_name: result.item_name,
|
|
2554
|
+
total_amount_usd: result.total_amount_usd,
|
|
2555
|
+
status: result.status,
|
|
2556
|
+
payment_id: result.payment_id,
|
|
2557
|
+
approval_id: approvalId,
|
|
2558
|
+
view_url: result.view_url,
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
catch (e) {
|
|
2562
|
+
return toErrorResult(e);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
// ── Fresh order branch ──────────────────────────────────────
|
|
2566
|
+
const itemId = String(args.item_id || '').trim();
|
|
2567
|
+
const customerName = String(args.customer_name || '').trim();
|
|
2568
|
+
if (!itemId || !customerName) {
|
|
2569
|
+
return { error: 'missing_fields', hint: 'item_id and customer_name are required.' };
|
|
2570
|
+
}
|
|
2571
|
+
const vccId = (typeof args.vcc_id === 'string' && args.vcc_id) ||
|
|
2572
|
+
(await resolveDefaultVcc({ merchantLock: 'putien' }));
|
|
2573
|
+
if (!vccId) {
|
|
2574
|
+
return {
|
|
2575
|
+
error: 'no_vcc',
|
|
2576
|
+
hint: 'No suitable install-stamped VCC found with merchant_lock allowing putien. Run create_vcc(merchant_lock="any") + top_up_vcc, then retry.',
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
try {
|
|
2580
|
+
const result = unwrap(await client.post('/api/putien/checkout', {
|
|
2581
|
+
item_id: itemId,
|
|
2582
|
+
quantity: Number(args.quantity ?? 1),
|
|
2583
|
+
customer_name: customerName,
|
|
2584
|
+
vcc_id: vccId,
|
|
2585
|
+
agent_id: stampAgent('default'),
|
|
2586
|
+
caller_agent_id: callerAgentId(),
|
|
2587
|
+
}));
|
|
2588
|
+
if (result?.status === 'pending_approval') {
|
|
2589
|
+
return {
|
|
2590
|
+
status: 'pending_approval',
|
|
2591
|
+
approval_id: result.approval_id,
|
|
2592
|
+
approval_url: result.approval_url,
|
|
2593
|
+
amount_usd: result.amount_usd,
|
|
2594
|
+
threshold_usd: result.threshold_usd,
|
|
2595
|
+
expires_at: result.expires_at,
|
|
2596
|
+
hint: result.hint ?? `Order requires human approval. Open ${result.approval_url}, decide, then re-run order_putien with approval_id="${result.approval_id}" (other args ignored on replay).`,
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
return stripIframe({
|
|
2600
|
+
order_id: result.order_id ?? result.id,
|
|
2601
|
+
confirmation_code: result.confirmation_code,
|
|
2602
|
+
item_id: itemId,
|
|
2603
|
+
item_name: result.item_name,
|
|
2604
|
+
quantity: result.quantity,
|
|
2605
|
+
unit_price_usd: result.unit_price_usd,
|
|
2606
|
+
total_amount_usd: result.total_amount_usd,
|
|
2607
|
+
currency: 'USD',
|
|
2608
|
+
status: result.status,
|
|
2609
|
+
payment_id: result.payment_id,
|
|
2610
|
+
vcc_id: vccId,
|
|
2611
|
+
view_url: result.view_url,
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
catch (e) {
|
|
2615
|
+
return toErrorResult(e);
|
|
2616
|
+
}
|
|
2617
|
+
},
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
name: 'list_my_putien_orders',
|
|
2621
|
+
description: '[curless-mcp-server v0.1.46] List recent Curless Putien reservations + menu orders for the active agent.',
|
|
2622
|
+
inputSchema: {
|
|
2623
|
+
type: 'object',
|
|
2624
|
+
properties: { limit: { type: 'integer', minimum: 1, maximum: 50, default: 20 } },
|
|
2625
|
+
},
|
|
2626
|
+
handler: async (args) => {
|
|
2627
|
+
try {
|
|
2628
|
+
const result = unwrap(await client.get('/api/putien/orders', {
|
|
2629
|
+
agent_id: callerAgentId(),
|
|
2630
|
+
limit: Number(args.limit ?? 20),
|
|
2631
|
+
}));
|
|
2632
|
+
const orders = result?.orders ?? [];
|
|
2633
|
+
return {
|
|
2634
|
+
count: orders.length,
|
|
2635
|
+
orders: orders.map((o, idx) => ({
|
|
2636
|
+
index: idx + 1,
|
|
2637
|
+
order_id: o.id,
|
|
2638
|
+
confirmation_code: o.confirmation_code,
|
|
2639
|
+
kind: o.kind,
|
|
2640
|
+
item_name: o.item_name,
|
|
2641
|
+
outlet: o.outlet,
|
|
2642
|
+
reservation_date: o.reservation_date,
|
|
2643
|
+
reservation_time: o.reservation_time,
|
|
2644
|
+
party_size: o.party_size,
|
|
2645
|
+
room_type: o.room_type,
|
|
2646
|
+
category: o.category,
|
|
2647
|
+
quantity: o.quantity,
|
|
2648
|
+
total_amount_usd: o.total_amount_usd,
|
|
2649
|
+
customer_name: o.customer_name,
|
|
2650
|
+
status: o.status,
|
|
2651
|
+
view_url: o.kind === 'reservation'
|
|
2652
|
+
? `https://putien.curless.ai/outlet/${o.item_id}`
|
|
2653
|
+
: `https://putien.curless.ai/menu/${o.category}`,
|
|
2654
|
+
})),
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
catch (e) {
|
|
2658
|
+
return toErrorResult(e);
|
|
2659
|
+
}
|
|
2660
|
+
},
|
|
2661
|
+
},
|
|
2662
|
+
// ─── CURLESS A1 (a1.curless.ai) ──────────────────────────────────────
|
|
2663
|
+
{
|
|
2664
|
+
name: 'a1_telco_catalog',
|
|
2665
|
+
description: '[curless-mcp-server v0.1.47] Browse the Curless A1 (a1.curless.ai) A1 Telekom telco catalog (eSIM / mobile plans / roaming / add-ons). Pass category for one section or omit for all.',
|
|
2666
|
+
inputSchema: {
|
|
2667
|
+
type: 'object',
|
|
2668
|
+
properties: {
|
|
2669
|
+
category: { type: 'string', description: 'esim | mobile_plans | roaming | addons. Omit for the full catalog.' },
|
|
2670
|
+
},
|
|
2671
|
+
},
|
|
2672
|
+
handler: async (args) => {
|
|
2673
|
+
try {
|
|
2674
|
+
const category = typeof args.category === 'string' ? args.category.trim() : '';
|
|
2675
|
+
if (!category) {
|
|
2676
|
+
const data = unwrap(await client.get('/api/a1/catalog'));
|
|
2677
|
+
return { service: data?.service, sections: data?.sections ?? [] };
|
|
2678
|
+
}
|
|
2679
|
+
try {
|
|
2680
|
+
const data = unwrap(await client.get(`/api/a1/catalog/${encodeURIComponent(category)}`));
|
|
2681
|
+
return { category: data?.section?.category, section: data?.section };
|
|
2682
|
+
}
|
|
2683
|
+
catch (e) {
|
|
2684
|
+
if (e?.status === 404 || /unknown_category/.test(String(e?.message ?? ''))) {
|
|
2685
|
+
return {
|
|
2686
|
+
error: 'unknown_category',
|
|
2687
|
+
valid_categories: ['esim', 'mobile_plans', 'roaming', 'addons'],
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
throw e;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
catch (e) {
|
|
2694
|
+
return toErrorResult(e);
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
2697
|
+
},
|
|
2698
|
+
{
|
|
2699
|
+
name: 'pay_a1_service',
|
|
2700
|
+
description: '[curless-mcp-server v0.1.47] Buy a Curless A1 telco item (eSIM / mobile plan / roaming / top-up) with the user\'s VCC. Required item_id + customer_name. Approval-gated ≥ $50.',
|
|
2701
|
+
inputSchema: {
|
|
2702
|
+
type: 'object',
|
|
2703
|
+
required: ['item_id', 'customer_name'],
|
|
2704
|
+
properties: {
|
|
2705
|
+
item_id: { type: 'string', description: 'Catalog item id / SKU from a1_telco_catalog.' },
|
|
2706
|
+
customer_name: { type: 'string', description: 'Customer full name.' },
|
|
2707
|
+
quantity: { type: 'integer', minimum: 1, maximum: 20, default: 1 },
|
|
2708
|
+
vcc_id: { type: 'string', description: 'Optional. Defaults to session\'s last-created VCC, then most recent install-stamped active VCC with a1-compatible merchant_lock.' },
|
|
2709
|
+
approval_id: { type: 'string', description: 'When retrying a previously-gated order (≥ $50), pass the approval_id from the first call. Other args ignored on replay.' },
|
|
2710
|
+
},
|
|
2711
|
+
},
|
|
2712
|
+
handler: async (args) => {
|
|
2713
|
+
// ── Replay branch ──────────────────────────────────────────
|
|
2714
|
+
const approvalId = typeof args.approval_id === 'string' ? args.approval_id.trim() : '';
|
|
2715
|
+
if (approvalId) {
|
|
2716
|
+
try {
|
|
2717
|
+
const result = unwrap(await client.post('/api/a1/checkout', { approval_id: approvalId }));
|
|
2718
|
+
if (result?.status === 'pending_approval') {
|
|
2719
|
+
return {
|
|
2720
|
+
status: 'pending_approval',
|
|
2721
|
+
approval_id: result.approval_id,
|
|
2722
|
+
approval_url: result.approval_url,
|
|
2723
|
+
amount_usd: result.amount_usd,
|
|
2724
|
+
expires_at: result.expires_at,
|
|
2725
|
+
hint: result.hint ?? 'Approval still pending. Open the URL and decide before retrying.',
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
return stripIframe({
|
|
2729
|
+
order_id: result.order_id ?? result.id,
|
|
2730
|
+
confirmation_code: result.confirmation_code,
|
|
2731
|
+
item_name: result.item_name,
|
|
2732
|
+
category: result.category,
|
|
2733
|
+
total_amount_usd: result.total_amount_usd,
|
|
2734
|
+
status: result.status,
|
|
2735
|
+
payment_id: result.payment_id,
|
|
2736
|
+
approval_id: approvalId,
|
|
2737
|
+
view_url: result.view_url,
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
catch (e) {
|
|
2741
|
+
return toErrorResult(e);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
// ── Fresh order branch ──────────────────────────────────────
|
|
2745
|
+
const itemId = String(args.item_id || '').trim();
|
|
2746
|
+
const customerName = String(args.customer_name || '').trim();
|
|
2747
|
+
if (!itemId || !customerName) {
|
|
2748
|
+
return { error: 'missing_fields', hint: 'item_id and customer_name are required.' };
|
|
2749
|
+
}
|
|
2750
|
+
const vccId = (typeof args.vcc_id === 'string' && args.vcc_id) ||
|
|
2751
|
+
(await resolveDefaultVcc({ merchantLock: 'a1' }));
|
|
2752
|
+
if (!vccId) {
|
|
2753
|
+
return {
|
|
2754
|
+
error: 'no_vcc',
|
|
2755
|
+
hint: 'No suitable install-stamped VCC found with merchant_lock allowing a1. Run create_vcc(merchant_lock="any") + top_up_vcc, then retry.',
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
try {
|
|
2759
|
+
const result = unwrap(await client.post('/api/a1/checkout', {
|
|
2760
|
+
item_id: itemId,
|
|
2761
|
+
quantity: Number(args.quantity ?? 1),
|
|
2762
|
+
customer_name: customerName,
|
|
2763
|
+
vcc_id: vccId,
|
|
2764
|
+
agent_id: stampAgent('default'),
|
|
2765
|
+
caller_agent_id: callerAgentId(),
|
|
2766
|
+
}));
|
|
2767
|
+
if (result?.status === 'pending_approval') {
|
|
2768
|
+
return {
|
|
2769
|
+
status: 'pending_approval',
|
|
2770
|
+
approval_id: result.approval_id,
|
|
2771
|
+
approval_url: result.approval_url,
|
|
2772
|
+
amount_usd: result.amount_usd,
|
|
2773
|
+
threshold_usd: result.threshold_usd,
|
|
2774
|
+
expires_at: result.expires_at,
|
|
2775
|
+
hint: result.hint ?? `Order requires human approval. Open ${result.approval_url}, decide, then re-run pay_a1_service with approval_id="${result.approval_id}" (other args ignored on replay).`,
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
return stripIframe({
|
|
2779
|
+
order_id: result.order_id ?? result.id,
|
|
2780
|
+
confirmation_code: result.confirmation_code,
|
|
2781
|
+
item_name: result.item_name,
|
|
2782
|
+
category: result.category,
|
|
2783
|
+
total_amount_usd: result.total_amount_usd,
|
|
2784
|
+
status: result.status,
|
|
2785
|
+
payment_id: result.payment_id,
|
|
2786
|
+
vcc_id: vccId,
|
|
2787
|
+
view_url: result.view_url,
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
catch (e) {
|
|
2791
|
+
return toErrorResult(e);
|
|
2792
|
+
}
|
|
2793
|
+
},
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
name: 'list_my_a1_orders',
|
|
2797
|
+
description: '[curless-mcp-server v0.1.47] List recent Curless A1 telco purchases for the active agent.',
|
|
2798
|
+
inputSchema: {
|
|
2799
|
+
type: 'object',
|
|
2800
|
+
properties: { limit: { type: 'integer', minimum: 1, maximum: 50, default: 20 } },
|
|
2801
|
+
},
|
|
2802
|
+
handler: async (args) => {
|
|
2803
|
+
try {
|
|
2804
|
+
const result = unwrap(await client.get('/api/a1/orders', {
|
|
2805
|
+
agent_id: callerAgentId(),
|
|
2806
|
+
limit: Number(args.limit ?? 20),
|
|
2807
|
+
}));
|
|
2808
|
+
const orders = result?.orders ?? [];
|
|
2809
|
+
return {
|
|
2810
|
+
count: orders.length,
|
|
2811
|
+
orders: orders.map((o) => ({
|
|
2812
|
+
order_id: o.id,
|
|
2813
|
+
confirmation_code: o.confirmation_code,
|
|
2814
|
+
item_name: o.item_name,
|
|
2815
|
+
category: o.category,
|
|
2816
|
+
kind: o.kind,
|
|
2817
|
+
quantity: o.quantity,
|
|
2818
|
+
total_amount_usd: o.total_amount_usd,
|
|
2819
|
+
customer_name: o.customer_name,
|
|
2820
|
+
status: o.status,
|
|
2821
|
+
})),
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
catch (e) {
|
|
2825
|
+
return toErrorResult(e);
|
|
2826
|
+
}
|
|
2827
|
+
},
|
|
2828
|
+
},
|
|
2829
|
+
// ─── CURLESS AUSTRIA (austriatravel.curless.ai) ───────────────────────
|
|
2830
|
+
{
|
|
2831
|
+
name: 'austria_travel_catalog',
|
|
2832
|
+
description: '[curless-mcp-server v0.1.48] Browse the Curless Austria (austriatravel.curless.ai) Austria-tourism catalog (experiences / city cards / ski-alpine / tours / green stays). Pass category for one section or omit for all.',
|
|
2833
|
+
inputSchema: {
|
|
2834
|
+
type: 'object',
|
|
2835
|
+
properties: {
|
|
2836
|
+
category: { type: 'string', description: 'experiences | city_cards | ski_alpine | tours | green_stays. Omit for the full catalog.' },
|
|
2837
|
+
},
|
|
2838
|
+
},
|
|
2839
|
+
handler: async (args) => {
|
|
2840
|
+
try {
|
|
2841
|
+
const category = typeof args.category === 'string' ? args.category.trim() : '';
|
|
2842
|
+
if (!category) {
|
|
2843
|
+
const data = unwrap(await client.get('/api/austria/catalog'));
|
|
2844
|
+
return { service: data?.service, sections: data?.sections ?? [] };
|
|
2845
|
+
}
|
|
2846
|
+
try {
|
|
2847
|
+
const data = unwrap(await client.get(`/api/austria/catalog/${encodeURIComponent(category)}`));
|
|
2848
|
+
return { category: data?.section?.category, section: data?.section };
|
|
2849
|
+
}
|
|
2850
|
+
catch (e) {
|
|
2851
|
+
if (e?.status === 404 || /unknown_category/.test(String(e?.message ?? ''))) {
|
|
2852
|
+
return {
|
|
2853
|
+
error: 'unknown_category',
|
|
2854
|
+
valid_categories: ['experiences', 'city_cards', 'ski_alpine', 'tours', 'green_stays'],
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
throw e;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
catch (e) {
|
|
2861
|
+
return toErrorResult(e);
|
|
2862
|
+
}
|
|
2863
|
+
},
|
|
2864
|
+
},
|
|
2865
|
+
{
|
|
2866
|
+
name: 'pay_austria_service',
|
|
2867
|
+
description: '[curless-mcp-server v0.1.48] Buy a Curless Austria tourism item (experience / city card / ski pass / tour / green stay) with the user\'s VCC. Required item_id + customer_name. Approval-gated ≥ $200.',
|
|
2868
|
+
inputSchema: {
|
|
2869
|
+
type: 'object',
|
|
2870
|
+
required: ['item_id', 'customer_name'],
|
|
2871
|
+
properties: {
|
|
2872
|
+
item_id: { type: 'string', description: 'Catalog item id / SKU from austria_travel_catalog.' },
|
|
2873
|
+
customer_name: { type: 'string', description: 'Customer full name.' },
|
|
2874
|
+
quantity: { type: 'integer', minimum: 1, maximum: 20, default: 1 },
|
|
2875
|
+
vcc_id: { type: 'string', description: 'Optional. Defaults to session\'s last-created VCC, then most recent install-stamped active VCC with austria-compatible merchant_lock.' },
|
|
2876
|
+
approval_id: { type: 'string', description: 'When retrying a previously-gated order (≥ $200), pass the approval_id from the first call. Other args ignored on replay.' },
|
|
2877
|
+
},
|
|
2878
|
+
},
|
|
2879
|
+
handler: async (args) => {
|
|
2880
|
+
// ── Replay branch ──────────────────────────────────────────
|
|
2881
|
+
const approvalId = typeof args.approval_id === 'string' ? args.approval_id.trim() : '';
|
|
2882
|
+
if (approvalId) {
|
|
2883
|
+
try {
|
|
2884
|
+
const result = unwrap(await client.post('/api/austria/checkout', { approval_id: approvalId }));
|
|
2885
|
+
if (result?.status === 'pending_approval') {
|
|
2886
|
+
return {
|
|
2887
|
+
status: 'pending_approval',
|
|
2888
|
+
approval_id: result.approval_id,
|
|
2889
|
+
approval_url: result.approval_url,
|
|
2890
|
+
amount_usd: result.amount_usd,
|
|
2891
|
+
expires_at: result.expires_at,
|
|
2892
|
+
hint: result.hint ?? 'Approval still pending. Open the URL and decide before retrying.',
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
return stripIframe({
|
|
2896
|
+
order_id: result.order_id ?? result.id,
|
|
2897
|
+
confirmation_code: result.confirmation_code,
|
|
2898
|
+
item_name: result.item_name,
|
|
2899
|
+
category: result.category,
|
|
2900
|
+
total_amount_usd: result.total_amount_usd,
|
|
2901
|
+
status: result.status,
|
|
2902
|
+
payment_id: result.payment_id,
|
|
2903
|
+
approval_id: approvalId,
|
|
2904
|
+
view_url: result.view_url,
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
catch (e) {
|
|
2908
|
+
return toErrorResult(e);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
// ── Fresh order branch ──────────────────────────────────────
|
|
2912
|
+
const itemId = String(args.item_id || '').trim();
|
|
2913
|
+
const customerName = String(args.customer_name || '').trim();
|
|
2914
|
+
if (!itemId || !customerName) {
|
|
2915
|
+
return { error: 'missing_fields', hint: 'item_id and customer_name are required.' };
|
|
2916
|
+
}
|
|
2917
|
+
const vccId = (typeof args.vcc_id === 'string' && args.vcc_id) ||
|
|
2918
|
+
(await resolveDefaultVcc({ merchantLock: 'austria' }));
|
|
2919
|
+
if (!vccId) {
|
|
2920
|
+
return {
|
|
2921
|
+
error: 'no_vcc',
|
|
2922
|
+
hint: 'No suitable install-stamped VCC found with merchant_lock allowing austria. Run create_vcc(merchant_lock="any") + top_up_vcc, then retry.',
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
try {
|
|
2926
|
+
const result = unwrap(await client.post('/api/austria/checkout', {
|
|
2927
|
+
item_id: itemId,
|
|
2928
|
+
quantity: Number(args.quantity ?? 1),
|
|
2929
|
+
customer_name: customerName,
|
|
2930
|
+
vcc_id: vccId,
|
|
2931
|
+
agent_id: stampAgent('default'),
|
|
2932
|
+
caller_agent_id: callerAgentId(),
|
|
2933
|
+
}));
|
|
2934
|
+
if (result?.status === 'pending_approval') {
|
|
2935
|
+
return {
|
|
2936
|
+
status: 'pending_approval',
|
|
2937
|
+
approval_id: result.approval_id,
|
|
2938
|
+
approval_url: result.approval_url,
|
|
2939
|
+
amount_usd: result.amount_usd,
|
|
2940
|
+
threshold_usd: result.threshold_usd,
|
|
2941
|
+
expires_at: result.expires_at,
|
|
2942
|
+
hint: result.hint ?? `Order requires human approval. Open ${result.approval_url}, decide, then re-run pay_austria_service with approval_id="${result.approval_id}" (other args ignored on replay).`,
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
return stripIframe({
|
|
2946
|
+
order_id: result.order_id ?? result.id,
|
|
2947
|
+
confirmation_code: result.confirmation_code,
|
|
2948
|
+
item_name: result.item_name,
|
|
2949
|
+
category: result.category,
|
|
2950
|
+
total_amount_usd: result.total_amount_usd,
|
|
2951
|
+
status: result.status,
|
|
2952
|
+
payment_id: result.payment_id,
|
|
2953
|
+
vcc_id: vccId,
|
|
2954
|
+
view_url: result.view_url,
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
catch (e) {
|
|
2958
|
+
return toErrorResult(e);
|
|
2959
|
+
}
|
|
2960
|
+
},
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
name: 'list_my_austria_orders',
|
|
2964
|
+
description: '[curless-mcp-server v0.1.48] List recent Curless Austria tourism purchases for the active agent.',
|
|
2965
|
+
inputSchema: {
|
|
2966
|
+
type: 'object',
|
|
2967
|
+
properties: { limit: { type: 'integer', minimum: 1, maximum: 50, default: 20 } },
|
|
2968
|
+
},
|
|
2969
|
+
handler: async (args) => {
|
|
2970
|
+
try {
|
|
2971
|
+
const result = unwrap(await client.get('/api/austria/orders', {
|
|
2972
|
+
agent_id: callerAgentId(),
|
|
2973
|
+
limit: Number(args.limit ?? 20),
|
|
2974
|
+
}));
|
|
2975
|
+
const orders = result?.orders ?? [];
|
|
2976
|
+
return {
|
|
2977
|
+
count: orders.length,
|
|
2978
|
+
orders: orders.map((o) => ({
|
|
2979
|
+
order_id: o.id,
|
|
2980
|
+
confirmation_code: o.confirmation_code,
|
|
2981
|
+
item_name: o.item_name,
|
|
2982
|
+
category: o.category,
|
|
2983
|
+
kind: o.kind,
|
|
2984
|
+
quantity: o.quantity,
|
|
2985
|
+
total_amount_usd: o.total_amount_usd,
|
|
2986
|
+
customer_name: o.customer_name,
|
|
2987
|
+
status: o.status,
|
|
2988
|
+
})),
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
catch (e) {
|
|
2992
|
+
return toErrorResult(e);
|
|
2993
|
+
}
|
|
2994
|
+
},
|
|
2995
|
+
},
|
|
2351
2996
|
// ─── OFFICE SUPPLIES (Procurement) ────────────────────────────────────
|
|
2352
2997
|
{
|
|
2353
2998
|
name: 'list_supplies',
|
|
@@ -3486,7 +4131,7 @@ NEVER fabricate a tx hash. NEVER mention 'Carousell Payment Router', 'escrow', '
|
|
|
3486
4131
|
// ─────────────────────────────────────────────────────────────────────────
|
|
3487
4132
|
// MCP Server wiring
|
|
3488
4133
|
// ─────────────────────────────────────────────────────────────────────────
|
|
3489
|
-
const server = new Server({ name: 'curless-mcp-server', version: '0.1.
|
|
4134
|
+
const server = new Server({ name: 'curless-mcp-server', version: '0.1.48' }, { capabilities: { tools: {} } });
|
|
3490
4135
|
// Auto-open of the storefront confirmation page was disabled by
|
|
3491
4136
|
// product decision (2026-05-13). Users found the surprise browser
|
|
3492
4137
|
// jump after every successful order_coffee / book_room / order_supplies
|