tiny-http-mcp-server 0.1.28 → 0.1.30
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/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/mcp-oauth/README.md +10 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +15 -2
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +5 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +26 -2
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -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
|
}
|
|
@@ -104,6 +104,10 @@ Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
|
104
104
|
using the original configured client. Persisted sessions take precedence,
|
|
105
105
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
106
106
|
Input tokens are copied and invalid expiry values fail before authorization.
|
|
107
|
+
Static clients and dynamic initial-grant imports require cached grants to match
|
|
108
|
+
the original normalized client ID and secret. A different client configuration
|
|
109
|
+
fails before attaching or refreshing credentials and retains the stored record;
|
|
110
|
+
select separate persistence or explicitly reset the session to change apps.
|
|
107
111
|
|
|
108
112
|
`createAuthStoreSessionStore(options)` accepts the standard `auth-store` config.
|
|
109
113
|
|
|
@@ -130,6 +134,12 @@ can recover it; headless requests fail with an explicit unknown-outcome error.
|
|
|
130
134
|
Only complete OAuth error responses establish a rejected request and allow a
|
|
131
135
|
transient retry or restoration of the original grant. Gateway error pages do not.
|
|
132
136
|
|
|
137
|
+
Native persisted OAuth reads always reject corrupt encrypted documents and
|
|
138
|
+
invalid stored JSON, with diagnostics that omit decrypted contents. They retain
|
|
139
|
+
the existing record for explicit reset rather than interpreting corruption as
|
|
140
|
+
an absent session and reviving an initial grant. Caller file-backend settings
|
|
141
|
+
cannot disable this policy.
|
|
142
|
+
|
|
133
143
|
## Environment Variables
|
|
134
144
|
|
|
135
145
|
This package exposes no direct environment variables. When `authStore` is used,
|
|
@@ -23,7 +23,13 @@ export function createAuthStoreSessionStore(options = {}) {
|
|
|
23
23
|
if (value === null) {
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(value);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
32
|
+
}
|
|
27
33
|
if (isStoredOAuthSession(parsed)) {
|
|
28
34
|
return parsed;
|
|
29
35
|
}
|
|
@@ -47,7 +53,13 @@ export function createAuthStoreClientStore(options) {
|
|
|
47
53
|
if (value === null) {
|
|
48
54
|
return null;
|
|
49
55
|
}
|
|
50
|
-
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(value);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
62
|
+
}
|
|
51
63
|
const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : undefined;
|
|
52
64
|
if (clientId !== undefined) {
|
|
53
65
|
const client = { clientId };
|
|
@@ -75,6 +87,7 @@ function createNamedSecretStore(key, options, defaults) {
|
|
|
75
87
|
const parsedFilePath = configuredFilePath === undefined ? null : path.parse(configuredFilePath);
|
|
76
88
|
const fileStore = {
|
|
77
89
|
...options.fileStore,
|
|
90
|
+
throwOnInvalidDocument: true,
|
|
78
91
|
filePath: parsedFilePath === null
|
|
79
92
|
? undefined
|
|
80
93
|
: path.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
@@ -140,6 +140,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
140
140
|
if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
141
141
|
forceRefresh = false;
|
|
142
142
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
143
|
+
if ((options.client.mode === "static" || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
|
|
144
|
+
const configured = normalizeConfiguredClient(options.client);
|
|
145
|
+
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
146
|
+
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
147
|
+
}
|
|
143
148
|
if (session?.refreshState === "pending") {
|
|
144
149
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
|
|
145
150
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -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 {
|
|
@@ -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
|
}
|
|
@@ -4083,7 +4091,12 @@ function createAuthStoreSessionStore(options = {}) {
|
|
|
4083
4091
|
if (value === null) {
|
|
4084
4092
|
return null;
|
|
4085
4093
|
}
|
|
4086
|
-
|
|
4094
|
+
let parsed;
|
|
4095
|
+
try {
|
|
4096
|
+
parsed = JSON.parse(value);
|
|
4097
|
+
} catch {
|
|
4098
|
+
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
4099
|
+
}
|
|
4087
4100
|
if (isStoredOAuthSession(parsed)) {
|
|
4088
4101
|
return parsed;
|
|
4089
4102
|
}
|
|
@@ -4107,7 +4120,12 @@ function createAuthStoreClientStore(options) {
|
|
|
4107
4120
|
if (value === null) {
|
|
4108
4121
|
return null;
|
|
4109
4122
|
}
|
|
4110
|
-
|
|
4123
|
+
let parsed;
|
|
4124
|
+
try {
|
|
4125
|
+
parsed = JSON.parse(value);
|
|
4126
|
+
} catch {
|
|
4127
|
+
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
4128
|
+
}
|
|
4111
4129
|
const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : void 0;
|
|
4112
4130
|
if (clientId !== void 0) {
|
|
4113
4131
|
const client = { clientId };
|
|
@@ -4134,6 +4152,7 @@ function createNamedSecretStore(key2, options, defaults) {
|
|
|
4134
4152
|
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4135
4153
|
const fileStore = {
|
|
4136
4154
|
...options.fileStore,
|
|
4155
|
+
throwOnInvalidDocument: true,
|
|
4137
4156
|
filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
4138
4157
|
salt: options.fileStore?.salt ?? defaults.salt,
|
|
4139
4158
|
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
@@ -4939,6 +4958,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4939
4958
|
if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
4940
4959
|
forceRefresh = false;
|
|
4941
4960
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
4961
|
+
if ((options.client.mode === "static" || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
|
|
4962
|
+
const configured = normalizeConfiguredClient(options.client);
|
|
4963
|
+
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
4964
|
+
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
4965
|
+
}
|
|
4942
4966
|
if (session?.refreshState === "pending") {
|
|
4943
4967
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
|
|
4944
4968
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|