tiny-http-mcp-server 0.1.38 → 0.1.40
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 +1 -1
- package/node_modules/mcp-oauth/README.md +8 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +20 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +8 -0
- package/node_modules/tiny-mcp-client/README.md +4 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +19 -1
- package/node_modules/tiny-mcp-client/dist/index.js +47 -5
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -95,6 +95,14 @@ without redeeming its refresh token again. A proven current token is refreshed
|
|
|
95
95
|
on 401 even when the server omits `error="invalid_token"`. Invalid provenance
|
|
96
96
|
fails without quoting token values.
|
|
97
97
|
|
|
98
|
+
The native provider also exposes `authenticate({ requestUrl, fetch, signal,
|
|
99
|
+
discover })` for explicit login, including servers whose initialization is
|
|
100
|
+
public. `discover` lazily supplies validated OAuth metadata and is skipped for
|
|
101
|
+
usable existing grants. Omit it to recover/reuse known sessions only; the method
|
|
102
|
+
returns `void` if a new discovery lookup is needed. It honors `allowInteractive`,
|
|
103
|
+
recovers pending refresh outcomes through consent, and returns an owned token
|
|
104
|
+
snapshot. Normal transport request authorization remains noninteractive.
|
|
105
|
+
|
|
98
106
|
Configure `client.metadata.scope` to request a precise scope set; broader
|
|
99
107
|
discovery metadata does not override it. Explicit scopes must match the cached
|
|
100
108
|
or imported grant's scope set; ordering, repeated spaces and duplicates are
|
|
@@ -66,6 +66,26 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
66
66
|
}
|
|
67
67
|
let initialGrantConsumed = false;
|
|
68
68
|
return {
|
|
69
|
+
async authenticate(input) {
|
|
70
|
+
input.signal?.throwIfAborted();
|
|
71
|
+
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
72
|
+
const resource = canonicalizeResourceIndicator(input.requestUrl);
|
|
73
|
+
let session = await ensureAuthorizedSession(resource, undefined, input.fetch, true, false, input.signal);
|
|
74
|
+
if (session?.tokens !== undefined && !isExpired(session.tokens, now))
|
|
75
|
+
return { ...session.tokens };
|
|
76
|
+
if (session === null && !initialGrantConsumed && initialGrant?.resource === resource &&
|
|
77
|
+
initialGrant.tokens !== undefined && !isExpired(initialGrant.tokens, now))
|
|
78
|
+
return { ...initialGrant.tokens };
|
|
79
|
+
if (input.discover === undefined)
|
|
80
|
+
return;
|
|
81
|
+
const discovery = await input.discover();
|
|
82
|
+
input.signal?.throwIfAborted();
|
|
83
|
+
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
84
|
+
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
85
|
+
if (session?.tokens === undefined || isExpired(session.tokens, now))
|
|
86
|
+
throw new Error("OAuth authentication did not establish a usable grant");
|
|
87
|
+
return { ...session.tokens };
|
|
88
|
+
},
|
|
69
89
|
async authorizeRequest(input) {
|
|
70
90
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
71
91
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
@@ -28,6 +28,14 @@ export interface OAuthUnauthorizedChallenge {
|
|
|
28
28
|
raw: string;
|
|
29
29
|
}
|
|
30
30
|
export interface OAuthClientProvider {
|
|
31
|
+
/** Establish a grant explicitly, including when resource initialization is public. */
|
|
32
|
+
authenticate?(input: {
|
|
33
|
+
requestUrl: URL;
|
|
34
|
+
fetch: OAuthMetadataFetch;
|
|
35
|
+
/** Lazy validated discovery. Without it, recover/reuse known grants only. */
|
|
36
|
+
discover?: () => Promise<OAuthDiscoveryResult>;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}): Promise<StoredOAuthTokens | void>;
|
|
31
39
|
authorizeRequest?(input: {
|
|
32
40
|
requestUrl: URL;
|
|
33
41
|
headers: Headers;
|
|
@@ -37,7 +37,10 @@ by the server. Announced endpoints must remain on the original origin without
|
|
|
37
37
|
embedded credentials or fragments; endpoint changes close the connection.
|
|
38
38
|
Both transports accept the same headers, OAuth provider and response limits.
|
|
39
39
|
HTTP failures expose `HttpTransportError.status` and `.method`, so callers can
|
|
40
|
-
make transport decisions without parsing error messages.
|
|
40
|
+
make transport decisions without parsing error messages. Legacy HTTP/SSE
|
|
41
|
+
`connect()` also waits for the initialized notification POST to complete before
|
|
42
|
+
reporting ready. Completion failures reject the connection and expose
|
|
43
|
+
`rpcMethod: "notifications/initialized"`; they are not setup transport mismatches.
|
|
41
44
|
|
|
42
45
|
## OAuth HTTP support
|
|
43
46
|
|
|
@@ -131,6 +131,14 @@ interface OAuthUnauthorizedChallenge {
|
|
|
131
131
|
raw: string;
|
|
132
132
|
}
|
|
133
133
|
interface OAuthClientProvider {
|
|
134
|
+
/** Establish a grant explicitly, including when resource initialization is public. */
|
|
135
|
+
authenticate?(input: {
|
|
136
|
+
requestUrl: URL;
|
|
137
|
+
fetch: OAuthMetadataFetch;
|
|
138
|
+
/** Lazy validated discovery. Without it, recover/reuse known grants only. */
|
|
139
|
+
discover?: () => Promise<OAuthDiscoveryResult>;
|
|
140
|
+
signal?: AbortSignal;
|
|
141
|
+
}): Promise<StoredOAuthTokens | void>;
|
|
134
142
|
authorizeRequest?(input: {
|
|
135
143
|
requestUrl: URL;
|
|
136
144
|
headers: Headers;
|
|
@@ -641,6 +649,11 @@ interface McpTransport {
|
|
|
641
649
|
closed: Promise<McpTransportClosedEvent>;
|
|
642
650
|
dispose(reason?: Error): void;
|
|
643
651
|
filterTools?(tools: Tool[], reset?: boolean): Tool[];
|
|
652
|
+
/** Complete a legacy initialization handshake before the client reports ready. */
|
|
653
|
+
completeInitialization?(options: {
|
|
654
|
+
signal?: AbortSignal;
|
|
655
|
+
timeoutMs: number;
|
|
656
|
+
}): Promise<void>;
|
|
644
657
|
}
|
|
645
658
|
interface InMemoryServerTransport {
|
|
646
659
|
readable: Readable;
|
|
@@ -676,7 +689,8 @@ type HttpTransportFetch = (input: string | URL, init?: RequestInit) => Promise<R
|
|
|
676
689
|
declare class HttpTransportError extends Error {
|
|
677
690
|
readonly status: number;
|
|
678
691
|
readonly method: "GET" | "POST" | "DELETE";
|
|
679
|
-
|
|
692
|
+
readonly rpcMethod?: string | undefined;
|
|
693
|
+
constructor(message: string, status: number, method: "GET" | "POST" | "DELETE", rpcMethod?: string | undefined);
|
|
680
694
|
}
|
|
681
695
|
interface HttpTransportOptions {
|
|
682
696
|
url: string;
|
|
@@ -733,6 +747,10 @@ declare class HttpTransport implements McpTransport {
|
|
|
733
747
|
private readonly toolParameterHeaders;
|
|
734
748
|
private readonly onWarning;
|
|
735
749
|
constructor({ url, mode, headers, fetch: fetchImpl, oauth, oauthDiscoveryCache, onWarning, maxResponseBytes, }: HttpTransportOptions);
|
|
750
|
+
completeInitialization(options: {
|
|
751
|
+
signal?: AbortSignal;
|
|
752
|
+
timeoutMs: number;
|
|
753
|
+
}): Promise<void>;
|
|
736
754
|
filterTools(tools: Tool[], reset?: boolean): Tool[];
|
|
737
755
|
dispose(reason?: Error): void;
|
|
738
756
|
private closeWithSessionTermination;
|
|
@@ -5172,6 +5172,25 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5172
5172
|
}
|
|
5173
5173
|
let initialGrantConsumed = false;
|
|
5174
5174
|
return {
|
|
5175
|
+
async authenticate(input) {
|
|
5176
|
+
input.signal?.throwIfAborted();
|
|
5177
|
+
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5178
|
+
const resource = canonicalizeResourceIndicator(input.requestUrl);
|
|
5179
|
+
let session = await ensureAuthorizedSession(resource, void 0, input.fetch, true, false, input.signal);
|
|
5180
|
+
if (session?.tokens !== void 0 && !isExpired(session.tokens, now))
|
|
5181
|
+
return { ...session.tokens };
|
|
5182
|
+
if (session === null && !initialGrantConsumed && initialGrant?.resource === resource && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now))
|
|
5183
|
+
return { ...initialGrant.tokens };
|
|
5184
|
+
if (input.discover === void 0)
|
|
5185
|
+
return;
|
|
5186
|
+
const discovery = await input.discover();
|
|
5187
|
+
input.signal?.throwIfAborted();
|
|
5188
|
+
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
5189
|
+
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
5190
|
+
if (session?.tokens === void 0 || isExpired(session.tokens, now))
|
|
5191
|
+
throw new Error("OAuth authentication did not establish a usable grant");
|
|
5192
|
+
return { ...session.tokens };
|
|
5193
|
+
},
|
|
5175
5194
|
async authorizeRequest(input) {
|
|
5176
5195
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5177
5196
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
@@ -6788,7 +6807,8 @@ var McpClient = class {
|
|
|
6788
6807
|
async (params, context) => this.options.onElicitationRequest(params, context)
|
|
6789
6808
|
);
|
|
6790
6809
|
}
|
|
6791
|
-
messageLayer.sendNotification("notifications/initialized");
|
|
6810
|
+
if (transport.completeInitialization === void 0) messageLayer.sendNotification("notifications/initialized");
|
|
6811
|
+
else await transport.completeInitialization({ signal: options.signal, timeoutMs: this.options.requestTimeoutMs ?? 3e4 });
|
|
6792
6812
|
this.currentState = "ready";
|
|
6793
6813
|
return initializeResult;
|
|
6794
6814
|
} catch (error) {
|
|
@@ -7288,14 +7308,16 @@ async function createTestPair(server, createClient) {
|
|
|
7288
7308
|
return { client, cleanup };
|
|
7289
7309
|
}
|
|
7290
7310
|
var HttpTransportError = class extends Error {
|
|
7291
|
-
constructor(message, status, method) {
|
|
7311
|
+
constructor(message, status, method, rpcMethod) {
|
|
7292
7312
|
super(message);
|
|
7293
7313
|
this.status = status;
|
|
7294
7314
|
this.method = method;
|
|
7315
|
+
this.rpcMethod = rpcMethod;
|
|
7295
7316
|
this.name = "HttpTransportError";
|
|
7296
7317
|
}
|
|
7297
7318
|
status;
|
|
7298
7319
|
method;
|
|
7320
|
+
rpcMethod;
|
|
7299
7321
|
};
|
|
7300
7322
|
function defaultStdioSpawn(command, args, options) {
|
|
7301
7323
|
return spawn2(command, args, options);
|
|
@@ -7470,6 +7492,21 @@ var HttpTransport = class {
|
|
|
7470
7492
|
this.dispose(error instanceof Error ? error : new Error(String(error)));
|
|
7471
7493
|
});
|
|
7472
7494
|
}
|
|
7495
|
+
async completeInitialization(options) {
|
|
7496
|
+
const deadline = options.timeoutMs > 0 ? AbortSignal.timeout(Math.ceil(options.timeoutMs)) : void 0;
|
|
7497
|
+
const signals = [options.signal, deadline].filter((signal2) => signal2 !== void 0);
|
|
7498
|
+
const signal = signals.length === 0 ? new AbortController().signal : AbortSignal.any(signals);
|
|
7499
|
+
signal.throwIfAborted();
|
|
7500
|
+
try {
|
|
7501
|
+
await this.sendPost(serializeJsonRpcMessage({ jsonrpc: "2.0", method: "notifications/initialized" }), signal);
|
|
7502
|
+
signal.throwIfAborted();
|
|
7503
|
+
if (this.disposed) throw (await this.closed).reason;
|
|
7504
|
+
} catch (error) {
|
|
7505
|
+
if (error instanceof HttpTransportError)
|
|
7506
|
+
throw new HttpTransportError(error.message, error.status, error.method, "notifications/initialized");
|
|
7507
|
+
throw error;
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
7473
7510
|
filterTools(tools, reset = true) {
|
|
7474
7511
|
if (reset) this.toolParameterHeaders.clear();
|
|
7475
7512
|
const accepted = [];
|
|
@@ -7581,7 +7618,8 @@ var HttpTransport = class {
|
|
|
7581
7618
|
}
|
|
7582
7619
|
}
|
|
7583
7620
|
}
|
|
7584
|
-
async sendPost(line) {
|
|
7621
|
+
async sendPost(line, signal) {
|
|
7622
|
+
signal?.throwIfAborted();
|
|
7585
7623
|
const parsed = parseJsonRpcMessage(line);
|
|
7586
7624
|
const message = parsed.type === "request" || parsed.type === "notification" ? parsed.message : void 0;
|
|
7587
7625
|
const metadata = isObjectRecord5(message?.params) ? message.params._meta : void 0;
|
|
@@ -7595,7 +7633,10 @@ var HttpTransport = class {
|
|
|
7595
7633
|
this.modernRequests.get(requestId)?.abort();
|
|
7596
7634
|
return;
|
|
7597
7635
|
}
|
|
7598
|
-
const controller = modern && parsed.type === "request" ? new AbortController() : void 0;
|
|
7636
|
+
const controller = modern && parsed.type === "request" || signal !== void 0 ? new AbortController() : void 0;
|
|
7637
|
+
const aborted = () => controller?.abort(signal?.reason);
|
|
7638
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
7639
|
+
if (signal?.aborted) aborted();
|
|
7599
7640
|
const id = parsed.type === "request" ? parsed.message.id : void 0;
|
|
7600
7641
|
if (controller !== void 0 && id !== void 0) this.modernRequests.set(id, controller);
|
|
7601
7642
|
try {
|
|
@@ -7604,7 +7645,7 @@ var HttpTransport = class {
|
|
|
7604
7645
|
const response = await this.fetchWithOAuthRetry({
|
|
7605
7646
|
url: postUrl,
|
|
7606
7647
|
method: "POST",
|
|
7607
|
-
createHeaders: (
|
|
7648
|
+
createHeaders: (signal2) => this.createPostHeaders(message, modern, signal2),
|
|
7608
7649
|
body: line,
|
|
7609
7650
|
controller
|
|
7610
7651
|
});
|
|
@@ -7640,6 +7681,7 @@ var HttpTransport = class {
|
|
|
7640
7681
|
} catch (error) {
|
|
7641
7682
|
if (!controller?.signal.aborted) throw error;
|
|
7642
7683
|
} finally {
|
|
7684
|
+
signal?.removeEventListener("abort", aborted);
|
|
7643
7685
|
if (id !== void 0 && this.modernRequests.get(id) === controller)
|
|
7644
7686
|
this.modernRequests.delete(id);
|
|
7645
7687
|
}
|