toolcraft 0.0.311 → 0.0.313
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/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/auth-store/README.md +4 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +3 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +8 -0
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +18 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +3 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +36 -17
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +9 -3
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +4 -0
- package/node_modules/tiny-mcp-client/dist/index.js +50 -19
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.31",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.313",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.313",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
package/dist/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.31",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.313",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.313",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -56,6 +56,10 @@ directory or filesystem adapter. Injected encrypted-file adapters need
|
|
|
56
56
|
- Configurable salt, directory, and file name
|
|
57
57
|
- File permissions: `0600`
|
|
58
58
|
- Random IV per write
|
|
59
|
+
- `fileStore.throwOnInvalidDocument: true` makes malformed or unauthenticated
|
|
60
|
+
existing documents fail with a safe diagnostic; missing files still return
|
|
61
|
+
`null`. The default treats invalid documents as absent. Strict reads preserve
|
|
62
|
+
the existing file until an explicit reset or replacement.
|
|
59
63
|
|
|
60
64
|
### macOS Keychain
|
|
61
65
|
|
|
@@ -31,6 +31,8 @@ export interface EncryptedFileStoreInput {
|
|
|
31
31
|
getMachineIdentity?: () => MachineIdentity | Promise<MachineIdentity>;
|
|
32
32
|
getHomeDirectory?: () => string;
|
|
33
33
|
getRandomBytes?: (size: number) => Buffer;
|
|
34
|
+
/** Fail closed instead of treating malformed or unauthenticated documents as absent. */
|
|
35
|
+
throwOnInvalidDocument?: boolean;
|
|
34
36
|
}
|
|
35
37
|
export declare class EncryptedFileStore implements SecretStore {
|
|
36
38
|
private readonly fs;
|
|
@@ -40,6 +42,7 @@ export declare class EncryptedFileStore implements SecretStore {
|
|
|
40
42
|
private readonly getMachineIdentity;
|
|
41
43
|
private readonly getRandomBytes;
|
|
42
44
|
private keyPromise;
|
|
45
|
+
private readonly throwOnInvalidDocument;
|
|
43
46
|
constructor(input: EncryptedFileStoreInput);
|
|
44
47
|
get(): Promise<string | null>;
|
|
45
48
|
withLock<T>(operation: () => Promise<T>, options?: SecretStoreLockOptions): Promise<T>;
|
|
@@ -19,6 +19,7 @@ export class EncryptedFileStore {
|
|
|
19
19
|
getMachineIdentity;
|
|
20
20
|
getRandomBytes;
|
|
21
21
|
keyPromise = null;
|
|
22
|
+
throwOnInvalidDocument;
|
|
22
23
|
constructor(input) {
|
|
23
24
|
this.fs = input.fs ?? fs;
|
|
24
25
|
this.salt = input.salt;
|
|
@@ -37,6 +38,7 @@ export class EncryptedFileStore {
|
|
|
37
38
|
}
|
|
38
39
|
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
39
40
|
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
41
|
+
this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
|
|
40
42
|
}
|
|
41
43
|
async get() {
|
|
42
44
|
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
@@ -52,6 +54,8 @@ export class EncryptedFileStore {
|
|
|
52
54
|
}
|
|
53
55
|
const document = parseEncryptedDocument(rawDocument);
|
|
54
56
|
if (!document) {
|
|
57
|
+
if (this.throwOnInvalidDocument)
|
|
58
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
55
59
|
return null;
|
|
56
60
|
}
|
|
57
61
|
const key = await this.getEncryptionKey();
|
|
@@ -61,6 +65,8 @@ export class EncryptedFileStore {
|
|
|
61
65
|
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
62
66
|
if (iv.byteLength !== ENCRYPTION_IV_BYTES ||
|
|
63
67
|
authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
68
|
+
if (this.throwOnInvalidDocument)
|
|
69
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
64
70
|
return null;
|
|
65
71
|
}
|
|
66
72
|
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
@@ -69,6 +75,8 @@ export class EncryptedFileStore {
|
|
|
69
75
|
return plaintext.toString("utf8");
|
|
70
76
|
}
|
|
71
77
|
catch {
|
|
78
|
+
if (this.throwOnInvalidDocument)
|
|
79
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
72
80
|
return null;
|
|
73
81
|
}
|
|
74
82
|
}
|
|
@@ -42,6 +42,7 @@ const verifier = createJwksTokenVerifier({
|
|
|
42
42
|
- `mode: "static"` with `clientId`, optional `clientSecret`, optional `metadata`
|
|
43
43
|
- `allowInteractive: false` prevents interactive login while retaining cached tokens and silent refresh
|
|
44
44
|
- `sessionLockTimeoutMs` limits acquisition waits for a session transaction lock (default 30,000 ms; integer from 1 to 2147483647)
|
|
45
|
+
- `persistenceNamespace` isolates native sessions and registrations for a named profile (nonempty string, at most 1024 UTF-8 bytes)
|
|
45
46
|
- `initialGrant: { resource, tokens }` optionally imports an existing Bearer grant for one HTTP resource; requires the original client ID
|
|
46
47
|
- `browser.openBrowser(url)` optional
|
|
47
48
|
- `browser.readLine()` optional
|
|
@@ -104,6 +105,17 @@ Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
|
104
105
|
using the original configured client. Persisted sessions take precedence,
|
|
105
106
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
106
107
|
Input tokens are copied and invalid expiry values fail before authorization.
|
|
108
|
+
Static clients and dynamic initial-grant imports require cached grants to match
|
|
109
|
+
the original normalized client ID and secret. A different client configuration
|
|
110
|
+
fails before attaching or refreshing credentials and retains the stored record;
|
|
111
|
+
select separate persistence or explicitly reset the session to change apps.
|
|
112
|
+
Use `persistenceNamespace: "personal"` or `"work"` to keep separate native
|
|
113
|
+
profiles for the same resource/issuer. `createAuthStoreSessionStore(options,
|
|
114
|
+
namespace)` addresses the same profile when seeding or inspecting credentials.
|
|
115
|
+
Namespaces are hashed into file/Keychain identities and transaction locks;
|
|
116
|
+
omitting one preserves the default storage keys. Switching namespaces selects a
|
|
117
|
+
different record and does not migrate or reset the previous profile. Host-owned
|
|
118
|
+
`sessionStore` implementations remain responsible for their own profile keys.
|
|
107
119
|
|
|
108
120
|
`createAuthStoreSessionStore(options)` accepts the standard `auth-store` config.
|
|
109
121
|
|
|
@@ -130,6 +142,12 @@ can recover it; headless requests fail with an explicit unknown-outcome error.
|
|
|
130
142
|
Only complete OAuth error responses establish a rejected request and allow a
|
|
131
143
|
transient retry or restoration of the original grant. Gateway error pages do not.
|
|
132
144
|
|
|
145
|
+
Native persisted OAuth reads always reject corrupt encrypted documents and
|
|
146
|
+
invalid stored JSON, with diagnostics that omit decrypted contents. They retain
|
|
147
|
+
the existing record for explicit reset rather than interpreting corruption as
|
|
148
|
+
an absent session and reviving an initial grant. Caller file-backend settings
|
|
149
|
+
cannot disable this policy.
|
|
150
|
+
|
|
133
151
|
## Environment Variables
|
|
134
152
|
|
|
135
153
|
This package exposes no direct environment variables. When `authStore` is used,
|
|
@@ -9,6 +9,7 @@ export interface OAuthClientStore {
|
|
|
9
9
|
save(issuer: string, client: StoredOAuthClient): Promise<void>;
|
|
10
10
|
clear(issuer: string): Promise<void>;
|
|
11
11
|
}
|
|
12
|
-
export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput): OAuthSessionStore;
|
|
13
|
-
export declare function createAuthStoreClientStore(options: CreateSecretStoreInput): OAuthClientStore;
|
|
12
|
+
export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput, namespace?: string): OAuthSessionStore;
|
|
13
|
+
export declare function createAuthStoreClientStore(options: CreateSecretStoreInput, namespace?: string): OAuthClientStore;
|
|
14
|
+
export declare function assertPersistenceNamespace(namespace: string | undefined): void;
|
|
14
15
|
export {};
|
|
@@ -9,45 +9,59 @@ const DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
|
|
|
9
9
|
const DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
|
|
10
10
|
const DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
|
|
11
11
|
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
12
|
-
export function createAuthStoreSessionStore(options = {}) {
|
|
12
|
+
export function createAuthStoreSessionStore(options = {}, namespace) {
|
|
13
|
+
assertPersistenceNamespace(namespace);
|
|
13
14
|
return {
|
|
14
15
|
async withLock(resource, operation, lockOptions) {
|
|
15
|
-
const store = createResourceSecretStore(resource, options);
|
|
16
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
16
17
|
if (store.withLock === undefined)
|
|
17
18
|
throw new Error("OAuth secret-store backend does not support transaction locks");
|
|
18
19
|
return store.withLock(operation, lockOptions);
|
|
19
20
|
},
|
|
20
21
|
async load(resource) {
|
|
21
|
-
const store = createResourceSecretStore(resource, options);
|
|
22
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
22
23
|
const value = await store.get();
|
|
23
24
|
if (value === null) {
|
|
24
25
|
return null;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(value);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
33
|
+
}
|
|
27
34
|
if (isStoredOAuthSession(parsed)) {
|
|
28
35
|
return parsed;
|
|
29
36
|
}
|
|
30
37
|
throw new Error("Stored OAuth session must match the expected shape");
|
|
31
38
|
},
|
|
32
39
|
async save(resource, session) {
|
|
33
|
-
const store = createResourceSecretStore(resource, options);
|
|
40
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
34
41
|
await store.set(JSON.stringify(session));
|
|
35
42
|
},
|
|
36
43
|
async clear(resource) {
|
|
37
|
-
const store = createResourceSecretStore(resource, options);
|
|
44
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
38
45
|
await store.delete();
|
|
39
46
|
}
|
|
40
47
|
};
|
|
41
48
|
}
|
|
42
|
-
export function createAuthStoreClientStore(options) {
|
|
49
|
+
export function createAuthStoreClientStore(options, namespace) {
|
|
50
|
+
assertPersistenceNamespace(namespace);
|
|
43
51
|
return {
|
|
44
52
|
async load(issuer) {
|
|
45
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
53
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
46
54
|
const value = await store.get();
|
|
47
55
|
if (value === null) {
|
|
48
56
|
return null;
|
|
49
57
|
}
|
|
50
|
-
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(value);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
64
|
+
}
|
|
51
65
|
const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : undefined;
|
|
52
66
|
if (clientId !== undefined) {
|
|
53
67
|
const client = { clientId };
|
|
@@ -60,21 +74,22 @@ export function createAuthStoreClientStore(options) {
|
|
|
60
74
|
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
61
75
|
},
|
|
62
76
|
async save(issuer, client) {
|
|
63
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
77
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
64
78
|
await store.set(JSON.stringify(client));
|
|
65
79
|
},
|
|
66
80
|
async clear(issuer) {
|
|
67
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
81
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
68
82
|
await store.delete();
|
|
69
83
|
}
|
|
70
84
|
};
|
|
71
85
|
}
|
|
72
|
-
function createNamedSecretStore(key, options, defaults) {
|
|
73
|
-
const hash = crypto.createHash("sha256").update(key).digest("hex");
|
|
86
|
+
function createNamedSecretStore(key, options, defaults, namespace) {
|
|
87
|
+
const hash = crypto.createHash("sha256").update(namespace === undefined ? key : JSON.stringify([namespace, key])).digest("hex");
|
|
74
88
|
const configuredFilePath = options.fileStore?.filePath;
|
|
75
89
|
const parsedFilePath = configuredFilePath === undefined ? null : path.parse(configuredFilePath);
|
|
76
90
|
const fileStore = {
|
|
77
91
|
...options.fileStore,
|
|
92
|
+
throwOnInvalidDocument: true,
|
|
78
93
|
filePath: parsedFilePath === null
|
|
79
94
|
? undefined
|
|
80
95
|
: path.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
@@ -91,21 +106,25 @@ function createNamedSecretStore(key, options, defaults) {
|
|
|
91
106
|
};
|
|
92
107
|
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
93
108
|
}
|
|
94
|
-
function createResourceSecretStore(resource, options) {
|
|
109
|
+
function createResourceSecretStore(resource, options, namespace) {
|
|
95
110
|
return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
|
|
96
111
|
salt: DEFAULT_FILE_SALT,
|
|
97
112
|
directory: DEFAULT_FILE_DIRECTORY,
|
|
98
113
|
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
99
114
|
accountPrefix: "provider"
|
|
100
|
-
});
|
|
115
|
+
}, namespace);
|
|
101
116
|
}
|
|
102
|
-
function createIssuerSecretStore(issuer, options) {
|
|
117
|
+
function createIssuerSecretStore(issuer, options, namespace) {
|
|
103
118
|
return createNamedSecretStore(issuer, options, {
|
|
104
119
|
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
105
120
|
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
106
121
|
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
107
122
|
accountPrefix: "issuer"
|
|
108
|
-
});
|
|
123
|
+
}, namespace);
|
|
124
|
+
}
|
|
125
|
+
export function assertPersistenceNamespace(namespace) {
|
|
126
|
+
if (namespace !== undefined && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
|
|
127
|
+
throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
|
|
109
128
|
}
|
|
110
129
|
function isObjectRecord(value) {
|
|
111
130
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isIP } from "node:net";
|
|
2
2
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
|
-
import { createAuthStoreClientStore, createAuthStoreSessionStore } from "./auth-store-session-store.js";
|
|
4
|
+
import { createAuthStoreClientStore, createAuthStoreSessionStore, assertPersistenceNamespace } from "./auth-store-session-store.js";
|
|
5
5
|
import { createLoopbackAuthorizationSession, loopbackTarget } from "./loopback-authorization.js";
|
|
6
6
|
import { createAuthorizationState } from "./authorization-state.js";
|
|
7
7
|
import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
@@ -17,8 +17,9 @@ export function createOAuthClientProvider(options) {
|
|
|
17
17
|
}
|
|
18
18
|
export function createDefaultOAuthClientProvider(options) {
|
|
19
19
|
loopbackTarget(options.browser);
|
|
20
|
-
|
|
21
|
-
const
|
|
20
|
+
assertPersistenceNamespace(options.persistenceNamespace);
|
|
21
|
+
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
22
|
+
const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
22
23
|
const now = options.now ?? Date.now;
|
|
23
24
|
const registeredClients = new Map();
|
|
24
25
|
if (options.initialGrant !== undefined) {
|
|
@@ -140,6 +141,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
140
141
|
if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
141
142
|
forceRefresh = false;
|
|
142
143
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
144
|
+
if ((options.client.mode === "static" || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
|
|
145
|
+
const configured = normalizeConfiguredClient(options.client);
|
|
146
|
+
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
147
|
+
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
148
|
+
}
|
|
143
149
|
if (session?.refreshState === "pending") {
|
|
144
150
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
|
|
145
151
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -112,6 +112,8 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
112
112
|
allowInteractive?: boolean;
|
|
113
113
|
/** Maximum wait to acquire a session transaction lock (default 30,000 ms). */
|
|
114
114
|
sessionLockTimeoutMs?: number;
|
|
115
|
+
/** Isolate native persisted sessions and registrations for a named profile. */
|
|
116
|
+
persistenceNamespace?: string;
|
|
115
117
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
116
118
|
initialGrant?: {
|
|
117
119
|
resource: string;
|
|
@@ -70,6 +70,8 @@ interface EncryptedFileStoreInput {
|
|
|
70
70
|
getMachineIdentity?: () => MachineIdentity | Promise<MachineIdentity>;
|
|
71
71
|
getHomeDirectory?: () => string;
|
|
72
72
|
getRandomBytes?: (size: number) => Buffer;
|
|
73
|
+
/** Fail closed instead of treating malformed or unauthenticated documents as absent. */
|
|
74
|
+
throwOnInvalidDocument?: boolean;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
interface KeychainCommandResult {
|
|
@@ -213,6 +215,8 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
213
215
|
allowInteractive?: boolean;
|
|
214
216
|
/** Maximum wait to acquire a session transaction lock (default 30,000 ms). */
|
|
215
217
|
sessionLockTimeoutMs?: number;
|
|
218
|
+
/** Isolate native persisted sessions and registrations for a named profile. */
|
|
219
|
+
persistenceNamespace?: string;
|
|
216
220
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
217
221
|
initialGrant?: {
|
|
218
222
|
resource: string;
|
|
@@ -3575,6 +3575,7 @@ var EncryptedFileStore = class {
|
|
|
3575
3575
|
getMachineIdentity;
|
|
3576
3576
|
getRandomBytes;
|
|
3577
3577
|
keyPromise = null;
|
|
3578
|
+
throwOnInvalidDocument;
|
|
3578
3579
|
constructor(input) {
|
|
3579
3580
|
this.fs = input.fs ?? fs;
|
|
3580
3581
|
this.salt = input.salt;
|
|
@@ -3592,6 +3593,7 @@ var EncryptedFileStore = class {
|
|
|
3592
3593
|
}
|
|
3593
3594
|
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
3594
3595
|
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
3596
|
+
this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
|
|
3595
3597
|
}
|
|
3596
3598
|
async get() {
|
|
3597
3599
|
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
@@ -3606,6 +3608,8 @@ var EncryptedFileStore = class {
|
|
|
3606
3608
|
}
|
|
3607
3609
|
const document = parseEncryptedDocument(rawDocument);
|
|
3608
3610
|
if (!document) {
|
|
3611
|
+
if (this.throwOnInvalidDocument)
|
|
3612
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3609
3613
|
return null;
|
|
3610
3614
|
}
|
|
3611
3615
|
const key2 = await this.getEncryptionKey();
|
|
@@ -3614,6 +3618,8 @@ var EncryptedFileStore = class {
|
|
|
3614
3618
|
const authTag = Buffer.from(document.authTag, "base64");
|
|
3615
3619
|
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
3616
3620
|
if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
3621
|
+
if (this.throwOnInvalidDocument)
|
|
3622
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3617
3623
|
return null;
|
|
3618
3624
|
}
|
|
3619
3625
|
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
@@ -3621,6 +3627,8 @@ var EncryptedFileStore = class {
|
|
|
3621
3627
|
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
3622
3628
|
return plaintext.toString("utf8");
|
|
3623
3629
|
} catch {
|
|
3630
|
+
if (this.throwOnInvalidDocument)
|
|
3631
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3624
3632
|
return null;
|
|
3625
3633
|
}
|
|
3626
3634
|
}
|
|
@@ -4069,45 +4077,57 @@ var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
|
|
|
4069
4077
|
var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
|
|
4070
4078
|
var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
|
|
4071
4079
|
var MAX_JS_DATE_MS = 864e13;
|
|
4072
|
-
function createAuthStoreSessionStore(options = {}) {
|
|
4080
|
+
function createAuthStoreSessionStore(options = {}, namespace) {
|
|
4081
|
+
assertPersistenceNamespace(namespace);
|
|
4073
4082
|
return {
|
|
4074
4083
|
async withLock(resource, operation, lockOptions) {
|
|
4075
|
-
const store = createResourceSecretStore(resource, options);
|
|
4084
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4076
4085
|
if (store.withLock === void 0)
|
|
4077
4086
|
throw new Error("OAuth secret-store backend does not support transaction locks");
|
|
4078
4087
|
return store.withLock(operation, lockOptions);
|
|
4079
4088
|
},
|
|
4080
4089
|
async load(resource) {
|
|
4081
|
-
const store = createResourceSecretStore(resource, options);
|
|
4090
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4082
4091
|
const value = await store.get();
|
|
4083
4092
|
if (value === null) {
|
|
4084
4093
|
return null;
|
|
4085
4094
|
}
|
|
4086
|
-
|
|
4095
|
+
let parsed;
|
|
4096
|
+
try {
|
|
4097
|
+
parsed = JSON.parse(value);
|
|
4098
|
+
} catch {
|
|
4099
|
+
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
4100
|
+
}
|
|
4087
4101
|
if (isStoredOAuthSession(parsed)) {
|
|
4088
4102
|
return parsed;
|
|
4089
4103
|
}
|
|
4090
4104
|
throw new Error("Stored OAuth session must match the expected shape");
|
|
4091
4105
|
},
|
|
4092
4106
|
async save(resource, session) {
|
|
4093
|
-
const store = createResourceSecretStore(resource, options);
|
|
4107
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4094
4108
|
await store.set(JSON.stringify(session));
|
|
4095
4109
|
},
|
|
4096
4110
|
async clear(resource) {
|
|
4097
|
-
const store = createResourceSecretStore(resource, options);
|
|
4111
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4098
4112
|
await store.delete();
|
|
4099
4113
|
}
|
|
4100
4114
|
};
|
|
4101
4115
|
}
|
|
4102
|
-
function createAuthStoreClientStore(options) {
|
|
4116
|
+
function createAuthStoreClientStore(options, namespace) {
|
|
4117
|
+
assertPersistenceNamespace(namespace);
|
|
4103
4118
|
return {
|
|
4104
4119
|
async load(issuer) {
|
|
4105
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
4120
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4106
4121
|
const value = await store.get();
|
|
4107
4122
|
if (value === null) {
|
|
4108
4123
|
return null;
|
|
4109
4124
|
}
|
|
4110
|
-
|
|
4125
|
+
let parsed;
|
|
4126
|
+
try {
|
|
4127
|
+
parsed = JSON.parse(value);
|
|
4128
|
+
} catch {
|
|
4129
|
+
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
4130
|
+
}
|
|
4111
4131
|
const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : void 0;
|
|
4112
4132
|
if (clientId !== void 0) {
|
|
4113
4133
|
const client = { clientId };
|
|
@@ -4119,21 +4139,22 @@ function createAuthStoreClientStore(options) {
|
|
|
4119
4139
|
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
4120
4140
|
},
|
|
4121
4141
|
async save(issuer, client) {
|
|
4122
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
4142
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4123
4143
|
await store.set(JSON.stringify(client));
|
|
4124
4144
|
},
|
|
4125
4145
|
async clear(issuer) {
|
|
4126
|
-
const store = createIssuerSecretStore(issuer, options);
|
|
4146
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4127
4147
|
await store.delete();
|
|
4128
4148
|
}
|
|
4129
4149
|
};
|
|
4130
4150
|
}
|
|
4131
|
-
function createNamedSecretStore(key2, options, defaults) {
|
|
4132
|
-
const hash = crypto.createHash("sha256").update(key2).digest("hex");
|
|
4151
|
+
function createNamedSecretStore(key2, options, defaults, namespace) {
|
|
4152
|
+
const hash = crypto.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
|
|
4133
4153
|
const configuredFilePath = options.fileStore?.filePath;
|
|
4134
4154
|
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4135
4155
|
const fileStore = {
|
|
4136
4156
|
...options.fileStore,
|
|
4157
|
+
throwOnInvalidDocument: true,
|
|
4137
4158
|
filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
4138
4159
|
salt: options.fileStore?.salt ?? defaults.salt,
|
|
4139
4160
|
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
@@ -4146,21 +4167,25 @@ function createNamedSecretStore(key2, options, defaults) {
|
|
|
4146
4167
|
};
|
|
4147
4168
|
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
4148
4169
|
}
|
|
4149
|
-
function createResourceSecretStore(resource, options) {
|
|
4170
|
+
function createResourceSecretStore(resource, options, namespace) {
|
|
4150
4171
|
return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
|
|
4151
4172
|
salt: DEFAULT_FILE_SALT,
|
|
4152
4173
|
directory: DEFAULT_FILE_DIRECTORY,
|
|
4153
4174
|
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
4154
4175
|
accountPrefix: "provider"
|
|
4155
|
-
});
|
|
4176
|
+
}, namespace);
|
|
4156
4177
|
}
|
|
4157
|
-
function createIssuerSecretStore(issuer, options) {
|
|
4178
|
+
function createIssuerSecretStore(issuer, options, namespace) {
|
|
4158
4179
|
return createNamedSecretStore(issuer, options, {
|
|
4159
4180
|
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
4160
4181
|
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
4161
4182
|
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
4162
4183
|
accountPrefix: "issuer"
|
|
4163
|
-
});
|
|
4184
|
+
}, namespace);
|
|
4185
|
+
}
|
|
4186
|
+
function assertPersistenceNamespace(namespace) {
|
|
4187
|
+
if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
|
|
4188
|
+
throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
|
|
4164
4189
|
}
|
|
4165
4190
|
function isObjectRecord(value) {
|
|
4166
4191
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -4821,8 +4846,9 @@ function createOAuthClientProvider(options) {
|
|
|
4821
4846
|
}
|
|
4822
4847
|
function createDefaultOAuthClientProvider(options) {
|
|
4823
4848
|
loopbackTarget(options.browser);
|
|
4824
|
-
|
|
4825
|
-
const
|
|
4849
|
+
assertPersistenceNamespace(options.persistenceNamespace);
|
|
4850
|
+
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
4851
|
+
const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
4826
4852
|
const now = options.now ?? Date.now;
|
|
4827
4853
|
const registeredClients = /* @__PURE__ */ new Map();
|
|
4828
4854
|
if (options.initialGrant !== void 0) {
|
|
@@ -4939,6 +4965,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4939
4965
|
if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
4940
4966
|
forceRefresh = false;
|
|
4941
4967
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
4968
|
+
if ((options.client.mode === "static" || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
|
|
4969
|
+
const configured = normalizeConfiguredClient(options.client);
|
|
4970
|
+
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
4971
|
+
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
4972
|
+
}
|
|
4942
4973
|
if (session?.refreshState === "pending") {
|
|
4943
4974
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
|
|
4944
4975
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.313",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"yaml"
|
|
159
159
|
],
|
|
160
160
|
"optionalDependencies": {
|
|
161
|
-
"toolcraft-schema": "0.0.
|
|
161
|
+
"toolcraft-schema": "0.0.313",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|