askell-mcp 0.4.5 → 0.4.7
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 +2 -1
- package/package.json +2 -2
- package/spec/openapi-v2.json +511 -0
- package/src/client/paths.ts +24 -1
- package/src/openapi/registry.ts +30 -14
- package/src/resources/register.ts +10 -3
- package/src/server.ts +10 -2
- package/src/tools/analysis.ts +1 -1
- package/src/tools/discovery.ts +1 -1
package/README.md
CHANGED
|
@@ -127,7 +127,8 @@ Typical agent workflow:
|
|
|
127
127
|
|
|
128
128
|
- **v1** — legacy paths like `/customers/`, `/subscriptions/` (no `/v2` prefix)
|
|
129
129
|
- **v2** — current model: catalogs, quotes, checkouts, contracts, billing runs under `/v2/`
|
|
130
|
-
- **v2 discounts** — coupons: `GET/POST /v2/subscription-contracts/{id}/discount|apply-code|remove-discount` (one active). Quotes take `promotion_code` and, for an existing buyer, `customer` (id) so combo discounts + promo restrictions apply.
|
|
130
|
+
- **v2 discounts** — coupons: `GET/POST /v2/subscription-contracts/{id}/discount|apply-code|remove-discount` (one active). Quotes take `promotion_code` and, for an existing buyer, `customer` (id) so combo discounts + promo restrictions apply. First-period totals already include coupon + combo; `quote.recurring_*` include combo but not the coupon (`discount.recurring_final_amount` while the coupon is active). Recurring `finalize` needs a verified payment method even when due-now is 0. Not the v1 `discount` 0–100 field.
|
|
131
|
+
- **v2 fulfilment** — British spelling: `GET /v2/fulfilment-orders/` (read-only). Path param is order `id`, not `number`. List `403` if shipping is off. Quote/checkout `shipping_fee` is already in totals.
|
|
131
132
|
- Paths use **trailing slashes**
|
|
132
133
|
- Prefer **v2** for new integrations; v1 remains for existing ones
|
|
133
134
|
- Docs: [docs.askell.is](https://docs.askell.is/) · OpenAPI: [v1](https://askell.is/api/swagger/swagger.json) · [v2](https://askell.is/api/swagger/v2/swagger.json)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "askell-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "MCP server for the Askell payment and subscription API (Bun + stdio)",
|
|
5
5
|
"author": "Neschadin Oleksandr",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@modelcontextprotocol/server": "2.0.0",
|
|
70
|
-
"zod": "4.6.
|
|
70
|
+
"zod": "4.6.5"
|
|
71
71
|
}
|
|
72
72
|
}
|
package/spec/openapi-v2.json
CHANGED
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
{
|
|
56
56
|
"name": "V2 Billing Runs",
|
|
57
57
|
"description": "Subscription V2 billing-run read and retry APIs, requires secret api key."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "V2 Fulfilment",
|
|
61
|
+
"description": "Subscription V2 fulfilment order read APIs for warehouse integrations, requires secret api key."
|
|
58
62
|
}
|
|
59
63
|
],
|
|
60
64
|
"paths": {
|
|
@@ -1672,6 +1676,119 @@
|
|
|
1672
1676
|
}
|
|
1673
1677
|
]
|
|
1674
1678
|
}
|
|
1679
|
+
},
|
|
1680
|
+
"/v2/fulfilment-orders/": {
|
|
1681
|
+
"get": {
|
|
1682
|
+
"tags": [
|
|
1683
|
+
"V2 Fulfilment"
|
|
1684
|
+
],
|
|
1685
|
+
"summary": "List fulfilment orders",
|
|
1686
|
+
"description": "Returns the authenticated account's fulfilment orders, newest first, with the same body the `fulfilment_order.*` webhooks carry. Intended for backfill and for polling-based reconciliation after a missed webhook delivery. Read-only. Requires a secret key, that the account uses subscription contracts, and that shipping is enabled for the account.",
|
|
1687
|
+
"parameters": [
|
|
1688
|
+
{
|
|
1689
|
+
"in": "query",
|
|
1690
|
+
"name": "status",
|
|
1691
|
+
"schema": {
|
|
1692
|
+
"type": "string",
|
|
1693
|
+
"enum": [
|
|
1694
|
+
"open",
|
|
1695
|
+
"partially_fulfilled",
|
|
1696
|
+
"fulfilled",
|
|
1697
|
+
"cancelled"
|
|
1698
|
+
]
|
|
1699
|
+
},
|
|
1700
|
+
"description": "Filter by fulfilment status."
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
"in": "query",
|
|
1704
|
+
"name": "contract",
|
|
1705
|
+
"schema": {
|
|
1706
|
+
"type": "integer"
|
|
1707
|
+
},
|
|
1708
|
+
"description": "Filter by subscription contract id."
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
"in": "query",
|
|
1712
|
+
"name": "customer",
|
|
1713
|
+
"schema": {
|
|
1714
|
+
"type": "integer"
|
|
1715
|
+
},
|
|
1716
|
+
"description": "Filter by customer id."
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
"in": "query",
|
|
1720
|
+
"name": "customer_reference",
|
|
1721
|
+
"schema": {
|
|
1722
|
+
"type": "string"
|
|
1723
|
+
},
|
|
1724
|
+
"description": "Filter by customer reference."
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
"in": "query",
|
|
1728
|
+
"name": "updated_since",
|
|
1729
|
+
"schema": {
|
|
1730
|
+
"type": "string",
|
|
1731
|
+
"format": "date-time"
|
|
1732
|
+
},
|
|
1733
|
+
"description": "Only orders changed at or after this ISO 8601 timestamp. A bare date means the start of that day. This is the reconciliation filter: poll it with the `updated_at` of the last order you processed."
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
"in": "query",
|
|
1737
|
+
"name": "created_since",
|
|
1738
|
+
"schema": {
|
|
1739
|
+
"type": "string",
|
|
1740
|
+
"format": "date-time"
|
|
1741
|
+
},
|
|
1742
|
+
"description": "Only orders created at or after this ISO 8601 timestamp. A bare date means the start of that day."
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"$ref": "#/components/parameters/PageSizeFilter"
|
|
1746
|
+
}
|
|
1747
|
+
],
|
|
1748
|
+
"responses": {
|
|
1749
|
+
"200": {
|
|
1750
|
+
"$ref": "#/components/responses/V2FulfilmentOrderList"
|
|
1751
|
+
},
|
|
1752
|
+
"403": {
|
|
1753
|
+
"$ref": "#/components/responses/V2PermissionDenied"
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
"security": [
|
|
1757
|
+
{
|
|
1758
|
+
"Secret-Api-Key": []
|
|
1759
|
+
}
|
|
1760
|
+
]
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
"/v2/fulfilment-orders/{fulfilmentOrderId}/": {
|
|
1764
|
+
"get": {
|
|
1765
|
+
"tags": [
|
|
1766
|
+
"V2 Fulfilment"
|
|
1767
|
+
],
|
|
1768
|
+
"summary": "Get a fulfilment order",
|
|
1769
|
+
"description": "Returns one fulfilment order belonging to the authenticated account, with its lines, delivery address, shipping selection and booked shipments. Orders belonging to another account return `404`. Read-only. Requires a secret key.",
|
|
1770
|
+
"parameters": [
|
|
1771
|
+
{
|
|
1772
|
+
"$ref": "#/components/parameters/V2FulfilmentOrderId"
|
|
1773
|
+
}
|
|
1774
|
+
],
|
|
1775
|
+
"responses": {
|
|
1776
|
+
"200": {
|
|
1777
|
+
"$ref": "#/components/responses/V2FulfilmentOrder"
|
|
1778
|
+
},
|
|
1779
|
+
"403": {
|
|
1780
|
+
"$ref": "#/components/responses/V2PermissionDenied"
|
|
1781
|
+
},
|
|
1782
|
+
"404": {
|
|
1783
|
+
"$ref": "#/components/responses/V2NotFound"
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
"security": [
|
|
1787
|
+
{
|
|
1788
|
+
"Secret-Api-Key": []
|
|
1789
|
+
}
|
|
1790
|
+
]
|
|
1791
|
+
}
|
|
1675
1792
|
}
|
|
1676
1793
|
},
|
|
1677
1794
|
"components": {
|
|
@@ -3088,6 +3205,10 @@
|
|
|
3088
3205
|
"shipping": {
|
|
3089
3206
|
"$ref": "#/components/schemas/V2ShippingSelectionInput",
|
|
3090
3207
|
"description": "The shipping option the customer chose. Required when the offer contains physical products and the account has active shipping options."
|
|
3208
|
+
},
|
|
3209
|
+
"allowed_origin": {
|
|
3210
|
+
"type": "string",
|
|
3211
|
+
"description": "Origin allowed to embed the hosted card form for this checkout in an iframe, for example `https://shop.example`. A single http(s) origin with no path; http is accepted only for `localhost` and loopback addresses (`127.0.0.0/8`, `[::1]`). When set it replaces the account-level checkout origins in the hosted page's `frame-ancestors` policy. Not accepted on checkout-session endpoints, where the sales channel's allowed origins apply."
|
|
3091
3212
|
}
|
|
3092
3213
|
}
|
|
3093
3214
|
}
|
|
@@ -3168,6 +3289,51 @@
|
|
|
3168
3289
|
},
|
|
3169
3290
|
"location_address": {
|
|
3170
3291
|
"type": "string"
|
|
3292
|
+
},
|
|
3293
|
+
"location": {
|
|
3294
|
+
"$ref": "#/components/schemas/V2PickupLocation"
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
},
|
|
3298
|
+
"V2PickupLocation": {
|
|
3299
|
+
"type": "object",
|
|
3300
|
+
"nullable": true,
|
|
3301
|
+
"description": "The chosen pickup location as a structured block an external fulfilment system can book against, or null when the selection has none (home delivery, freight, store pickup without an address). Derived from the shipping provider's own payload, with the flat `location_*` values as the fallback.",
|
|
3302
|
+
"properties": {
|
|
3303
|
+
"id": {
|
|
3304
|
+
"type": "string",
|
|
3305
|
+
"description": "The provider's id for the location."
|
|
3306
|
+
},
|
|
3307
|
+
"name": {
|
|
3308
|
+
"type": "string"
|
|
3309
|
+
},
|
|
3310
|
+
"address": {
|
|
3311
|
+
"type": "string",
|
|
3312
|
+
"description": "One-line address, composed from street, zip code and town when the provider does not supply one."
|
|
3313
|
+
},
|
|
3314
|
+
"street": {
|
|
3315
|
+
"type": "string"
|
|
3316
|
+
},
|
|
3317
|
+
"zip_code": {
|
|
3318
|
+
"type": "string"
|
|
3319
|
+
},
|
|
3320
|
+
"town": {
|
|
3321
|
+
"type": "string"
|
|
3322
|
+
},
|
|
3323
|
+
"external_id": {
|
|
3324
|
+
"type": "string",
|
|
3325
|
+
"nullable": true,
|
|
3326
|
+
"description": "The location's id in the provider's upstream system, when it exposes one."
|
|
3327
|
+
},
|
|
3328
|
+
"latitude": {
|
|
3329
|
+
"type": "number",
|
|
3330
|
+
"format": "double",
|
|
3331
|
+
"nullable": true
|
|
3332
|
+
},
|
|
3333
|
+
"longitude": {
|
|
3334
|
+
"type": "number",
|
|
3335
|
+
"format": "double",
|
|
3336
|
+
"nullable": true
|
|
3171
3337
|
}
|
|
3172
3338
|
}
|
|
3173
3339
|
},
|
|
@@ -3638,6 +3804,10 @@
|
|
|
3638
3804
|
},
|
|
3639
3805
|
"metadata": {
|
|
3640
3806
|
"$ref": "#/components/schemas/V2Metadata"
|
|
3807
|
+
},
|
|
3808
|
+
"allowed_origin": {
|
|
3809
|
+
"type": "string",
|
|
3810
|
+
"description": "Origin allowed to embed this hosted card form in an iframe, for example `https://shop.example`. A single http(s) origin with no path; http is accepted only for `localhost` and loopback addresses (`127.0.0.0/8`, `[::1]`). Defaults to the checkout's `allowed_origin`, and when set it replaces the account-level checkout origins in the hosted page's `frame-ancestors` policy. Not accepted on checkout-session endpoints, where the sales channel's allowed origins apply."
|
|
3641
3811
|
}
|
|
3642
3812
|
}
|
|
3643
3813
|
},
|
|
@@ -3690,6 +3860,10 @@
|
|
|
3690
3860
|
"metadata": {
|
|
3691
3861
|
"$ref": "#/components/schemas/V2Metadata"
|
|
3692
3862
|
},
|
|
3863
|
+
"allowed_origin": {
|
|
3864
|
+
"type": "string",
|
|
3865
|
+
"description": "Origin allowed to embed this hosted card form, or an empty string when the account-level checkout origins apply."
|
|
3866
|
+
},
|
|
3693
3867
|
"error_message": {
|
|
3694
3868
|
"type": "string"
|
|
3695
3869
|
},
|
|
@@ -3753,6 +3927,25 @@
|
|
|
3753
3927
|
"type": "string",
|
|
3754
3928
|
"format": "decimal"
|
|
3755
3929
|
},
|
|
3930
|
+
"shipping_fee": {
|
|
3931
|
+
"type": "object",
|
|
3932
|
+
"nullable": true,
|
|
3933
|
+
"description": "Sendingargjald sem er hluti af checkout-samtölunum, eða null.",
|
|
3934
|
+
"properties": {
|
|
3935
|
+
"amount": {
|
|
3936
|
+
"type": "string"
|
|
3937
|
+
},
|
|
3938
|
+
"subtotal_amount": {
|
|
3939
|
+
"type": "string"
|
|
3940
|
+
},
|
|
3941
|
+
"tax_amount": {
|
|
3942
|
+
"type": "string"
|
|
3943
|
+
},
|
|
3944
|
+
"total_amount": {
|
|
3945
|
+
"type": "string"
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
},
|
|
3756
3949
|
"contract_id": {
|
|
3757
3950
|
"type": "integer",
|
|
3758
3951
|
"nullable": true
|
|
@@ -3767,6 +3960,10 @@
|
|
|
3767
3960
|
"metadata": {
|
|
3768
3961
|
"$ref": "#/components/schemas/V2Metadata"
|
|
3769
3962
|
},
|
|
3963
|
+
"allowed_origin": {
|
|
3964
|
+
"type": "string",
|
|
3965
|
+
"description": "Origin allowed to embed hosted pages for this checkout, or an empty string when the account-level checkout origins apply."
|
|
3966
|
+
},
|
|
3770
3967
|
"created_at": {
|
|
3771
3968
|
"type": "string",
|
|
3772
3969
|
"format": "date-time"
|
|
@@ -3916,6 +4113,25 @@
|
|
|
3916
4113
|
"type": "string",
|
|
3917
4114
|
"format": "decimal"
|
|
3918
4115
|
},
|
|
4116
|
+
"shipping_fee": {
|
|
4117
|
+
"type": "object",
|
|
4118
|
+
"nullable": true,
|
|
4119
|
+
"description": "Sendingargjald sem er hluti af tilboðinu (samtölur innihalda það), eða null.",
|
|
4120
|
+
"properties": {
|
|
4121
|
+
"amount": {
|
|
4122
|
+
"type": "string"
|
|
4123
|
+
},
|
|
4124
|
+
"subtotal_amount": {
|
|
4125
|
+
"type": "string"
|
|
4126
|
+
},
|
|
4127
|
+
"tax_amount": {
|
|
4128
|
+
"type": "string"
|
|
4129
|
+
},
|
|
4130
|
+
"total_amount": {
|
|
4131
|
+
"type": "string"
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
},
|
|
3919
4135
|
"first_period_recurring_subtotal_amount": {
|
|
3920
4136
|
"type": "string",
|
|
3921
4137
|
"format": "decimal"
|
|
@@ -3998,6 +4214,21 @@
|
|
|
3998
4214
|
"original_total_amount": {
|
|
3999
4215
|
"type": "string",
|
|
4000
4216
|
"format": "decimal"
|
|
4217
|
+
},
|
|
4218
|
+
"recurring_original_amount": {
|
|
4219
|
+
"type": "string",
|
|
4220
|
+
"format": "decimal",
|
|
4221
|
+
"description": "What a renewal bills without the coupon. The recurring_* totals of the quote do not include the promotion code discount."
|
|
4222
|
+
},
|
|
4223
|
+
"recurring_discount_amount": {
|
|
4224
|
+
"type": "string",
|
|
4225
|
+
"format": "decimal",
|
|
4226
|
+
"description": "The coupon's discount applied to a renewal's recurring lines. Computed regardless of duration: a once coupon lapses after the first payment and a repeating coupon after duration_in_months, so only treat this as the renewal discount while the coupon is active."
|
|
4227
|
+
},
|
|
4228
|
+
"recurring_final_amount": {
|
|
4229
|
+
"type": "string",
|
|
4230
|
+
"format": "decimal",
|
|
4231
|
+
"description": "What a renewal bills with the coupon's discount applied. Only meaningful while the coupon is still active (see duration and duration_in_months)."
|
|
4001
4232
|
}
|
|
4002
4233
|
}
|
|
4003
4234
|
},
|
|
@@ -4551,6 +4782,211 @@
|
|
|
4551
4782
|
}
|
|
4552
4783
|
}
|
|
4553
4784
|
},
|
|
4785
|
+
"V2FulfilmentAddress": {
|
|
4786
|
+
"type": "object",
|
|
4787
|
+
"nullable": true,
|
|
4788
|
+
"description": "The delivery address as it stood when the order was created. This is the order's own snapshot, so editing the contract's address afterwards does not change it.",
|
|
4789
|
+
"properties": {
|
|
4790
|
+
"id": {
|
|
4791
|
+
"type": "integer"
|
|
4792
|
+
},
|
|
4793
|
+
"delivery_name": {
|
|
4794
|
+
"type": "string",
|
|
4795
|
+
"nullable": true
|
|
4796
|
+
},
|
|
4797
|
+
"address_1": {
|
|
4798
|
+
"type": "string"
|
|
4799
|
+
},
|
|
4800
|
+
"address_2": {
|
|
4801
|
+
"type": "string",
|
|
4802
|
+
"nullable": true
|
|
4803
|
+
},
|
|
4804
|
+
"address_3": {
|
|
4805
|
+
"type": "string",
|
|
4806
|
+
"nullable": true
|
|
4807
|
+
},
|
|
4808
|
+
"zip_code": {
|
|
4809
|
+
"type": "string"
|
|
4810
|
+
},
|
|
4811
|
+
"city": {
|
|
4812
|
+
"type": "string",
|
|
4813
|
+
"nullable": true
|
|
4814
|
+
},
|
|
4815
|
+
"state": {
|
|
4816
|
+
"type": "string",
|
|
4817
|
+
"nullable": true
|
|
4818
|
+
},
|
|
4819
|
+
"country": {
|
|
4820
|
+
"type": "string",
|
|
4821
|
+
"description": "ISO 3166-1 alpha-2 country code."
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
},
|
|
4825
|
+
"V2FulfilmentOrderLine": {
|
|
4826
|
+
"type": "object",
|
|
4827
|
+
"properties": {
|
|
4828
|
+
"id": {
|
|
4829
|
+
"type": "integer"
|
|
4830
|
+
},
|
|
4831
|
+
"product_id": {
|
|
4832
|
+
"type": "integer",
|
|
4833
|
+
"nullable": true
|
|
4834
|
+
},
|
|
4835
|
+
"product_reference": {
|
|
4836
|
+
"type": "string",
|
|
4837
|
+
"nullable": true,
|
|
4838
|
+
"description": "The seller's own product reference, i.e. the SKU a warehouse picks by. Null when the catalog product has been deleted."
|
|
4839
|
+
},
|
|
4840
|
+
"product_name": {
|
|
4841
|
+
"type": "string",
|
|
4842
|
+
"description": "Product name at the time the order was created."
|
|
4843
|
+
},
|
|
4844
|
+
"quantity": {
|
|
4845
|
+
"type": "integer"
|
|
4846
|
+
},
|
|
4847
|
+
"quantity_fulfilled": {
|
|
4848
|
+
"type": "integer",
|
|
4849
|
+
"description": "How many units have been included in a shipment."
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
},
|
|
4853
|
+
"V2Fulfilment": {
|
|
4854
|
+
"type": "object",
|
|
4855
|
+
"description": "A shipment booked for (part of) an order.",
|
|
4856
|
+
"properties": {
|
|
4857
|
+
"id": {
|
|
4858
|
+
"type": "integer"
|
|
4859
|
+
},
|
|
4860
|
+
"order_id": {
|
|
4861
|
+
"type": "integer"
|
|
4862
|
+
},
|
|
4863
|
+
"handler": {
|
|
4864
|
+
"type": "string",
|
|
4865
|
+
"enum": [
|
|
4866
|
+
"dropp",
|
|
4867
|
+
"posturinn",
|
|
4868
|
+
"store_pickup"
|
|
4869
|
+
]
|
|
4870
|
+
},
|
|
4871
|
+
"status": {
|
|
4872
|
+
"type": "string",
|
|
4873
|
+
"enum": [
|
|
4874
|
+
"pending",
|
|
4875
|
+
"booked",
|
|
4876
|
+
"failed",
|
|
4877
|
+
"shipped",
|
|
4878
|
+
"delivered",
|
|
4879
|
+
"cancelled"
|
|
4880
|
+
]
|
|
4881
|
+
},
|
|
4882
|
+
"provider_order_id": {
|
|
4883
|
+
"type": "string",
|
|
4884
|
+
"description": "The provider's own identifier for the shipment, such as a Dropp order UUID or a Pósturinn shipment id."
|
|
4885
|
+
},
|
|
4886
|
+
"tracking_number": {
|
|
4887
|
+
"type": "string"
|
|
4888
|
+
},
|
|
4889
|
+
"weight_grams": {
|
|
4890
|
+
"type": "integer",
|
|
4891
|
+
"nullable": true,
|
|
4892
|
+
"description": "Package weight used for the booking."
|
|
4893
|
+
},
|
|
4894
|
+
"error": {
|
|
4895
|
+
"type": "string",
|
|
4896
|
+
"description": "The last booking error, empty when the booking succeeded."
|
|
4897
|
+
},
|
|
4898
|
+
"booked_at": {
|
|
4899
|
+
"type": "string",
|
|
4900
|
+
"format": "date-time",
|
|
4901
|
+
"nullable": true
|
|
4902
|
+
},
|
|
4903
|
+
"created_at": {
|
|
4904
|
+
"type": "string",
|
|
4905
|
+
"format": "date-time"
|
|
4906
|
+
},
|
|
4907
|
+
"updated_at": {
|
|
4908
|
+
"type": "string",
|
|
4909
|
+
"format": "date-time"
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
},
|
|
4913
|
+
"V2FulfilmentOrder": {
|
|
4914
|
+
"type": "object",
|
|
4915
|
+
"description": "A physical order generated by a paid billing run. This is also the body of the `fulfilment_order.*` webhooks.",
|
|
4916
|
+
"properties": {
|
|
4917
|
+
"id": {
|
|
4918
|
+
"type": "integer"
|
|
4919
|
+
},
|
|
4920
|
+
"number": {
|
|
4921
|
+
"type": "integer",
|
|
4922
|
+
"description": "Per-account sequential order number."
|
|
4923
|
+
},
|
|
4924
|
+
"status": {
|
|
4925
|
+
"type": "string",
|
|
4926
|
+
"enum": [
|
|
4927
|
+
"open",
|
|
4928
|
+
"partially_fulfilled",
|
|
4929
|
+
"fulfilled",
|
|
4930
|
+
"cancelled"
|
|
4931
|
+
]
|
|
4932
|
+
},
|
|
4933
|
+
"contract_id": {
|
|
4934
|
+
"type": "integer"
|
|
4935
|
+
},
|
|
4936
|
+
"billing_run_id": {
|
|
4937
|
+
"type": "integer",
|
|
4938
|
+
"description": "The successful billing run that generated this order. Each run generates at most one order."
|
|
4939
|
+
},
|
|
4940
|
+
"customer_id": {
|
|
4941
|
+
"type": "integer"
|
|
4942
|
+
},
|
|
4943
|
+
"customer_reference": {
|
|
4944
|
+
"type": "string"
|
|
4945
|
+
},
|
|
4946
|
+
"customer_name": {
|
|
4947
|
+
"type": "string"
|
|
4948
|
+
},
|
|
4949
|
+
"customer_email": {
|
|
4950
|
+
"type": "string",
|
|
4951
|
+
"nullable": true
|
|
4952
|
+
},
|
|
4953
|
+
"delivery_address": {
|
|
4954
|
+
"$ref": "#/components/schemas/V2FulfilmentAddress"
|
|
4955
|
+
},
|
|
4956
|
+
"shipping_selection": {
|
|
4957
|
+
"$ref": "#/components/schemas/V2ShippingSelection"
|
|
4958
|
+
},
|
|
4959
|
+
"lines": {
|
|
4960
|
+
"type": "array",
|
|
4961
|
+
"items": {
|
|
4962
|
+
"$ref": "#/components/schemas/V2FulfilmentOrderLine"
|
|
4963
|
+
}
|
|
4964
|
+
},
|
|
4965
|
+
"fulfilments": {
|
|
4966
|
+
"type": "array",
|
|
4967
|
+
"description": "Shipments booked for this order. Empty until one is booked, and always empty for an account with no shipping providers configured.",
|
|
4968
|
+
"items": {
|
|
4969
|
+
"$ref": "#/components/schemas/V2Fulfilment"
|
|
4970
|
+
}
|
|
4971
|
+
},
|
|
4972
|
+
"estimated_weight_grams": {
|
|
4973
|
+
"type": "integer",
|
|
4974
|
+
"nullable": true,
|
|
4975
|
+
"description": "Sum of the products' chargeable unit weights, or null when any line lacks weight data, so a partial estimate is never mistaken for a total."
|
|
4976
|
+
},
|
|
4977
|
+
"metadata": {
|
|
4978
|
+
"$ref": "#/components/schemas/V2Metadata"
|
|
4979
|
+
},
|
|
4980
|
+
"created_at": {
|
|
4981
|
+
"type": "string",
|
|
4982
|
+
"format": "date-time"
|
|
4983
|
+
},
|
|
4984
|
+
"updated_at": {
|
|
4985
|
+
"type": "string",
|
|
4986
|
+
"format": "date-time"
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
},
|
|
4554
4990
|
"V2SubscriptionContractCustomer": {
|
|
4555
4991
|
"type": "object",
|
|
4556
4992
|
"properties": {
|
|
@@ -5077,6 +5513,17 @@
|
|
|
5077
5513
|
"type": "integer"
|
|
5078
5514
|
}
|
|
5079
5515
|
},
|
|
5516
|
+
"V2FulfilmentOrderId": {
|
|
5517
|
+
"name": "fulfilmentOrderId",
|
|
5518
|
+
"in": "path",
|
|
5519
|
+
"description": "Fulfilment order id. This is the order's `id`, not its per-account `number`.",
|
|
5520
|
+
"required": true,
|
|
5521
|
+
"style": "simple",
|
|
5522
|
+
"explode": false,
|
|
5523
|
+
"schema": {
|
|
5524
|
+
"type": "integer"
|
|
5525
|
+
}
|
|
5526
|
+
},
|
|
5080
5527
|
"V2CustomerReference": {
|
|
5081
5528
|
"name": "customerReference",
|
|
5082
5529
|
"in": "path",
|
|
@@ -5439,6 +5886,70 @@
|
|
|
5439
5886
|
}
|
|
5440
5887
|
}
|
|
5441
5888
|
},
|
|
5889
|
+
"V2PermissionDenied": {
|
|
5890
|
+
"description": "The account may not use this endpoint, for example because it does not use subscription contracts or does not have shipping enabled",
|
|
5891
|
+
"content": {
|
|
5892
|
+
"application/json": {
|
|
5893
|
+
"schema": {
|
|
5894
|
+
"type": "object",
|
|
5895
|
+
"properties": {
|
|
5896
|
+
"detail": {
|
|
5897
|
+
"type": "string"
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
},
|
|
5904
|
+
"V2FulfilmentOrder": {
|
|
5905
|
+
"description": "V2 fulfilment order",
|
|
5906
|
+
"content": {
|
|
5907
|
+
"application/json": {
|
|
5908
|
+
"schema": {
|
|
5909
|
+
"$ref": "#/components/schemas/V2FulfilmentOrder"
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
},
|
|
5914
|
+
"V2FulfilmentOrderList": {
|
|
5915
|
+
"description": "V2 fulfilment order list",
|
|
5916
|
+
"content": {
|
|
5917
|
+
"application/json": {
|
|
5918
|
+
"schema": {
|
|
5919
|
+
"anyOf": [
|
|
5920
|
+
{
|
|
5921
|
+
"type": "array",
|
|
5922
|
+
"items": {
|
|
5923
|
+
"$ref": "#/components/schemas/V2FulfilmentOrder"
|
|
5924
|
+
}
|
|
5925
|
+
},
|
|
5926
|
+
{
|
|
5927
|
+
"type": "object",
|
|
5928
|
+
"properties": {
|
|
5929
|
+
"count": {
|
|
5930
|
+
"type": "integer"
|
|
5931
|
+
},
|
|
5932
|
+
"next": {
|
|
5933
|
+
"type": "string",
|
|
5934
|
+
"nullable": true
|
|
5935
|
+
},
|
|
5936
|
+
"previous": {
|
|
5937
|
+
"type": "string",
|
|
5938
|
+
"nullable": true
|
|
5939
|
+
},
|
|
5940
|
+
"results": {
|
|
5941
|
+
"type": "array",
|
|
5942
|
+
"items": {
|
|
5943
|
+
"$ref": "#/components/schemas/V2FulfilmentOrder"
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
]
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
},
|
|
5442
5953
|
"V2BillingRun": {
|
|
5443
5954
|
"description": "V2 billing run",
|
|
5444
5955
|
"content": {
|
package/src/client/paths.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Askell OpenAPI uses British "fulfilment"; agents (and Shopify/Stripe training)
|
|
3
|
+
* type American "fulfillment". Fold to the British form Askell actually serves.
|
|
4
|
+
*/
|
|
5
|
+
export function foldFulfilmentSpelling(text: string): string {
|
|
6
|
+
return text.replace(/fulfillment/gi, (match) => {
|
|
7
|
+
const first = match[0];
|
|
8
|
+
if (match === match.toUpperCase()) {
|
|
9
|
+
return 'FULFILMENT';
|
|
10
|
+
}
|
|
11
|
+
if (first !== undefined && first === first.toUpperCase()) {
|
|
12
|
+
return 'Fulfilment';
|
|
13
|
+
}
|
|
14
|
+
return 'fulfilment';
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Lowercased text with American/British fulfilment spelling folded together. */
|
|
19
|
+
export function canonicalSearchText(text: string): string {
|
|
20
|
+
return foldFulfilmentSpelling(text).toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
/** Normalize Askell API paths: leading slash + trailing slash (OpenAPI convention). */
|
|
2
24
|
export function normalizeApiPath(path: string): string {
|
|
3
|
-
let normalized = path
|
|
25
|
+
let normalized = foldFulfilmentSpelling(path);
|
|
26
|
+
normalized = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
4
27
|
|
|
5
28
|
if (normalized !== '/' && !normalized.endsWith('/')) {
|
|
6
29
|
normalized += '/';
|
package/src/openapi/registry.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import v1Spec from '../../spec/openapi-v1.json';
|
|
2
2
|
import v2Spec from '../../spec/openapi-v2.json';
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
canonicalSearchText,
|
|
6
|
+
foldFulfilmentSpelling,
|
|
7
|
+
} from '../client/paths.ts';
|
|
4
8
|
import type {
|
|
5
9
|
ApiKeyKind,
|
|
6
10
|
ApiOperation,
|
|
@@ -184,7 +188,10 @@ export class OperationRegistry {
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
getById(id: string): ApiOperation | undefined {
|
|
187
|
-
|
|
191
|
+
const folded = foldFulfilmentSpelling(id);
|
|
192
|
+
return this.operations.find(
|
|
193
|
+
(operation) => operation.id === id || operation.id === folded,
|
|
194
|
+
);
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
find(filters: {
|
|
@@ -206,7 +213,13 @@ export class OperationRegistry {
|
|
|
206
213
|
return false;
|
|
207
214
|
}
|
|
208
215
|
|
|
209
|
-
|
|
216
|
+
const tagFilter = filters.tag;
|
|
217
|
+
if (
|
|
218
|
+
tagFilter &&
|
|
219
|
+
!operation.tags.some(
|
|
220
|
+
(tag) => canonicalSearchText(tag) === canonicalSearchText(tagFilter),
|
|
221
|
+
)
|
|
222
|
+
) {
|
|
210
223
|
return false;
|
|
211
224
|
}
|
|
212
225
|
|
|
@@ -216,7 +229,9 @@ export class OperationRegistry {
|
|
|
216
229
|
|
|
217
230
|
if (
|
|
218
231
|
filters.pathPrefix &&
|
|
219
|
-
!operation.path.startsWith(
|
|
232
|
+
!canonicalSearchText(operation.path).startsWith(
|
|
233
|
+
canonicalSearchText(filters.pathPrefix),
|
|
234
|
+
)
|
|
220
235
|
) {
|
|
221
236
|
return false;
|
|
222
237
|
}
|
|
@@ -226,17 +241,18 @@ export class OperationRegistry {
|
|
|
226
241
|
}
|
|
227
242
|
|
|
228
243
|
if (search) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
const needle = canonicalSearchText(search);
|
|
245
|
+
const haystack = canonicalSearchText(
|
|
246
|
+
[
|
|
247
|
+
operation.id,
|
|
248
|
+
operation.path,
|
|
249
|
+
operation.summary,
|
|
250
|
+
operation.description ?? '',
|
|
251
|
+
operation.tags.join(' '),
|
|
252
|
+
].join(' '),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (!haystack.includes(needle)) {
|
|
240
256
|
return false;
|
|
241
257
|
}
|
|
242
258
|
}
|
|
@@ -9,13 +9,15 @@ Askell POSTs signed JSON to each URL you register. Verify \`Hook-HMAC\` before p
|
|
|
9
9
|
Headers:
|
|
10
10
|
- Hook-HMAC: base64 HMAC-SHA512 of the **raw body** (secret = \`hmac_secret\` from webhook create)
|
|
11
11
|
- Hook-Event: event type (\`subscription.renewed\`, \`payment.changed\`, or a family wildcard \`subscription.*\`)
|
|
12
|
-
- Hook-API-Version: \`v1\` for plan/subscription/customer/payment/checkout, \`v2\` for subscription_contract / billing_run
|
|
12
|
+
- Hook-API-Version: \`v1\` for plan/subscription/customer/payment/checkout, \`v2\` for subscription_contract / billing_run / fulfilment_order
|
|
13
13
|
|
|
14
|
-
## Body shape
|
|
14
|
+
## Body shape
|
|
15
15
|
|
|
16
16
|
JSON body **is the event object**. It is **not** \`{ event, data }\`.
|
|
17
17
|
|
|
18
|
-
Upstream swagger used to document a dummy \`POST /your-webhook-url/\` with \`SubscriptionMultiLite\` (\`{ customer, subscriptions[] }\`). \`sync-specs\` strips that path.
|
|
18
|
+
Upstream swagger used to document a dummy \`POST /your-webhook-url/\` with \`SubscriptionMultiLite\` (\`{ customer, subscriptions[] }\`). \`sync-specs\` strips that path.
|
|
19
|
+
|
|
20
|
+
Most inbound payloads are still undocumented in OpenAPI — this resource is the overlay. Exception: \`fulfilment_order.*\` is \`V2FulfilmentOrder\` (same body as \`GET /v2/fulfilment-orders/\` / \`GET /v2/fulfilment-orders/{id}/\`). Live webhook docs omit this family; bundled spec is right. Spelling is British \`fulfilment_order.*\`, not American \`fulfillment_order.*\`. Specific verbs beyond the wildcard are not listed — do not invent \`created\`/\`shipped\`/….
|
|
19
21
|
|
|
20
22
|
Rare historical payloads used \`{ event, data, ref?, sender? }\`. If both \`event\` and \`data\` are objects, use \`data\`.
|
|
21
23
|
|
|
@@ -59,6 +61,11 @@ V2 migration: \`subscription.*\` is **not** aliased onto the new contract (paylo
|
|
|
59
61
|
|
|
60
62
|
### checkout.* (v1)
|
|
61
63
|
\`created\`, \`changed\` — \`token\`, \`checkout_url\`, \`status\`.
|
|
64
|
+
|
|
65
|
+
### fulfilment_order.* (v2)
|
|
66
|
+
Family wildcard only in OpenAPI (\`fulfilment_order.*\`). Not American \`fulfillment_order.*\`. Live docs omit this family.
|
|
67
|
+
|
|
68
|
+
Body is \`V2FulfilmentOrder\`: \`id\` (path param; not per-account \`number\`), \`number\`, \`status\` (\`open\` | \`partially_fulfilled\` | \`fulfilled\` | \`cancelled\`), \`contract_id\`, \`billing_run_id\` (successful run that generated the order; at most one order per run), \`customer_id\`, \`customer_reference\`, \`customer_name\`, \`customer_email\`, \`delivery_address\` (snapshot; later contract address edits do not change it), \`shipping_selection\` (includes structured \`location\` plus flat \`location_*\` fallback), \`lines[]\` (\`product_id\`, \`product_reference\` SKU, \`product_name\`, \`quantity\`, \`quantity_fulfilled\`), \`fulfilments[]\` (booked shipments; empty until booked / if no shipping provider), \`estimated_weight_grams\` (null if any line lacks weight — never a partial total), \`metadata\`, \`created_at\`, \`updated_at\`.
|
|
62
69
|
`;
|
|
63
70
|
|
|
64
71
|
export function registerResources(server: McpServer): void {
|
package/src/server.ts
CHANGED
|
@@ -47,15 +47,23 @@ API layout:
|
|
|
47
47
|
- V2 list endpoints paginate only when page_size is provided (default 10, max 1000).
|
|
48
48
|
- GET /v2/customer-entitlements/ requires customer_reference query param.
|
|
49
49
|
|
|
50
|
+
V2 fulfilment (British spelling on the wire — not American fulfillment):
|
|
51
|
+
- Read-only GET /v2/fulfilment-orders/ and GET /v2/fulfilment-orders/{fulfilmentOrderId}/. No POST/PATCH in OpenAPI. Path param is the order's id, not per-account number.
|
|
52
|
+
- List 403 = account does not use V2 contracts or shipping is disabled (not a bad API key). GET 404 = missing or other-account.
|
|
53
|
+
- Reconciliation: poll list with updated_since = updated_at of the last order you processed. Filter by contract, customer, customer_reference, status.
|
|
54
|
+
- Webhook family fulfilment_order.* (not fulfillment_order.*). Body is V2FulfilmentOrder, same as GET. Live webhook docs omit this family; bundled spec is right. Do not invent event verbs beyond the wildcard until Askell lists them.
|
|
55
|
+
- Quote and checkout shipping_fee is already in totals; do not add it again (askell_describe_operation omits response schemas). shipping_selection.location is the structured pickup block; flat location_* are fallback.
|
|
56
|
+
|
|
50
57
|
V2 discounts — two systems, not v1 Subscription.discount (0-100 on a PlanVariant; never send that to v2):
|
|
51
58
|
- Coupons: one active per contract. GET /v2/subscription-contracts/{id}/discount/ (also nested as contract.discount). Apply with POST .../apply-code/ {promotion_code}. Remove with POST .../remove-discount/.
|
|
52
|
-
- Quotes (POST /v2/subscription-offer-quotes/): pass promotion_code for coupons. When quoting an existing customer, pass customer (numeric id) or combo discounts from their other active contracts and promo-code customer restrictions are skipped.
|
|
59
|
+
- Quotes (POST /v2/subscription-offer-quotes/): pass promotion_code for coupons. When quoting an existing customer, pass customer (numeric id) or combo discounts from their other active contracts and promo-code customer restrictions are skipped. First-period subtotal/tax/total already include coupon + combo. quote.recurring_* include combo, not the coupon — renewal-with-coupon is discount.recurring_final_amount, and only while duration still applies (once → after first payment use recurring_*). combo_discounts[] and discount.recurring_* are on the quote response (askell_describe_operation omits response schemas). Combo is automatic, not apply-code.
|
|
53
60
|
|
|
54
61
|
V2 checkout notes:
|
|
55
62
|
- checkout_url on V2 checkouts points to the API object URL, not a hosted payment page.
|
|
56
63
|
- GET contract.subscriber_page is the customer-facing subscription management URL (readOnly, nullable). Not checkout_url, not v1 /public/payments/{id}/ (hosted signup). Do not send it on create/patch.
|
|
57
64
|
- finalize: a recurring offer needs a verified payment method even when due-now/total is 0 (trial or fully discounted first period). Only a free one-time purchase finalizes without one. Live docs still say "unless 0 ISK" — ignore that; bundled OpenAPI is right.
|
|
58
65
|
- Hosted POST /v2/checkouts/: shipping {option, location_id?} is required when the offer has physical products and the account has active shipping options. No shipping-options list in OpenAPI (ids are account config). Pickup options need location_id. Snapshot is contract.shipping_selection, not on V2Checkout.
|
|
66
|
+
- Hosted iframe (not askell.js): POST /v2/checkouts/ and POST .../payment-method-registrations/ take allowed_origin (one origin, no path; http only localhost/loopback). Replaces account-level frame-ancestors; GET empty string = account-level. Rejected on /v2/checkout-sessions/ (sales-channel allowed_origins[]).
|
|
59
67
|
- Embedded checkout uses POST /v2/checkout-sessions/ plus browser session-token sub-paths (widget collects address/shipping; see docs, not all in OpenAPI).
|
|
60
68
|
|
|
61
69
|
Auth:
|
|
@@ -70,7 +78,7 @@ Safety:
|
|
|
70
78
|
|
|
71
79
|
Resources:
|
|
72
80
|
- askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
|
|
73
|
-
- askell://docs/webhook-events — inbound webhook payloads (not in OpenAPI;
|
|
81
|
+
- askell://docs/webhook-events — inbound webhook payloads (most not in OpenAPI; fulfilment_order.* is V2FulfilmentOrder), HMAC-SHA512, /webhooks/ hmac_secret`;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
export function createServer(config: AppConfig): McpServer {
|
package/src/tools/analysis.ts
CHANGED
|
@@ -141,7 +141,7 @@ export function registerAnalysisTools(
|
|
|
141
141
|
{
|
|
142
142
|
title: 'Subscription contract overview (v2)',
|
|
143
143
|
description:
|
|
144
|
-
'Fetch a v2 subscription contract and recent billing runs filtered by contract id. The contract payload includes `discount` (active coupon) when one is applied, `shipping_selection` when shipping was chosen at checkout, and `subscriber_page` (customer-facing management URL, read-only).',
|
|
144
|
+
'Fetch a v2 subscription contract and recent billing runs filtered by contract id. The contract payload includes `discount` (active coupon) when one is applied, `shipping_selection` when shipping was chosen at checkout, and `subscriber_page` (customer-facing management URL, read-only). Physical shipping: GET /v2/fulfilment-orders/?contract= (read-only; 403 if shipping is disabled — do not treat as a bad API key).',
|
|
145
145
|
inputSchema: z.object({
|
|
146
146
|
contractId: z
|
|
147
147
|
.union([z.string().min(1), z.int()])
|
package/src/tools/discovery.ts
CHANGED
|
@@ -21,7 +21,7 @@ const listInputSchema = z.object({
|
|
|
21
21
|
.string()
|
|
22
22
|
.optional()
|
|
23
23
|
.describe(
|
|
24
|
-
'Case-insensitive search in id, path, summary, description, tags',
|
|
24
|
+
'Case-insensitive search in id, path, summary, description, tags. American "fulfillment" matches British "fulfilment" (Askell paths use the latter).',
|
|
25
25
|
),
|
|
26
26
|
apiKeyKind: z
|
|
27
27
|
.enum(['secret', 'public'])
|