askell-mcp 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +9 -8
- package/spec/openapi-v2.json +435 -49
- package/src/client/response-formatter.ts +113 -32
- package/src/config.ts +11 -21
- package/src/openapi/registry.ts +17 -7
- package/src/server.ts +6 -1
- package/src/tools/analysis.ts +38 -10
- package/src/tools/call.ts +2 -2
- package/src/tools/discovery.ts +9 -5
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Connect it to Cursor, Claude Desktop, or any MCP client to discover Askell endpo
|
|
|
8
8
|
|
|
9
9
|
- An [Askell](https://askell.is) account and **secret API key** (from the Askell dashboard)
|
|
10
10
|
- One of:
|
|
11
|
-
- [Bun](https://bun.sh) ≥ 1.
|
|
11
|
+
- [Bun](https://bun.sh) ≥ 1.4.0 (for `bunx`), or
|
|
12
12
|
- a prebuilt binary from [Releases](https://github.com/Neschadin/askell-mcp/releases) (no Bun needed)
|
|
13
13
|
|
|
14
14
|
## Quick start
|
|
@@ -99,6 +99,7 @@ Typical agent workflow:
|
|
|
99
99
|
|
|
100
100
|
- **v1** — legacy paths like `/customers/`, `/subscriptions/` (no `/v2` prefix)
|
|
101
101
|
- **v2** — current model: catalogs, quotes, checkouts, contracts, billing runs under `/v2/`
|
|
102
|
+
- **v2 coupons** — `GET/POST /v2/subscription-contracts/{id}/discount|apply-code|remove-discount` (one active coupon). Quotes take `promotion_code`. Not the v1 `discount` 0–100 field.
|
|
102
103
|
- Paths use **trailing slashes**
|
|
103
104
|
- Prefer **v2** for new integrations; v1 remains for existing ones
|
|
104
105
|
- 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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for the Askell payment and subscription API (Bun + stdio)",
|
|
5
5
|
"author": "Neschadin Oleksandr",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"mcp.json.example"
|
|
36
36
|
],
|
|
37
37
|
"engines": {
|
|
38
|
-
"bun": ">=1.
|
|
38
|
+
"bun": ">=1.4.0"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public",
|
|
@@ -47,19 +47,20 @@
|
|
|
47
47
|
"test": "bun test",
|
|
48
48
|
"smoke": "bun run scripts/smoke-test.ts",
|
|
49
49
|
"test:integration": "bun run scripts/integration-test.ts",
|
|
50
|
+
"eval:tools": "bun run scripts/eval-tools.ts",
|
|
50
51
|
"typecheck": "tsc --noEmit",
|
|
51
52
|
"inspect": "bunx @modelcontextprotocol/inspector bun bin/askell-mcp",
|
|
52
53
|
"sync-specs": "bun run scripts/sync-specs.ts",
|
|
53
54
|
"prepack": "bun test && bun run typecheck",
|
|
54
|
-
"build": "bun build --compile --minify src/index.ts --outfile dist/askell-mcp",
|
|
55
|
-
"build:linux-x64": "bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile dist/askell-mcp-linux-x64",
|
|
56
|
-
"build:linux-arm64": "bun build --compile --minify --target=bun-linux-arm64 src/index.ts --outfile dist/askell-mcp-linux-arm64",
|
|
57
|
-
"build:darwin-arm64": "bun build --compile --minify --target=bun-darwin-arm64 src/index.ts --outfile dist/askell-mcp-darwin-arm64",
|
|
58
|
-
"build:darwin-x64": "bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile dist/askell-mcp-darwin-x64",
|
|
55
|
+
"build": "bun build --compile --minify --bytecode --format=esm src/index.ts --outfile dist/askell-mcp",
|
|
56
|
+
"build:linux-x64": "bun build --compile --minify --bytecode --format=esm --target=bun-linux-x64 src/index.ts --outfile dist/askell-mcp-linux-x64",
|
|
57
|
+
"build:linux-arm64": "bun build --compile --minify --bytecode --format=esm --target=bun-linux-arm64 src/index.ts --outfile dist/askell-mcp-linux-arm64",
|
|
58
|
+
"build:darwin-arm64": "bun build --compile --minify --bytecode --format=esm --target=bun-darwin-arm64 src/index.ts --outfile dist/askell-mcp-darwin-arm64",
|
|
59
|
+
"build:darwin-x64": "bun build --compile --minify --bytecode --format=esm --target=bun-darwin-x64 src/index.ts --outfile dist/askell-mcp-darwin-x64",
|
|
59
60
|
"build:all": "bun run build && bun run build:linux-x64 && bun run build:linux-arm64 && bun run build:darwin-arm64 && bun run build:darwin-x64"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
|
-
"@types/bun": "1.
|
|
63
|
+
"@types/bun": "1.4.0",
|
|
63
64
|
"typescript": "7.0.2"
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
package/spec/openapi-v2.json
CHANGED
|
@@ -1137,6 +1137,161 @@
|
|
|
1137
1137
|
]
|
|
1138
1138
|
}
|
|
1139
1139
|
},
|
|
1140
|
+
"/v2/subscription-contracts/{contractId}/apply-code/": {
|
|
1141
|
+
"post": {
|
|
1142
|
+
"tags": [
|
|
1143
|
+
"V2 Subscription Contracts"
|
|
1144
|
+
],
|
|
1145
|
+
"summary": "Apply a promotion code to a V2 subscription contract",
|
|
1146
|
+
"description": "Validates and redeems a promotion code for the contract, creating an active discount that is applied to subsequent billing runs. A contract can only carry one active discount at a time. Requires a secret key.",
|
|
1147
|
+
"parameters": [
|
|
1148
|
+
{
|
|
1149
|
+
"$ref": "#/components/parameters/V2ContractId"
|
|
1150
|
+
}
|
|
1151
|
+
],
|
|
1152
|
+
"requestBody": {
|
|
1153
|
+
"content": {
|
|
1154
|
+
"application/json": {
|
|
1155
|
+
"schema": {
|
|
1156
|
+
"type": "object",
|
|
1157
|
+
"required": [
|
|
1158
|
+
"promotion_code"
|
|
1159
|
+
],
|
|
1160
|
+
"properties": {
|
|
1161
|
+
"promotion_code": {
|
|
1162
|
+
"type": "string"
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
"required": true
|
|
1169
|
+
},
|
|
1170
|
+
"responses": {
|
|
1171
|
+
"200": {
|
|
1172
|
+
"description": "Promotion code applied",
|
|
1173
|
+
"content": {
|
|
1174
|
+
"application/json": {
|
|
1175
|
+
"schema": {
|
|
1176
|
+
"type": "object",
|
|
1177
|
+
"properties": {
|
|
1178
|
+
"success": {
|
|
1179
|
+
"type": "boolean"
|
|
1180
|
+
},
|
|
1181
|
+
"message": {
|
|
1182
|
+
"type": "string"
|
|
1183
|
+
},
|
|
1184
|
+
"discount": {
|
|
1185
|
+
"$ref": "#/components/schemas/V2ContractDiscount"
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
"400": {
|
|
1193
|
+
"$ref": "#/components/responses/V2ValidationError"
|
|
1194
|
+
},
|
|
1195
|
+
"404": {
|
|
1196
|
+
"$ref": "#/components/responses/V2NotFound"
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1199
|
+
"security": [
|
|
1200
|
+
{
|
|
1201
|
+
"Secret-Api-Key": []
|
|
1202
|
+
}
|
|
1203
|
+
]
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
"/v2/subscription-contracts/{contractId}/discount/": {
|
|
1207
|
+
"get": {
|
|
1208
|
+
"tags": [
|
|
1209
|
+
"V2 Subscription Contracts"
|
|
1210
|
+
],
|
|
1211
|
+
"summary": "Get the active discount for a V2 subscription contract",
|
|
1212
|
+
"description": "Returns the contract's active coupon discount, or null when none is active. Requires a secret key.",
|
|
1213
|
+
"parameters": [
|
|
1214
|
+
{
|
|
1215
|
+
"$ref": "#/components/parameters/V2ContractId"
|
|
1216
|
+
}
|
|
1217
|
+
],
|
|
1218
|
+
"responses": {
|
|
1219
|
+
"200": {
|
|
1220
|
+
"description": "Active discount",
|
|
1221
|
+
"content": {
|
|
1222
|
+
"application/json": {
|
|
1223
|
+
"schema": {
|
|
1224
|
+
"type": "object",
|
|
1225
|
+
"properties": {
|
|
1226
|
+
"discount": {
|
|
1227
|
+
"allOf": [
|
|
1228
|
+
{
|
|
1229
|
+
"$ref": "#/components/schemas/V2ContractDiscount"
|
|
1230
|
+
}
|
|
1231
|
+
],
|
|
1232
|
+
"nullable": true
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
"404": {
|
|
1240
|
+
"$ref": "#/components/responses/V2NotFound"
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
"security": [
|
|
1244
|
+
{
|
|
1245
|
+
"Secret-Api-Key": []
|
|
1246
|
+
}
|
|
1247
|
+
]
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
"/v2/subscription-contracts/{contractId}/remove-discount/": {
|
|
1251
|
+
"post": {
|
|
1252
|
+
"tags": [
|
|
1253
|
+
"V2 Subscription Contracts"
|
|
1254
|
+
],
|
|
1255
|
+
"summary": "Remove the active discount from a V2 subscription contract",
|
|
1256
|
+
"description": "Ends the contract's active coupon discount. Subsequent billing runs are charged at full price. Requires a secret key.",
|
|
1257
|
+
"parameters": [
|
|
1258
|
+
{
|
|
1259
|
+
"$ref": "#/components/parameters/V2ContractId"
|
|
1260
|
+
}
|
|
1261
|
+
],
|
|
1262
|
+
"responses": {
|
|
1263
|
+
"200": {
|
|
1264
|
+
"description": "Discount removed",
|
|
1265
|
+
"content": {
|
|
1266
|
+
"application/json": {
|
|
1267
|
+
"schema": {
|
|
1268
|
+
"type": "object",
|
|
1269
|
+
"properties": {
|
|
1270
|
+
"success": {
|
|
1271
|
+
"type": "boolean"
|
|
1272
|
+
},
|
|
1273
|
+
"message": {
|
|
1274
|
+
"type": "string"
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
"400": {
|
|
1282
|
+
"$ref": "#/components/responses/V2ValidationError"
|
|
1283
|
+
},
|
|
1284
|
+
"404": {
|
|
1285
|
+
"$ref": "#/components/responses/V2NotFound"
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
"security": [
|
|
1289
|
+
{
|
|
1290
|
+
"Secret-Api-Key": []
|
|
1291
|
+
}
|
|
1292
|
+
]
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1140
1295
|
"/v2/checkouts/": {
|
|
1141
1296
|
"post": {
|
|
1142
1297
|
"tags": [
|
|
@@ -2091,6 +2246,80 @@
|
|
|
2091
2246
|
}
|
|
2092
2247
|
}
|
|
2093
2248
|
},
|
|
2249
|
+
"V2ContractDiscount": {
|
|
2250
|
+
"type": "object",
|
|
2251
|
+
"description": "An applied coupon discount on a V2 subscription contract.",
|
|
2252
|
+
"properties": {
|
|
2253
|
+
"id": {
|
|
2254
|
+
"type": "string"
|
|
2255
|
+
},
|
|
2256
|
+
"coupon": {
|
|
2257
|
+
"type": "object",
|
|
2258
|
+
"properties": {
|
|
2259
|
+
"id": {
|
|
2260
|
+
"type": "string"
|
|
2261
|
+
},
|
|
2262
|
+
"name": {
|
|
2263
|
+
"type": "string",
|
|
2264
|
+
"nullable": true
|
|
2265
|
+
},
|
|
2266
|
+
"amount_off": {
|
|
2267
|
+
"type": "string",
|
|
2268
|
+
"format": "decimal",
|
|
2269
|
+
"nullable": true
|
|
2270
|
+
},
|
|
2271
|
+
"currency": {
|
|
2272
|
+
"type": "string",
|
|
2273
|
+
"nullable": true
|
|
2274
|
+
},
|
|
2275
|
+
"percent_off": {
|
|
2276
|
+
"type": "string",
|
|
2277
|
+
"format": "decimal",
|
|
2278
|
+
"nullable": true
|
|
2279
|
+
},
|
|
2280
|
+
"duration": {
|
|
2281
|
+
"type": "string",
|
|
2282
|
+
"enum": [
|
|
2283
|
+
"forever",
|
|
2284
|
+
"once",
|
|
2285
|
+
"repeating"
|
|
2286
|
+
]
|
|
2287
|
+
},
|
|
2288
|
+
"duration_in_months": {
|
|
2289
|
+
"type": "integer",
|
|
2290
|
+
"nullable": true
|
|
2291
|
+
},
|
|
2292
|
+
"discount_display": {
|
|
2293
|
+
"type": "string"
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
},
|
|
2297
|
+
"promotion_code": {
|
|
2298
|
+
"type": "object",
|
|
2299
|
+
"nullable": true,
|
|
2300
|
+
"properties": {
|
|
2301
|
+
"id": {
|
|
2302
|
+
"type": "string"
|
|
2303
|
+
},
|
|
2304
|
+
"code": {
|
|
2305
|
+
"type": "string"
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
},
|
|
2309
|
+
"start": {
|
|
2310
|
+
"type": "string",
|
|
2311
|
+
"format": "date-time"
|
|
2312
|
+
},
|
|
2313
|
+
"end": {
|
|
2314
|
+
"type": "string",
|
|
2315
|
+
"format": "date-time",
|
|
2316
|
+
"nullable": true
|
|
2317
|
+
},
|
|
2318
|
+
"is_active": {
|
|
2319
|
+
"type": "boolean"
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
},
|
|
2094
2323
|
"V2SubscriptionOfferQuoteCreate": {
|
|
2095
2324
|
"type": "object",
|
|
2096
2325
|
"properties": {
|
|
@@ -2135,6 +2364,11 @@
|
|
|
2135
2364
|
"items": {
|
|
2136
2365
|
"$ref": "#/components/schemas/V2AdditionalBundle"
|
|
2137
2366
|
}
|
|
2367
|
+
},
|
|
2368
|
+
"promotion_code": {
|
|
2369
|
+
"type": "string",
|
|
2370
|
+
"nullable": true,
|
|
2371
|
+
"description": "Optional promotion code. When valid, the resulting discount is folded into the offer totals; on contract creation the code is redeemed and a discount is attached to the contract."
|
|
2138
2372
|
}
|
|
2139
2373
|
}
|
|
2140
2374
|
},
|
|
@@ -2405,58 +2639,14 @@
|
|
|
2405
2639
|
}
|
|
2406
2640
|
}
|
|
2407
2641
|
},
|
|
2408
|
-
"
|
|
2642
|
+
"V2SubscriptionContractChangeCommon": {
|
|
2409
2643
|
"type": "object",
|
|
2410
2644
|
"properties": {
|
|
2411
|
-
"operation": {
|
|
2412
|
-
"type": "string",
|
|
2413
|
-
"enum": [
|
|
2414
|
-
"add_item",
|
|
2415
|
-
"remove_item",
|
|
2416
|
-
"update_item",
|
|
2417
|
-
"pause",
|
|
2418
|
-
"resume",
|
|
2419
|
-
"cancel",
|
|
2420
|
-
"restart"
|
|
2421
|
-
],
|
|
2422
|
-
"default": "update_item"
|
|
2423
|
-
},
|
|
2424
|
-
"contract_item": {
|
|
2425
|
-
"type": "integer"
|
|
2426
|
-
},
|
|
2427
|
-
"price": {
|
|
2428
|
-
"type": "integer",
|
|
2429
|
-
"nullable": true
|
|
2430
|
-
},
|
|
2431
|
-
"quantity": {
|
|
2432
|
-
"type": "integer",
|
|
2433
|
-
"nullable": true
|
|
2434
|
-
},
|
|
2435
|
-
"discount_percent": {
|
|
2436
|
-
"type": "string",
|
|
2437
|
-
"format": "decimal",
|
|
2438
|
-
"nullable": true
|
|
2439
|
-
},
|
|
2440
2645
|
"effective_at": {
|
|
2441
2646
|
"type": "string",
|
|
2442
2647
|
"format": "date-time",
|
|
2443
2648
|
"nullable": true
|
|
2444
2649
|
},
|
|
2445
|
-
"start_date": {
|
|
2446
|
-
"type": "string",
|
|
2447
|
-
"format": "date",
|
|
2448
|
-
"nullable": true
|
|
2449
|
-
},
|
|
2450
|
-
"end_date": {
|
|
2451
|
-
"type": "string",
|
|
2452
|
-
"format": "date",
|
|
2453
|
-
"nullable": true
|
|
2454
|
-
},
|
|
2455
|
-
"pause_end_at": {
|
|
2456
|
-
"type": "string",
|
|
2457
|
-
"format": "date-time",
|
|
2458
|
-
"nullable": true
|
|
2459
|
-
},
|
|
2460
2650
|
"proration_behavior": {
|
|
2461
2651
|
"type": "string",
|
|
2462
2652
|
"enum": [
|
|
@@ -2486,13 +2676,86 @@
|
|
|
2486
2676
|
}
|
|
2487
2677
|
}
|
|
2488
2678
|
},
|
|
2679
|
+
"V2SubscriptionContractProrationPreview": {
|
|
2680
|
+
"description": "Polymorphic preview request. Fields beyond the common ones are operation-specific; fields not supported by the selected operation are rejected with a 400 validation error.",
|
|
2681
|
+
"allOf": [
|
|
2682
|
+
{
|
|
2683
|
+
"$ref": "#/components/schemas/V2SubscriptionContractChangeCommon"
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
"type": "object",
|
|
2687
|
+
"properties": {
|
|
2688
|
+
"operation": {
|
|
2689
|
+
"type": "string",
|
|
2690
|
+
"enum": [
|
|
2691
|
+
"add_item",
|
|
2692
|
+
"remove_item",
|
|
2693
|
+
"update_item",
|
|
2694
|
+
"pause",
|
|
2695
|
+
"resume",
|
|
2696
|
+
"cancel",
|
|
2697
|
+
"restart"
|
|
2698
|
+
],
|
|
2699
|
+
"default": "update_item"
|
|
2700
|
+
},
|
|
2701
|
+
"contract_item": {
|
|
2702
|
+
"type": "integer",
|
|
2703
|
+
"description": "Required for update_item and remove_item."
|
|
2704
|
+
},
|
|
2705
|
+
"price": {
|
|
2706
|
+
"type": "integer",
|
|
2707
|
+
"nullable": true,
|
|
2708
|
+
"description": "Only applies to add_item and update_item. Required for add_item."
|
|
2709
|
+
},
|
|
2710
|
+
"quantity": {
|
|
2711
|
+
"type": "integer",
|
|
2712
|
+
"nullable": true,
|
|
2713
|
+
"description": "Only applies to add_item and update_item."
|
|
2714
|
+
},
|
|
2715
|
+
"discount_percent": {
|
|
2716
|
+
"type": "string",
|
|
2717
|
+
"format": "decimal",
|
|
2718
|
+
"nullable": true,
|
|
2719
|
+
"description": "Only applies to add_item and update_item."
|
|
2720
|
+
},
|
|
2721
|
+
"unit_amount_override": {
|
|
2722
|
+
"type": "string",
|
|
2723
|
+
"format": "decimal",
|
|
2724
|
+
"nullable": true,
|
|
2725
|
+
"description": "Fixed per-unit amount for the item, used instead of the catalog price. Only applies to add_item and update_item. On update_item, omitting the field keeps the item's current override while an explicit null clears it so the item reverts to the catalog price."
|
|
2726
|
+
},
|
|
2727
|
+
"start_date": {
|
|
2728
|
+
"type": "string",
|
|
2729
|
+
"format": "date",
|
|
2730
|
+
"nullable": true,
|
|
2731
|
+
"description": "Only applies to pause."
|
|
2732
|
+
},
|
|
2733
|
+
"end_date": {
|
|
2734
|
+
"type": "string",
|
|
2735
|
+
"format": "date",
|
|
2736
|
+
"nullable": true,
|
|
2737
|
+
"description": "Only applies to pause."
|
|
2738
|
+
},
|
|
2739
|
+
"pause_end_at": {
|
|
2740
|
+
"type": "string",
|
|
2741
|
+
"format": "date-time",
|
|
2742
|
+
"nullable": true,
|
|
2743
|
+
"description": "Only applies to pause."
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
]
|
|
2748
|
+
},
|
|
2489
2749
|
"V2SubscriptionContractItemAdd": {
|
|
2490
2750
|
"allOf": [
|
|
2491
2751
|
{
|
|
2492
|
-
"$ref": "#/components/schemas/
|
|
2752
|
+
"$ref": "#/components/schemas/V2SubscriptionContractChangeCommon"
|
|
2493
2753
|
},
|
|
2494
2754
|
{
|
|
2495
2755
|
"type": "object",
|
|
2756
|
+
"required": [
|
|
2757
|
+
"price"
|
|
2758
|
+
],
|
|
2496
2759
|
"properties": {
|
|
2497
2760
|
"operation": {
|
|
2498
2761
|
"type": "string",
|
|
@@ -2501,6 +2764,24 @@
|
|
|
2501
2764
|
],
|
|
2502
2765
|
"default": "add_item"
|
|
2503
2766
|
},
|
|
2767
|
+
"price": {
|
|
2768
|
+
"type": "integer"
|
|
2769
|
+
},
|
|
2770
|
+
"quantity": {
|
|
2771
|
+
"type": "integer",
|
|
2772
|
+
"nullable": true
|
|
2773
|
+
},
|
|
2774
|
+
"discount_percent": {
|
|
2775
|
+
"type": "string",
|
|
2776
|
+
"format": "decimal",
|
|
2777
|
+
"nullable": true
|
|
2778
|
+
},
|
|
2779
|
+
"unit_amount_override": {
|
|
2780
|
+
"type": "string",
|
|
2781
|
+
"format": "decimal",
|
|
2782
|
+
"nullable": true,
|
|
2783
|
+
"description": "Fixed per-unit amount for the new item, used instead of the catalog price."
|
|
2784
|
+
},
|
|
2504
2785
|
"idempotency_key": {
|
|
2505
2786
|
"type": "string",
|
|
2506
2787
|
"nullable": true
|
|
@@ -2515,7 +2796,7 @@
|
|
|
2515
2796
|
"V2SubscriptionContractItemRemove": {
|
|
2516
2797
|
"allOf": [
|
|
2517
2798
|
{
|
|
2518
|
-
"$ref": "#/components/schemas/
|
|
2799
|
+
"$ref": "#/components/schemas/V2SubscriptionContractChangeCommon"
|
|
2519
2800
|
},
|
|
2520
2801
|
{
|
|
2521
2802
|
"type": "object",
|
|
@@ -2530,6 +2811,9 @@
|
|
|
2530
2811
|
],
|
|
2531
2812
|
"default": "remove_item"
|
|
2532
2813
|
},
|
|
2814
|
+
"contract_item": {
|
|
2815
|
+
"type": "integer"
|
|
2816
|
+
},
|
|
2533
2817
|
"idempotency_key": {
|
|
2534
2818
|
"type": "string",
|
|
2535
2819
|
"nullable": true
|
|
@@ -2544,7 +2828,7 @@
|
|
|
2544
2828
|
"V2SubscriptionContractItemUpdate": {
|
|
2545
2829
|
"allOf": [
|
|
2546
2830
|
{
|
|
2547
|
-
"$ref": "#/components/schemas/
|
|
2831
|
+
"$ref": "#/components/schemas/V2SubscriptionContractChangeCommon"
|
|
2548
2832
|
},
|
|
2549
2833
|
{
|
|
2550
2834
|
"type": "object",
|
|
@@ -2552,6 +2836,35 @@
|
|
|
2552
2836
|
"contract_item"
|
|
2553
2837
|
],
|
|
2554
2838
|
"properties": {
|
|
2839
|
+
"operation": {
|
|
2840
|
+
"type": "string",
|
|
2841
|
+
"enum": [
|
|
2842
|
+
"update_item"
|
|
2843
|
+
],
|
|
2844
|
+
"default": "update_item"
|
|
2845
|
+
},
|
|
2846
|
+
"contract_item": {
|
|
2847
|
+
"type": "integer"
|
|
2848
|
+
},
|
|
2849
|
+
"price": {
|
|
2850
|
+
"type": "integer",
|
|
2851
|
+
"nullable": true
|
|
2852
|
+
},
|
|
2853
|
+
"quantity": {
|
|
2854
|
+
"type": "integer",
|
|
2855
|
+
"nullable": true
|
|
2856
|
+
},
|
|
2857
|
+
"discount_percent": {
|
|
2858
|
+
"type": "string",
|
|
2859
|
+
"format": "decimal",
|
|
2860
|
+
"nullable": true
|
|
2861
|
+
},
|
|
2862
|
+
"unit_amount_override": {
|
|
2863
|
+
"type": "string",
|
|
2864
|
+
"format": "decimal",
|
|
2865
|
+
"nullable": true,
|
|
2866
|
+
"description": "Fixed per-unit amount for the item, used instead of the catalog price. Omitting the field keeps the item's current override; an explicit null clears it so the item reverts to the catalog price."
|
|
2867
|
+
},
|
|
2555
2868
|
"idempotency_key": {
|
|
2556
2869
|
"type": "string",
|
|
2557
2870
|
"nullable": true
|
|
@@ -2688,9 +3001,15 @@
|
|
|
2688
3001
|
"type": "string",
|
|
2689
3002
|
"format": "decimal"
|
|
2690
3003
|
},
|
|
3004
|
+
"discount_amount_due_now": {
|
|
3005
|
+
"type": "string",
|
|
3006
|
+
"format": "decimal",
|
|
3007
|
+
"description": "Contract coupon discount applied to the immediate (invoice_now) charge. Percent-off, non-once coupons only; 0.0000 otherwise."
|
|
3008
|
+
},
|
|
2691
3009
|
"next_billing_estimate": {
|
|
2692
3010
|
"type": "string",
|
|
2693
|
-
"format": "decimal"
|
|
3011
|
+
"format": "decimal",
|
|
3012
|
+
"description": "Estimated next recurring charge, net of the contract's active coupon discount."
|
|
2694
3013
|
},
|
|
2695
3014
|
"resolved_price_version_ids": {
|
|
2696
3015
|
"type": "object",
|
|
@@ -3537,6 +3856,64 @@
|
|
|
3537
3856
|
"billing_schedule_preview": {
|
|
3538
3857
|
"$ref": "#/components/schemas/V2Metadata"
|
|
3539
3858
|
},
|
|
3859
|
+
"promotion_code": {
|
|
3860
|
+
"type": "string",
|
|
3861
|
+
"nullable": true,
|
|
3862
|
+
"description": "The promotion code applied to this quote, if any. When set, subtotal_amount, tax_amount, and total_amount already include the discount."
|
|
3863
|
+
},
|
|
3864
|
+
"discount": {
|
|
3865
|
+
"type": "object",
|
|
3866
|
+
"nullable": true,
|
|
3867
|
+
"description": "Discount preview when a promotion code was applied.",
|
|
3868
|
+
"properties": {
|
|
3869
|
+
"code": {
|
|
3870
|
+
"type": "string"
|
|
3871
|
+
},
|
|
3872
|
+
"coupon_display": {
|
|
3873
|
+
"type": "string"
|
|
3874
|
+
},
|
|
3875
|
+
"duration": {
|
|
3876
|
+
"type": "string",
|
|
3877
|
+
"enum": [
|
|
3878
|
+
"forever",
|
|
3879
|
+
"once",
|
|
3880
|
+
"repeating"
|
|
3881
|
+
]
|
|
3882
|
+
},
|
|
3883
|
+
"duration_in_months": {
|
|
3884
|
+
"type": "integer",
|
|
3885
|
+
"nullable": true
|
|
3886
|
+
},
|
|
3887
|
+
"skip_trial_period": {
|
|
3888
|
+
"type": "boolean",
|
|
3889
|
+
"description": "When true, the coupon skipped the offer's trial period: the quoted totals bill the first period immediately."
|
|
3890
|
+
},
|
|
3891
|
+
"discount_subtotal_amount": {
|
|
3892
|
+
"type": "string",
|
|
3893
|
+
"format": "decimal"
|
|
3894
|
+
},
|
|
3895
|
+
"discount_tax_amount": {
|
|
3896
|
+
"type": "string",
|
|
3897
|
+
"format": "decimal"
|
|
3898
|
+
},
|
|
3899
|
+
"discount_total_amount": {
|
|
3900
|
+
"type": "string",
|
|
3901
|
+
"format": "decimal"
|
|
3902
|
+
},
|
|
3903
|
+
"original_subtotal_amount": {
|
|
3904
|
+
"type": "string",
|
|
3905
|
+
"format": "decimal"
|
|
3906
|
+
},
|
|
3907
|
+
"original_tax_amount": {
|
|
3908
|
+
"type": "string",
|
|
3909
|
+
"format": "decimal"
|
|
3910
|
+
},
|
|
3911
|
+
"original_total_amount": {
|
|
3912
|
+
"type": "string",
|
|
3913
|
+
"format": "decimal"
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
},
|
|
3540
3917
|
"recurring_items": {
|
|
3541
3918
|
"type": "array",
|
|
3542
3919
|
"items": {
|
|
@@ -4253,6 +4630,15 @@
|
|
|
4253
4630
|
"customer_reference": {
|
|
4254
4631
|
"type": "string"
|
|
4255
4632
|
},
|
|
4633
|
+
"discount": {
|
|
4634
|
+
"allOf": [
|
|
4635
|
+
{
|
|
4636
|
+
"$ref": "#/components/schemas/V2ContractDiscount"
|
|
4637
|
+
}
|
|
4638
|
+
],
|
|
4639
|
+
"nullable": true,
|
|
4640
|
+
"description": "The active coupon discount on this contract, if any."
|
|
4641
|
+
},
|
|
4256
4642
|
"state": {
|
|
4257
4643
|
"type": "string"
|
|
4258
4644
|
},
|
|
@@ -59,7 +59,6 @@ function summarizeListItem(item: unknown): unknown {
|
|
|
59
59
|
'email',
|
|
60
60
|
'first_name',
|
|
61
61
|
'last_name',
|
|
62
|
-
'description',
|
|
63
62
|
'currency',
|
|
64
63
|
'amount',
|
|
65
64
|
'total_amount',
|
|
@@ -91,6 +90,56 @@ function summarizeListItem(item: unknown): unknown {
|
|
|
91
90
|
return Object.keys(out).length > 0 ? out : obj;
|
|
92
91
|
}
|
|
93
92
|
|
|
93
|
+
/** Tight projection for analytical list queries (dates, plan name, customer). */
|
|
94
|
+
function indexListItem(item: unknown): unknown {
|
|
95
|
+
if (item == null || typeof item !== 'object' || Array.isArray(item)) {
|
|
96
|
+
return item;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const obj = item as Record<string, unknown>;
|
|
100
|
+
const out: Record<string, unknown> = {};
|
|
101
|
+
|
|
102
|
+
if ('id' in obj) {
|
|
103
|
+
out.id = obj.id;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if ('start_date' in obj) {
|
|
107
|
+
out.start_date = obj.start_date;
|
|
108
|
+
} else if ('created_at' in obj) {
|
|
109
|
+
out.created_at = obj.created_at;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ('ended_at' in obj && obj.ended_at != null) {
|
|
113
|
+
out.ended_at = obj.ended_at;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const plan = obj.plan;
|
|
117
|
+
if (typeof plan === 'string' || typeof plan === 'number') {
|
|
118
|
+
out.plan = plan;
|
|
119
|
+
} else if (plan && typeof plan === 'object' && 'name' in plan) {
|
|
120
|
+
out.plan = (plan as { name: unknown }).name;
|
|
121
|
+
} else if ('name' in obj && typeof obj.name === 'string') {
|
|
122
|
+
out.name = obj.name;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const customerRef =
|
|
126
|
+
obj.customer_reference ??
|
|
127
|
+
(obj.customer && typeof obj.customer === 'object'
|
|
128
|
+
? ((obj.customer as Record<string, unknown>).customer_reference ??
|
|
129
|
+
(obj.customer as Record<string, unknown>).reference ??
|
|
130
|
+
(obj.customer as Record<string, unknown>).id)
|
|
131
|
+
: undefined);
|
|
132
|
+
if (customerRef !== undefined) {
|
|
133
|
+
out.customer_reference = customerRef;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof obj.email === 'string') {
|
|
137
|
+
out.email = obj.email;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return Object.keys(out).length > 0 ? out : summarizeListItem(item);
|
|
141
|
+
}
|
|
142
|
+
|
|
94
143
|
function serializeListPayload(
|
|
95
144
|
status: number,
|
|
96
145
|
meta: Record<string, unknown>,
|
|
@@ -146,6 +195,8 @@ function maxFittingCount(
|
|
|
146
195
|
return lo;
|
|
147
196
|
}
|
|
148
197
|
|
|
198
|
+
type CompactedMode = 'none' | 'summary' | 'index';
|
|
199
|
+
|
|
149
200
|
export function buildBoundedListPayload(input: {
|
|
150
201
|
status: number;
|
|
151
202
|
meta: Record<string, unknown>;
|
|
@@ -153,30 +204,42 @@ export function buildBoundedListPayload(input: {
|
|
|
153
204
|
maxBytes: number;
|
|
154
205
|
}): FormattedResponse {
|
|
155
206
|
const { status, meta, items, maxBytes } = input;
|
|
156
|
-
const itemCount = items.length;
|
|
157
207
|
|
|
158
208
|
const attempts: Array<{
|
|
159
209
|
items: unknown[];
|
|
160
210
|
pretty: boolean;
|
|
161
|
-
|
|
211
|
+
compactedMode: CompactedMode;
|
|
162
212
|
note?: string;
|
|
163
213
|
}> = [
|
|
164
|
-
{ items, pretty: true,
|
|
165
|
-
{ items, pretty: false,
|
|
214
|
+
{ items, pretty: true, compactedMode: 'none' },
|
|
215
|
+
{ items, pretty: false, compactedMode: 'none' },
|
|
166
216
|
{
|
|
167
217
|
items: items.map(summarizeListItem),
|
|
168
218
|
pretty: false,
|
|
169
|
-
|
|
219
|
+
compactedMode: 'summary',
|
|
170
220
|
note: 'Items summarized to fit responseMaxBytes',
|
|
171
221
|
},
|
|
222
|
+
{
|
|
223
|
+
items: items.map(indexListItem),
|
|
224
|
+
pretty: false,
|
|
225
|
+
compactedMode: 'index',
|
|
226
|
+
note: 'Items reduced to an index (id, dates, plan, customer) to fit responseMaxBytes',
|
|
227
|
+
},
|
|
172
228
|
];
|
|
173
229
|
|
|
174
230
|
let fullByteLength = 0;
|
|
231
|
+
let bestPartial: {
|
|
232
|
+
text: string;
|
|
233
|
+
returnedCount: number;
|
|
234
|
+
compactedMode: CompactedMode;
|
|
235
|
+
note?: string;
|
|
236
|
+
} | null = null;
|
|
175
237
|
|
|
176
238
|
for (const attempt of attempts) {
|
|
239
|
+
const attemptMeta = compactMeta(meta, attempt.compactedMode, attempt.note);
|
|
177
240
|
const fullText = serializeListPayload(
|
|
178
241
|
status,
|
|
179
|
-
|
|
242
|
+
attemptMeta,
|
|
180
243
|
attempt.items,
|
|
181
244
|
attempt.items.length,
|
|
182
245
|
false,
|
|
@@ -195,42 +258,43 @@ export function buildBoundedListPayload(input: {
|
|
|
195
258
|
const returnedCount = maxFittingCount(
|
|
196
259
|
attempt.items,
|
|
197
260
|
status,
|
|
198
|
-
|
|
199
|
-
...meta,
|
|
200
|
-
...(attempt.compacted ? { compacted: true, note: attempt.note } : {}),
|
|
201
|
-
},
|
|
261
|
+
attemptMeta,
|
|
202
262
|
maxBytes,
|
|
203
263
|
attempt.pretty,
|
|
204
264
|
);
|
|
205
265
|
|
|
206
|
-
if (returnedCount > 0) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
266
|
+
if (returnedCount > (bestPartial?.returnedCount ?? 0)) {
|
|
267
|
+
bestPartial = {
|
|
268
|
+
text: serializeListPayload(
|
|
269
|
+
status,
|
|
270
|
+
attemptMeta,
|
|
271
|
+
attempt.items,
|
|
272
|
+
returnedCount,
|
|
273
|
+
true,
|
|
274
|
+
attempt.pretty,
|
|
275
|
+
),
|
|
214
276
|
returnedCount,
|
|
215
|
-
|
|
216
|
-
attempt.
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
text,
|
|
221
|
-
truncated: true,
|
|
222
|
-
byteLength: fullByteLength,
|
|
277
|
+
compactedMode: attempt.compactedMode,
|
|
278
|
+
note: attempt.note,
|
|
223
279
|
};
|
|
224
280
|
}
|
|
225
281
|
}
|
|
226
282
|
|
|
283
|
+
if (bestPartial && bestPartial.returnedCount > 0) {
|
|
284
|
+
return {
|
|
285
|
+
text: bestPartial.text,
|
|
286
|
+
truncated: true,
|
|
287
|
+
byteLength: fullByteLength,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
227
291
|
const text = serializeListPayload(
|
|
228
292
|
status,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
293
|
+
compactMeta(
|
|
294
|
+
meta,
|
|
295
|
+
'index',
|
|
296
|
+
'Response too large; returning metadata only',
|
|
297
|
+
),
|
|
234
298
|
[],
|
|
235
299
|
0,
|
|
236
300
|
true,
|
|
@@ -244,6 +308,23 @@ export function buildBoundedListPayload(input: {
|
|
|
244
308
|
};
|
|
245
309
|
}
|
|
246
310
|
|
|
311
|
+
function compactMeta(
|
|
312
|
+
meta: Record<string, unknown>,
|
|
313
|
+
compactedMode: CompactedMode,
|
|
314
|
+
note?: string,
|
|
315
|
+
): Record<string, unknown> {
|
|
316
|
+
if (compactedMode === 'none') {
|
|
317
|
+
return meta;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
...meta,
|
|
322
|
+
compacted: true,
|
|
323
|
+
compactedMode,
|
|
324
|
+
...(note ? { note } : {}),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
247
328
|
export function formatApiResponse(
|
|
248
329
|
status: number,
|
|
249
330
|
headers: Headers,
|
package/src/config.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
|
|
3
|
+
const httpUrl = z
|
|
4
|
+
.url({ protocol: /^https?$/ })
|
|
5
|
+
.describe('Askell API base URL (default production host)');
|
|
6
|
+
|
|
3
7
|
export const ConfigSchema = z.object({
|
|
4
|
-
apiBaseUrl:
|
|
5
|
-
.httpUrl()
|
|
6
|
-
.default('https://askell.is/api')
|
|
7
|
-
.describe('Askell API base URL (default production host)'),
|
|
8
|
+
apiBaseUrl: httpUrl.default('https://askell.is/api'),
|
|
8
9
|
secretApiKey: z.string().min(1).describe('Secret (private) API key'),
|
|
9
10
|
publicApiKey: z
|
|
10
11
|
.string()
|
|
12
|
+
.min(1)
|
|
11
13
|
.optional()
|
|
12
14
|
.describe('Public API key for temporary payment method endpoints'),
|
|
13
|
-
responseMaxBytes: z
|
|
15
|
+
responseMaxBytes: z.coerce
|
|
16
|
+
.number()
|
|
14
17
|
.int()
|
|
15
18
|
.positive()
|
|
16
19
|
.default(64_000)
|
|
17
20
|
.describe('Max response body size returned to the model'),
|
|
18
21
|
requireMutationApproval: z
|
|
19
|
-
.boolean()
|
|
22
|
+
.union([z.boolean(), z.stringbool()])
|
|
20
23
|
.default(true)
|
|
21
24
|
.describe('Require operator confirmation before mutating requests'),
|
|
22
25
|
});
|
|
@@ -45,17 +48,6 @@ Set ASKELL_PRIVATE_API_KEY (or ASKELL_SECRET_API_KEY), optionally ASKELL_PUBLIC_
|
|
|
45
48
|
}
|
|
46
49
|
}`;
|
|
47
50
|
|
|
48
|
-
function parseEnvFlag(
|
|
49
|
-
value: string | undefined,
|
|
50
|
-
defaultValue: boolean,
|
|
51
|
-
): boolean {
|
|
52
|
-
if (value === undefined) {
|
|
53
|
-
return defaultValue;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return !['0', 'false', 'no', 'off'].includes(value.toLowerCase());
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
function loadConfigFromEnv(): unknown {
|
|
60
52
|
const env = Bun.env;
|
|
61
53
|
const secretApiKey = env.ASKELL_PRIVATE_API_KEY ?? env.ASKELL_SECRET_API_KEY;
|
|
@@ -74,11 +66,9 @@ function loadConfigFromEnv(): unknown {
|
|
|
74
66
|
...(env.ASKELL_PUBLIC_API_KEY
|
|
75
67
|
? { publicApiKey: env.ASKELL_PUBLIC_API_KEY }
|
|
76
68
|
: {}),
|
|
77
|
-
...(responseMaxBytes ? { responseMaxBytes
|
|
69
|
+
...(responseMaxBytes ? { responseMaxBytes } : {}),
|
|
78
70
|
...(requireMutationApproval !== undefined
|
|
79
|
-
? {
|
|
80
|
-
requireMutationApproval: parseEnvFlag(requireMutationApproval, true),
|
|
81
|
-
}
|
|
71
|
+
? { requireMutationApproval }
|
|
82
72
|
: {}),
|
|
83
73
|
};
|
|
84
74
|
}
|
package/src/openapi/registry.ts
CHANGED
|
@@ -69,20 +69,30 @@ function resolveParameters(
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return parameters.map((parameter) => {
|
|
72
|
+
let source: OpenApiParameter = parameter;
|
|
73
|
+
|
|
72
74
|
if ('$ref' in parameter && typeof parameter.$ref === 'string') {
|
|
73
75
|
const resolved = resolveRef(doc, parameter.$ref);
|
|
74
76
|
if (resolved && typeof resolved === 'object') {
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
...param,
|
|
78
|
-
schema: resolveSchema(doc, param.schema),
|
|
79
|
-
};
|
|
77
|
+
source = resolved as OpenApiParameter;
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
80
|
|
|
81
|
+
// Pick known fields only — OpenAPI params often include style/explode/example,
|
|
82
|
+
// which MCP outputSchema rejects (additionalProperties: false).
|
|
83
83
|
return {
|
|
84
|
-
...
|
|
85
|
-
|
|
84
|
+
...(source.name !== undefined ? { name: source.name } : {}),
|
|
85
|
+
...(source.in !== undefined ? { in: source.in } : {}),
|
|
86
|
+
...(source.required !== undefined ? { required: source.required } : {}),
|
|
87
|
+
...(source.description !== undefined
|
|
88
|
+
? { description: source.description }
|
|
89
|
+
: {}),
|
|
90
|
+
...(source.schema !== undefined
|
|
91
|
+
? { schema: resolveSchema(doc, source.schema) }
|
|
92
|
+
: {}),
|
|
93
|
+
...(source.$ref !== undefined && source.name === undefined
|
|
94
|
+
? { $ref: source.$ref }
|
|
95
|
+
: {}),
|
|
86
96
|
};
|
|
87
97
|
});
|
|
88
98
|
}
|
package/src/server.ts
CHANGED
|
@@ -26,6 +26,11 @@ API layout:
|
|
|
26
26
|
- V2 list endpoints paginate only when page_size is provided (default 10, max 1000).
|
|
27
27
|
- GET /v2/customer-entitlements/ requires customer_reference query param.
|
|
28
28
|
|
|
29
|
+
V2 discounts (coupons / promotion codes) — not the same as v1:
|
|
30
|
+
- v2 contracts: one active coupon at a time. GET /v2/subscription-contracts/{id}/discount/ (also nested as contract.discount). Apply with POST .../apply-code/ {promotion_code}. Remove with POST .../remove-discount/.
|
|
31
|
+
- Quotes: pass promotion_code on POST /v2/subscription-offer-quotes/; totals already include the discount when set.
|
|
32
|
+
- v1 Subscription.discount is a 0-100 percent override on a PlanVariant subscription. Do not send it to v2 contract endpoints.
|
|
33
|
+
|
|
29
34
|
V2 checkout notes:
|
|
30
35
|
- checkout_url on V2 checkouts points to the API object URL, not a browser payment page.
|
|
31
36
|
- Embedded checkout uses POST /v2/checkout-sessions/ plus browser session-token sub-paths (see docs, not all in OpenAPI).
|
|
@@ -36,7 +41,7 @@ Auth:
|
|
|
36
41
|
|
|
37
42
|
Safety:
|
|
38
43
|
- Mutating askell_call requests require operator approval when requireMutationApproval is enabled.
|
|
39
|
-
- Large list responses
|
|
44
|
+
- Large list responses are compacted (index of id/dates/plan/customer) to fit responseMaxBytes before dropping rows; check meta.truncatedByMaxBytes, meta.compacted, and meta.compactedMode.
|
|
40
45
|
|
|
41
46
|
Resources:
|
|
42
47
|
- askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
|
package/src/tools/analysis.ts
CHANGED
|
@@ -36,12 +36,23 @@ export function registerAnalysisTools(
|
|
|
36
36
|
{
|
|
37
37
|
title: 'Paginate Askell list endpoint',
|
|
38
38
|
description:
|
|
39
|
-
'Fetch all pages from a paginated Askell list endpoint (v1/v2). Follows `next` links until exhausted or maxPages is reached.
|
|
39
|
+
'Fetch all pages from a paginated Askell list endpoint (v1/v2). Follows `next` links until exhausted or maxPages is reached. When the full payload exceeds responseMaxBytes, items are compacted (summary, then an index of id/dates/plan/customer) so rows are kept — dropping rows is last resort. Check meta.truncatedByMaxBytes, meta.compacted, and meta.compactedMode.',
|
|
40
40
|
inputSchema: z.object({
|
|
41
41
|
path: z.string().describe('List endpoint path, e.g. /subscriptions/'),
|
|
42
|
-
query: z
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
query: z
|
|
43
|
+
.record(z.string(), z.json())
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Query string parameters forwarded to the list endpoint'),
|
|
46
|
+
apiKeyKind: z
|
|
47
|
+
.enum(['secret', 'public'])
|
|
48
|
+
.default('secret')
|
|
49
|
+
.describe('Which configured API key to use'),
|
|
50
|
+
maxPages: z
|
|
51
|
+
.int()
|
|
52
|
+
.positive()
|
|
53
|
+
.max(100)
|
|
54
|
+
.default(20)
|
|
55
|
+
.describe('Stop after this many pages (default 20, max 100)'),
|
|
45
56
|
}),
|
|
46
57
|
annotations: {
|
|
47
58
|
readOnlyHint: true,
|
|
@@ -130,10 +141,17 @@ export function registerAnalysisTools(
|
|
|
130
141
|
{
|
|
131
142
|
title: 'Subscription contract overview (v2)',
|
|
132
143
|
description:
|
|
133
|
-
'Fetch a v2 subscription contract and recent billing runs filtered by contract id.',
|
|
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.',
|
|
134
145
|
inputSchema: z.object({
|
|
135
|
-
contractId: z
|
|
136
|
-
|
|
146
|
+
contractId: z
|
|
147
|
+
.union([z.string().min(1), z.int()])
|
|
148
|
+
.describe('V2 subscription contract id'),
|
|
149
|
+
billingRunLimit: z
|
|
150
|
+
.int()
|
|
151
|
+
.positive()
|
|
152
|
+
.max(50)
|
|
153
|
+
.default(10)
|
|
154
|
+
.describe('Max billing runs to include (page_size)'),
|
|
137
155
|
}),
|
|
138
156
|
annotations: {
|
|
139
157
|
readOnlyHint: true,
|
|
@@ -192,8 +210,13 @@ export function registerAnalysisTools(
|
|
|
192
210
|
description:
|
|
193
211
|
'Fetch a billing run by id with optional related contract context for failure analysis.',
|
|
194
212
|
inputSchema: z.object({
|
|
195
|
-
billingRunId: z
|
|
196
|
-
|
|
213
|
+
billingRunId: z
|
|
214
|
+
.union([z.string().min(1), z.int()])
|
|
215
|
+
.describe('V2 billing run id'),
|
|
216
|
+
includeContract: z
|
|
217
|
+
.boolean()
|
|
218
|
+
.default(true)
|
|
219
|
+
.describe('Also fetch the related subscription contract when the run has a contract id'),
|
|
197
220
|
}),
|
|
198
221
|
annotations: {
|
|
199
222
|
readOnlyHint: true,
|
|
@@ -257,7 +280,12 @@ export function registerAnalysisTools(
|
|
|
257
280
|
description:
|
|
258
281
|
'List Askell webhook endpoints configured for the account (management API only).',
|
|
259
282
|
inputSchema: z.object({
|
|
260
|
-
page_size: z
|
|
283
|
+
page_size: z
|
|
284
|
+
.int()
|
|
285
|
+
.positive()
|
|
286
|
+
.max(1000)
|
|
287
|
+
.optional()
|
|
288
|
+
.describe('Page size for GET /webhooks/ (Askell default 10, max 1000)'),
|
|
261
289
|
}),
|
|
262
290
|
annotations: {
|
|
263
291
|
readOnlyHint: true,
|
package/src/tools/call.ts
CHANGED
|
@@ -25,10 +25,10 @@ const callInputSchema = z.object({
|
|
|
25
25
|
.string()
|
|
26
26
|
.describe('API path relative to apiBaseUrl, e.g. /v2/subscription-contracts/'),
|
|
27
27
|
query: z
|
|
28
|
-
.record(z.string(), z.
|
|
28
|
+
.record(z.string(), z.json())
|
|
29
29
|
.optional()
|
|
30
30
|
.describe('Query string parameters'),
|
|
31
|
-
body: z.
|
|
31
|
+
body: z.json().optional().describe('JSON request body'),
|
|
32
32
|
apiKeyKind: z
|
|
33
33
|
.enum(['secret', 'public'])
|
|
34
34
|
.default('secret')
|
package/src/tools/discovery.ts
CHANGED
|
@@ -77,11 +77,11 @@ const openApiParameterSchema = z.object({
|
|
|
77
77
|
in: z.enum(['query', 'path', 'header', 'cookie']).optional(),
|
|
78
78
|
required: z.boolean().optional(),
|
|
79
79
|
description: z.string().optional(),
|
|
80
|
-
schema: z.
|
|
80
|
+
schema: z.json().optional(),
|
|
81
81
|
$ref: z.string().optional(),
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
const operationDetailSchema = z.object({
|
|
84
|
+
export const operationDetailSchema = z.object({
|
|
85
85
|
id: z.string(),
|
|
86
86
|
apiVersion: apiVersionSchema,
|
|
87
87
|
method: httpMethodSchema,
|
|
@@ -95,7 +95,7 @@ const operationDetailSchema = z.object({
|
|
|
95
95
|
required: z.boolean().optional(),
|
|
96
96
|
description: z.string().optional(),
|
|
97
97
|
contentTypes: z.array(z.string()),
|
|
98
|
-
schema: z.
|
|
98
|
+
schema: z.json().optional(),
|
|
99
99
|
})
|
|
100
100
|
.optional(),
|
|
101
101
|
apiKeyKind: apiKeyKindSchema,
|
|
@@ -171,9 +171,13 @@ export function registerDiscoveryTools(server: McpServer): void {
|
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// Strip OpenAPI extras (style/explode/…) so structuredContent matches
|
|
175
|
+
// the output JSON Schema (additionalProperties: false).
|
|
176
|
+
const payload = operationDetailSchema.parse(operation);
|
|
177
|
+
|
|
174
178
|
return {
|
|
175
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
176
|
-
structuredContent:
|
|
179
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
180
|
+
structuredContent: payload,
|
|
177
181
|
};
|
|
178
182
|
},
|
|
179
183
|
);
|