tiny-http-mcp-server 0.1.40 → 0.1.42
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 +11 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.js +6 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +14 -2
- 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 +2 -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 +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +51 -3
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -103,6 +103,14 @@ returns `void` if a new discovery lookup is needed. It honors `allowInteractive`
|
|
|
103
103
|
recovers pending refresh outcomes through consent, and returns an owned token
|
|
104
104
|
snapshot. Normal transport request authorization remains noninteractive.
|
|
105
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
|
+
|
|
106
114
|
Configure `client.metadata.scope` to request a precise scope set; broader
|
|
107
115
|
discovery metadata does not override it. Explicit scopes must match the cached
|
|
108
116
|
or imported grant's scope set; ordering, repeated spaces and duplicates are
|
|
@@ -157,6 +165,9 @@ Authorization and code exchange always use the actual listener URI. Silent
|
|
|
157
165
|
refresh keeps its original client regardless of callback changes. At interactive
|
|
158
166
|
authorization, a native registration with an obsolete captured callback is
|
|
159
167
|
replaced; caller-owned full registration imports retain their original identity.
|
|
168
|
+
Their persisted `registrationOwnership: "caller"` survives reload and refresh;
|
|
169
|
+
invalid-client responses never silently replace these apps. Callback changes or
|
|
170
|
+
expired imported secrets require an explicit registration update.
|
|
160
171
|
Set `client.tokenEndpointAuthMethod` to `none`, `client_secret_post` or
|
|
161
172
|
`client_secret_basic`; a full registration can supply the same field as
|
|
162
173
|
`token_endpoint_auth_method`. Public clients never transmit a stored secret.
|
|
@@ -112,6 +112,12 @@ export function normalizeStoredOAuthClient(value) {
|
|
|
112
112
|
}
|
|
113
113
|
if (method !== undefined)
|
|
114
114
|
client.tokenEndpointAuthMethod = method;
|
|
115
|
+
const ownership = Object.hasOwn(record, "registrationOwnership") ? record.registrationOwnership : undefined;
|
|
116
|
+
if (ownership !== undefined) {
|
|
117
|
+
if (ownership !== "caller" || client.registration === undefined)
|
|
118
|
+
throw new Error("Invalid stored OAuth registration ownership");
|
|
119
|
+
client.registrationOwnership = "caller";
|
|
120
|
+
}
|
|
115
121
|
return client;
|
|
116
122
|
}
|
|
117
123
|
/** Fresh DCR may describe a normalized loopback port; saved identity stays exact. */
|
|
@@ -401,6 +401,16 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
401
401
|
};
|
|
402
402
|
}
|
|
403
403
|
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
404
|
+
const importedClient = existingSession?.client.registrationOwnership === "caller" ? existingSession.client :
|
|
405
|
+
storedClient?.registrationOwnership === "caller" ? storedClient : undefined;
|
|
406
|
+
if (importedClient !== undefined) {
|
|
407
|
+
assertRegistrationIssuer(importedClient, discovery.authorizationServer);
|
|
408
|
+
if (hasExpiredClientSecret(importedClient, now))
|
|
409
|
+
throw new Error("OAuth client secret has expired; update the imported registration");
|
|
410
|
+
if (!registrationMatchesRedirect(importedClient, redirectUri))
|
|
411
|
+
throw new Error("OAuth imported registration does not match the configured redirect URI; update its callback configuration");
|
|
412
|
+
return { kind: "static", fromStoredRegistration: false, client: importedClient };
|
|
413
|
+
}
|
|
404
414
|
if (storedClient !== null) {
|
|
405
415
|
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
406
416
|
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
@@ -653,7 +663,9 @@ function normalizeConfiguredClient(client) {
|
|
|
653
663
|
if (clientId === undefined)
|
|
654
664
|
return null;
|
|
655
665
|
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
|
|
656
|
-
return normalizeStoredOAuthClient({ clientId, clientSecret, registration,
|
|
666
|
+
return normalizeStoredOAuthClient({ clientId, clientSecret, registration,
|
|
667
|
+
...(registration === undefined ? {} : { registrationOwnership: "caller" }),
|
|
668
|
+
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
|
|
657
669
|
}
|
|
658
670
|
function normalizeOptionalOAuthString(value) {
|
|
659
671
|
if (value === undefined) {
|
|
@@ -829,5 +841,5 @@ function shouldReRegisterStoredDynamicClient(error, client, alreadyAttempted) {
|
|
|
829
841
|
if ("kind" in client) {
|
|
830
842
|
return client.kind === "dynamic" && client.fromStoredRegistration;
|
|
831
843
|
}
|
|
832
|
-
return
|
|
844
|
+
return client.registrationOwnership !== "caller";
|
|
833
845
|
}
|
|
@@ -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)
|
|
@@ -100,6 +100,8 @@ export interface OAuthClientRegistration extends Record<string, unknown> {
|
|
|
100
100
|
}
|
|
101
101
|
export interface StoredOAuthClient {
|
|
102
102
|
clientId: string;
|
|
103
|
+
/** Externally registered apps must never be replaced by native DCR on reload. */
|
|
104
|
+
registrationOwnership?: "caller";
|
|
103
105
|
/** Actual listener URI submitted when this native client was registered. */
|
|
104
106
|
requestedRedirectUri?: string;
|
|
105
107
|
clientSecret?: string;
|
|
@@ -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";
|
|
@@ -203,6 +203,8 @@ interface OAuthClientRegistration extends Record<string, unknown> {
|
|
|
203
203
|
}
|
|
204
204
|
interface StoredOAuthClient {
|
|
205
205
|
clientId: string;
|
|
206
|
+
/** Externally registered apps must never be replaced by native DCR on reload. */
|
|
207
|
+
registrationOwnership?: "caller";
|
|
206
208
|
/** Actual listener URI submitted when this native client was registered. */
|
|
207
209
|
requestedRedirectUri?: string;
|
|
208
210
|
clientSecret?: string;
|
|
@@ -3843,6 +3843,12 @@ function normalizeStoredOAuthClient(value) {
|
|
|
3843
3843
|
}
|
|
3844
3844
|
if (method !== void 0)
|
|
3845
3845
|
client.tokenEndpointAuthMethod = method;
|
|
3846
|
+
const ownership = Object.hasOwn(record2, "registrationOwnership") ? record2.registrationOwnership : void 0;
|
|
3847
|
+
if (ownership !== void 0) {
|
|
3848
|
+
if (ownership !== "caller" || client.registration === void 0)
|
|
3849
|
+
throw new Error("Invalid stored OAuth registration ownership");
|
|
3850
|
+
client.registrationOwnership = "caller";
|
|
3851
|
+
}
|
|
3846
3852
|
return client;
|
|
3847
3853
|
}
|
|
3848
3854
|
function registrationMatchesRedirect(client, requestedUri, fresh = false) {
|
|
@@ -4712,13 +4718,40 @@ function isNonBlankOwnString(record2, key2) {
|
|
|
4712
4718
|
// ../mcp-oauth/dist/client/resource-bound-store.js
|
|
4713
4719
|
function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
4714
4720
|
assertPersistenceNamespace(identity);
|
|
4721
|
+
assertPersistenceNamespace(namespace);
|
|
4715
4722
|
const store = createNamedSecretStore(identity, options, {
|
|
4716
4723
|
salt: "poe-code:mcp-oauth:resources:v1",
|
|
4717
4724
|
directory: ".poe-code/mcp-oauth/resources",
|
|
4718
4725
|
service: "poe-code-mcp-oauth-resources",
|
|
4719
4726
|
accountPrefix: "resource"
|
|
4720
4727
|
}, namespace);
|
|
4721
|
-
const result = {
|
|
4728
|
+
const result = {
|
|
4729
|
+
initialGrantAllowed: true,
|
|
4730
|
+
sessionStore: {},
|
|
4731
|
+
clientStore: {},
|
|
4732
|
+
async reset(resource, options2 = {}) {
|
|
4733
|
+
options2.signal?.throwIfAborted();
|
|
4734
|
+
let url;
|
|
4735
|
+
try {
|
|
4736
|
+
url = new URL(resource);
|
|
4737
|
+
} catch {
|
|
4738
|
+
throw new Error("OAuth reset resource must be an absolute HTTP URL");
|
|
4739
|
+
}
|
|
4740
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
4741
|
+
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
4742
|
+
const timeoutMs = options2.timeoutMs ?? 3e4;
|
|
4743
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
4744
|
+
throw new Error("OAuth reset timeoutMs must be a positive supported timer interval");
|
|
4745
|
+
if (store.withLock === void 0)
|
|
4746
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
4747
|
+
await store.withLock(async () => {
|
|
4748
|
+
options2.signal?.throwIfAborted();
|
|
4749
|
+
const record2 = { version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} };
|
|
4750
|
+
await store.set(JSON.stringify(record2));
|
|
4751
|
+
result.initialGrantAllowed = false;
|
|
4752
|
+
}, { signal: options2.signal, timeoutMs });
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4722
4755
|
async function read() {
|
|
4723
4756
|
const raw = await store.get();
|
|
4724
4757
|
if (raw === null)
|
|
@@ -5497,6 +5530,15 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5497
5530
|
};
|
|
5498
5531
|
}
|
|
5499
5532
|
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
5533
|
+
const importedClient = existingSession?.client.registrationOwnership === "caller" ? existingSession.client : storedClient?.registrationOwnership === "caller" ? storedClient : void 0;
|
|
5534
|
+
if (importedClient !== void 0) {
|
|
5535
|
+
assertRegistrationIssuer(importedClient, discovery.authorizationServer);
|
|
5536
|
+
if (hasExpiredClientSecret(importedClient, now))
|
|
5537
|
+
throw new Error("OAuth client secret has expired; update the imported registration");
|
|
5538
|
+
if (!registrationMatchesRedirect(importedClient, redirectUri))
|
|
5539
|
+
throw new Error("OAuth imported registration does not match the configured redirect URI; update its callback configuration");
|
|
5540
|
+
return { kind: "static", fromStoredRegistration: false, client: importedClient };
|
|
5541
|
+
}
|
|
5500
5542
|
if (storedClient !== null) {
|
|
5501
5543
|
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
5502
5544
|
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
@@ -5723,7 +5765,13 @@ function normalizeConfiguredClient(client) {
|
|
|
5723
5765
|
if (clientId === void 0)
|
|
5724
5766
|
return null;
|
|
5725
5767
|
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
|
|
5726
|
-
return normalizeStoredOAuthClient({
|
|
5768
|
+
return normalizeStoredOAuthClient({
|
|
5769
|
+
clientId,
|
|
5770
|
+
clientSecret,
|
|
5771
|
+
registration,
|
|
5772
|
+
...registration === void 0 ? {} : { registrationOwnership: "caller" },
|
|
5773
|
+
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod
|
|
5774
|
+
});
|
|
5727
5775
|
}
|
|
5728
5776
|
function normalizeOptionalOAuthString(value) {
|
|
5729
5777
|
if (value === void 0) {
|
|
@@ -5892,7 +5940,7 @@ function shouldReRegisterStoredDynamicClient(error, client, alreadyAttempted) {
|
|
|
5892
5940
|
if ("kind" in client) {
|
|
5893
5941
|
return client.kind === "dynamic" && client.fromStoredRegistration;
|
|
5894
5942
|
}
|
|
5895
|
-
return
|
|
5943
|
+
return client.registrationOwnership !== "caller";
|
|
5896
5944
|
}
|
|
5897
5945
|
|
|
5898
5946
|
// ../tiny-stdio-mcp-server/dist/headers.js
|