@runtab/mcp 0.0.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/LICENSE +21 -0
- package/dist/bootstrap.d.ts +18 -0
- package/dist/bootstrap.js +125 -0
- package/dist/cli-config.d.ts +11 -0
- package/dist/cli-config.js +59 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/control-plane-origin.d.ts +4 -0
- package/dist/control-plane-origin.js +27 -0
- package/dist/detect.d.ts +10 -0
- package/dist/detect.js +74 -0
- package/dist/durable-mcp-payment.d.ts +40 -0
- package/dist/durable-mcp-payment.js +105 -0
- package/dist/durable-payment-fetch.d.ts +19 -0
- package/dist/durable-payment-fetch.js +117 -0
- package/dist/eip3009-authorization.d.ts +59 -0
- package/dist/eip3009-authorization.js +142 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/fetch-wire.d.ts +46 -0
- package/dist/fetch-wire.js +143 -0
- package/dist/fetch-wrapper.d.ts +24 -0
- package/dist/fetch-wrapper.js +70 -0
- package/dist/fetch.d.ts +3 -0
- package/dist/fetch.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/origin-context.d.ts +5 -0
- package/dist/origin-context.js +15 -0
- package/dist/paid-fetch-server.d.ts +49 -0
- package/dist/paid-fetch-server.js +104 -0
- package/dist/payment-authorization-state.d.ts +48 -0
- package/dist/payment-authorization-state.js +150 -0
- package/dist/payment-client.d.ts +4 -0
- package/dist/payment-client.js +14 -0
- package/dist/payment-correlation.d.ts +9 -0
- package/dist/payment-correlation.js +36 -0
- package/dist/payment-envelope-capacity.d.ts +2 -0
- package/dist/payment-envelope-capacity.js +28 -0
- package/dist/payment-envelope-disk.d.ts +5 -0
- package/dist/payment-envelope-disk.js +97 -0
- package/dist/payment-envelope-journal.d.ts +54 -0
- package/dist/payment-envelope-journal.js +116 -0
- package/dist/payment-envelope-lock.d.ts +5 -0
- package/dist/payment-envelope-lock.js +88 -0
- package/dist/payment-envelope-model.d.ts +32 -0
- package/dist/payment-envelope-model.js +123 -0
- package/dist/payment-envelope-store.d.ts +31 -0
- package/dist/payment-envelope-store.js +210 -0
- package/dist/payment-envelope.d.ts +17 -0
- package/dist/payment-envelope.js +145 -0
- package/dist/payment-idempotency.d.ts +2 -0
- package/dist/payment-idempotency.js +8 -0
- package/dist/payment-observation-reporter.d.ts +31 -0
- package/dist/payment-observation-reporter.js +151 -0
- package/dist/payment-profile.d.ts +3 -0
- package/dist/payment-profile.js +4 -0
- package/dist/payment-request-fingerprint.d.ts +5 -0
- package/dist/payment-request-fingerprint.js +54 -0
- package/dist/payment-settlement-observation.d.ts +9 -0
- package/dist/payment-settlement-observation.js +74 -0
- package/dist/payment-signal.d.ts +2 -0
- package/dist/payment-signal.js +8 -0
- package/dist/payment-target-network.d.ts +16 -0
- package/dist/payment-target-network.js +178 -0
- package/dist/payment-target-policy.d.ts +10 -0
- package/dist/payment-target-policy.js +75 -0
- package/dist/proxy.d.ts +47 -0
- package/dist/proxy.js +72 -0
- package/dist/remote-signer-http.d.ts +16 -0
- package/dist/remote-signer-http.js +182 -0
- package/dist/remote-signer.d.ts +34 -0
- package/dist/remote-signer.js +157 -0
- package/dist/resource-url.d.ts +5 -0
- package/dist/resource-url.js +36 -0
- package/dist/routing.d.ts +13 -0
- package/dist/routing.js +42 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +92 -0
- package/dist/upstream.d.ts +43 -0
- package/dist/upstream.js +38 -0
- package/package.json +65 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { isAddress, isAddressEqual, recoverTypedDataAddress } from "viem";
|
|
2
|
+
import { validateControlPlaneOrigin } from "./control-plane-origin.js";
|
|
3
|
+
import { InvalidEip3009AuthorizationError, parseExactEip3009Authorization, } from "./eip3009-authorization.js";
|
|
4
|
+
import { currentPaymentOrigin, currentPaymentResourceUrl } from "./origin-context.js";
|
|
5
|
+
import { PaymentCorrelations } from "./payment-correlation.js";
|
|
6
|
+
import { PaymentObservationReporter } from "./payment-observation-reporter.js";
|
|
7
|
+
import { currentPaymentSignal } from "./payment-signal.js";
|
|
8
|
+
import { postRemoteSignerJson, RemoteSignerError } from "./remote-signer-http.js";
|
|
9
|
+
import { redactPaymentResourceUrl } from "./resource-url.js";
|
|
10
|
+
export { RemoteSignerError } from "./remote-signer-http.js";
|
|
11
|
+
function record(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
export class TabRemoteSigner {
|
|
15
|
+
address;
|
|
16
|
+
#apiKey;
|
|
17
|
+
#correlations;
|
|
18
|
+
#endpoint;
|
|
19
|
+
#fetch;
|
|
20
|
+
#nowSeconds;
|
|
21
|
+
#observationReporter;
|
|
22
|
+
#origin;
|
|
23
|
+
#paymentProfile;
|
|
24
|
+
#reconcileEndpoint;
|
|
25
|
+
#resourceUrl;
|
|
26
|
+
#signal;
|
|
27
|
+
#signTimeoutMs;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
if (!isAddress(options.address))
|
|
30
|
+
throw new Error("Agent signer address is invalid");
|
|
31
|
+
if (!options.apiKey)
|
|
32
|
+
throw new Error("TAB_AGENT_KEY is required");
|
|
33
|
+
this.address = options.address;
|
|
34
|
+
this.#apiKey = options.apiKey;
|
|
35
|
+
const apiBaseUrl = validateControlPlaneOrigin(options.apiBaseUrl);
|
|
36
|
+
this.#endpoint = new URL("/api/agent/sign", apiBaseUrl);
|
|
37
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
38
|
+
this.#nowSeconds = options.nowSeconds ?? (() => Math.floor(Date.now() / 1_000));
|
|
39
|
+
this.#correlations = new PaymentCorrelations(this.#nowSeconds);
|
|
40
|
+
this.#origin = options.origin ?? currentPaymentOrigin;
|
|
41
|
+
this.#paymentProfile = options.paymentProfile;
|
|
42
|
+
this.#resourceUrl = options.resourceUrl ?? currentPaymentResourceUrl;
|
|
43
|
+
const reportAttempts = options.reportAttempts ?? 3;
|
|
44
|
+
const reportRetryDelayMs = options.reportRetryDelayMs ?? 250;
|
|
45
|
+
const reportTimeoutMs = options.reportTimeoutMs ?? 7_500;
|
|
46
|
+
this.#reconcileEndpoint = new URL("/api/agent/pay/reconcile", apiBaseUrl);
|
|
47
|
+
this.#signal = options.signal ?? currentPaymentSignal;
|
|
48
|
+
this.#signTimeoutMs = options.signTimeoutMs ?? 10_000;
|
|
49
|
+
if (!Number.isSafeInteger(reportAttempts) ||
|
|
50
|
+
reportAttempts < 1 ||
|
|
51
|
+
!Number.isSafeInteger(reportRetryDelayMs) ||
|
|
52
|
+
reportRetryDelayMs < 0 ||
|
|
53
|
+
!Number.isSafeInteger(reportTimeoutMs) ||
|
|
54
|
+
reportTimeoutMs < 1 ||
|
|
55
|
+
!Number.isSafeInteger(this.#signTimeoutMs) ||
|
|
56
|
+
this.#signTimeoutMs < 1) {
|
|
57
|
+
throw new Error("Agent result-report timeout is invalid");
|
|
58
|
+
}
|
|
59
|
+
this.#observationReporter = new PaymentObservationReporter({
|
|
60
|
+
apiKey: this.#apiKey,
|
|
61
|
+
attempts: reportAttempts,
|
|
62
|
+
correlations: this.#correlations,
|
|
63
|
+
endpoint: new URL("/api/agent/pay/result", apiBaseUrl),
|
|
64
|
+
fetch: this.#fetch,
|
|
65
|
+
retryDelayMs: reportRetryDelayMs,
|
|
66
|
+
timeoutMs: reportTimeoutMs,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async signTypedData(signerRequest) {
|
|
70
|
+
let authorization;
|
|
71
|
+
try {
|
|
72
|
+
authorization = parseExactEip3009Authorization(signerRequest, {
|
|
73
|
+
address: this.address,
|
|
74
|
+
nowSeconds: this.#nowSeconds(),
|
|
75
|
+
paymentProfile: this.#paymentProfile,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof InvalidEip3009AuthorizationError))
|
|
80
|
+
throw error;
|
|
81
|
+
throw new RemoteSignerError("INVALID_SIGNER_REQUEST", "The signer request is invalid.", 400);
|
|
82
|
+
}
|
|
83
|
+
const origin = this.#origin?.();
|
|
84
|
+
const rawResourceUrl = this.#resourceUrl?.();
|
|
85
|
+
const resourceUrl = rawResourceUrl === undefined ? undefined : redactPaymentResourceUrl(rawResourceUrl);
|
|
86
|
+
const paymentSignal = this.#signal();
|
|
87
|
+
const body = (await postRemoteSignerJson({
|
|
88
|
+
apiKey: this.#apiKey,
|
|
89
|
+
body: {
|
|
90
|
+
amount: authorization.amount,
|
|
91
|
+
asset: authorization.asset,
|
|
92
|
+
network: authorization.network,
|
|
93
|
+
...(origin ? { origin } : {}),
|
|
94
|
+
payTo: authorization.payTo,
|
|
95
|
+
...(resourceUrl ? { resourceUrl } : {}),
|
|
96
|
+
signerRequest: authorization.typedData,
|
|
97
|
+
},
|
|
98
|
+
endpoint: this.#endpoint,
|
|
99
|
+
fetch: this.#fetch,
|
|
100
|
+
...(paymentSignal ? { signal: paymentSignal } : {}),
|
|
101
|
+
timeoutMs: this.#signTimeoutMs,
|
|
102
|
+
}));
|
|
103
|
+
if (typeof body.receiptId !== "string" ||
|
|
104
|
+
typeof body.signature !== "string" ||
|
|
105
|
+
!/^0x[0-9a-fA-F]{130}$/.test(body.signature)) {
|
|
106
|
+
throw new RemoteSignerError("INVALID_SIGNER_RESPONSE", "The signer response is invalid.", 502);
|
|
107
|
+
}
|
|
108
|
+
const signature = body.signature;
|
|
109
|
+
try {
|
|
110
|
+
const recovered = await recoverTypedDataAddress({
|
|
111
|
+
...authorization.typedData,
|
|
112
|
+
signature,
|
|
113
|
+
});
|
|
114
|
+
if (!isAddressEqual(recovered, this.address))
|
|
115
|
+
throw new Error("Signer mismatch");
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
throw new RemoteSignerError("INVALID_SIGNER_RESPONSE", "The signer response is invalid.", 502);
|
|
119
|
+
}
|
|
120
|
+
this.#correlations.set(signature, body.receiptId, Number(authorization.typedData.message.validBefore));
|
|
121
|
+
return signature;
|
|
122
|
+
}
|
|
123
|
+
receiptIdForSignature(signature) {
|
|
124
|
+
return this.#correlations.get(signature);
|
|
125
|
+
}
|
|
126
|
+
restorePaymentCorrelation(signature, receiptId, validBeforeSeconds) {
|
|
127
|
+
this.#correlations.restore(signature, receiptId, validBeforeSeconds);
|
|
128
|
+
}
|
|
129
|
+
async reconcileExpiredPayment(receiptId) {
|
|
130
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(receiptId))
|
|
131
|
+
return false;
|
|
132
|
+
try {
|
|
133
|
+
const signal = this.#signal();
|
|
134
|
+
const acknowledgement = await postRemoteSignerJson({
|
|
135
|
+
apiKey: this.#apiKey,
|
|
136
|
+
body: { receiptId },
|
|
137
|
+
endpoint: this.#reconcileEndpoint,
|
|
138
|
+
fetch: this.#fetch,
|
|
139
|
+
...(signal ? { signal } : {}),
|
|
140
|
+
timeoutMs: this.#signTimeoutMs,
|
|
141
|
+
});
|
|
142
|
+
return (record(acknowledgement) &&
|
|
143
|
+
acknowledgement.receiptId === receiptId &&
|
|
144
|
+
acknowledgement.status === "failed" &&
|
|
145
|
+
acknowledgement.verified === true);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
reportPaymentObservation(context) {
|
|
152
|
+
return this.#observationReporter.report(context, this.address);
|
|
153
|
+
}
|
|
154
|
+
async flushPaymentObservations() {
|
|
155
|
+
await this.#observationReporter.flush();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const MAX_RESOURCE_HOST_LENGTH = 253;
|
|
2
|
+
const MAX_RESOURCE_URL_LENGTH = 2_048;
|
|
3
|
+
const SUPPORTED_RESOURCE_PROTOCOLS = new Set(["http:", "https:", "mcp:"]);
|
|
4
|
+
export class InvalidPaymentResourceUrlError extends Error {
|
|
5
|
+
code = "INVALID_PAYMENT_RESOURCE_URL";
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Payment resource URL must be a supported absolute URL.");
|
|
8
|
+
this.name = "InvalidPaymentResourceUrlError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function redactPaymentResourceUrl(value) {
|
|
12
|
+
if (value.length < 1 || value.length > MAX_RESOURCE_URL_LENGTH) {
|
|
13
|
+
throw new InvalidPaymentResourceUrlError();
|
|
14
|
+
}
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new InvalidPaymentResourceUrlError();
|
|
21
|
+
}
|
|
22
|
+
if (!SUPPORTED_RESOURCE_PROTOCOLS.has(url.protocol) ||
|
|
23
|
+
url.hostname.length < 1 ||
|
|
24
|
+
url.hostname.length > MAX_RESOURCE_HOST_LENGTH) {
|
|
25
|
+
throw new InvalidPaymentResourceUrlError();
|
|
26
|
+
}
|
|
27
|
+
url.hostname = url.hostname.toLowerCase();
|
|
28
|
+
url.username = "";
|
|
29
|
+
url.password = "";
|
|
30
|
+
url.search = "";
|
|
31
|
+
url.hash = "";
|
|
32
|
+
const redacted = url.toString();
|
|
33
|
+
if (redacted.length > MAX_RESOURCE_URL_LENGTH)
|
|
34
|
+
throw new InvalidPaymentResourceUrlError();
|
|
35
|
+
return redacted;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PaymentRequirements } from "@x402/core/types";
|
|
2
|
+
import type { PaymentProfile } from "./payment-profile.js";
|
|
3
|
+
export declare const BASE_NETWORK: "eip155:8453";
|
|
4
|
+
export declare const ARBITRUM_NETWORK: "eip155:42161";
|
|
5
|
+
export declare const BASE_SEPOLIA_NETWORK: "eip155:84532";
|
|
6
|
+
export declare const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
7
|
+
export declare const ARBITRUM_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
|
|
8
|
+
export declare const BASE_SEPOLIA_USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
9
|
+
export declare class UnsupportedPaymentNetworkError extends Error {
|
|
10
|
+
readonly code = "UNSUPPORTED_NETWORK";
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
13
|
+
export declare function selectLeashPaymentRequirements(paymentProfile: PaymentProfile, x402Version: number, requirements: PaymentRequirements[]): PaymentRequirements;
|
package/dist/routing.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const BASE_NETWORK = "eip155:8453";
|
|
2
|
+
export const ARBITRUM_NETWORK = "eip155:42161";
|
|
3
|
+
export const BASE_SEPOLIA_NETWORK = "eip155:84532";
|
|
4
|
+
export const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
5
|
+
export const ARBITRUM_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
|
|
6
|
+
export const BASE_SEPOLIA_USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
7
|
+
export class UnsupportedPaymentNetworkError extends Error {
|
|
8
|
+
code = "UNSUPPORTED_NETWORK";
|
|
9
|
+
constructor() {
|
|
10
|
+
super("The resource does not accept a supported Agent payment network.");
|
|
11
|
+
this.name = "UnsupportedPaymentNetworkError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const PROFILE_NETWORKS = {
|
|
15
|
+
base_sepolia_integration: [
|
|
16
|
+
{ asset: BASE_SEPOLIA_USDC, name: "USDC", network: BASE_SEPOLIA_NETWORK },
|
|
17
|
+
],
|
|
18
|
+
mainnet: [
|
|
19
|
+
{ asset: BASE_USDC, name: "USD Coin", network: BASE_NETWORK },
|
|
20
|
+
{ asset: ARBITRUM_USDC, name: "USD Coin", network: ARBITRUM_NETWORK },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
function supportedNetwork(paymentProfile, requirement) {
|
|
24
|
+
return PROFILE_NETWORKS[paymentProfile].find((entry) => requirement.network === entry.network &&
|
|
25
|
+
requirement.asset.toLowerCase() === entry.asset.toLowerCase() &&
|
|
26
|
+
requirement.extra.name === entry.name &&
|
|
27
|
+
requirement.extra.version === "2");
|
|
28
|
+
}
|
|
29
|
+
export function selectLeashPaymentRequirements(paymentProfile, x402Version, requirements) {
|
|
30
|
+
if (x402Version !== 2)
|
|
31
|
+
throw new UnsupportedPaymentNetworkError();
|
|
32
|
+
const exact = requirements.filter((requirement) => requirement.scheme === "exact" &&
|
|
33
|
+
supportedNetwork(paymentProfile, requirement) !== undefined &&
|
|
34
|
+
(requirement.extra.assetTransferMethod === undefined ||
|
|
35
|
+
requirement.extra.assetTransferMethod === "eip3009"));
|
|
36
|
+
for (const entry of PROFILE_NETWORKS[paymentProfile]) {
|
|
37
|
+
const match = exact.find((requirement) => requirement.network === entry.network);
|
|
38
|
+
if (match)
|
|
39
|
+
return match;
|
|
40
|
+
}
|
|
41
|
+
throw new UnsupportedPaymentNetworkError();
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import type { LeashCliConfig } from "./cli-config.js";
|
|
3
|
+
interface StartLeashMcpOptions {
|
|
4
|
+
config: LeashCliConfig;
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
stdin?: Readable;
|
|
7
|
+
stdout?: Writable;
|
|
8
|
+
}
|
|
9
|
+
export interface LeashMcpRuntime {
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function startLeashMcp(options: StartLeashMcpOptions): Promise<LeashMcpRuntime>;
|
|
13
|
+
export {};
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { connectLeashAgent } from "./bootstrap.js";
|
|
3
|
+
import { createDurableMcpPayment } from "./durable-mcp-payment.js";
|
|
4
|
+
import { createPaidFetchServer } from "./paid-fetch-server.js";
|
|
5
|
+
import { createLeashPaymentClient } from "./payment-client.js";
|
|
6
|
+
import { defaultPaymentStateDirectory, PaymentEnvelopeStore } from "./payment-envelope-store.js";
|
|
7
|
+
import { createLeashProxyServer } from "./proxy.js";
|
|
8
|
+
import { TabRemoteSigner } from "./remote-signer.js";
|
|
9
|
+
import { connectStreamableHttpUpstream } from "./upstream.js";
|
|
10
|
+
export async function startLeashMcp(options) {
|
|
11
|
+
const fetch_ = options.fetch ?? globalThis.fetch;
|
|
12
|
+
const agent = await connectLeashAgent({
|
|
13
|
+
apiBaseUrl: options.config.apiBaseUrl,
|
|
14
|
+
apiKey: options.config.apiKey,
|
|
15
|
+
fetch: fetch_,
|
|
16
|
+
});
|
|
17
|
+
let signer;
|
|
18
|
+
let upstreamConnection;
|
|
19
|
+
if (agent.address) {
|
|
20
|
+
signer = new TabRemoteSigner({
|
|
21
|
+
address: agent.address,
|
|
22
|
+
apiBaseUrl: options.config.apiBaseUrl,
|
|
23
|
+
apiKey: options.config.apiKey,
|
|
24
|
+
fetch: fetch_,
|
|
25
|
+
paymentProfile: agent.paymentProfile,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (options.config.upstreamUrl) {
|
|
29
|
+
upstreamConnection = await connectStreamableHttpUpstream(options.config.upstreamUrl, {
|
|
30
|
+
allowDevelopmentLoopback: options.config.allowDevelopmentLoopback,
|
|
31
|
+
fetch: fetch_,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const upstream = upstreamConnection?.client;
|
|
35
|
+
const server = upstream
|
|
36
|
+
? createLeashProxyServer({
|
|
37
|
+
...(signer
|
|
38
|
+
? {
|
|
39
|
+
payment: createDurableMcpPayment({
|
|
40
|
+
address: signer.address,
|
|
41
|
+
client: createLeashPaymentClient(signer, agent.paymentProfile),
|
|
42
|
+
paymentProfile: agent.paymentProfile,
|
|
43
|
+
signer,
|
|
44
|
+
store: new PaymentEnvelopeStore(signer.address, defaultPaymentStateDirectory()),
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
: {}),
|
|
48
|
+
upstream,
|
|
49
|
+
})
|
|
50
|
+
: createPaidFetchServer({
|
|
51
|
+
address: agent.address,
|
|
52
|
+
allowDevelopmentLoopback: options.config.allowDevelopmentLoopback,
|
|
53
|
+
apiBaseUrl: options.config.apiBaseUrl,
|
|
54
|
+
apiKey: options.config.apiKey,
|
|
55
|
+
fetch: fetch_,
|
|
56
|
+
paymentProfile: agent.paymentProfile,
|
|
57
|
+
...(signer ? { signer } : {}),
|
|
58
|
+
});
|
|
59
|
+
let resourceClosePromise;
|
|
60
|
+
const closeResources = () => {
|
|
61
|
+
resourceClosePromise ??= (async () => {
|
|
62
|
+
if (signer)
|
|
63
|
+
await signer.flushPaymentObservations();
|
|
64
|
+
if (upstreamConnection)
|
|
65
|
+
await upstreamConnection.close();
|
|
66
|
+
})();
|
|
67
|
+
return resourceClosePromise;
|
|
68
|
+
};
|
|
69
|
+
const closeServerResources = server.onclose;
|
|
70
|
+
server.onclose = () => {
|
|
71
|
+
closeServerResources?.();
|
|
72
|
+
void closeResources();
|
|
73
|
+
};
|
|
74
|
+
const transport = new StdioServerTransport(options.stdin, options.stdout);
|
|
75
|
+
try {
|
|
76
|
+
await server.connect(transport);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await closeResources();
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
let closePromise;
|
|
83
|
+
return {
|
|
84
|
+
close() {
|
|
85
|
+
closePromise ??= (async () => {
|
|
86
|
+
await server.close();
|
|
87
|
+
await closeResources();
|
|
88
|
+
})();
|
|
89
|
+
return closePromise;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { type PaymentTargetLookup } from "./payment-target-network.js";
|
|
3
|
+
export declare function connectStreamableHttpUpstream(endpoint: string, options?: {
|
|
4
|
+
allowDevelopmentLoopback?: boolean;
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
lookup?: PaymentTargetLookup;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
client: Client<{
|
|
9
|
+
method: string;
|
|
10
|
+
params?: {
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
_meta?: {
|
|
13
|
+
[x: string]: unknown;
|
|
14
|
+
progressToken?: string | number | undefined;
|
|
15
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
16
|
+
taskId: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
} | undefined;
|
|
19
|
+
} | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
method: string;
|
|
22
|
+
params?: {
|
|
23
|
+
[x: string]: unknown;
|
|
24
|
+
_meta?: {
|
|
25
|
+
[x: string]: unknown;
|
|
26
|
+
progressToken?: string | number | undefined;
|
|
27
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
28
|
+
taskId: string;
|
|
29
|
+
} | undefined;
|
|
30
|
+
} | undefined;
|
|
31
|
+
} | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
[x: string]: unknown;
|
|
34
|
+
_meta?: {
|
|
35
|
+
[x: string]: unknown;
|
|
36
|
+
progressToken?: string | number | undefined;
|
|
37
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
38
|
+
taskId: string;
|
|
39
|
+
} | undefined;
|
|
40
|
+
} | undefined;
|
|
41
|
+
}>;
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
}>;
|
package/dist/upstream.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
import { createPinnedPaymentFetch } from "./payment-target-network.js";
|
|
4
|
+
export async function connectStreamableHttpUpstream(endpoint, options = {}) {
|
|
5
|
+
const client = new Client({ name: "tab-mcp-upstream", version: "0.0.1" });
|
|
6
|
+
const policy = createPinnedPaymentFetch({
|
|
7
|
+
allowDevelopmentLoopback: options.allowDevelopmentLoopback === true,
|
|
8
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
9
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
10
|
+
});
|
|
11
|
+
const transport = new StreamableHTTPClientTransport(new URL(endpoint), {
|
|
12
|
+
fetch: policy.fetch,
|
|
13
|
+
});
|
|
14
|
+
try {
|
|
15
|
+
// SDK 1.29's exact-optional transport declarations are structurally incompatible.
|
|
16
|
+
await client.connect(transport);
|
|
17
|
+
let closePromise;
|
|
18
|
+
return {
|
|
19
|
+
client,
|
|
20
|
+
close() {
|
|
21
|
+
closePromise ??= (async () => {
|
|
22
|
+
try {
|
|
23
|
+
await client.close();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
await policy.close();
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
return closePromise;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
await transport.close().catch(() => undefined);
|
|
35
|
+
await policy.close().catch(() => undefined);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@runtab/mcp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"tab-mcp": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./fetch": {
|
|
17
|
+
"types": "./dist/fetch.d.ts",
|
|
18
|
+
"import": "./dist/fetch.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "rm -rf dist && tsc --build tsconfig.build.json --force",
|
|
26
|
+
"dev": "tsc --watch --preserveWatchOutput --project tsconfig.build.json",
|
|
27
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
28
|
+
"test": "vitest run --passWithNoTests",
|
|
29
|
+
"lint": "biome check .",
|
|
30
|
+
"verify:pack": "node scripts/verify-packed-artifact.mjs"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
|
+
"@x402/core": "~2.17.0",
|
|
35
|
+
"@x402/evm": "~2.17.0",
|
|
36
|
+
"@x402/mcp": "~2.17.0",
|
|
37
|
+
"fs-native-extensions": "1.5.0",
|
|
38
|
+
"undici": "7.28.0",
|
|
39
|
+
"viem": "^2.48.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"tsx": "4.23.1",
|
|
44
|
+
"typescript": "^5.8",
|
|
45
|
+
"vitest": "^4.1.10"
|
|
46
|
+
},
|
|
47
|
+
"description": "Tab MCP proxy: lets an AI agent pay x402 (HTTP 402) charges in USDC under a server-enforced spending cap \u2014 the agent never holds a key.",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/Blockchain-Oracle/tab.git",
|
|
52
|
+
"directory": "apps/agent"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"mcp",
|
|
56
|
+
"x402",
|
|
57
|
+
"ai-agent",
|
|
58
|
+
"payments",
|
|
59
|
+
"usdc",
|
|
60
|
+
"model-context-protocol"
|
|
61
|
+
],
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
}
|
|
65
|
+
}
|