opentool 0.19.2 → 0.19.4
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 +6 -0
- package/dist/adapters/hyperliquid/browser.d.ts +1 -1
- package/dist/adapters/hyperliquid/browser.js +83 -3
- package/dist/adapters/hyperliquid/browser.js.map +1 -1
- package/dist/adapters/hyperliquid/index.d.ts +2 -2
- package/dist/adapters/hyperliquid/index.js +83 -3
- package/dist/adapters/hyperliquid/index.js.map +1 -1
- package/dist/{browser-CnpOj35m.d.ts → browser-DxqvU3eA.d.ts} +11 -6
- package/dist/index.d.ts +4 -15
- package/dist/index.js +207 -440
- package/dist/index.js.map +1 -1
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.js +804 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +5 -1
- package/templates/base/package.json +1 -1
- package/templates/polymarket-simple-trade/package.json +1 -1
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { zodToJsonSchema } from '@alcyone-labs/zod-to-json-schema';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
11
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
+
}) : x)(function(x) {
|
|
13
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
+
});
|
|
16
|
+
var X402_VERSION = 1;
|
|
17
|
+
var HEADER_X402 = "X-PAYMENT";
|
|
18
|
+
var HEADER_PAYMENT_RESPONSE = "X-PAYMENT-RESPONSE";
|
|
19
|
+
var x402RequirementSchema = z.object({
|
|
20
|
+
scheme: z.string().min(1),
|
|
21
|
+
network: z.string().min(1),
|
|
22
|
+
maxAmountRequired: z.string().min(1),
|
|
23
|
+
asset: z.string().min(1),
|
|
24
|
+
payTo: z.string().min(1),
|
|
25
|
+
resource: z.string().optional(),
|
|
26
|
+
description: z.string().optional(),
|
|
27
|
+
mimeType: z.string().optional(),
|
|
28
|
+
outputSchema: z.unknown().optional(),
|
|
29
|
+
maxTimeoutSeconds: z.number().int().positive().optional(),
|
|
30
|
+
extra: z.record(z.string(), z.unknown()).nullable().optional()
|
|
31
|
+
});
|
|
32
|
+
var x402PaymentHeaderSchema = z.object({
|
|
33
|
+
x402Version: z.number().int().positive(),
|
|
34
|
+
scheme: z.string().min(1),
|
|
35
|
+
network: z.string().min(1),
|
|
36
|
+
correlationId: z.string().optional(),
|
|
37
|
+
payload: z.unknown()
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/x402/helpers.ts
|
|
41
|
+
function createX402PaymentRequired(definition) {
|
|
42
|
+
const requirement = toX402Requirement(definition);
|
|
43
|
+
const body = {
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
message: definition.description ?? "Payment required",
|
|
46
|
+
resource: definition.resource,
|
|
47
|
+
accepts: [
|
|
48
|
+
{
|
|
49
|
+
id: "x402",
|
|
50
|
+
title: `Pay ${definition.amount} ${definition.currency.code}`,
|
|
51
|
+
description: definition.description,
|
|
52
|
+
amount: {
|
|
53
|
+
value: definition.amount,
|
|
54
|
+
currency: {
|
|
55
|
+
code: definition.currency.code,
|
|
56
|
+
symbol: definition.currency.symbol,
|
|
57
|
+
decimals: definition.currency.decimals,
|
|
58
|
+
kind: "crypto"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
asset: {
|
|
62
|
+
symbol: definition.asset.symbol,
|
|
63
|
+
network: definition.asset.network,
|
|
64
|
+
address: definition.asset.address,
|
|
65
|
+
decimals: definition.asset.decimals,
|
|
66
|
+
standard: "erc20"
|
|
67
|
+
},
|
|
68
|
+
payTo: definition.payTo,
|
|
69
|
+
resource: definition.resource,
|
|
70
|
+
proof: {
|
|
71
|
+
mode: "x402",
|
|
72
|
+
scheme: definition.scheme,
|
|
73
|
+
network: definition.network,
|
|
74
|
+
version: X402_VERSION,
|
|
75
|
+
facilitator: definition.facilitator,
|
|
76
|
+
verifier: "x402:facilitator"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
metadata: definition.metadata ?? {},
|
|
81
|
+
x402: {
|
|
82
|
+
x402Version: X402_VERSION,
|
|
83
|
+
error: definition.description ?? "Payment required",
|
|
84
|
+
accepts: [requirement]
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return new Response(JSON.stringify(body), {
|
|
88
|
+
status: 402,
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function extractX402Attempt(request) {
|
|
95
|
+
const raw = request.headers.get(HEADER_X402);
|
|
96
|
+
if (!raw) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const payload = decodeJson(raw, x402PaymentHeaderSchema);
|
|
101
|
+
return {
|
|
102
|
+
type: "x402",
|
|
103
|
+
headerName: HEADER_X402,
|
|
104
|
+
raw,
|
|
105
|
+
payload
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function verifyX402Payment(attempt, definition, options = {}) {
|
|
112
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
113
|
+
const timeout = options.timeout ?? 25e3;
|
|
114
|
+
const facilitator = definition.facilitator;
|
|
115
|
+
const verifierUrl = new URL(
|
|
116
|
+
facilitator.verifyPath ?? "/verify",
|
|
117
|
+
ensureTrailingSlash(facilitator.url)
|
|
118
|
+
).toString();
|
|
119
|
+
const requirement = toX402Requirement(definition);
|
|
120
|
+
const headers = buildFacilitatorHeaders(facilitator);
|
|
121
|
+
try {
|
|
122
|
+
const verifyBody = {
|
|
123
|
+
x402Version: attempt.payload.x402Version,
|
|
124
|
+
paymentPayload: attempt.payload,
|
|
125
|
+
paymentRequirements: requirement
|
|
126
|
+
};
|
|
127
|
+
console.log("[x402] Calling facilitator /verify", {
|
|
128
|
+
url: verifierUrl,
|
|
129
|
+
fullBody: JSON.stringify(verifyBody, null, 2)
|
|
130
|
+
});
|
|
131
|
+
const verifyResponse = await Promise.race([
|
|
132
|
+
fetchImpl(verifierUrl, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers,
|
|
135
|
+
body: JSON.stringify(verifyBody)
|
|
136
|
+
}),
|
|
137
|
+
new Promise(
|
|
138
|
+
(_, reject) => setTimeout(() => reject(new Error(`Verification timeout after ${timeout}ms`)), timeout)
|
|
139
|
+
)
|
|
140
|
+
]);
|
|
141
|
+
console.log("[x402] Facilitator /verify response", { status: verifyResponse.status });
|
|
142
|
+
if (!verifyResponse.ok) {
|
|
143
|
+
const errorText = await verifyResponse.text().catch(() => "");
|
|
144
|
+
console.error("[x402] Facilitator /verify error", {
|
|
145
|
+
status: verifyResponse.status,
|
|
146
|
+
body: errorText
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
failure: {
|
|
151
|
+
reason: `Facilitator verify request failed: ${verifyResponse.status}${errorText ? ` - ${errorText}` : ""}`,
|
|
152
|
+
code: "verification_failed"
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const verifyPayload = await verifyResponse.json();
|
|
157
|
+
if (!verifyPayload.isValid) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
failure: {
|
|
161
|
+
reason: verifyPayload.invalidReason ?? "Facilitator verification failed",
|
|
162
|
+
code: "verification_failed"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const responseHeaders = {};
|
|
167
|
+
if (options.settle) {
|
|
168
|
+
const settleUrl = new URL(
|
|
169
|
+
facilitator.settlePath ?? "/settle",
|
|
170
|
+
ensureTrailingSlash(facilitator.url)
|
|
171
|
+
).toString();
|
|
172
|
+
try {
|
|
173
|
+
const settleBody = {
|
|
174
|
+
x402Version: attempt.payload.x402Version,
|
|
175
|
+
paymentPayload: attempt.payload,
|
|
176
|
+
paymentRequirements: requirement
|
|
177
|
+
};
|
|
178
|
+
console.log("[x402] Calling facilitator /settle", {
|
|
179
|
+
url: settleUrl,
|
|
180
|
+
bodyPreview: JSON.stringify(settleBody).substring(0, 300)
|
|
181
|
+
});
|
|
182
|
+
const settleResponse = await Promise.race([
|
|
183
|
+
fetchImpl(settleUrl, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers,
|
|
186
|
+
body: JSON.stringify(settleBody)
|
|
187
|
+
}),
|
|
188
|
+
new Promise(
|
|
189
|
+
(_, reject) => setTimeout(() => reject(new Error(`Settlement timeout after ${timeout}ms`)), timeout)
|
|
190
|
+
)
|
|
191
|
+
]);
|
|
192
|
+
console.log("[x402] Facilitator /settle response", { status: settleResponse.status });
|
|
193
|
+
if (!settleResponse.ok) {
|
|
194
|
+
const errorText = await settleResponse.text().catch(() => "");
|
|
195
|
+
console.error("[x402] Facilitator /settle error", {
|
|
196
|
+
status: settleResponse.status,
|
|
197
|
+
body: errorText
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
failure: {
|
|
202
|
+
reason: `Facilitator settlement failed: ${settleResponse.status}${errorText ? ` - ${errorText}` : ""}`,
|
|
203
|
+
code: "settlement_failed"
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const settlePayload = await settleResponse.json();
|
|
208
|
+
console.log("[x402] Facilitator /settle success", { txHash: settlePayload.txHash });
|
|
209
|
+
if (settlePayload.txHash) {
|
|
210
|
+
responseHeaders[HEADER_PAYMENT_RESPONSE] = JSON.stringify({
|
|
211
|
+
settled: true,
|
|
212
|
+
txHash: settlePayload.txHash
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error("[x402] Settlement exception", {
|
|
217
|
+
error: error instanceof Error ? error.message : String(error)
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
failure: {
|
|
222
|
+
reason: error instanceof Error ? error.message : "Settlement failed",
|
|
223
|
+
code: "settlement_failed"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const result = {
|
|
229
|
+
success: true,
|
|
230
|
+
metadata: {
|
|
231
|
+
optionId: "x402",
|
|
232
|
+
verifier: "x402:facilitator",
|
|
233
|
+
amount: definition.amount,
|
|
234
|
+
currency: definition.currency.code,
|
|
235
|
+
network: definition.network
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
if (Object.keys(responseHeaders).length > 0) {
|
|
239
|
+
result.responseHeaders = responseHeaders;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
failure: {
|
|
246
|
+
reason: error instanceof Error ? error.message : "Unknown error",
|
|
247
|
+
code: "verification_failed"
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function toX402Requirement(definition) {
|
|
253
|
+
const decimals = definition.asset.decimals;
|
|
254
|
+
const units = decimalToBaseUnits(definition.amount, decimals);
|
|
255
|
+
return x402RequirementSchema.parse({
|
|
256
|
+
scheme: definition.scheme,
|
|
257
|
+
network: definition.network,
|
|
258
|
+
maxAmountRequired: units,
|
|
259
|
+
asset: definition.asset.address,
|
|
260
|
+
payTo: definition.payTo,
|
|
261
|
+
resource: definition.resource,
|
|
262
|
+
description: definition.description,
|
|
263
|
+
mimeType: "application/json",
|
|
264
|
+
maxTimeoutSeconds: 900,
|
|
265
|
+
extra: {
|
|
266
|
+
symbol: definition.asset.symbol,
|
|
267
|
+
currencyCode: definition.currency.code,
|
|
268
|
+
decimals
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function decimalToBaseUnits(value, decimals) {
|
|
273
|
+
const [whole, fraction = ""] = value.split(".");
|
|
274
|
+
const sanitizedFraction = fraction.slice(0, decimals);
|
|
275
|
+
const paddedFraction = sanitizedFraction.padEnd(decimals, "0");
|
|
276
|
+
const combined = `${whole}${paddedFraction}`.replace(/^0+/, "");
|
|
277
|
+
return combined.length > 0 ? combined : "0";
|
|
278
|
+
}
|
|
279
|
+
function decodeJson(value, schema) {
|
|
280
|
+
const base64 = normalizeBase64(value);
|
|
281
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
282
|
+
const parsed = JSON.parse(json);
|
|
283
|
+
return schema.parse(parsed);
|
|
284
|
+
}
|
|
285
|
+
function normalizeBase64(input) {
|
|
286
|
+
if (/^[A-Za-z0-9+/=]+$/.test(input)) {
|
|
287
|
+
return input;
|
|
288
|
+
}
|
|
289
|
+
const restored = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
290
|
+
const paddingNeeded = (4 - restored.length % 4) % 4;
|
|
291
|
+
return restored + "=".repeat(paddingNeeded);
|
|
292
|
+
}
|
|
293
|
+
function buildFacilitatorHeaders(facilitator) {
|
|
294
|
+
const headers = {
|
|
295
|
+
"Content-Type": "application/json"
|
|
296
|
+
};
|
|
297
|
+
if (facilitator.apiKeyHeader && process.env.X402_FACILITATOR_API_KEY) {
|
|
298
|
+
headers[facilitator.apiKeyHeader] = process.env.X402_FACILITATOR_API_KEY;
|
|
299
|
+
}
|
|
300
|
+
return headers;
|
|
301
|
+
}
|
|
302
|
+
function ensureTrailingSlash(url) {
|
|
303
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/x402/payment.ts
|
|
307
|
+
var PAYMENT_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("opentool.x402.context");
|
|
308
|
+
var X402PaymentRequiredError = class extends Error {
|
|
309
|
+
constructor(response, verification) {
|
|
310
|
+
super("X402 Payment required");
|
|
311
|
+
this.name = "X402PaymentRequiredError";
|
|
312
|
+
this.response = response;
|
|
313
|
+
this.verification = verification;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
function setPaymentContext(request, context) {
|
|
317
|
+
try {
|
|
318
|
+
Object.defineProperty(request, PAYMENT_CONTEXT_SYMBOL, {
|
|
319
|
+
value: context,
|
|
320
|
+
configurable: true,
|
|
321
|
+
enumerable: false,
|
|
322
|
+
writable: true
|
|
323
|
+
});
|
|
324
|
+
} catch {
|
|
325
|
+
request[PAYMENT_CONTEXT_SYMBOL] = context;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function requireX402Payment(request, payment, options = {}) {
|
|
329
|
+
const definition = isX402Payment(payment) ? payment.definition : payment;
|
|
330
|
+
const attempt = extractX402Attempt(request);
|
|
331
|
+
if (!attempt) {
|
|
332
|
+
const response = createX402PaymentRequired(definition);
|
|
333
|
+
throw new X402PaymentRequiredError(response);
|
|
334
|
+
}
|
|
335
|
+
const verifyOptions = {
|
|
336
|
+
settle: options.settle !== void 0 ? options.settle : true
|
|
337
|
+
};
|
|
338
|
+
if (options.fetchImpl !== void 0) {
|
|
339
|
+
verifyOptions.fetchImpl = options.fetchImpl;
|
|
340
|
+
}
|
|
341
|
+
const verification = await verifyX402Payment(attempt, definition, verifyOptions);
|
|
342
|
+
if (!verification.success || !verification.metadata) {
|
|
343
|
+
if (options.onFailure) {
|
|
344
|
+
return options.onFailure(verification);
|
|
345
|
+
}
|
|
346
|
+
const response = createX402PaymentRequired(definition);
|
|
347
|
+
throw new X402PaymentRequiredError(response, verification);
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
payment: verification.metadata,
|
|
351
|
+
headers: verification.responseHeaders ?? {},
|
|
352
|
+
result: verification
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function withX402Payment(handler, payment, options = {}) {
|
|
356
|
+
return async (request) => {
|
|
357
|
+
const verification = await requireX402Payment(request, payment, options);
|
|
358
|
+
if (verification instanceof Response) {
|
|
359
|
+
return verification;
|
|
360
|
+
}
|
|
361
|
+
setPaymentContext(request, verification);
|
|
362
|
+
const response = await Promise.resolve(handler(request));
|
|
363
|
+
return applyPaymentHeaders(response, verification.headers);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function applyPaymentHeaders(response, headers) {
|
|
367
|
+
const entries = Object.entries(headers ?? {});
|
|
368
|
+
if (entries.length === 0) {
|
|
369
|
+
return response;
|
|
370
|
+
}
|
|
371
|
+
let mutated = false;
|
|
372
|
+
const merged = new Headers(response.headers);
|
|
373
|
+
for (const [key, value] of entries) {
|
|
374
|
+
if (!merged.has(key)) {
|
|
375
|
+
merged.set(key, value);
|
|
376
|
+
mutated = true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (!mutated) {
|
|
380
|
+
return response;
|
|
381
|
+
}
|
|
382
|
+
return new Response(response.body, {
|
|
383
|
+
status: response.status,
|
|
384
|
+
statusText: response.statusText,
|
|
385
|
+
headers: merged
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
function isX402Payment(value) {
|
|
389
|
+
return !!value && typeof value === "object" && "definition" in value && value.definition !== void 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/adapters/mcp.ts
|
|
393
|
+
var HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
|
|
394
|
+
function createMcpAdapter(options) {
|
|
395
|
+
const normalizedSchema = ensureSchema(options.schema);
|
|
396
|
+
const defaultMethod = resolveDefaultMethod(options);
|
|
397
|
+
const httpHandler = options.httpHandlers[defaultMethod];
|
|
398
|
+
if (!httpHandler) {
|
|
399
|
+
throw new Error(`Tool "${options.name}" does not export an HTTP handler for ${defaultMethod}`);
|
|
400
|
+
}
|
|
401
|
+
return async function invoke(rawArguments) {
|
|
402
|
+
const validated = normalizedSchema ? normalizedSchema.parse(rawArguments ?? {}) : rawArguments;
|
|
403
|
+
const request = buildRequest(options.name, defaultMethod, validated);
|
|
404
|
+
try {
|
|
405
|
+
const response = await Promise.resolve(httpHandler(request));
|
|
406
|
+
return await responseToToolResponse(response);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error instanceof X402PaymentRequiredError) {
|
|
409
|
+
return await responseToToolResponse(error.response);
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function resolveDefaultMethod(options) {
|
|
416
|
+
const explicit = options.defaultMethod?.toUpperCase();
|
|
417
|
+
if (explicit && typeof options.httpHandlers[explicit] === "function") {
|
|
418
|
+
return explicit;
|
|
419
|
+
}
|
|
420
|
+
const preferredOrder = ["POST", "PUT", "PATCH", "GET", "DELETE", "OPTIONS", "HEAD"];
|
|
421
|
+
for (const method of preferredOrder) {
|
|
422
|
+
if (typeof options.httpHandlers[method] === "function") {
|
|
423
|
+
return method;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const available = Object.keys(options.httpHandlers).filter(
|
|
427
|
+
(method) => typeof options.httpHandlers[method] === "function"
|
|
428
|
+
);
|
|
429
|
+
if (available.length > 0) {
|
|
430
|
+
return available[0];
|
|
431
|
+
}
|
|
432
|
+
throw new Error(`No HTTP handlers available for tool "${options.name}"`);
|
|
433
|
+
}
|
|
434
|
+
function ensureSchema(schema) {
|
|
435
|
+
if (!schema) {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
if (schema instanceof z.ZodType) {
|
|
439
|
+
return schema;
|
|
440
|
+
}
|
|
441
|
+
if (typeof schema?.parse === "function") {
|
|
442
|
+
return schema;
|
|
443
|
+
}
|
|
444
|
+
throw new Error("MCP adapter requires a valid Zod schema to validate arguments");
|
|
445
|
+
}
|
|
446
|
+
function buildRequest(name, method, params) {
|
|
447
|
+
const url = new URL(`https://opentool.local/${encodeURIComponent(name)}`);
|
|
448
|
+
const headers = new Headers({
|
|
449
|
+
"x-opentool-invocation": "mcp",
|
|
450
|
+
"x-opentool-tool": name
|
|
451
|
+
});
|
|
452
|
+
if (method === "GET" || method === "HEAD") {
|
|
453
|
+
if (params && typeof params === "object") {
|
|
454
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
455
|
+
if (value == null) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
url.searchParams.set(key, String(value));
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return new Request(url, { method, headers });
|
|
462
|
+
}
|
|
463
|
+
headers.set("Content-Type", "application/json");
|
|
464
|
+
const init = { method, headers };
|
|
465
|
+
if (params != null) {
|
|
466
|
+
init.body = JSON.stringify(params);
|
|
467
|
+
}
|
|
468
|
+
return new Request(url, init);
|
|
469
|
+
}
|
|
470
|
+
async function responseToToolResponse(response) {
|
|
471
|
+
const statusIsError = response.status >= 400;
|
|
472
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
473
|
+
const text = await response.text();
|
|
474
|
+
if (contentType.includes("application/json")) {
|
|
475
|
+
try {
|
|
476
|
+
const payload = text ? JSON.parse(text) : {};
|
|
477
|
+
if (payload && typeof payload === "object" && Array.isArray(payload.content)) {
|
|
478
|
+
return {
|
|
479
|
+
content: payload.content,
|
|
480
|
+
isError: payload.isError ?? statusIsError
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
485
|
+
isError: statusIsError
|
|
486
|
+
};
|
|
487
|
+
} catch {
|
|
488
|
+
return {
|
|
489
|
+
content: [{ type: "text", text }],
|
|
490
|
+
isError: statusIsError
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!text) {
|
|
495
|
+
return {
|
|
496
|
+
content: [],
|
|
497
|
+
isError: statusIsError
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
content: [{ type: "text", text }],
|
|
502
|
+
isError: statusIsError
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/runtime/index.ts
|
|
507
|
+
function createDevServer(tools) {
|
|
508
|
+
const metadata = loadMetadata();
|
|
509
|
+
const metadataMap = buildMetadataMap(metadata);
|
|
510
|
+
const adapters = buildAdapters(tools);
|
|
511
|
+
const server = new Server(
|
|
512
|
+
{
|
|
513
|
+
name: "opentool-dev",
|
|
514
|
+
version: "1.0.0"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
capabilities: {
|
|
518
|
+
tools: {}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
523
|
+
tools: adapters.map(({ tool }) => serializeTool(tool, metadataMap))
|
|
524
|
+
}));
|
|
525
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
526
|
+
const entry = adapters.find(({ tool }) => {
|
|
527
|
+
const toolName = tool.metadata?.name || tool.filename;
|
|
528
|
+
return toolName === request.params.name;
|
|
529
|
+
});
|
|
530
|
+
if (!entry) {
|
|
531
|
+
throw new Error(`Tool ${request.params.name} not found or not MCP-enabled`);
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
return await entry.invoke(request.params.arguments);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
const message = error && error.message || String(error);
|
|
537
|
+
return {
|
|
538
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
539
|
+
isError: true
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
return server;
|
|
544
|
+
}
|
|
545
|
+
async function createStdioServer(tools) {
|
|
546
|
+
const metadata = loadMetadata();
|
|
547
|
+
const metadataMap = buildMetadataMap(metadata);
|
|
548
|
+
const toolDefinitions = tools || await loadToolsFromDirectory(metadataMap);
|
|
549
|
+
const adapters = buildAdapters(toolDefinitions);
|
|
550
|
+
const server = new Server(
|
|
551
|
+
{
|
|
552
|
+
name: "opentool-runtime",
|
|
553
|
+
version: "1.0.0"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
capabilities: {
|
|
557
|
+
tools: {}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
562
|
+
tools: adapters.map(({ tool }) => serializeTool(tool, metadataMap))
|
|
563
|
+
}));
|
|
564
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
565
|
+
const entry = adapters.find(({ tool }) => {
|
|
566
|
+
const toolName = tool.metadata?.name || tool.filename;
|
|
567
|
+
return toolName === request.params.name;
|
|
568
|
+
});
|
|
569
|
+
if (!entry) {
|
|
570
|
+
throw new Error(`Tool ${request.params.name} not found or not MCP-enabled`);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
return await entry.invoke(request.params.arguments);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const message = error && error.message || String(error);
|
|
576
|
+
return {
|
|
577
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
578
|
+
isError: true
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
const transport = new StdioServerTransport();
|
|
583
|
+
await server.connect(transport);
|
|
584
|
+
console.error("MCP stdio server started");
|
|
585
|
+
}
|
|
586
|
+
function buildAdapters(tools) {
|
|
587
|
+
return tools.filter((tool) => isMcpEnabled(tool)).map((tool) => {
|
|
588
|
+
const httpHandlers = toHttpHandlerMap(tool.httpHandlers);
|
|
589
|
+
const adapterOptions = {
|
|
590
|
+
name: tool.metadata?.name || tool.filename,
|
|
591
|
+
httpHandlers,
|
|
592
|
+
...tool.schema ? { schema: tool.schema } : {},
|
|
593
|
+
...tool.mcpConfig?.defaultMethod ? { defaultMethod: tool.mcpConfig.defaultMethod } : {}
|
|
594
|
+
};
|
|
595
|
+
const adapter = createMcpAdapter(adapterOptions);
|
|
596
|
+
return {
|
|
597
|
+
tool,
|
|
598
|
+
invoke: adapter
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async function loadToolsFromDirectory(metadataMap) {
|
|
603
|
+
const tools = [];
|
|
604
|
+
const toolsDir = path.join(process.cwd(), "tools");
|
|
605
|
+
if (!fs.existsSync(toolsDir)) {
|
|
606
|
+
return tools;
|
|
607
|
+
}
|
|
608
|
+
const files = fs.readdirSync(toolsDir);
|
|
609
|
+
for (const file of files) {
|
|
610
|
+
if (!isSupportedToolFile(file)) {
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const toolPath = path.join(toolsDir, file);
|
|
614
|
+
try {
|
|
615
|
+
const exportsObject = __require(toolPath);
|
|
616
|
+
const candidate = resolveModuleCandidate(exportsObject);
|
|
617
|
+
if (!candidate?.schema) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const baseName = file.replace(/\.[^.]+$/, "");
|
|
621
|
+
const name = candidate.metadata?.name || baseName;
|
|
622
|
+
const meta = metadataMap.get(name);
|
|
623
|
+
let inputSchema = meta?.inputSchema;
|
|
624
|
+
if (!inputSchema) {
|
|
625
|
+
try {
|
|
626
|
+
inputSchema = zodToJsonSchema(candidate.schema, {
|
|
627
|
+
name: `${name}Schema`,
|
|
628
|
+
target: "jsonSchema7",
|
|
629
|
+
$refStrategy: "none"
|
|
630
|
+
});
|
|
631
|
+
} catch {
|
|
632
|
+
inputSchema = { type: "object" };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
inputSchema = normalizeInputSchema(inputSchema);
|
|
636
|
+
const payment = candidate.payment ?? null;
|
|
637
|
+
const httpHandlersRaw = collectHttpHandlers(candidate);
|
|
638
|
+
const httpHandlers = [...httpHandlersRaw];
|
|
639
|
+
if (httpHandlers.length === 0) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (payment) {
|
|
643
|
+
for (let index = 0; index < httpHandlers.length; index += 1) {
|
|
644
|
+
const entry = httpHandlers[index];
|
|
645
|
+
httpHandlers[index] = {
|
|
646
|
+
...entry,
|
|
647
|
+
handler: withX402Payment(entry.handler, payment)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const mcpConfig = normalizeRuntimeMcpConfig(candidate.mcp);
|
|
652
|
+
const adapterOptions = {
|
|
653
|
+
name,
|
|
654
|
+
httpHandlers: toHttpHandlerMap(httpHandlers),
|
|
655
|
+
...candidate.schema ? { schema: candidate.schema } : {},
|
|
656
|
+
...typeof candidate.mcp?.defaultMethod === "string" ? { defaultMethod: candidate.mcp.defaultMethod } : {}
|
|
657
|
+
};
|
|
658
|
+
const adapter = createMcpAdapter(adapterOptions);
|
|
659
|
+
const tool = {
|
|
660
|
+
...candidate.schema ? { schema: candidate.schema } : {},
|
|
661
|
+
inputSchema,
|
|
662
|
+
metadata: candidate.metadata || meta || null,
|
|
663
|
+
filename: baseName,
|
|
664
|
+
httpHandlers,
|
|
665
|
+
mcpConfig,
|
|
666
|
+
handler: async (params) => adapter(params),
|
|
667
|
+
payment
|
|
668
|
+
};
|
|
669
|
+
tools.push(tool);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.warn(`Failed to load tool from ${file}: ${error}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return tools;
|
|
675
|
+
}
|
|
676
|
+
function loadMetadata() {
|
|
677
|
+
const metadataPath = path.join(process.cwd(), "metadata.json");
|
|
678
|
+
if (!fs.existsSync(metadataPath)) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const contents = fs.readFileSync(metadataPath, "utf8");
|
|
683
|
+
return JSON.parse(contents);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
console.warn(`Failed to parse metadata.json: ${error}`);
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function buildMetadataMap(metadata) {
|
|
690
|
+
const map = /* @__PURE__ */ new Map();
|
|
691
|
+
if (!metadata?.tools) {
|
|
692
|
+
return map;
|
|
693
|
+
}
|
|
694
|
+
metadata.tools.forEach((tool) => {
|
|
695
|
+
map.set(tool.name, tool);
|
|
696
|
+
});
|
|
697
|
+
return map;
|
|
698
|
+
}
|
|
699
|
+
function serializeTool(tool, metadataMap) {
|
|
700
|
+
const name = tool.metadata?.name || tool.filename;
|
|
701
|
+
const meta = metadataMap.get(name);
|
|
702
|
+
return {
|
|
703
|
+
name,
|
|
704
|
+
description: meta?.description || tool.metadata?.description || `${tool.filename} tool`,
|
|
705
|
+
inputSchema: meta?.inputSchema || tool.inputSchema,
|
|
706
|
+
annotations: meta?.annotations || tool.metadata?.annotations,
|
|
707
|
+
payment: meta?.payment || tool.metadata?.payment,
|
|
708
|
+
discovery: meta?.discovery || tool.metadata?.discovery
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function isSupportedToolFile(file) {
|
|
712
|
+
return /\.(cjs|mjs|js|ts)$/i.test(file);
|
|
713
|
+
}
|
|
714
|
+
function resolveModuleCandidate(exportsObject) {
|
|
715
|
+
if (!exportsObject) {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
if (exportsObject.schema) {
|
|
719
|
+
return exportsObject;
|
|
720
|
+
}
|
|
721
|
+
if (exportsObject.default && exportsObject.default.schema) {
|
|
722
|
+
return exportsObject.default;
|
|
723
|
+
}
|
|
724
|
+
return exportsObject;
|
|
725
|
+
}
|
|
726
|
+
function collectHttpHandlers(module) {
|
|
727
|
+
const handlers = [];
|
|
728
|
+
HTTP_METHODS.forEach((method) => {
|
|
729
|
+
const handler = module?.[method];
|
|
730
|
+
if (typeof handler === "function") {
|
|
731
|
+
handlers.push({
|
|
732
|
+
method,
|
|
733
|
+
handler: async (request) => handler.call(module, request)
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
return handlers;
|
|
738
|
+
}
|
|
739
|
+
function toHttpHandlerMap(handlers) {
|
|
740
|
+
return handlers.reduce((acc, handler) => {
|
|
741
|
+
acc[handler.method.toUpperCase()] = handler.handler;
|
|
742
|
+
return acc;
|
|
743
|
+
}, {});
|
|
744
|
+
}
|
|
745
|
+
function normalizeInputSchema(schema) {
|
|
746
|
+
if (!schema || typeof schema !== "object") {
|
|
747
|
+
return schema;
|
|
748
|
+
}
|
|
749
|
+
const clone = JSON.parse(JSON.stringify(schema));
|
|
750
|
+
if (typeof clone.$ref === "string" && clone.$ref.startsWith("#/definitions/")) {
|
|
751
|
+
const refKey = clone.$ref.replace("#/definitions/", "");
|
|
752
|
+
if (clone.definitions && typeof clone.definitions[refKey] === "object") {
|
|
753
|
+
return normalizeInputSchema(clone.definitions[refKey]);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
delete clone.$ref;
|
|
757
|
+
delete clone.definitions;
|
|
758
|
+
if (!clone.type) {
|
|
759
|
+
clone.type = "object";
|
|
760
|
+
}
|
|
761
|
+
return clone;
|
|
762
|
+
}
|
|
763
|
+
function normalizeRuntimeMcpConfig(rawConfig) {
|
|
764
|
+
if (isPlainObject(rawConfig) && rawConfig.enabled === true) {
|
|
765
|
+
let normalizedMode;
|
|
766
|
+
if (typeof rawConfig.mode === "string") {
|
|
767
|
+
const candidate = rawConfig.mode.toLowerCase();
|
|
768
|
+
if (candidate === "stdio" || candidate === "lambda" || candidate === "dual") {
|
|
769
|
+
normalizedMode = candidate;
|
|
770
|
+
} else {
|
|
771
|
+
throw new Error('mcp.mode must be one of "stdio", "lambda", or "dual"');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const metadataOverrides = isPlainObject(rawConfig.metadataOverrides) ? rawConfig.metadataOverrides : void 0;
|
|
775
|
+
const config = { enabled: true };
|
|
776
|
+
if (normalizedMode) {
|
|
777
|
+
config.mode = normalizedMode;
|
|
778
|
+
}
|
|
779
|
+
if (typeof rawConfig.defaultMethod === "string") {
|
|
780
|
+
config.defaultMethod = rawConfig.defaultMethod.toUpperCase();
|
|
781
|
+
}
|
|
782
|
+
if (metadataOverrides) {
|
|
783
|
+
config.metadataOverrides = metadataOverrides;
|
|
784
|
+
}
|
|
785
|
+
return config;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
function isPlainObject(value) {
|
|
790
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
791
|
+
}
|
|
792
|
+
function isMcpEnabled(tool) {
|
|
793
|
+
return Boolean(tool.mcpConfig?.enabled);
|
|
794
|
+
}
|
|
795
|
+
function resolveRuntimePath(value) {
|
|
796
|
+
if (value.startsWith("file://")) {
|
|
797
|
+
return fileURLToPath(value);
|
|
798
|
+
}
|
|
799
|
+
return path.resolve(value);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export { createDevServer, createStdioServer, resolveRuntimePath };
|
|
803
|
+
//# sourceMappingURL=index.js.map
|
|
804
|
+
//# sourceMappingURL=index.js.map
|