askell-mcp 0.4.6 → 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 +1 -0
- package/package.json +2 -2
- package/spec/openapi-v2.json +480 -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 +8 -1
- package/src/tools/analysis.ts +1 -1
- package/src/tools/discovery.ts +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,7 @@ Typical agent workflow:
|
|
|
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
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": {
|
|
@@ -3172,6 +3289,51 @@
|
|
|
3172
3289
|
},
|
|
3173
3290
|
"location_address": {
|
|
3174
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
|
|
3175
3337
|
}
|
|
3176
3338
|
}
|
|
3177
3339
|
},
|
|
@@ -3765,6 +3927,25 @@
|
|
|
3765
3927
|
"type": "string",
|
|
3766
3928
|
"format": "decimal"
|
|
3767
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
|
+
},
|
|
3768
3949
|
"contract_id": {
|
|
3769
3950
|
"type": "integer",
|
|
3770
3951
|
"nullable": true
|
|
@@ -3932,6 +4113,25 @@
|
|
|
3932
4113
|
"type": "string",
|
|
3933
4114
|
"format": "decimal"
|
|
3934
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
|
+
},
|
|
3935
4135
|
"first_period_recurring_subtotal_amount": {
|
|
3936
4136
|
"type": "string",
|
|
3937
4137
|
"format": "decimal"
|
|
@@ -4582,6 +4782,211 @@
|
|
|
4582
4782
|
}
|
|
4583
4783
|
}
|
|
4584
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
|
+
},
|
|
4585
4990
|
"V2SubscriptionContractCustomer": {
|
|
4586
4991
|
"type": "object",
|
|
4587
4992
|
"properties": {
|
|
@@ -5108,6 +5513,17 @@
|
|
|
5108
5513
|
"type": "integer"
|
|
5109
5514
|
}
|
|
5110
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
|
+
},
|
|
5111
5527
|
"V2CustomerReference": {
|
|
5112
5528
|
"name": "customerReference",
|
|
5113
5529
|
"in": "path",
|
|
@@ -5470,6 +5886,70 @@
|
|
|
5470
5886
|
}
|
|
5471
5887
|
}
|
|
5472
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
|
+
},
|
|
5473
5953
|
"V2BillingRun": {
|
|
5474
5954
|
"description": "V2 billing run",
|
|
5475
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,6 +47,13 @@ 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
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.
|
|
@@ -71,7 +78,7 @@ Safety:
|
|
|
71
78
|
|
|
72
79
|
Resources:
|
|
73
80
|
- askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
|
|
74
|
-
- 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`;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
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'])
|