tiny-http-mcp-server 0.1.19 → 0.1.21
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/dist/composition.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tiny-mcp-client
|
|
2
2
|
|
|
3
|
-
`tiny-mcp-client` is a lightweight Model Context Protocol client used by tests, fixtures, and package integrations. It supports stdio transports,
|
|
3
|
+
`tiny-mcp-client` is a lightweight Model Context Protocol client used by tests, fixtures, and package integrations. It supports stdio transports, Streamable HTTP and legacy HTTP/SSE transports, in-memory test pairs, JSON-RPC helpers, and OAuth metadata discovery for OAuth-protected MCP HTTP servers.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -29,9 +29,16 @@ await client.close();
|
|
|
29
29
|
| Transport | Description |
|
|
30
30
|
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
31
31
|
| `StdioTransport` | Spawns an MCP server process and communicates over stdio. |
|
|
32
|
-
| `HttpTransport` | Connects to
|
|
32
|
+
| `HttpTransport` | Connects to Streamable HTTP endpoints, or legacy HTTP/SSE with `mode: "sse"`. |
|
|
33
33
|
| `createInMemoryTransportPair()` | Creates paired streams for in-process tests. |
|
|
34
34
|
|
|
35
|
+
Legacy SSE mode opens a GET stream and posts messages to the endpoint announced
|
|
36
|
+
by the server. Announced endpoints must remain on the original origin without
|
|
37
|
+
embedded credentials or fragments; endpoint changes close the connection.
|
|
38
|
+
Both transports accept the same headers, OAuth provider and response limits.
|
|
39
|
+
HTTP failures expose `HttpTransportError.status` and `.method`, so callers can
|
|
40
|
+
make transport decisions without parsing error messages.
|
|
41
|
+
|
|
35
42
|
## OAuth HTTP support
|
|
36
43
|
|
|
37
44
|
`HttpTransport` accepts `oauth` options from `mcp-oauth`. When a protected server returns a Bearer `WWW-Authenticate` challenge, the transport discovers protected-resource metadata, loads authorization-server metadata, lets the OAuth provider handle authorization, and retries the request when credentials are available.
|
|
@@ -583,8 +583,15 @@ interface StdioTransportOptions {
|
|
|
583
583
|
spawn?: StdioSpawn;
|
|
584
584
|
}
|
|
585
585
|
type HttpTransportFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
586
|
+
declare class HttpTransportError extends Error {
|
|
587
|
+
readonly status: number;
|
|
588
|
+
readonly method: "GET" | "POST" | "DELETE";
|
|
589
|
+
constructor(message: string, status: number, method: "GET" | "POST" | "DELETE");
|
|
590
|
+
}
|
|
586
591
|
interface HttpTransportOptions {
|
|
587
592
|
url: string;
|
|
593
|
+
/** Legacy SSE uses a GET stream that announces the RPC POST endpoint. */
|
|
594
|
+
mode?: "streamable-http" | "sse";
|
|
588
595
|
headers?: RequestInit["headers"];
|
|
589
596
|
fetch?: HttpTransportFetch;
|
|
590
597
|
oauth?: OAuthClientProviderOptions;
|
|
@@ -610,6 +617,11 @@ declare class HttpTransport implements McpTransport {
|
|
|
610
617
|
readonly writable: Writable;
|
|
611
618
|
readonly closed: Promise<McpTransportClosedEvent>;
|
|
612
619
|
private readonly url;
|
|
620
|
+
private readonly mode;
|
|
621
|
+
private legacyEndpoint;
|
|
622
|
+
private legacyEndpointReady;
|
|
623
|
+
private resolveLegacyEndpoint;
|
|
624
|
+
private rejectLegacyEndpoint;
|
|
613
625
|
private readonly headers;
|
|
614
626
|
private readonly fetchImpl;
|
|
615
627
|
private readonly readStream;
|
|
@@ -628,7 +640,7 @@ declare class HttpTransport implements McpTransport {
|
|
|
628
640
|
private readonly maxResponseBytes;
|
|
629
641
|
private readonly toolParameterHeaders;
|
|
630
642
|
private readonly onWarning;
|
|
631
|
-
constructor({ url, headers, fetch: fetchImpl, oauth, oauthDiscoveryCache, onWarning, maxResponseBytes, }: HttpTransportOptions);
|
|
643
|
+
constructor({ url, mode, headers, fetch: fetchImpl, oauth, oauthDiscoveryCache, onWarning, maxResponseBytes, }: HttpTransportOptions);
|
|
632
644
|
filterTools(tools: Tool[], reset?: boolean): Tool[];
|
|
633
645
|
dispose(reason?: Error): void;
|
|
634
646
|
private closeWithSessionTermination;
|
|
@@ -643,6 +655,7 @@ declare class HttpTransport implements McpTransport {
|
|
|
643
655
|
private authorizeRequestHeaders;
|
|
644
656
|
private captureSessionId;
|
|
645
657
|
private maybeOpenGetSseStream;
|
|
658
|
+
private ensureLegacyEndpoint;
|
|
646
659
|
private sendSessionTerminationRequest;
|
|
647
660
|
private consumeGetSseStream;
|
|
648
661
|
private throwForPostHttpError;
|
|
@@ -735,5 +748,5 @@ declare class JsonRpcMessageLayer {
|
|
|
735
748
|
private handleCancellationNotification;
|
|
736
749
|
}
|
|
737
750
|
|
|
738
|
-
export { ERROR_INTERNAL, ERROR_INVALID_PARAMS, ERROR_INVALID_REQUEST, ERROR_METHOD_NOT_FOUND, ERROR_PARSE, HttpTransport, JsonRpcMessageLayer, McpClient, McpError, OAuthMetadataDiscovery, StdioTransport, createInMemoryTransportPair, createSdkTestPair, createTestPair, discoverOAuthMetadata, fetchMcpResponse };
|
|
751
|
+
export { ERROR_INTERNAL, ERROR_INVALID_PARAMS, ERROR_INVALID_REQUEST, ERROR_METHOD_NOT_FOUND, ERROR_PARSE, HttpTransport, HttpTransportError, JsonRpcMessageLayer, McpClient, McpError, OAuthMetadataDiscovery, StdioTransport, createInMemoryTransportPair, createSdkTestPair, createTestPair, discoverOAuthMetadata, fetchMcpResponse };
|
|
739
752
|
export type { AudioContent, BlobResourceContents, CacheableResultMetadata, CallToolOptions, CallToolParams, CallToolResult, ClientCapabilities, CompleteArgument, CompleteParams, CompleteResult, Completion, ConnectResult, ContentItem, CreateMessageParams, CreateMessageResult, ElicitationParams, ElicitationResult, EmbeddedResource, GetPromptParams, GetPromptResult, HttpTransportFetch, HttpTransportOptions, ImageContent, InMemoryTransportPair, IncludeContext, InitializeParams, InitializeResult, JsonRpcErrorObject, JsonRpcErrorResponse, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcRequestOptions, JsonRpcResponse, JsonRpcSuccessResponse, LogLevel, LogMessage, McpClientConnection, McpClientOptions, McpRequestContext, McpSubscription, McpTransport, McpTransportClosedEvent, ModelHint, ModelPreferences, NotificationFilter, OAuthAuthorizationServerMetadata, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryCache, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, PaginatedParams, PaginatedResult, ProgressParams, ProgressToken, PromptMessage, PromptReference, ReadResourceParams, RequestId, ResourceContents, ResourceReference, ResultMetadata, Root, SamplingContent, SamplingMessage, SdkTestPair, ServerCapabilities, StdioSpawn, StdioTransportOptions, StoredOAuthSession, SubscriptionOptions, TextContent, TextResourceContents, Tool, ToolResultContent, ToolUseContent };
|
|
@@ -6505,6 +6505,16 @@ async function createTestPair(server, createClient) {
|
|
|
6505
6505
|
};
|
|
6506
6506
|
return { client, cleanup };
|
|
6507
6507
|
}
|
|
6508
|
+
var HttpTransportError = class extends Error {
|
|
6509
|
+
constructor(message, status, method) {
|
|
6510
|
+
super(message);
|
|
6511
|
+
this.status = status;
|
|
6512
|
+
this.method = method;
|
|
6513
|
+
this.name = "HttpTransportError";
|
|
6514
|
+
}
|
|
6515
|
+
status;
|
|
6516
|
+
method;
|
|
6517
|
+
};
|
|
6508
6518
|
function defaultStdioSpawn(command, args, options) {
|
|
6509
6519
|
return spawn2(command, args, options);
|
|
6510
6520
|
}
|
|
@@ -6615,6 +6625,11 @@ var HttpTransport = class {
|
|
|
6615
6625
|
writable;
|
|
6616
6626
|
closed;
|
|
6617
6627
|
url;
|
|
6628
|
+
mode;
|
|
6629
|
+
legacyEndpoint;
|
|
6630
|
+
legacyEndpointReady;
|
|
6631
|
+
resolveLegacyEndpoint;
|
|
6632
|
+
rejectLegacyEndpoint;
|
|
6618
6633
|
headers;
|
|
6619
6634
|
fetchImpl;
|
|
6620
6635
|
readStream = new PassThrough();
|
|
@@ -6635,6 +6650,7 @@ var HttpTransport = class {
|
|
|
6635
6650
|
onWarning;
|
|
6636
6651
|
constructor({
|
|
6637
6652
|
url,
|
|
6653
|
+
mode = "streamable-http",
|
|
6638
6654
|
headers = {},
|
|
6639
6655
|
fetch: fetchImpl = defaultHttpTransportFetch,
|
|
6640
6656
|
oauth,
|
|
@@ -6646,6 +6662,7 @@ var HttpTransport = class {
|
|
|
6646
6662
|
throw new Error("HTTP response byte limit must be a positive safe integer");
|
|
6647
6663
|
this.maxResponseBytes = maxResponseBytes;
|
|
6648
6664
|
this.url = url;
|
|
6665
|
+
this.mode = mode;
|
|
6649
6666
|
this.headers = headers;
|
|
6650
6667
|
this.fetchImpl = fetchImpl;
|
|
6651
6668
|
this.onWarning = onWarning;
|
|
@@ -6690,6 +6707,9 @@ var HttpTransport = class {
|
|
|
6690
6707
|
return;
|
|
6691
6708
|
}
|
|
6692
6709
|
this.disposed = true;
|
|
6710
|
+
this.rejectLegacyEndpoint?.(reason);
|
|
6711
|
+
this.rejectLegacyEndpoint = void 0;
|
|
6712
|
+
this.resolveLegacyEndpoint = void 0;
|
|
6693
6713
|
this.toolParameterHeaders.clear();
|
|
6694
6714
|
this.abortInFlightFetches();
|
|
6695
6715
|
this.cancelOpenResponseReaders();
|
|
@@ -6793,8 +6813,10 @@ var HttpTransport = class {
|
|
|
6793
6813
|
const id = parsed.type === "request" ? parsed.message.id : void 0;
|
|
6794
6814
|
if (controller !== void 0 && id !== void 0) this.modernRequests.set(id, controller);
|
|
6795
6815
|
try {
|
|
6816
|
+
const postUrl = this.mode === "sse" ? await this.ensureLegacyEndpoint() : this.url;
|
|
6796
6817
|
const hasSessionId = !modern && this.sessionId !== void 0;
|
|
6797
6818
|
const response = await this.fetchWithOAuthRetry({
|
|
6819
|
+
url: postUrl,
|
|
6798
6820
|
method: "POST",
|
|
6799
6821
|
createHeaders: () => this.createPostHeaders(message, modern),
|
|
6800
6822
|
body: line,
|
|
@@ -6807,7 +6829,7 @@ var HttpTransport = class {
|
|
|
6807
6829
|
if (hasSessionId && response.status === 404) {
|
|
6808
6830
|
void response.body?.cancel().catch(() => void 0);
|
|
6809
6831
|
this.sessionId = void 0;
|
|
6810
|
-
this.dispose(new
|
|
6832
|
+
this.dispose(new HttpTransportError("HTTP transport session expired (404 response)", 404, "POST"));
|
|
6811
6833
|
return;
|
|
6812
6834
|
}
|
|
6813
6835
|
if (await this.throwForPostHttpError(
|
|
@@ -6820,7 +6842,7 @@ var HttpTransport = class {
|
|
|
6820
6842
|
void response.body?.cancel().catch(() => void 0);
|
|
6821
6843
|
return;
|
|
6822
6844
|
}
|
|
6823
|
-
if (!modern) {
|
|
6845
|
+
if (!modern && this.mode !== "sse") {
|
|
6824
6846
|
this.captureSessionId(response);
|
|
6825
6847
|
this.maybeOpenGetSseStream();
|
|
6826
6848
|
}
|
|
@@ -6916,6 +6938,17 @@ var HttpTransport = class {
|
|
|
6916
6938
|
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
6917
6939
|
});
|
|
6918
6940
|
}
|
|
6941
|
+
ensureLegacyEndpoint() {
|
|
6942
|
+
if (this.legacyEndpointReady !== void 0) return this.legacyEndpointReady;
|
|
6943
|
+
this.legacyEndpointReady = new Promise((resolve, reject) => {
|
|
6944
|
+
this.resolveLegacyEndpoint = resolve;
|
|
6945
|
+
this.rejectLegacyEndpoint = reject;
|
|
6946
|
+
});
|
|
6947
|
+
void this.consumeGetSseStream().catch((error) => {
|
|
6948
|
+
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
6949
|
+
});
|
|
6950
|
+
return this.legacyEndpointReady;
|
|
6951
|
+
}
|
|
6919
6952
|
async sendSessionTerminationRequest(sessionId, signal) {
|
|
6920
6953
|
const headers = await this.createDeleteHeaders(sessionId);
|
|
6921
6954
|
signal.throwIfAborted();
|
|
@@ -6935,7 +6968,7 @@ var HttpTransport = class {
|
|
|
6935
6968
|
const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders, signal)).trim();
|
|
6936
6969
|
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
6937
6970
|
const message = responseBody.length === 0 ? `HTTP transport DELETE failed (${statusDescriptor})` : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
|
|
6938
|
-
throw new
|
|
6971
|
+
throw new HttpTransportError(message, response.status, "DELETE");
|
|
6939
6972
|
}
|
|
6940
6973
|
async consumeGetSseStream() {
|
|
6941
6974
|
const response = await this.fetchWithOAuthRetry({
|
|
@@ -6948,26 +6981,32 @@ var HttpTransport = class {
|
|
|
6948
6981
|
}
|
|
6949
6982
|
if (response.status === 405) {
|
|
6950
6983
|
void response.body?.cancel().catch(() => void 0);
|
|
6984
|
+
if (this.mode === "sse") throw new HttpTransportError("Legacy SSE GET failed (405)", 405, "GET");
|
|
6951
6985
|
throw new HttpTransportGetSseNotSupportedError();
|
|
6952
6986
|
}
|
|
6953
6987
|
if (response.status === 404) {
|
|
6954
6988
|
void response.body?.cancel().catch(() => void 0);
|
|
6955
6989
|
this.sessionId = void 0;
|
|
6956
|
-
throw new
|
|
6990
|
+
throw new HttpTransportError("HTTP transport session expired (GET 404 response)", 404, "GET");
|
|
6957
6991
|
}
|
|
6958
6992
|
if (!response.ok) {
|
|
6959
6993
|
const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders)).trim();
|
|
6960
6994
|
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
6961
6995
|
const message = responseBody.length === 0 ? `HTTP transport GET failed (${statusDescriptor})` : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
|
|
6962
|
-
throw new
|
|
6996
|
+
throw new HttpTransportError(message, response.status, "GET");
|
|
6963
6997
|
}
|
|
6964
6998
|
const contentType = response.headers.get("Content-Type");
|
|
6965
6999
|
if (contentType === null) {
|
|
6966
7000
|
void response.body?.cancel().catch(() => void 0);
|
|
7001
|
+
if (this.mode === "sse") throw new Error("Legacy SSE GET returned an unsupported content type");
|
|
6967
7002
|
return;
|
|
6968
7003
|
}
|
|
6969
7004
|
if (contentType.split(";")[0]?.trim().toLowerCase() === "text/event-stream") {
|
|
6970
|
-
await this.forwardSseResponseMessages(response);
|
|
7005
|
+
await this.forwardSseResponseMessages(response, void 0, void 0, this.mode === "sse");
|
|
7006
|
+
if (this.mode === "sse") {
|
|
7007
|
+
if (!this.disposed) throw new Error("Legacy SSE stream ended");
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
6971
7010
|
this.getSseStreamStarted = false;
|
|
6972
7011
|
if (!this.disposed && this.sessionId !== void 0 && this.lastEventId !== void 0) {
|
|
6973
7012
|
this.maybeOpenGetSseStream();
|
|
@@ -6975,6 +7014,7 @@ var HttpTransport = class {
|
|
|
6975
7014
|
return;
|
|
6976
7015
|
}
|
|
6977
7016
|
void response.body?.cancel().catch(() => void 0);
|
|
7017
|
+
if (this.mode === "sse") throw new Error("Legacy SSE GET returned an unsupported content type");
|
|
6978
7018
|
}
|
|
6979
7019
|
async throwForPostHttpError(response, request, signal) {
|
|
6980
7020
|
if (response.status < 400) {
|
|
@@ -7014,7 +7054,7 @@ var HttpTransport = class {
|
|
|
7014
7054
|
}
|
|
7015
7055
|
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
7016
7056
|
const message = responseBody.length === 0 ? `HTTP transport POST failed (${statusDescriptor})` : `HTTP transport POST failed (${statusDescriptor}): ${responseBody}`;
|
|
7017
|
-
throw new
|
|
7057
|
+
throw new HttpTransportError(message, response.status, "POST");
|
|
7018
7058
|
}
|
|
7019
7059
|
async maybeHandleUnauthorizedResponse(response) {
|
|
7020
7060
|
if (response.status !== 401 || this.oauthProvider === void 0) {
|
|
@@ -7074,11 +7114,11 @@ var HttpTransport = class {
|
|
|
7074
7114
|
void response.body?.cancel().catch(() => void 0);
|
|
7075
7115
|
throw new Error("HTTP transport POST returned an unsupported response content type");
|
|
7076
7116
|
}
|
|
7077
|
-
async forwardSseResponseMessages(response, signal, context) {
|
|
7117
|
+
async forwardSseResponseMessages(response, signal, context, acceptEndpoint = false) {
|
|
7078
7118
|
if (response.body === null) {
|
|
7079
7119
|
return;
|
|
7080
7120
|
}
|
|
7081
|
-
const parser = new SseParser(this.maxResponseBytes);
|
|
7121
|
+
const parser = new SseParser(this.maxResponseBytes, acceptEndpoint);
|
|
7082
7122
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
7083
7123
|
const reader = response.body.getReader();
|
|
7084
7124
|
this.openResponseReaders.add(reader);
|
|
@@ -7142,6 +7182,19 @@ var HttpTransport = class {
|
|
|
7142
7182
|
}
|
|
7143
7183
|
writeSseMessages(messages, context) {
|
|
7144
7184
|
for (const message of messages) {
|
|
7185
|
+
if (message.event === "endpoint") {
|
|
7186
|
+
const endpoint = new URL(message.data, this.url);
|
|
7187
|
+
const resource = new URL(this.url);
|
|
7188
|
+
if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:" || endpoint.origin !== resource.origin || endpoint.username || endpoint.password || endpoint.hash)
|
|
7189
|
+
throw new Error("Unsafe legacy SSE endpoint");
|
|
7190
|
+
if (this.legacyEndpoint !== void 0 && this.legacyEndpoint !== endpoint.href)
|
|
7191
|
+
throw new Error("Legacy SSE endpoint changed during the active connection");
|
|
7192
|
+
this.legacyEndpoint = endpoint.href;
|
|
7193
|
+
this.resolveLegacyEndpoint?.(endpoint.href);
|
|
7194
|
+
this.resolveLegacyEndpoint = void 0;
|
|
7195
|
+
this.rejectLegacyEndpoint = void 0;
|
|
7196
|
+
continue;
|
|
7197
|
+
}
|
|
7145
7198
|
this.writeReadableLine(context?.validate(message.data, true) ?? message.data);
|
|
7146
7199
|
if (context?.completed) return;
|
|
7147
7200
|
}
|
|
@@ -7155,7 +7208,7 @@ var HttpTransport = class {
|
|
|
7155
7208
|
}
|
|
7156
7209
|
async fetchWithOAuthRetry(input) {
|
|
7157
7210
|
const request = async () => this.fetchWithAbort(
|
|
7158
|
-
this.url,
|
|
7211
|
+
input.url ?? this.url,
|
|
7159
7212
|
{
|
|
7160
7213
|
method: input.method,
|
|
7161
7214
|
headers: await input.createHeaders(),
|
|
@@ -7252,12 +7305,14 @@ async function* readLines(stream, maxLineBytes = 16 * 1024 * 1024) {
|
|
|
7252
7305
|
if (parts.length > 0) yield normalizeLine(parts.join(""));
|
|
7253
7306
|
}
|
|
7254
7307
|
var SseParser = class {
|
|
7255
|
-
constructor(maxEventBytes = 16 * 1024 * 1024) {
|
|
7308
|
+
constructor(maxEventBytes = 16 * 1024 * 1024, acceptEndpoint = false) {
|
|
7256
7309
|
this.maxEventBytes = maxEventBytes;
|
|
7310
|
+
this.acceptEndpoint = acceptEndpoint;
|
|
7257
7311
|
if (!Number.isSafeInteger(maxEventBytes) || maxEventBytes < 1)
|
|
7258
7312
|
throw new Error("SSE event byte limit must be a positive safe integer");
|
|
7259
7313
|
}
|
|
7260
7314
|
maxEventBytes;
|
|
7315
|
+
acceptEndpoint;
|
|
7261
7316
|
buffer = "";
|
|
7262
7317
|
skipLf = false;
|
|
7263
7318
|
eventType;
|
|
@@ -7338,13 +7393,14 @@ var SseParser = class {
|
|
|
7338
7393
|
if (this.hasEventId) {
|
|
7339
7394
|
this._lastEventId = this.eventId;
|
|
7340
7395
|
}
|
|
7341
|
-
if (this.dataLines.length === 0 || eventType !== "message") {
|
|
7396
|
+
if (this.dataLines.length === 0 || eventType !== "message" && !(this.acceptEndpoint && eventType === "endpoint")) {
|
|
7342
7397
|
this.resetEvent();
|
|
7343
7398
|
return;
|
|
7344
7399
|
}
|
|
7345
7400
|
const message = {
|
|
7346
7401
|
data: this.dataLines.join("\n")
|
|
7347
7402
|
};
|
|
7403
|
+
if (eventType === "endpoint") message.event = "endpoint";
|
|
7348
7404
|
if (this.hasEventId) {
|
|
7349
7405
|
message.id = this.eventId;
|
|
7350
7406
|
}
|
|
@@ -8078,6 +8134,7 @@ export {
|
|
|
8078
8134
|
ERROR_METHOD_NOT_FOUND,
|
|
8079
8135
|
ERROR_PARSE,
|
|
8080
8136
|
HttpTransport,
|
|
8137
|
+
HttpTransportError,
|
|
8081
8138
|
JsonRpcMessageLayer,
|
|
8082
8139
|
McpClient,
|
|
8083
8140
|
McpError,
|