askell-mcp 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/spec/openapi-v1.json +170 -46
- package/src/openapi/patch-v1.ts +238 -0
- package/src/resources/register.ts +2 -2
- package/src/server.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "askell-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP server for the Askell payment and subscription API (Bun + stdio)",
|
|
5
5
|
"author": "Neschadin Oleksandr",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,6 +65,6 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@modelcontextprotocol/server": "2.0.0",
|
|
68
|
-
"zod": "4.4
|
|
68
|
+
"zod": "4.5.4"
|
|
69
69
|
}
|
|
70
70
|
}
|
package/spec/openapi-v1.json
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"name": "Documentation",
|
|
9
9
|
"url": "https://docs.askell.is"
|
|
10
10
|
},
|
|
11
|
-
"version": "1.0.0"
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"x-askell-mcp-patched": "v1-overlay-1"
|
|
12
13
|
},
|
|
13
14
|
"servers": [
|
|
14
15
|
{
|
|
@@ -52,10 +53,6 @@
|
|
|
52
53
|
"name": "Webhooks",
|
|
53
54
|
"description": "Management of webhooks. Requires secret api key."
|
|
54
55
|
},
|
|
55
|
-
{
|
|
56
|
-
"name": "Webhook calls",
|
|
57
|
-
"description": "Webhook calls from Áskell to your system."
|
|
58
|
-
},
|
|
59
56
|
{
|
|
60
57
|
"name": "Transaction",
|
|
61
58
|
"description": "Transaction operations, requires secret api key."
|
|
@@ -339,7 +336,14 @@
|
|
|
339
336
|
},
|
|
340
337
|
"responses": {
|
|
341
338
|
"201": {
|
|
342
|
-
"description": "Successful operation"
|
|
339
|
+
"description": "Successful operation",
|
|
340
|
+
"content": {
|
|
341
|
+
"application/json": {
|
|
342
|
+
"schema": {
|
|
343
|
+
"$ref": "#/components/schemas/Customer"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
343
347
|
},
|
|
344
348
|
"400": {
|
|
345
349
|
"description": "Invalid status value",
|
|
@@ -506,7 +510,14 @@
|
|
|
506
510
|
],
|
|
507
511
|
"responses": {
|
|
508
512
|
"200": {
|
|
509
|
-
"description": "Successful operation"
|
|
513
|
+
"description": "Successful operation",
|
|
514
|
+
"content": {
|
|
515
|
+
"application/json": {
|
|
516
|
+
"schema": {
|
|
517
|
+
"$ref": "#/components/schemas/Customer"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
510
521
|
},
|
|
511
522
|
"404": {
|
|
512
523
|
"description": "Not found"
|
|
@@ -572,7 +583,14 @@
|
|
|
572
583
|
],
|
|
573
584
|
"responses": {
|
|
574
585
|
"200": {
|
|
575
|
-
"description": "Successful operation"
|
|
586
|
+
"description": "Successful operation",
|
|
587
|
+
"content": {
|
|
588
|
+
"application/json": {
|
|
589
|
+
"schema": {
|
|
590
|
+
"$ref": "#/components/schemas/Customer"
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
576
594
|
},
|
|
577
595
|
"404": {
|
|
578
596
|
"description": "Not found"
|
|
@@ -1367,23 +1385,6 @@
|
|
|
1367
1385
|
]
|
|
1368
1386
|
}
|
|
1369
1387
|
},
|
|
1370
|
-
"/your-webhook-url/": {
|
|
1371
|
-
"post": {
|
|
1372
|
-
"tags": [
|
|
1373
|
-
"Webhook calls"
|
|
1374
|
-
],
|
|
1375
|
-
"summary": "Called when subscription changed or renewed",
|
|
1376
|
-
"description": "We recommend that you verify all calls from us to your webhook endpoints. We sign each request with the `Hook-HMAC` header. The value is a HMAC digest derived from the body of the request and a secret key only known by the API and you. We also supply the type of event in the `Hook-Event` header.\n\nHere is a sample implementation in Python. This process is very similar to the one Shopify uses so more information can possibly be gleaned from their [documentation](https://shopify.dev/tutorials/manage-webhooks#verify-webhook).\n```\nimport base64\nimport hmac\nimport hashlib\n\nWEBHOOK_SECRET = \"Your webhook secret\".encode()\nWEBHOOK_DIGEST_TYPE = 'sha512'\n\ndef verify(hmac_header, digest_method, secret, message):\n digestmod = getattr(hashlib, digest_method)\n signed = base64.b64encode(\n hmac.new(secret, message, digestmod).digest(),\n ).strip()\n return hmac.compare_digest(signed, hmac_header)\n\n# your view function\ndef handle_webhook(request):\n # The signature\n digest = request.META.get('HTTP_HOOK_HMAC').encode()\n # The name of the webhook event\n event = request.META.get('HTTP_HOOK_EVENT').encode()\n\n body = request.body\n if verify(digest, WEBHOOK_DIGEST_TYPE, WEBHOOK_SECRET, body):\n payload = json.loads(body)\n # ... the rest of your code here```\n",
|
|
1377
|
-
"requestBody": {
|
|
1378
|
-
"$ref": "#/components/requestBodies/SubscriptionMultiLite"
|
|
1379
|
-
},
|
|
1380
|
-
"responses": {
|
|
1381
|
-
"200": {
|
|
1382
|
-
"description": "Successful operation"
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
1388
|
"/webhooks/": {
|
|
1388
1389
|
"get": {
|
|
1389
1390
|
"tags": [
|
|
@@ -1423,7 +1424,14 @@
|
|
|
1423
1424
|
},
|
|
1424
1425
|
"responses": {
|
|
1425
1426
|
"201": {
|
|
1426
|
-
"description": "Successful operation"
|
|
1427
|
+
"description": "Successful operation",
|
|
1428
|
+
"content": {
|
|
1429
|
+
"application/json": {
|
|
1430
|
+
"schema": {
|
|
1431
|
+
"$ref": "#/components/schemas/Webhook"
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1427
1435
|
},
|
|
1428
1436
|
"400": {
|
|
1429
1437
|
"description": "Invalid status value",
|
|
@@ -1521,7 +1529,14 @@
|
|
|
1521
1529
|
},
|
|
1522
1530
|
"responses": {
|
|
1523
1531
|
"200": {
|
|
1524
|
-
"description": "Successful operation"
|
|
1532
|
+
"description": "Successful operation",
|
|
1533
|
+
"content": {
|
|
1534
|
+
"application/json": {
|
|
1535
|
+
"schema": {
|
|
1536
|
+
"$ref": "#/components/schemas/Webhook"
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1525
1540
|
},
|
|
1526
1541
|
"400": {
|
|
1527
1542
|
"description": "Invalid status value",
|
|
@@ -1964,27 +1979,94 @@
|
|
|
1964
1979
|
}
|
|
1965
1980
|
},
|
|
1966
1981
|
"Customer": {
|
|
1967
|
-
"
|
|
1968
|
-
|
|
1969
|
-
|
|
1982
|
+
"type": "object",
|
|
1983
|
+
"required": [
|
|
1984
|
+
"id",
|
|
1985
|
+
"first_name",
|
|
1986
|
+
"last_name",
|
|
1987
|
+
"email",
|
|
1988
|
+
"customer_reference",
|
|
1989
|
+
"phone",
|
|
1990
|
+
"delivery_name",
|
|
1991
|
+
"address_1",
|
|
1992
|
+
"address_2",
|
|
1993
|
+
"address_3",
|
|
1994
|
+
"zip_code",
|
|
1995
|
+
"city",
|
|
1996
|
+
"country",
|
|
1997
|
+
"payment_method",
|
|
1998
|
+
"address"
|
|
1999
|
+
],
|
|
2000
|
+
"properties": {
|
|
2001
|
+
"id": {
|
|
2002
|
+
"type": "integer",
|
|
2003
|
+
"format": "int64"
|
|
1970
2004
|
},
|
|
1971
|
-
{
|
|
1972
|
-
"type": "
|
|
1973
|
-
"
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2005
|
+
"first_name": {
|
|
2006
|
+
"type": "string",
|
|
2007
|
+
"maxLength": 128
|
|
2008
|
+
},
|
|
2009
|
+
"last_name": {
|
|
2010
|
+
"type": "string",
|
|
2011
|
+
"maxLength": 128
|
|
2012
|
+
},
|
|
2013
|
+
"email": {
|
|
2014
|
+
"type": "string",
|
|
2015
|
+
"nullable": true,
|
|
2016
|
+
"maxLength": 254
|
|
2017
|
+
},
|
|
2018
|
+
"phone": {
|
|
2019
|
+
"type": "string",
|
|
2020
|
+
"nullable": true,
|
|
2021
|
+
"maxLength": 32
|
|
2022
|
+
},
|
|
2023
|
+
"customer_reference": {
|
|
2024
|
+
"type": "string",
|
|
2025
|
+
"maxLength": 256
|
|
2026
|
+
},
|
|
2027
|
+
"delivery_name": {
|
|
2028
|
+
"type": "string",
|
|
2029
|
+
"nullable": true
|
|
2030
|
+
},
|
|
2031
|
+
"address_1": {
|
|
2032
|
+
"type": "string",
|
|
2033
|
+
"nullable": true
|
|
2034
|
+
},
|
|
2035
|
+
"address_2": {
|
|
2036
|
+
"type": "string",
|
|
2037
|
+
"nullable": true
|
|
2038
|
+
},
|
|
2039
|
+
"address_3": {
|
|
2040
|
+
"type": "string",
|
|
2041
|
+
"nullable": true
|
|
2042
|
+
},
|
|
2043
|
+
"zip_code": {
|
|
2044
|
+
"type": "string",
|
|
2045
|
+
"nullable": true
|
|
2046
|
+
},
|
|
2047
|
+
"city": {
|
|
2048
|
+
"type": "string",
|
|
2049
|
+
"nullable": true
|
|
2050
|
+
},
|
|
2051
|
+
"country": {
|
|
2052
|
+
"type": "string",
|
|
2053
|
+
"nullable": true
|
|
2054
|
+
},
|
|
2055
|
+
"payment_method": {
|
|
2056
|
+
"type": "array",
|
|
2057
|
+
"items": {
|
|
2058
|
+
"$ref": "#/components/schemas/PaymentMethod"
|
|
1985
2059
|
}
|
|
2060
|
+
},
|
|
2061
|
+
"address": {
|
|
2062
|
+
"allOf": [
|
|
2063
|
+
{
|
|
2064
|
+
"$ref": "#/components/schemas/CustomerAddress"
|
|
2065
|
+
}
|
|
2066
|
+
],
|
|
2067
|
+
"nullable": true
|
|
1986
2068
|
}
|
|
1987
|
-
|
|
2069
|
+
}
|
|
1988
2070
|
},
|
|
1989
2071
|
"CustomerPaginated": {
|
|
1990
2072
|
"type": "object",
|
|
@@ -2593,6 +2675,48 @@
|
|
|
2593
2675
|
"example": "tokencreated"
|
|
2594
2676
|
}
|
|
2595
2677
|
}
|
|
2678
|
+
},
|
|
2679
|
+
"CustomerAddress": {
|
|
2680
|
+
"type": "object",
|
|
2681
|
+
"required": [
|
|
2682
|
+
"delivery_name",
|
|
2683
|
+
"address_1",
|
|
2684
|
+
"address_2",
|
|
2685
|
+
"address_3",
|
|
2686
|
+
"zip_code",
|
|
2687
|
+
"city",
|
|
2688
|
+
"country"
|
|
2689
|
+
],
|
|
2690
|
+
"properties": {
|
|
2691
|
+
"delivery_name": {
|
|
2692
|
+
"type": "string",
|
|
2693
|
+
"nullable": true
|
|
2694
|
+
},
|
|
2695
|
+
"address_1": {
|
|
2696
|
+
"type": "string",
|
|
2697
|
+
"nullable": true
|
|
2698
|
+
},
|
|
2699
|
+
"address_2": {
|
|
2700
|
+
"type": "string",
|
|
2701
|
+
"nullable": true
|
|
2702
|
+
},
|
|
2703
|
+
"address_3": {
|
|
2704
|
+
"type": "string",
|
|
2705
|
+
"nullable": true
|
|
2706
|
+
},
|
|
2707
|
+
"zip_code": {
|
|
2708
|
+
"type": "string",
|
|
2709
|
+
"nullable": true
|
|
2710
|
+
},
|
|
2711
|
+
"city": {
|
|
2712
|
+
"type": "string",
|
|
2713
|
+
"nullable": true
|
|
2714
|
+
},
|
|
2715
|
+
"country": {
|
|
2716
|
+
"type": "string",
|
|
2717
|
+
"nullable": true
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2596
2720
|
}
|
|
2597
2721
|
},
|
|
2598
2722
|
"requestBodies": {
|
|
@@ -2780,4 +2904,4 @@
|
|
|
2780
2904
|
}
|
|
2781
2905
|
}
|
|
2782
2906
|
}
|
|
2783
|
-
}
|
|
2907
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay for Askell OpenAPI v1. Upstream swagger is wrong in a few places;
|
|
3
|
+
* this is the same transform as askell_client_v1 `patchAskellSpec` (without Orval).
|
|
4
|
+
*
|
|
5
|
+
* Idempotent: safe to run on an already-patched document.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type JsonSchema = Record<string, unknown>;
|
|
9
|
+
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
10
|
+
|
|
11
|
+
type OpenApiResponse = {
|
|
12
|
+
description?: string;
|
|
13
|
+
content?: Record<string, { schema?: JsonSchema }>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type OpenApiOperation = {
|
|
18
|
+
tags?: string[];
|
|
19
|
+
responses?: Record<string, OpenApiResponse>;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type OpenApiPathItem = Partial<Record<HttpMethod, OpenApiOperation>> & {
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type OpenApiDocument = {
|
|
28
|
+
info?: Record<string, unknown>;
|
|
29
|
+
tags?: Array<{ name: string; description?: string }>;
|
|
30
|
+
paths?: Record<string, OpenApiPathItem>;
|
|
31
|
+
components?: {
|
|
32
|
+
schemas?: Record<string, JsonSchema>;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const ASKELL_V1_PATCH = 'v1-overlay-1';
|
|
39
|
+
export const WEBHOOK_CALLS_TAG = 'Webhook calls';
|
|
40
|
+
|
|
41
|
+
/** Success bodies missing from swagger `content` (DELETE 204 stays empty). */
|
|
42
|
+
const RESPONSE_BODIES = [
|
|
43
|
+
['/customers/', 'post', '201', 'Customer'],
|
|
44
|
+
['/customers/{customerReference}/', 'put', '200', 'Customer'],
|
|
45
|
+
['/customers/{customerReference}/', 'patch', '200', 'Customer'],
|
|
46
|
+
['/webhooks/', 'post', '201', 'Webhook'],
|
|
47
|
+
['/webhooks/{id}/', 'patch', '200', 'Webhook'],
|
|
48
|
+
] as const satisfies ReadonlyArray<
|
|
49
|
+
readonly [string, HttpMethod, string, string]
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
const HTTP_METHODS = [
|
|
53
|
+
'get',
|
|
54
|
+
'post',
|
|
55
|
+
'put',
|
|
56
|
+
'patch',
|
|
57
|
+
'delete',
|
|
58
|
+
'head',
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
const nullableString = (maxLength?: number): JsonSchema => ({
|
|
62
|
+
type: 'string',
|
|
63
|
+
nullable: true,
|
|
64
|
+
...(maxLength === undefined ? {} : { maxLength }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** Flattened + nested address as returned by GET /customers/ and customer.* webhooks. */
|
|
68
|
+
const CUSTOMER_ADDRESS_SCHEMA: JsonSchema = {
|
|
69
|
+
type: 'object',
|
|
70
|
+
required: [
|
|
71
|
+
'delivery_name',
|
|
72
|
+
'address_1',
|
|
73
|
+
'address_2',
|
|
74
|
+
'address_3',
|
|
75
|
+
'zip_code',
|
|
76
|
+
'city',
|
|
77
|
+
'country',
|
|
78
|
+
],
|
|
79
|
+
properties: {
|
|
80
|
+
delivery_name: nullableString(),
|
|
81
|
+
address_1: nullableString(),
|
|
82
|
+
address_2: nullableString(),
|
|
83
|
+
address_3: nullableString(),
|
|
84
|
+
zip_code: nullableString(),
|
|
85
|
+
city: nullableString(),
|
|
86
|
+
country: nullableString(),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Askell swagger reuses CustomerCreate (write) as the GET model via allOf.
|
|
92
|
+
* Live GET / webhooks return extra address fields, null emails/phones, and a
|
|
93
|
+
* required numeric id. Keep CustomerCreate as the POST body.
|
|
94
|
+
*/
|
|
95
|
+
const CUSTOMER_READ_SCHEMA: JsonSchema = {
|
|
96
|
+
type: 'object',
|
|
97
|
+
required: [
|
|
98
|
+
'id',
|
|
99
|
+
'first_name',
|
|
100
|
+
'last_name',
|
|
101
|
+
'email',
|
|
102
|
+
'customer_reference',
|
|
103
|
+
'phone',
|
|
104
|
+
'delivery_name',
|
|
105
|
+
'address_1',
|
|
106
|
+
'address_2',
|
|
107
|
+
'address_3',
|
|
108
|
+
'zip_code',
|
|
109
|
+
'city',
|
|
110
|
+
'country',
|
|
111
|
+
'payment_method',
|
|
112
|
+
'address',
|
|
113
|
+
],
|
|
114
|
+
properties: {
|
|
115
|
+
id: { type: 'integer', format: 'int64' },
|
|
116
|
+
first_name: { type: 'string', maxLength: 128 },
|
|
117
|
+
last_name: { type: 'string', maxLength: 128 },
|
|
118
|
+
email: nullableString(254),
|
|
119
|
+
phone: nullableString(32),
|
|
120
|
+
customer_reference: { type: 'string', maxLength: 256 },
|
|
121
|
+
delivery_name: nullableString(),
|
|
122
|
+
address_1: nullableString(),
|
|
123
|
+
address_2: nullableString(),
|
|
124
|
+
address_3: nullableString(),
|
|
125
|
+
zip_code: nullableString(),
|
|
126
|
+
city: nullableString(),
|
|
127
|
+
country: nullableString(),
|
|
128
|
+
payment_method: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
items: { $ref: '#/components/schemas/PaymentMethod' },
|
|
131
|
+
},
|
|
132
|
+
address: {
|
|
133
|
+
allOf: [{ $ref: '#/components/schemas/CustomerAddress' }],
|
|
134
|
+
nullable: true,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
140
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function dropWebhookCallOperations(doc: OpenApiDocument): void {
|
|
144
|
+
const paths = doc.paths;
|
|
145
|
+
if (!paths) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const path of Object.keys(paths)) {
|
|
150
|
+
const pathItem = paths[path];
|
|
151
|
+
if (!pathItem) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const method of HTTP_METHODS) {
|
|
156
|
+
const operation = pathItem[method as HttpMethod];
|
|
157
|
+
if (operation?.tags?.includes(WEBHOOK_CALLS_TAG)) {
|
|
158
|
+
delete pathItem[method as HttpMethod];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const remaining = HTTP_METHODS.some(
|
|
163
|
+
(method) => pathItem[method as HttpMethod] != null,
|
|
164
|
+
);
|
|
165
|
+
if (!remaining) {
|
|
166
|
+
delete paths[path];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (doc.tags) {
|
|
171
|
+
doc.tags = doc.tags.filter((tag) => tag.name !== WEBHOOK_CALLS_TAG);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ensureResponseBody(
|
|
176
|
+
doc: OpenApiDocument,
|
|
177
|
+
path: string,
|
|
178
|
+
method: HttpMethod,
|
|
179
|
+
status: string,
|
|
180
|
+
schemaName: string,
|
|
181
|
+
): void {
|
|
182
|
+
const pathItem = doc.paths?.[path];
|
|
183
|
+
const operation = pathItem?.[method];
|
|
184
|
+
const response = operation?.responses?.[status];
|
|
185
|
+
const label = `${method.toUpperCase()} ${path} ${status}`;
|
|
186
|
+
|
|
187
|
+
if (!pathItem || !operation || !response) {
|
|
188
|
+
throw new Error(`Askell spec missing ${label}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (response.content?.['application/json']?.schema) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
response.content = {
|
|
196
|
+
...response.content,
|
|
197
|
+
'application/json': {
|
|
198
|
+
schema: { $ref: `#/components/schemas/${schemaName}` },
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function patchAskellV1Spec(spec: unknown): OpenApiDocument {
|
|
204
|
+
if (!isRecord(spec)) {
|
|
205
|
+
throw new Error('Askell v1 spec is not an object');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const doc = structuredClone(spec) as OpenApiDocument;
|
|
209
|
+
const schemas = doc.components?.schemas;
|
|
210
|
+
if (!schemas?.Customer) {
|
|
211
|
+
throw new Error('Askell spec missing components.schemas.Customer');
|
|
212
|
+
}
|
|
213
|
+
if (!schemas.Webhook) {
|
|
214
|
+
throw new Error('Askell spec missing components.schemas.Webhook');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
dropWebhookCallOperations(doc);
|
|
218
|
+
|
|
219
|
+
doc.components = {
|
|
220
|
+
...doc.components,
|
|
221
|
+
schemas: {
|
|
222
|
+
...schemas,
|
|
223
|
+
CustomerAddress: CUSTOMER_ADDRESS_SCHEMA,
|
|
224
|
+
Customer: CUSTOMER_READ_SCHEMA,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
for (const [path, method, status, schemaName] of RESPONSE_BODIES) {
|
|
229
|
+
ensureResponseBody(doc, path, method, status, schemaName);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
doc.info = {
|
|
233
|
+
...doc.info,
|
|
234
|
+
'x-askell-mcp-patched': ASKELL_V1_PATCH,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return doc;
|
|
238
|
+
}
|
|
@@ -11,11 +11,11 @@ Headers:
|
|
|
11
11
|
- Hook-Event: event type (\`subscription.renewed\`, \`payment.changed\`, or a family wildcard \`subscription.*\`)
|
|
12
12
|
- Hook-API-Version: \`v1\` for plan/subscription/customer/payment/checkout, \`v2\` for subscription_contract / billing_run
|
|
13
13
|
|
|
14
|
-
## Body shape (
|
|
14
|
+
## Body shape (not in OpenAPI)
|
|
15
15
|
|
|
16
16
|
JSON body **is the event object**. It is **not** \`{ event, data }\`.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Upstream swagger used to document a dummy \`POST /your-webhook-url/\` with \`SubscriptionMultiLite\` (\`{ customer, subscriptions[] }\`). \`sync-specs\` strips that path. Inbound payloads are still undocumented in OpenAPI — this resource is the overlay.
|
|
19
19
|
|
|
20
20
|
Rare historical payloads used \`{ event, data, ref?, sender? }\`. If both \`event\` and \`data\` are objects, use \`data\`.
|
|
21
21
|
|
package/src/server.ts
CHANGED
|
@@ -46,13 +46,13 @@ Safety:
|
|
|
46
46
|
|
|
47
47
|
Resources:
|
|
48
48
|
- askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
|
|
49
|
-
- askell://docs/webhook-events — inbound webhook payloads (
|
|
49
|
+
- askell://docs/webhook-events — inbound webhook payloads (not in OpenAPI; dummy /your-webhook-url/ is stripped on sync), HMAC-SHA512, /webhooks/ hmac_secret`;
|
|
50
50
|
|
|
51
51
|
export function createServer(config: AppConfig): McpServer {
|
|
52
52
|
const server = new McpServer(
|
|
53
53
|
{
|
|
54
54
|
name: 'askell-mcp',
|
|
55
|
-
version: '0.3.
|
|
55
|
+
version: '0.3.1',
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
instructions: SERVER_INSTRUCTIONS,
|