askell-mcp 0.1.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/LICENSE +21 -0
- package/README.md +204 -0
- package/bin/askell-mcp +2 -0
- package/mcp.json.example +12 -0
- package/package.json +69 -0
- package/spec/openapi-v1.json +2783 -0
- package/spec/openapi-v2.json +4951 -0
- package/src/client/askell-client.ts +243 -0
- package/src/client/paths.ts +10 -0
- package/src/client/response-formatter.ts +302 -0
- package/src/config.ts +104 -0
- package/src/index.ts +16 -0
- package/src/openapi/registry.ts +257 -0
- package/src/openapi/types.ts +68 -0
- package/src/resources/register.ts +95 -0
- package/src/server.ts +64 -0
- package/src/tools/analysis.ts +286 -0
- package/src/tools/call.ts +129 -0
- package/src/tools/discovery.ts +180 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import v1Spec from '../../spec/openapi-v1.json';
|
|
2
|
+
import v2Spec from '../../spec/openapi-v2.json';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ApiKeyKind,
|
|
6
|
+
ApiOperation,
|
|
7
|
+
ApiVersion,
|
|
8
|
+
HttpMethod,
|
|
9
|
+
OpenApiDocument,
|
|
10
|
+
OpenApiParameter,
|
|
11
|
+
} from './types.ts';
|
|
12
|
+
|
|
13
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head'] as const;
|
|
14
|
+
|
|
15
|
+
function resolveRef(
|
|
16
|
+
doc: OpenApiDocument,
|
|
17
|
+
ref: string,
|
|
18
|
+
seen = new Set<string>(),
|
|
19
|
+
): unknown {
|
|
20
|
+
if (!ref.startsWith('#/')) {
|
|
21
|
+
return ref;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (seen.has(ref)) {
|
|
25
|
+
return { $ref: ref, circular: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
seen.add(ref);
|
|
29
|
+
const parts = ref.slice(2).split('/');
|
|
30
|
+
let current: unknown = doc;
|
|
31
|
+
|
|
32
|
+
for (const part of parts) {
|
|
33
|
+
if (current == null || typeof current !== 'object') {
|
|
34
|
+
return ref;
|
|
35
|
+
}
|
|
36
|
+
current = (current as Record<string, unknown>)[part];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
current != null &&
|
|
41
|
+
typeof current === 'object' &&
|
|
42
|
+
'$ref' in current &&
|
|
43
|
+
typeof (current as { $ref: unknown }).$ref === 'string'
|
|
44
|
+
) {
|
|
45
|
+
return resolveRef(doc, (current as { $ref: string }).$ref, seen);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveSchema(doc: OpenApiDocument, schema: unknown): unknown {
|
|
52
|
+
if (schema == null || typeof schema !== 'object') {
|
|
53
|
+
return schema;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ('$ref' in schema && typeof schema.$ref === 'string') {
|
|
57
|
+
return resolveRef(doc, schema.$ref);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return schema;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveParameters(
|
|
64
|
+
doc: OpenApiDocument,
|
|
65
|
+
parameters: OpenApiParameter[] | undefined,
|
|
66
|
+
): OpenApiParameter[] {
|
|
67
|
+
if (!parameters?.length) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return parameters.map((parameter) => {
|
|
72
|
+
if ('$ref' in parameter && typeof parameter.$ref === 'string') {
|
|
73
|
+
const resolved = resolveRef(doc, parameter.$ref);
|
|
74
|
+
if (resolved && typeof resolved === 'object') {
|
|
75
|
+
const param = resolved as OpenApiParameter;
|
|
76
|
+
return {
|
|
77
|
+
...param,
|
|
78
|
+
schema: resolveSchema(doc, param.schema),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...parameter,
|
|
85
|
+
schema: resolveSchema(doc, parameter.schema),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function inferApiKeyKind(
|
|
91
|
+
security: Array<Record<string, unknown[]>> | undefined,
|
|
92
|
+
): ApiKeyKind {
|
|
93
|
+
if (!security?.length) {
|
|
94
|
+
return 'secret';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const requirement of security) {
|
|
98
|
+
if ('Public-Api-Key' in requirement) {
|
|
99
|
+
return 'public';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return 'secret';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildOperationId(
|
|
107
|
+
apiVersion: ApiVersion,
|
|
108
|
+
method: HttpMethod,
|
|
109
|
+
path: string,
|
|
110
|
+
): string {
|
|
111
|
+
return `${apiVersion}:${method}:${path}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseDocument(
|
|
115
|
+
doc: OpenApiDocument,
|
|
116
|
+
apiVersion: ApiVersion,
|
|
117
|
+
): ApiOperation[] {
|
|
118
|
+
const operations: ApiOperation[] = [];
|
|
119
|
+
|
|
120
|
+
for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
121
|
+
if (!pathItem) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const methodKey of HTTP_METHODS) {
|
|
126
|
+
const operation = pathItem[methodKey];
|
|
127
|
+
if (!operation) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const method = methodKey.toUpperCase() as HttpMethod;
|
|
132
|
+
const content = operation.requestBody?.content ?? {};
|
|
133
|
+
const contentTypes = Object.keys(content);
|
|
134
|
+
const firstContent = contentTypes[0]
|
|
135
|
+
? content[contentTypes[0]]
|
|
136
|
+
: undefined;
|
|
137
|
+
|
|
138
|
+
operations.push({
|
|
139
|
+
id: buildOperationId(apiVersion, method, path),
|
|
140
|
+
apiVersion,
|
|
141
|
+
method,
|
|
142
|
+
path,
|
|
143
|
+
tags: operation.tags ?? [],
|
|
144
|
+
summary: operation.summary ?? `${method} ${path}`,
|
|
145
|
+
description: operation.description,
|
|
146
|
+
parameters: resolveParameters(doc, operation.parameters),
|
|
147
|
+
requestBody: operation.requestBody
|
|
148
|
+
? {
|
|
149
|
+
required: operation.requestBody.required,
|
|
150
|
+
description: operation.requestBody.description,
|
|
151
|
+
contentTypes,
|
|
152
|
+
schema: firstContent
|
|
153
|
+
? resolveSchema(doc, firstContent.schema)
|
|
154
|
+
: undefined,
|
|
155
|
+
}
|
|
156
|
+
: undefined,
|
|
157
|
+
apiKeyKind: inferApiKeyKind(operation.security),
|
|
158
|
+
deprecated: operation.deprecated,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return operations;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class OperationRegistry {
|
|
167
|
+
readonly operations: ApiOperation[];
|
|
168
|
+
|
|
169
|
+
constructor() {
|
|
170
|
+
this.operations = [
|
|
171
|
+
...parseDocument(v1Spec as unknown as OpenApiDocument, 'v1'),
|
|
172
|
+
...parseDocument(v2Spec as unknown as OpenApiDocument, 'v2'),
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getById(id: string): ApiOperation | undefined {
|
|
177
|
+
return this.operations.find((operation) => operation.id === id);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
find(filters: {
|
|
181
|
+
apiVersion?: ApiVersion | 'all';
|
|
182
|
+
tag?: string;
|
|
183
|
+
method?: HttpMethod;
|
|
184
|
+
pathPrefix?: string;
|
|
185
|
+
search?: string;
|
|
186
|
+
apiKeyKind?: ApiKeyKind;
|
|
187
|
+
}): ApiOperation[] {
|
|
188
|
+
const search = filters.search?.trim().toLowerCase();
|
|
189
|
+
|
|
190
|
+
return this.operations.filter((operation) => {
|
|
191
|
+
if (
|
|
192
|
+
filters.apiVersion &&
|
|
193
|
+
filters.apiVersion !== 'all' &&
|
|
194
|
+
operation.apiVersion !== filters.apiVersion
|
|
195
|
+
) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (filters.tag && !operation.tags.includes(filters.tag)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (filters.method && operation.method !== filters.method) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
filters.pathPrefix &&
|
|
209
|
+
!operation.path.startsWith(filters.pathPrefix)
|
|
210
|
+
) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (filters.apiKeyKind && operation.apiKeyKind !== filters.apiKeyKind) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (search) {
|
|
219
|
+
const haystack = [
|
|
220
|
+
operation.id,
|
|
221
|
+
operation.path,
|
|
222
|
+
operation.summary,
|
|
223
|
+
operation.description ?? '',
|
|
224
|
+
operation.tags.join(' '),
|
|
225
|
+
]
|
|
226
|
+
.join(' ')
|
|
227
|
+
.toLowerCase();
|
|
228
|
+
|
|
229
|
+
if (!haystack.includes(search)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
listTags(apiVersion?: ApiVersion | 'all'): string[] {
|
|
239
|
+
const tags = new Set<string>();
|
|
240
|
+
|
|
241
|
+
for (const operation of this.find({ apiVersion })) {
|
|
242
|
+
for (const tag of operation.tags) {
|
|
243
|
+
tags.add(tag);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return [...tags].sort((a, b) => a.localeCompare(b));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const operationRegistry = new OperationRegistry();
|
|
252
|
+
|
|
253
|
+
export function getBundledSpec(apiVersion: ApiVersion): OpenApiDocument {
|
|
254
|
+
return apiVersion === 'v1'
|
|
255
|
+
? (v1Spec as unknown as OpenApiDocument)
|
|
256
|
+
: (v2Spec as unknown as OpenApiDocument);
|
|
257
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type ApiVersion = 'v1' | 'v2';
|
|
2
|
+
|
|
3
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
|
|
4
|
+
|
|
5
|
+
export type ApiKeyKind = 'secret' | 'public';
|
|
6
|
+
|
|
7
|
+
export interface OpenApiParameter {
|
|
8
|
+
name?: string;
|
|
9
|
+
in?: 'query' | 'path' | 'header' | 'cookie';
|
|
10
|
+
required?: boolean;
|
|
11
|
+
description?: string;
|
|
12
|
+
schema?: unknown;
|
|
13
|
+
$ref?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ApiOperation {
|
|
17
|
+
id: string;
|
|
18
|
+
apiVersion: ApiVersion;
|
|
19
|
+
method: HttpMethod;
|
|
20
|
+
path: string;
|
|
21
|
+
tags: string[];
|
|
22
|
+
summary: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
parameters: OpenApiParameter[];
|
|
25
|
+
requestBody?: {
|
|
26
|
+
required?: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
contentTypes: string[];
|
|
29
|
+
schema?: unknown;
|
|
30
|
+
};
|
|
31
|
+
apiKeyKind: ApiKeyKind;
|
|
32
|
+
deprecated?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface OpenApiDocument {
|
|
36
|
+
openapi?: string;
|
|
37
|
+
info?: {
|
|
38
|
+
title?: string;
|
|
39
|
+
version?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
};
|
|
42
|
+
paths?: Record<
|
|
43
|
+
string,
|
|
44
|
+
Partial<
|
|
45
|
+
Record<
|
|
46
|
+
Lowercase<HttpMethod>,
|
|
47
|
+
{
|
|
48
|
+
tags?: string[];
|
|
49
|
+
summary?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
deprecated?: boolean;
|
|
52
|
+
parameters?: OpenApiParameter[];
|
|
53
|
+
requestBody?: {
|
|
54
|
+
required?: boolean;
|
|
55
|
+
description?: string;
|
|
56
|
+
content?: Record<string, { schema?: unknown }>;
|
|
57
|
+
};
|
|
58
|
+
security?: Array<Record<string, unknown[]>>;
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
>
|
|
62
|
+
>;
|
|
63
|
+
components?: {
|
|
64
|
+
parameters?: Record<string, OpenApiParameter>;
|
|
65
|
+
schemas?: Record<string, unknown>;
|
|
66
|
+
responses?: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
|
|
3
|
+
import { getBundledSpec } from '../openapi/registry.ts';
|
|
4
|
+
|
|
5
|
+
const WEBHOOK_EVENTS_DOC = `# Askell webhook events (reference)
|
|
6
|
+
|
|
7
|
+
Askell sends signed webhook calls to your configured endpoints.
|
|
8
|
+
|
|
9
|
+
Headers:
|
|
10
|
+
- Hook-HMAC: base64 HMAC-SHA512 of the raw body
|
|
11
|
+
- Hook-Event: event type identifier
|
|
12
|
+
- Hook-API-Version: v1 for legacy plan/subscription webhooks, v2 for subscription contract webhooks
|
|
13
|
+
|
|
14
|
+
Event families:
|
|
15
|
+
- subscription.* (legacy v1): subscription.created, subscription.changed, subscription.renewed
|
|
16
|
+
- subscription_contract.* (v2): subscription_contract.created, subscription_contract.changed,
|
|
17
|
+
subscription_contract.renewed, subscription_contract.migrated (legacy subscription migrated to a
|
|
18
|
+
V2 contract; payload includes legacy_subscription_ids and migration_effective_at)
|
|
19
|
+
- billing_run.* (v2): billing_run.created, billing_run.changed, billing_run.succeeded,
|
|
20
|
+
billing_run.failed, billing_run.retry
|
|
21
|
+
- customer.*: customer.created, customer.changed
|
|
22
|
+
- payment.*: payment.created, payment.changed, payment.retry
|
|
23
|
+
- checkout.*: checkout.created, checkout.changed
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
- During migration to V2, legacy subscription.* aliases are NOT sent for the new contract (event data
|
|
27
|
+
is based on SubscriptionContract, not Subscription), and subscription.canceled is not sent solely
|
|
28
|
+
because billing moved to a V2 contract.
|
|
29
|
+
|
|
30
|
+
Webhook management is available via v1 API:
|
|
31
|
+
- GET/POST /webhooks/
|
|
32
|
+
- GET/PATCH/DELETE /webhooks/{id}/
|
|
33
|
+
|
|
34
|
+
This MCP server exposes webhook management through askell_call and askell_list_webhooks.
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export function registerResources(server: McpServer): void {
|
|
38
|
+
server.registerResource(
|
|
39
|
+
'openapi-v1',
|
|
40
|
+
'askell://spec/v1',
|
|
41
|
+
{
|
|
42
|
+
title: 'Askell OpenAPI v1',
|
|
43
|
+
description: 'Bundled OpenAPI 3 spec for Askell API v1',
|
|
44
|
+
mimeType: 'application/json',
|
|
45
|
+
},
|
|
46
|
+
async () => ({
|
|
47
|
+
contents: [
|
|
48
|
+
{
|
|
49
|
+
uri: 'askell://spec/v1',
|
|
50
|
+
mimeType: 'application/json',
|
|
51
|
+
text: JSON.stringify(getBundledSpec('v1'), null, 2),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
server.registerResource(
|
|
58
|
+
'openapi-v2',
|
|
59
|
+
'askell://spec/v2',
|
|
60
|
+
{
|
|
61
|
+
title: 'Askell OpenAPI v2',
|
|
62
|
+
description:
|
|
63
|
+
'Bundled OpenAPI 3 spec for Askell Subscription Contracts V2',
|
|
64
|
+
mimeType: 'application/json',
|
|
65
|
+
},
|
|
66
|
+
async () => ({
|
|
67
|
+
contents: [
|
|
68
|
+
{
|
|
69
|
+
uri: 'askell://spec/v2',
|
|
70
|
+
mimeType: 'application/json',
|
|
71
|
+
text: JSON.stringify(getBundledSpec('v2'), null, 2),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
server.registerResource(
|
|
78
|
+
'webhook-events',
|
|
79
|
+
'askell://docs/webhook-events',
|
|
80
|
+
{
|
|
81
|
+
title: 'Askell webhook events',
|
|
82
|
+
description: 'Webhook event types and management overview',
|
|
83
|
+
mimeType: 'text/markdown',
|
|
84
|
+
},
|
|
85
|
+
async () => ({
|
|
86
|
+
contents: [
|
|
87
|
+
{
|
|
88
|
+
uri: 'askell://docs/webhook-events',
|
|
89
|
+
mimeType: 'text/markdown',
|
|
90
|
+
text: WEBHOOK_EVENTS_DOC,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
|
|
3
|
+
import { AskellClient } from './client/askell-client.ts';
|
|
4
|
+
import type { AppConfig } from './config.ts';
|
|
5
|
+
import { registerResources } from './resources/register.ts';
|
|
6
|
+
import { registerAnalysisTools } from './tools/analysis.ts';
|
|
7
|
+
import { registerCallTool } from './tools/call.ts';
|
|
8
|
+
import { registerDiscoveryTools } from './tools/discovery.ts';
|
|
9
|
+
|
|
10
|
+
const SERVER_INSTRUCTIONS = `Askell MCP server for payment and subscription operations.
|
|
11
|
+
|
|
12
|
+
Workflow:
|
|
13
|
+
1. Use askell_list_operations and askell_describe_operation to discover endpoints, parameters, and auth requirements.
|
|
14
|
+
2. Prefer analysis tools (askell_customer_overview, askell_contract_overview, askell_billing_run_triage, askell_paginate_all, askell_list_webhooks) for common support tasks.
|
|
15
|
+
3. Use askell_call only when no dedicated tool covers the request.
|
|
16
|
+
|
|
17
|
+
API models:
|
|
18
|
+
- v1 (legacy): PlanVariant + Subscription at paths like /subscriptions/, /customers/. Still supported for existing integrations.
|
|
19
|
+
- v2 (current): Catalog, bundles, quotes, checkouts, subscription contracts, billing runs under /v2/. Prefer v2 for new integrations.
|
|
20
|
+
- Prose docs at https://docs.askell.is/api/ may describe flows (embedded checkout, 3D Secure, wallet passes) not fully listed in OpenAPI.
|
|
21
|
+
|
|
22
|
+
API layout:
|
|
23
|
+
- v1 paths have no prefix (e.g. /customers/, /subscriptions/, /webhooks/).
|
|
24
|
+
- v2 paths start with /v2/ (e.g. /v2/subscription-contracts/, /v2/billing-runs/).
|
|
25
|
+
- Askell paths use trailing slashes.
|
|
26
|
+
- V2 list endpoints paginate only when page_size is provided (default 10, max 1000).
|
|
27
|
+
- GET /v2/customer-entitlements/ requires customer_reference query param.
|
|
28
|
+
|
|
29
|
+
V2 checkout notes:
|
|
30
|
+
- checkout_url on V2 checkouts points to the API object URL, not a browser payment page.
|
|
31
|
+
- Embedded checkout uses POST /v2/checkout-sessions/ plus browser session-token sub-paths (see docs, not all in OpenAPI).
|
|
32
|
+
|
|
33
|
+
Auth:
|
|
34
|
+
- Most endpoints need the secret API key.
|
|
35
|
+
- Only temporary payment method and checkout status endpoints use the public key.
|
|
36
|
+
|
|
37
|
+
Safety:
|
|
38
|
+
- Mutating askell_call requests require operator approval when requireMutationApproval is enabled.
|
|
39
|
+
- Large list responses may be truncated or summarized to fit responseMaxBytes; check meta.truncatedByMaxBytes and meta.compacted.
|
|
40
|
+
|
|
41
|
+
Resources:
|
|
42
|
+
- askell://spec/v1 and askell://spec/v2 — bundled OpenAPI
|
|
43
|
+
- askell://docs/webhook-events — webhook event types including V2 subscription_contract.* and billing_run.*`;
|
|
44
|
+
|
|
45
|
+
export function createServer(config: AppConfig): McpServer {
|
|
46
|
+
const server = new McpServer(
|
|
47
|
+
{
|
|
48
|
+
name: 'askell-mcp',
|
|
49
|
+
version: '0.1.0',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const client = new AskellClient(config);
|
|
57
|
+
|
|
58
|
+
registerDiscoveryTools(server);
|
|
59
|
+
registerCallTool(server, client, config);
|
|
60
|
+
registerAnalysisTools(server, client);
|
|
61
|
+
registerResources(server);
|
|
62
|
+
|
|
63
|
+
return server;
|
|
64
|
+
}
|