tiny-http-mcp-server 0.1.39 → 0.1.41
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 +16 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +20 -0
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.d.ts +11 -5
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.js +28 -1
- package/node_modules/mcp-oauth/dist/client/types.d.ts +8 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/index.js +1 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +8 -0
- package/node_modules/tiny-mcp-client/dist/index.js +47 -1
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -95,6 +95,22 @@ 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
|
+
|
|
106
|
+
`createResourceBoundOAuthStores(authStore, persistenceNamespace, resourceIdentity)`
|
|
107
|
+
exposes the native named session/client stores and `reset(resource, { signal,
|
|
108
|
+
timeoutMs })`. Reset acquires the raw identity backend lock, so it can recover
|
|
109
|
+
corrupt or undecryptable records without reading their old contents. It atomically
|
|
110
|
+
retires the identity's grant and registrations and writes a marker that suppresses
|
|
111
|
+
stale initial grants. The default lock wait is 30 seconds. Other names/profiles
|
|
112
|
+
are untouched, and symlink paths are still refused.
|
|
113
|
+
|
|
98
114
|
Configure `client.metadata.scope` to request a precise scope set; broader
|
|
99
115
|
discovery metadata does not override it. Explicit scopes must match the cached
|
|
100
116
|
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);
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { CreateSecretStoreInput } from "auth-store";
|
|
2
2
|
import { type OAuthClientStore } from "./auth-store-session-store.js";
|
|
3
3
|
import type { OAuthSessionStore } from "./types.js";
|
|
4
|
+
export interface ResourceBoundOAuthStores {
|
|
5
|
+
readonly sessionStore: OAuthSessionStore;
|
|
6
|
+
readonly clientStore: OAuthClientStore;
|
|
7
|
+
readonly initialGrantAllowed: boolean;
|
|
8
|
+
/** Retire grants/clients even when their document cannot be decrypted or parsed. */
|
|
9
|
+
reset(resource: string, options?: {
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
}
|
|
4
14
|
/** One locked document owns a logical server's URL history, grant and clients. */
|
|
5
|
-
export declare function createResourceBoundOAuthStores(options: CreateSecretStoreInput, namespace: string | undefined, identity: string):
|
|
6
|
-
sessionStore: OAuthSessionStore;
|
|
7
|
-
clientStore: OAuthClientStore;
|
|
8
|
-
initialGrantAllowed: boolean;
|
|
9
|
-
};
|
|
15
|
+
export declare function createResourceBoundOAuthStores(options: CreateSecretStoreInput, namespace: string | undefined, identity: string): ResourceBoundOAuthStores;
|
|
@@ -4,9 +4,36 @@ import { normalizeStoredOAuthClient } from "./client-registration.js";
|
|
|
4
4
|
/** One locked document owns a logical server's URL history, grant and clients. */
|
|
5
5
|
export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
6
6
|
assertPersistenceNamespace(identity);
|
|
7
|
+
assertPersistenceNamespace(namespace);
|
|
7
8
|
const store = createNamedSecretStore(identity, options, { salt: "poe-code:mcp-oauth:resources:v1",
|
|
8
9
|
directory: ".poe-code/mcp-oauth/resources", service: "poe-code-mcp-oauth-resources", accountPrefix: "resource" }, namespace);
|
|
9
|
-
const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {}
|
|
10
|
+
const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {},
|
|
11
|
+
async reset(resource, options = {}) {
|
|
12
|
+
options.signal?.throwIfAborted();
|
|
13
|
+
let url;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(resource);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error("OAuth reset resource must be an absolute HTTP URL");
|
|
19
|
+
}
|
|
20
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
21
|
+
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
22
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
23
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
|
|
24
|
+
throw new Error("OAuth reset timeoutMs must be a positive supported timer interval");
|
|
25
|
+
if (store.withLock === undefined)
|
|
26
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
27
|
+
// Acquire the raw backend lock: the session lock reconciles by strictly
|
|
28
|
+
// reading the document, which must be bypassed for explicit corruption recovery.
|
|
29
|
+
await store.withLock(async () => {
|
|
30
|
+
options.signal?.throwIfAborted();
|
|
31
|
+
const record = { version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} };
|
|
32
|
+
await store.set(JSON.stringify(record));
|
|
33
|
+
result.initialGrantAllowed = false;
|
|
34
|
+
}, { signal: options.signal, timeoutMs });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
10
37
|
async function read() {
|
|
11
38
|
const raw = await store.get();
|
|
12
39
|
if (raw === null)
|
|
@@ -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;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
|
+
export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
3
|
+
export type { ResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
2
4
|
export { parseOAuthClientRegistration } from "./client/client-registration.js";
|
|
3
5
|
export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
|
|
4
6
|
export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
|
+
export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
2
3
|
export { parseOAuthClientRegistration } from "./client/client-registration.js";
|
|
3
4
|
export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
|
|
4
5
|
export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
|
|
@@ -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;
|
|
@@ -4712,13 +4712,40 @@ function isNonBlankOwnString(record2, key2) {
|
|
|
4712
4712
|
// ../mcp-oauth/dist/client/resource-bound-store.js
|
|
4713
4713
|
function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
4714
4714
|
assertPersistenceNamespace(identity);
|
|
4715
|
+
assertPersistenceNamespace(namespace);
|
|
4715
4716
|
const store = createNamedSecretStore(identity, options, {
|
|
4716
4717
|
salt: "poe-code:mcp-oauth:resources:v1",
|
|
4717
4718
|
directory: ".poe-code/mcp-oauth/resources",
|
|
4718
4719
|
service: "poe-code-mcp-oauth-resources",
|
|
4719
4720
|
accountPrefix: "resource"
|
|
4720
4721
|
}, namespace);
|
|
4721
|
-
const result = {
|
|
4722
|
+
const result = {
|
|
4723
|
+
initialGrantAllowed: true,
|
|
4724
|
+
sessionStore: {},
|
|
4725
|
+
clientStore: {},
|
|
4726
|
+
async reset(resource, options2 = {}) {
|
|
4727
|
+
options2.signal?.throwIfAborted();
|
|
4728
|
+
let url;
|
|
4729
|
+
try {
|
|
4730
|
+
url = new URL(resource);
|
|
4731
|
+
} catch {
|
|
4732
|
+
throw new Error("OAuth reset resource must be an absolute HTTP URL");
|
|
4733
|
+
}
|
|
4734
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
4735
|
+
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
4736
|
+
const timeoutMs = options2.timeoutMs ?? 3e4;
|
|
4737
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
4738
|
+
throw new Error("OAuth reset timeoutMs must be a positive supported timer interval");
|
|
4739
|
+
if (store.withLock === void 0)
|
|
4740
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
4741
|
+
await store.withLock(async () => {
|
|
4742
|
+
options2.signal?.throwIfAborted();
|
|
4743
|
+
const record2 = { version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} };
|
|
4744
|
+
await store.set(JSON.stringify(record2));
|
|
4745
|
+
result.initialGrantAllowed = false;
|
|
4746
|
+
}, { signal: options2.signal, timeoutMs });
|
|
4747
|
+
}
|
|
4748
|
+
};
|
|
4722
4749
|
async function read() {
|
|
4723
4750
|
const raw = await store.get();
|
|
4724
4751
|
if (raw === null)
|
|
@@ -5172,6 +5199,25 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5172
5199
|
}
|
|
5173
5200
|
let initialGrantConsumed = false;
|
|
5174
5201
|
return {
|
|
5202
|
+
async authenticate(input) {
|
|
5203
|
+
input.signal?.throwIfAborted();
|
|
5204
|
+
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5205
|
+
const resource = canonicalizeResourceIndicator(input.requestUrl);
|
|
5206
|
+
let session = await ensureAuthorizedSession(resource, void 0, input.fetch, true, false, input.signal);
|
|
5207
|
+
if (session?.tokens !== void 0 && !isExpired(session.tokens, now))
|
|
5208
|
+
return { ...session.tokens };
|
|
5209
|
+
if (session === null && !initialGrantConsumed && initialGrant?.resource === resource && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now))
|
|
5210
|
+
return { ...initialGrant.tokens };
|
|
5211
|
+
if (input.discover === void 0)
|
|
5212
|
+
return;
|
|
5213
|
+
const discovery = await input.discover();
|
|
5214
|
+
input.signal?.throwIfAborted();
|
|
5215
|
+
assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
|
|
5216
|
+
session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
|
|
5217
|
+
if (session?.tokens === void 0 || isExpired(session.tokens, now))
|
|
5218
|
+
throw new Error("OAuth authentication did not establish a usable grant");
|
|
5219
|
+
return { ...session.tokens };
|
|
5220
|
+
},
|
|
5175
5221
|
async authorizeRequest(input) {
|
|
5176
5222
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
5177
5223
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|