toolcraft 0.0.308 → 0.0.309
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 +13 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +5 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/loopback-authorization.js +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +5 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +5 -0
- package/node_modules/tiny-mcp-client/dist/index.js +46 -1
- 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.24",
|
|
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.309",
|
|
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.309",
|
|
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.24",
|
|
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.309",
|
|
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.309",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -41,6 +41,7 @@ const verifier = createJwksTokenVerifier({
|
|
|
41
41
|
- `mode: "dynamic"` with optional `metadata`
|
|
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
|
+
- `initialGrant: { resource, tokens }` optionally imports an existing Bearer grant for one HTTP resource; requires the original client ID
|
|
44
45
|
- `browser.openBrowser(url)` optional
|
|
45
46
|
- `browser.readLine()` optional
|
|
46
47
|
- `browser.createServer()` optional
|
|
@@ -69,7 +70,10 @@ const verifier = createJwksTokenVerifier({
|
|
|
69
70
|
Fixed redirects support `localhost`, `127.0.0.1`, and `::1` over HTTP. Their
|
|
70
71
|
exact spelling, port, path and query are preserved through registration,
|
|
71
72
|
authorization and code exchange. Credentials, fragments, port zero and reserved
|
|
72
|
-
OAuth callback query parameters are rejected before binding a listener.
|
|
73
|
+
OAuth callback query parameters are rejected before binding a listener.
|
|
74
|
+
`createDefaultOAuthClientProvider` also checks the configured redirect before
|
|
75
|
+
creating the provider. Imported tokens that cannot be sent as HTTP header
|
|
76
|
+
values fail with diagnostics that omit their contents. Omit
|
|
73
77
|
`redirectUri` to allocate a random loopback port. Standalone callback sessions
|
|
74
78
|
accept the same `redirectUri`, `signal` and `timeoutMs` options. Cancellation,
|
|
75
79
|
timeout and explicit close settle pending code waits and release listeners.
|
|
@@ -83,6 +87,14 @@ observe the supplied signal and pass it to any work they start.
|
|
|
83
87
|
Configure `client.metadata.scope` to request a precise scope set; broader
|
|
84
88
|
discovery metadata does not override it.
|
|
85
89
|
|
|
90
|
+
Imported `initialGrant.tokens` use `accessToken`, optional `refreshToken`,
|
|
91
|
+
`tokenType: "Bearer"`, `expiresAt` (Unix epoch milliseconds or `null` if unknown),
|
|
92
|
+
and optional `scope`. A fresh imported token is used only for its resource.
|
|
93
|
+
Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
94
|
+
using the original configured client. Persisted sessions take precedence,
|
|
95
|
+
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
96
|
+
Input tokens are copied and invalid expiry values fail before authorization.
|
|
97
|
+
|
|
86
98
|
`createAuthStoreSessionStore(options)` accepts the standard `auth-store` config.
|
|
87
99
|
|
|
88
100
|
## Environment Variables
|
|
@@ -2,7 +2,7 @@ import { isIP } from "node:net";
|
|
|
2
2
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
4
|
import { createAuthStoreClientStore, createAuthStoreSessionStore } from "./auth-store-session-store.js";
|
|
5
|
-
import { createLoopbackAuthorizationSession } from "./loopback-authorization.js";
|
|
5
|
+
import { createLoopbackAuthorizationSession, loopbackTarget } from "./loopback-authorization.js";
|
|
6
6
|
import { createAuthorizationState } from "./authorization-state.js";
|
|
7
7
|
import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
8
8
|
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse } from "./token-endpoint.js";
|
|
@@ -15,18 +15,51 @@ export function createOAuthClientProvider(options) {
|
|
|
15
15
|
return createDefaultOAuthClientProvider(options);
|
|
16
16
|
}
|
|
17
17
|
export function createDefaultOAuthClientProvider(options) {
|
|
18
|
+
loopbackTarget(options.browser);
|
|
18
19
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore);
|
|
19
20
|
const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore);
|
|
20
21
|
const now = options.now ?? Date.now;
|
|
21
22
|
const registeredClients = new Map();
|
|
22
23
|
const refreshPromises = new Map();
|
|
23
24
|
const authorizationPromises = new Map();
|
|
25
|
+
if (options.initialGrant !== undefined) {
|
|
26
|
+
let resource;
|
|
27
|
+
try {
|
|
28
|
+
resource = new URL(options.initialGrant.resource);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error("OAuth initial grant resource must be an absolute HTTP URL");
|
|
32
|
+
}
|
|
33
|
+
if ((resource.protocol !== "http:" && resource.protocol !== "https:") || resource.username || resource.password || resource.hash)
|
|
34
|
+
throw new Error("OAuth initial grant resource must be an HTTP URL without credentials or fragments");
|
|
35
|
+
}
|
|
36
|
+
const initialGrant = options.initialGrant === undefined ? undefined : {
|
|
37
|
+
resource: canonicalizeResourceIndicator(options.initialGrant.resource),
|
|
38
|
+
tokens: normalizeStoredTokens(options.initialGrant.tokens),
|
|
39
|
+
client: normalizeConfiguredClient(options.client)
|
|
40
|
+
};
|
|
41
|
+
if (initialGrant !== undefined && (initialGrant.tokens === undefined || initialGrant.client === null))
|
|
42
|
+
throw new Error("OAuth initial grant requires valid tokens and the original client ID");
|
|
43
|
+
if (initialGrant?.tokens !== undefined) {
|
|
44
|
+
try {
|
|
45
|
+
new Headers({ Authorization: `Bearer ${initialGrant.tokens.accessToken}` });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error("OAuth initial grant access token is not a valid HTTP header value");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let initialGrantConsumed = false;
|
|
24
52
|
return {
|
|
25
53
|
async authorizeRequest(input) {
|
|
26
54
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
27
55
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
28
56
|
const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false, false, input.signal);
|
|
29
57
|
const accessToken = session?.tokens?.accessToken;
|
|
58
|
+
if (session === null && !initialGrantConsumed && initialGrant?.resource === requestUrl &&
|
|
59
|
+
initialGrant.tokens !== undefined && !isExpired(initialGrant.tokens, now)) {
|
|
60
|
+
input.headers.set("Authorization", `Bearer ${initialGrant.tokens.accessToken}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
30
63
|
if (session === null ||
|
|
31
64
|
accessToken === undefined ||
|
|
32
65
|
session.tokens === undefined ||
|
|
@@ -42,7 +75,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
42
75
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
43
76
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
44
77
|
assertRequestMatchesResource(requestUrl, resource);
|
|
45
|
-
const forceRefresh = hasCachedAccessToken(await loadSession(resource)) &&
|
|
78
|
+
const forceRefresh = (hasCachedAccessToken(await loadSession(resource)) || (!initialGrantConsumed && initialGrant?.resource === resource)) &&
|
|
46
79
|
input.challenge?.params.error === "invalid_token";
|
|
47
80
|
const session = await ensureAuthorizedSession(resource, {
|
|
48
81
|
...input.discovery,
|
|
@@ -66,6 +99,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
66
99
|
signal?.throwIfAborted();
|
|
67
100
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
68
101
|
let session = await loadSession(canonicalResource);
|
|
102
|
+
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
103
|
+
initialGrantConsumed = true;
|
|
69
104
|
signal?.throwIfAborted();
|
|
70
105
|
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
71
106
|
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
@@ -76,6 +111,15 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
76
111
|
await clearSession(canonicalResource);
|
|
77
112
|
session = null;
|
|
78
113
|
}
|
|
114
|
+
if (session === null && discovery !== undefined && !initialGrantConsumed && initialGrant?.resource === canonicalResource &&
|
|
115
|
+
initialGrant.tokens !== undefined && initialGrant.client !== null) {
|
|
116
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
117
|
+
session = { resource: canonicalResource, authorizationServer: discovery.authorizationServer,
|
|
118
|
+
client: initialGrant.client, tokens: initialGrant.tokens, discovery: toStoredDiscovery(discovery) };
|
|
119
|
+
await saveSession(canonicalResource, session);
|
|
120
|
+
initialGrantConsumed = true;
|
|
121
|
+
signal?.throwIfAborted();
|
|
122
|
+
}
|
|
79
123
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
80
124
|
if (session?.tokens !== undefined && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
81
125
|
return session;
|
|
@@ -21,5 +21,10 @@ export interface LoopbackAuthorizationSession {
|
|
|
21
21
|
close(): void;
|
|
22
22
|
}
|
|
23
23
|
export declare function createLoopbackAuthorizationSession(options?: LoopbackAuthorizationOptions): Promise<LoopbackAuthorizationSession>;
|
|
24
|
+
export declare function loopbackTarget(options: LoopbackAuthorizationOptions): {
|
|
25
|
+
port: number;
|
|
26
|
+
host: string;
|
|
27
|
+
callbackPath: string;
|
|
28
|
+
};
|
|
24
29
|
export declare function extractCodeFromInput(input: string): string | null;
|
|
25
30
|
export declare function buildSuccessPage(landingPage?: OAuthLandingPage): string;
|
|
@@ -99,6 +99,11 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
99
99
|
};
|
|
100
100
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
101
101
|
allowInteractive?: boolean;
|
|
102
|
+
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
103
|
+
initialGrant?: {
|
|
104
|
+
resource: string;
|
|
105
|
+
tokens: StoredOAuthTokens;
|
|
106
|
+
};
|
|
102
107
|
browser: {
|
|
103
108
|
openBrowser?(url: string): Promise<void>;
|
|
104
109
|
/** Exact registered HTTP loopback redirect URI. */
|
|
@@ -176,6 +176,11 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
176
176
|
};
|
|
177
177
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
178
178
|
allowInteractive?: boolean;
|
|
179
|
+
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
180
|
+
initialGrant?: {
|
|
181
|
+
resource: string;
|
|
182
|
+
tokens: StoredOAuthTokens;
|
|
183
|
+
};
|
|
179
184
|
browser: {
|
|
180
185
|
openBrowser?(url: string): Promise<void>;
|
|
181
186
|
/** Exact registered HTTP loopback redirect URI. */
|
|
@@ -4594,18 +4594,48 @@ function createOAuthClientProvider(options) {
|
|
|
4594
4594
|
return createDefaultOAuthClientProvider(options);
|
|
4595
4595
|
}
|
|
4596
4596
|
function createDefaultOAuthClientProvider(options) {
|
|
4597
|
+
loopbackTarget(options.browser);
|
|
4597
4598
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore);
|
|
4598
4599
|
const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore);
|
|
4599
4600
|
const now = options.now ?? Date.now;
|
|
4600
4601
|
const registeredClients = /* @__PURE__ */ new Map();
|
|
4601
4602
|
const refreshPromises = /* @__PURE__ */ new Map();
|
|
4602
4603
|
const authorizationPromises = /* @__PURE__ */ new Map();
|
|
4604
|
+
if (options.initialGrant !== void 0) {
|
|
4605
|
+
let resource;
|
|
4606
|
+
try {
|
|
4607
|
+
resource = new URL2(options.initialGrant.resource);
|
|
4608
|
+
} catch {
|
|
4609
|
+
throw new Error("OAuth initial grant resource must be an absolute HTTP URL");
|
|
4610
|
+
}
|
|
4611
|
+
if (resource.protocol !== "http:" && resource.protocol !== "https:" || resource.username || resource.password || resource.hash)
|
|
4612
|
+
throw new Error("OAuth initial grant resource must be an HTTP URL without credentials or fragments");
|
|
4613
|
+
}
|
|
4614
|
+
const initialGrant = options.initialGrant === void 0 ? void 0 : {
|
|
4615
|
+
resource: canonicalizeResourceIndicator(options.initialGrant.resource),
|
|
4616
|
+
tokens: normalizeStoredTokens(options.initialGrant.tokens),
|
|
4617
|
+
client: normalizeConfiguredClient(options.client)
|
|
4618
|
+
};
|
|
4619
|
+
if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
|
|
4620
|
+
throw new Error("OAuth initial grant requires valid tokens and the original client ID");
|
|
4621
|
+
if (initialGrant?.tokens !== void 0) {
|
|
4622
|
+
try {
|
|
4623
|
+
new Headers({ Authorization: `Bearer ${initialGrant.tokens.accessToken}` });
|
|
4624
|
+
} catch {
|
|
4625
|
+
throw new Error("OAuth initial grant access token is not a valid HTTP header value");
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
let initialGrantConsumed = false;
|
|
4603
4629
|
return {
|
|
4604
4630
|
async authorizeRequest(input) {
|
|
4605
4631
|
assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
|
|
4606
4632
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
4607
4633
|
const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
|
|
4608
4634
|
const accessToken = session?.tokens?.accessToken;
|
|
4635
|
+
if (session === null && !initialGrantConsumed && initialGrant?.resource === requestUrl && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now)) {
|
|
4636
|
+
input.headers.set("Authorization", `Bearer ${initialGrant.tokens.accessToken}`);
|
|
4637
|
+
return;
|
|
4638
|
+
}
|
|
4609
4639
|
if (session === null || accessToken === void 0 || session.tokens === void 0 || isExpired(session.tokens, now)) {
|
|
4610
4640
|
return;
|
|
4611
4641
|
}
|
|
@@ -4618,7 +4648,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4618
4648
|
const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
|
|
4619
4649
|
const resource = canonicalizeResourceIndicator(input.discovery.resource);
|
|
4620
4650
|
assertRequestMatchesResource(requestUrl, resource);
|
|
4621
|
-
const forceRefresh = hasCachedAccessToken(await loadSession(resource)) && input.challenge?.params.error === "invalid_token";
|
|
4651
|
+
const forceRefresh = (hasCachedAccessToken(await loadSession(resource)) || !initialGrantConsumed && initialGrant?.resource === resource) && input.challenge?.params.error === "invalid_token";
|
|
4622
4652
|
const session = await ensureAuthorizedSession(resource, {
|
|
4623
4653
|
...input.discovery,
|
|
4624
4654
|
resource
|
|
@@ -4640,6 +4670,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4640
4670
|
signal?.throwIfAborted();
|
|
4641
4671
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
4642
4672
|
let session = await loadSession(canonicalResource);
|
|
4673
|
+
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
4674
|
+
initialGrantConsumed = true;
|
|
4643
4675
|
signal?.throwIfAborted();
|
|
4644
4676
|
if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
4645
4677
|
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
@@ -4648,6 +4680,19 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4648
4680
|
await clearSession(canonicalResource);
|
|
4649
4681
|
session = null;
|
|
4650
4682
|
}
|
|
4683
|
+
if (session === null && discovery !== void 0 && !initialGrantConsumed && initialGrant?.resource === canonicalResource && initialGrant.tokens !== void 0 && initialGrant.client !== null) {
|
|
4684
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
4685
|
+
session = {
|
|
4686
|
+
resource: canonicalResource,
|
|
4687
|
+
authorizationServer: discovery.authorizationServer,
|
|
4688
|
+
client: initialGrant.client,
|
|
4689
|
+
tokens: initialGrant.tokens,
|
|
4690
|
+
discovery: toStoredDiscovery(discovery)
|
|
4691
|
+
};
|
|
4692
|
+
await saveSession(canonicalResource, session);
|
|
4693
|
+
initialGrantConsumed = true;
|
|
4694
|
+
signal?.throwIfAborted();
|
|
4695
|
+
}
|
|
4651
4696
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
4652
4697
|
if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
4653
4698
|
return session;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.309",
|
|
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.309",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|