toolcraft 0.0.314 → 0.0.316
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/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +40 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +1 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -18
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.d.ts +6 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +148 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +112 -49
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.js +7 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +19 -9
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +36 -5
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +1 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +36 -5
- package/node_modules/tiny-mcp-client/dist/index.js +1312 -1095
- 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.36",
|
|
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.316",
|
|
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.316",
|
|
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.36",
|
|
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.316",
|
|
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.316",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -106,12 +106,50 @@ Select a separate persistence namespace for another scope profile. No scope is
|
|
|
106
106
|
invented when the client does not configure one.
|
|
107
107
|
|
|
108
108
|
Imported `initialGrant.tokens` use `accessToken`, optional `refreshToken`,
|
|
109
|
-
`tokenType: "Bearer"`, `expiresAt` (Unix epoch milliseconds or `null`
|
|
110
|
-
and optional `scope`.
|
|
109
|
+
`tokenType: "Bearer"`, optional `expiresAt` (Unix epoch milliseconds or `null`
|
|
110
|
+
if unknown), and optional `scope`. `expiresIn` is a lifetime in seconds and is
|
|
111
|
+
anchored once at import. For a delayed import, provide the original `issuedAt`
|
|
112
|
+
in epoch milliseconds or its real absolute `expiresAt`; a numeric absolute
|
|
113
|
+
expiry takes precedence. Without either, the relative value means remaining
|
|
114
|
+
lifetime at import. Omitted expiry stays unknown. A fresh imported token is
|
|
115
|
+
used only for its resource.
|
|
111
116
|
Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
112
117
|
using the original configured client. Persisted sessions take precedence,
|
|
113
118
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
114
119
|
Input tokens are copied and invalid expiry values fail before authorization.
|
|
120
|
+
Pass a complete DCR response as `client.registration` (or validate untrusted JSON
|
|
121
|
+
with `parseOAuthClientRegistration`). Dynamic clients infer their original ID
|
|
122
|
+
and secret from that response and reuse it without registering another app.
|
|
123
|
+
Explicit ID/secret values must agree with the imported response. Sessions and
|
|
124
|
+
native registration stores retain arrays, issuance/expiry timestamps and JSON
|
|
125
|
+
provider metadata. Registration input is copied, bounded to 64 KiB and 64
|
|
126
|
+
levels, and rejects invalid standard field types and non-JSON extensions.
|
|
127
|
+
An optional registration `issuer` must match discovery and the persisted
|
|
128
|
+
authorization server exactly. Contradictory metadata fails without activating
|
|
129
|
+
or redeeming the grant. `client_secret_expires_at` uses Unix epoch seconds;
|
|
130
|
+
zero means no expiry. Live access tokens remain usable after secret expiry,
|
|
131
|
+
but an expired secret is never submitted for refresh. Native DCR can replace
|
|
132
|
+
an expired registration during explicit authorization; caller-owned imports
|
|
133
|
+
must be updated. Headless requests retain the old record and report recovery
|
|
134
|
+
is required without creating a pending refresh marker.
|
|
135
|
+
Native registrations retain `requestedRedirectUri`, the actual listener URI
|
|
136
|
+
submitted to DCR, separately from the full response metadata. Fresh responses
|
|
137
|
+
may normalize a loopback port or represent IPv4 loopback as portless localhost;
|
|
138
|
+
host, path, scheme, query and fragment differences outside that boundary fail.
|
|
139
|
+
Authorization and code exchange always use the actual listener URI. Silent
|
|
140
|
+
refresh keeps its original client regardless of callback changes. At interactive
|
|
141
|
+
authorization, a native registration with an obsolete captured callback is
|
|
142
|
+
replaced; caller-owned full registration imports retain their original identity.
|
|
143
|
+
Set `client.tokenEndpointAuthMethod` to `none`, `client_secret_post` or
|
|
144
|
+
`client_secret_basic`; a full registration can supply the same field as
|
|
145
|
+
`token_endpoint_auth_method`. Public clients never transmit a stored secret.
|
|
146
|
+
Basic credentials are individually form-encoded before Base64 encoding and
|
|
147
|
+
are omitted from the form body. Cached grants retain their registered method;
|
|
148
|
+
an explicitly different configured method requires separate persistence or a
|
|
149
|
+
reset. Native DCR chooses a supported method, preferring public PKCE when
|
|
150
|
+
advertised. Unsupported methods and missing confidential secrets fail before
|
|
151
|
+
token requests. Existing clients without a method keep the previous default:
|
|
152
|
+
body authentication when a secret is present, public authentication otherwise.
|
|
115
153
|
Static clients and dynamic initial-grant imports require cached grants to match
|
|
116
154
|
the original normalized client ID and secret. A different client configuration
|
|
117
155
|
fails before attaching or refreshing credentials and retains the stored record;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { type CreateSecretStoreInput } from "auth-store";
|
|
2
|
-
import type { OAuthSessionStore } from "./types.js";
|
|
3
|
-
interface StoredOAuthClient {
|
|
4
|
-
clientId: string;
|
|
5
|
-
clientSecret?: string;
|
|
6
|
-
}
|
|
2
|
+
import type { OAuthSessionStore, StoredOAuthClient } from "./types.js";
|
|
7
3
|
export interface OAuthClientStore {
|
|
8
4
|
load(issuer: string): Promise<StoredOAuthClient | null>;
|
|
9
5
|
save(issuer: string, client: StoredOAuthClient): Promise<void>;
|
|
@@ -12,4 +8,3 @@ export interface OAuthClientStore {
|
|
|
12
8
|
export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput, namespace?: string): OAuthSessionStore;
|
|
13
9
|
export declare function createAuthStoreClientStore(options: CreateSecretStoreInput, namespace?: string): OAuthClientStore;
|
|
14
10
|
export declare function assertPersistenceNamespace(namespace: string | undefined): void;
|
|
15
|
-
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeStoredOAuthClient } from "./client-registration.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createSecretStore } from "auth-store";
|
|
@@ -62,15 +63,10 @@ export function createAuthStoreClientStore(options, namespace) {
|
|
|
62
63
|
catch {
|
|
63
64
|
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
|
|
70
|
-
client.clientSecret = getOwnEntry(parsed, "clientSecret");
|
|
71
|
-
}
|
|
72
|
-
return client;
|
|
73
|
-
}
|
|
66
|
+
// Preserve all registration metadata; provider normalization validates its
|
|
67
|
+
// identity and schema before it can authorize a request.
|
|
68
|
+
if (isObjectRecord(parsed) && typeof getOwnEntry(parsed, "clientId") === "string")
|
|
69
|
+
return parsed;
|
|
74
70
|
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
75
71
|
},
|
|
76
72
|
async save(issuer, client) {
|
|
@@ -142,21 +138,13 @@ function isStoredOAuthSession(value) {
|
|
|
142
138
|
}
|
|
143
139
|
return (isNonBlankOwnString(value, "resource") &&
|
|
144
140
|
isNonBlankOwnString(value, "authorizationServer") &&
|
|
145
|
-
|
|
141
|
+
normalizeStoredOAuthClient(getOwnEntry(value, "client")) !== null &&
|
|
146
142
|
isStoredOAuthDiscovery(getOwnEntry(value, "discovery")) &&
|
|
147
143
|
(getOwnEntry(value, "requestedScope") === undefined || isNonBlankOwnString(value, "requestedScope")) &&
|
|
148
144
|
(getOwnEntry(value, "refreshState") === undefined ||
|
|
149
145
|
(getOwnEntry(value, "refreshState") === "pending" && getOwnEntry(value, "tokens") === undefined)) &&
|
|
150
146
|
isStoredOAuthTokensOrMissing(getOwnEntry(value, "tokens")));
|
|
151
147
|
}
|
|
152
|
-
function isStoredOAuthClient(value) {
|
|
153
|
-
if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
const clientSecret = getOwnEntry(value, "clientSecret");
|
|
157
|
-
return (clientSecret === undefined ||
|
|
158
|
-
(typeof clientSecret === "string" && clientSecret.trim().length > 0));
|
|
159
|
-
}
|
|
160
148
|
function isStoredOAuthDiscovery(value) {
|
|
161
149
|
if (!isObjectRecord(value)) {
|
|
162
150
|
return false;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OAuthClientRegistration, StoredOAuthClient } from "./types.js";
|
|
2
|
+
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
3
|
+
export declare function parseOAuthClientRegistration(value: unknown): OAuthClientRegistration;
|
|
4
|
+
export declare function normalizeStoredOAuthClient(value: unknown): StoredOAuthClient | null;
|
|
5
|
+
/** Fresh DCR may describe a normalized loopback port; saved identity stays exact. */
|
|
6
|
+
export declare function registrationMatchesRedirect(client: StoredOAuthClient, requestedUri: string, fresh?: boolean): boolean;
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { loopbackTarget } from "./loopback-authorization.js";
|
|
2
|
+
import { normalizeOAuthScope } from "./scope.js";
|
|
3
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
4
|
+
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
5
|
+
export function parseOAuthClientRegistration(value) {
|
|
6
|
+
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
7
|
+
let nodes = 0;
|
|
8
|
+
function copy(input, depth) {
|
|
9
|
+
if (++nodes > 20_000 || depth > 64)
|
|
10
|
+
throw invalid();
|
|
11
|
+
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
12
|
+
return input;
|
|
13
|
+
if (typeof input === "number" && Number.isFinite(input))
|
|
14
|
+
return input;
|
|
15
|
+
if (typeof input !== "object" || input === null)
|
|
16
|
+
throw invalid();
|
|
17
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
18
|
+
if (Array.isArray(input)) {
|
|
19
|
+
const length = descriptors.length?.value;
|
|
20
|
+
if (length > 20_000)
|
|
21
|
+
throw invalid();
|
|
22
|
+
const result = [];
|
|
23
|
+
for (let index = 0; index < length; index++) {
|
|
24
|
+
const descriptor = descriptors[String(index)];
|
|
25
|
+
if (descriptor === undefined || !Object.hasOwn(descriptor, "value"))
|
|
26
|
+
throw invalid();
|
|
27
|
+
result.push(copy(descriptor.value, depth + 1));
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
32
|
+
throw invalid();
|
|
33
|
+
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key, descriptor]) => {
|
|
34
|
+
if (!Object.hasOwn(descriptor, "value"))
|
|
35
|
+
throw invalid();
|
|
36
|
+
return [key, copy(descriptor.value, depth + 1)];
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
let result;
|
|
40
|
+
try {
|
|
41
|
+
result = copy(value, 0);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw invalid();
|
|
45
|
+
}
|
|
46
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
47
|
+
throw invalid();
|
|
48
|
+
const record = result;
|
|
49
|
+
if (!Object.hasOwn(record, "client_id") || typeof record.client_id !== "string" || record.client_id.trim() === "")
|
|
50
|
+
throw new Error("OAuth client registration response missing client_id");
|
|
51
|
+
for (const key of ["client_id", "client_secret", "token_endpoint_auth_method", "application_type", "client_name", "client_uri", "logo_uri", "scope",
|
|
52
|
+
"tos_uri", "policy_uri", "jwks_uri", "software_id", "software_version", "software_statement", "registration_access_token", "registration_client_uri", "issuer"]) {
|
|
53
|
+
if (Object.hasOwn(record, key) && record[key] !== null && typeof record[key] !== "string")
|
|
54
|
+
throw invalid();
|
|
55
|
+
}
|
|
56
|
+
if (typeof record.client_secret === "string" && record.client_secret.trim() === "")
|
|
57
|
+
throw invalid();
|
|
58
|
+
for (const key of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
|
|
59
|
+
const entry = record[key];
|
|
60
|
+
if (Object.hasOwn(record, key) && entry !== null && (!Array.isArray(entry) || entry.some(item => typeof item !== "string")))
|
|
61
|
+
throw invalid();
|
|
62
|
+
}
|
|
63
|
+
for (const key of ["client_id_issued_at", "client_secret_expires_at"]) {
|
|
64
|
+
const entry = record[key];
|
|
65
|
+
if (Object.hasOwn(record, key) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
|
|
66
|
+
throw invalid();
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
normalizeOAuthScope(Object.hasOwn(record, "scope") && record.scope !== null ? record.scope : undefined);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw invalid();
|
|
73
|
+
}
|
|
74
|
+
if (Buffer.byteLength(JSON.stringify(record), "utf8") > 64 * 1024)
|
|
75
|
+
throw invalid();
|
|
76
|
+
return record;
|
|
77
|
+
}
|
|
78
|
+
export function normalizeStoredOAuthClient(value) {
|
|
79
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
80
|
+
return null;
|
|
81
|
+
const record = value;
|
|
82
|
+
const clientId = Object.hasOwn(record, "clientId") ? record.clientId : undefined;
|
|
83
|
+
const clientSecret = Object.hasOwn(record, "clientSecret") ? record.clientSecret : undefined;
|
|
84
|
+
if (typeof clientId !== "string" || clientId.trim() === "" ||
|
|
85
|
+
(clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
|
|
86
|
+
return null;
|
|
87
|
+
const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
|
|
88
|
+
const requestedRedirectUri = Object.hasOwn(record, "requestedRedirectUri") ? record.requestedRedirectUri : undefined;
|
|
89
|
+
if (requestedRedirectUri !== undefined) {
|
|
90
|
+
try {
|
|
91
|
+
if (typeof requestedRedirectUri !== "string")
|
|
92
|
+
throw new Error("Invalid redirect identity");
|
|
93
|
+
loopbackTarget({ redirectUri: requestedRedirectUri });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new Error("Invalid stored OAuth registration redirect identity");
|
|
97
|
+
}
|
|
98
|
+
client.requestedRedirectUri = requestedRedirectUri;
|
|
99
|
+
}
|
|
100
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record, "tokenEndpointAuthMethod") ? record.tokenEndpointAuthMethod : undefined);
|
|
101
|
+
if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
|
|
102
|
+
const registration = parseOAuthClientRegistration(record.registration);
|
|
103
|
+
const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : undefined;
|
|
104
|
+
if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
|
|
105
|
+
throw new Error("OAuth client registration does not match the client identity");
|
|
106
|
+
client.registration = registration;
|
|
107
|
+
const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : undefined);
|
|
108
|
+
if (method !== undefined && registrationMethod !== undefined && method !== registrationMethod)
|
|
109
|
+
throw new Error("OAuth token endpoint authentication conflicts with the client registration");
|
|
110
|
+
if (registrationMethod !== undefined)
|
|
111
|
+
client.tokenEndpointAuthMethod = registrationMethod;
|
|
112
|
+
}
|
|
113
|
+
if (method !== undefined)
|
|
114
|
+
client.tokenEndpointAuthMethod = method;
|
|
115
|
+
return client;
|
|
116
|
+
}
|
|
117
|
+
/** Fresh DCR may describe a normalized loopback port; saved identity stays exact. */
|
|
118
|
+
export function registrationMatchesRedirect(client, requestedUri, fresh = false) {
|
|
119
|
+
if (client.requestedRedirectUri !== undefined)
|
|
120
|
+
return client.requestedRedirectUri === requestedUri;
|
|
121
|
+
const redirects = client.registration?.redirect_uris;
|
|
122
|
+
if (redirects === undefined || redirects === null || redirects.length === 0)
|
|
123
|
+
return true;
|
|
124
|
+
return redirects.some(returnedUri => {
|
|
125
|
+
if (returnedUri === requestedUri)
|
|
126
|
+
return true;
|
|
127
|
+
if (!fresh)
|
|
128
|
+
return false;
|
|
129
|
+
let returned, requested;
|
|
130
|
+
try {
|
|
131
|
+
returned = new URL(returnedUri);
|
|
132
|
+
requested = new URL(requestedUri);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (requested.protocol !== "http:" || returned.protocol !== "http:" ||
|
|
138
|
+
!["127.0.0.1", "[::1]", "localhost"].includes(requested.hostname))
|
|
139
|
+
return false;
|
|
140
|
+
const sameHost = returned.hostname === requested.hostname;
|
|
141
|
+
const normalizedIpv4 = requested.hostname === "127.0.0.1" && returned.hostname === "localhost" && returned.port === "";
|
|
142
|
+
if (!sameHost && !normalizedIpv4)
|
|
143
|
+
return false;
|
|
144
|
+
returned.hostname = requested.hostname;
|
|
145
|
+
returned.port = requested.port;
|
|
146
|
+
return returned.href === requested.href;
|
|
147
|
+
});
|
|
148
|
+
}
|