toolcraft 0.0.316 → 0.0.318

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 CHANGED
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.36",
101
+ "version": "0.1.39",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.316",
116
+ "version": "0.0.318",
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.316",
126
+ "version": "0.0.318",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.36",
101
+ "version": "0.1.39",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.316",
116
+ "version": "0.0.318",
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.316",
126
+ "version": "0.0.318",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "name": "tiny-http-mcp-server",
21
- "version": "0.1.36",
21
+ "version": "0.1.39",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -102,6 +102,15 @@ normalized. An imported grant must declare its scope when a scope is configured.
102
102
  Authorization records the requested set when the endpoint omits scope, and
103
103
  refresh retains the previous granted set. Mismatched responses never activate
104
104
  credentials; an unusable refresh response retains the pending refresh record.
105
+ `resourceIdentity` selects a native-owned logical server within its optional
106
+ persistence namespace. One encrypted, locked document owns that identity's
107
+ current resource URL, sessions and registrations. Changing the URL retires its
108
+ credentials permanently; returning to the old URL does not restore them.
109
+ Retired identities also withhold stale initial grants; authorize again or select
110
+ a fresh explicit profile to import a new grant. This option requires native persistence; custom
111
+ session stores own their durable resource trust policy. Without it, the native
112
+ client retains its existing resource-URL cache behavior.
113
+
105
114
  Select a separate persistence namespace for another scope profile. No scope is
106
115
  invented when the client does not configure one.
107
116
 
@@ -1,5 +1,5 @@
1
- import { type CreateSecretStoreInput } from "auth-store";
2
- import type { OAuthSessionStore, StoredOAuthClient } from "./types.js";
1
+ import { type CreateSecretStoreInput, type SecretStore } from "auth-store";
2
+ import type { OAuthSessionStore, StoredOAuthSession, StoredOAuthClient } from "./types.js";
3
3
  export interface OAuthClientStore {
4
4
  load(issuer: string): Promise<StoredOAuthClient | null>;
5
5
  save(issuer: string, client: StoredOAuthClient): Promise<void>;
@@ -7,4 +7,11 @@ export interface OAuthClientStore {
7
7
  }
8
8
  export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput, namespace?: string): OAuthSessionStore;
9
9
  export declare function createAuthStoreClientStore(options: CreateSecretStoreInput, namespace?: string): OAuthClientStore;
10
+ export declare function createNamedSecretStore(key: string, options: CreateSecretStoreInput, defaults: {
11
+ salt: string;
12
+ directory: string;
13
+ service: string;
14
+ accountPrefix: string;
15
+ }, namespace?: string): SecretStore;
10
16
  export declare function assertPersistenceNamespace(namespace: string | undefined): void;
17
+ export declare function isStoredOAuthSession(value: unknown): value is StoredOAuthSession;
@@ -79,7 +79,7 @@ export function createAuthStoreClientStore(options, namespace) {
79
79
  }
80
80
  };
81
81
  }
82
- function createNamedSecretStore(key, options, defaults, namespace) {
82
+ export function createNamedSecretStore(key, options, defaults, namespace) {
83
83
  const hash = crypto.createHash("sha256").update(namespace === undefined ? key : JSON.stringify([namespace, key])).digest("hex");
84
84
  const configuredFilePath = options.fileStore?.filePath;
85
85
  const parsedFilePath = configuredFilePath === undefined ? null : path.parse(configuredFilePath);
@@ -132,7 +132,7 @@ function getOwnString(record, key) {
132
132
  const value = getOwnEntry(record, key);
133
133
  return typeof value === "string" ? value : undefined;
134
134
  }
135
- function isStoredOAuthSession(value) {
135
+ export function isStoredOAuthSession(value) {
136
136
  if (!isObjectRecord(value)) {
137
137
  return false;
138
138
  }
@@ -1,3 +1,4 @@
1
+ import { createResourceBoundOAuthStores } from "./resource-bound-store.js";
1
2
  import { normalizeStoredOAuthClient, parseOAuthClientRegistration, registrationMatchesRedirect } from "./client-registration.js";
2
3
  import { normalizeOAuthScope } from "./scope.js";
3
4
  import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
@@ -26,8 +27,13 @@ export function createDefaultOAuthClientProvider(options) {
26
27
  const configuredClient = normalizeConfiguredClient(options.client);
27
28
  const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
28
29
  const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
29
- const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
30
- const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
30
+ if (options.resourceIdentity !== undefined && options.sessionStore !== undefined)
31
+ throw new Error("OAuth resourceIdentity requires native-owned persistence; custom stores own their resource trust policy");
32
+ const resourceStores = options.resourceIdentity === undefined ? undefined :
33
+ createResourceBoundOAuthStores(options.authStore ?? {}, options.persistenceNamespace, options.resourceIdentity);
34
+ const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
35
+ const clientStore = resourceStores?.clientStore ??
36
+ (options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
31
37
  const now = options.now ?? Date.now;
32
38
  const registeredClients = new Map();
33
39
  if (options.initialGrant !== undefined) {
@@ -127,6 +133,11 @@ export function createDefaultOAuthClientProvider(options) {
127
133
  const canonicalResource = canonicalizeResourceIndicator(resource);
128
134
  return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
129
135
  let session = await loadSession(canonicalResource);
136
+ if (resourceStores !== undefined) {
137
+ registeredClients.clear();
138
+ if (!resourceStores.initialGrantAllowed)
139
+ initialGrantConsumed = true;
140
+ }
130
141
  if (session !== null)
131
142
  assertRegistrationIssuer(session.client, session.authorizationServer);
132
143
  if (configuredClient !== null && discovery !== undefined)
@@ -0,0 +1,9 @@
1
+ import type { CreateSecretStoreInput } from "auth-store";
2
+ import { type OAuthClientStore } from "./auth-store-session-store.js";
3
+ import type { OAuthSessionStore } from "./types.js";
4
+ /** 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
+ };
@@ -0,0 +1,118 @@
1
+ import { canonicalizeResourceIndicator } from "../resource-indicator.js";
2
+ import { assertPersistenceNamespace, createNamedSecretStore, isStoredOAuthSession } from "./auth-store-session-store.js";
3
+ import { normalizeStoredOAuthClient } from "./client-registration.js";
4
+ /** One locked document owns a logical server's URL history, grant and clients. */
5
+ export function createResourceBoundOAuthStores(options, namespace, identity) {
6
+ assertPersistenceNamespace(identity);
7
+ const store = createNamedSecretStore(identity, options, { salt: "poe-code:mcp-oauth:resources:v1",
8
+ directory: ".poe-code/mcp-oauth/resources", service: "poe-code-mcp-oauth-resources", accountPrefix: "resource" }, namespace);
9
+ const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {} };
10
+ async function read() {
11
+ const raw = await store.get();
12
+ if (raw === null)
13
+ return null;
14
+ let value;
15
+ try {
16
+ value = JSON.parse(raw);
17
+ }
18
+ catch {
19
+ throw new Error("Stored OAuth resource identity must be valid JSON; reset explicitly to recover");
20
+ }
21
+ if (typeof value !== "object" || value === null || Array.isArray(value))
22
+ throw new Error("Invalid stored OAuth resource identity");
23
+ const record = value;
24
+ if (!["version", "resource", "generation", "session", "clients"].every(key => Object.hasOwn(record, key)) || record.version !== 1 || typeof record.resource !== "string" || record.resource !== canonicalizeResourceIndicator(record.resource) ||
25
+ !Number.isSafeInteger(record.generation) || record.generation < 0 ||
26
+ (record.session !== null && (!isStoredOAuthSession(record.session) || record.session.resource !== record.resource)) ||
27
+ typeof record.clients !== "object" || record.clients === null || Array.isArray(record.clients))
28
+ throw new Error("Invalid stored OAuth resource identity; reset explicitly to recover");
29
+ let resourceUrl;
30
+ try {
31
+ resourceUrl = new URL(record.resource);
32
+ }
33
+ catch {
34
+ throw new Error("Invalid stored OAuth resource identity URL");
35
+ }
36
+ if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.hash)
37
+ throw new Error("Invalid stored OAuth resource identity URL");
38
+ for (const [issuer, client] of Object.entries(record.clients)) {
39
+ const normalized = normalizeStoredOAuthClient(client);
40
+ let url;
41
+ try {
42
+ url = new URL(issuer);
43
+ }
44
+ catch {
45
+ throw new Error("Invalid stored OAuth resource client issuer");
46
+ }
47
+ if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
48
+ throw new Error("Invalid stored OAuth resource client");
49
+ Object.defineProperty(record.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
50
+ }
51
+ return record;
52
+ }
53
+ async function reconcile(resource) {
54
+ resource = canonicalizeResourceIndicator(resource);
55
+ const existing = await read();
56
+ if (existing?.resource === resource) {
57
+ result.initialGrantAllowed = existing.generation === 0;
58
+ return existing;
59
+ }
60
+ const record = { version: 1, resource, generation: existing === null ? 0 : existing.generation + 1, session: null, clients: {} };
61
+ if (!Number.isSafeInteger(record.generation))
62
+ throw new Error("OAuth resource identity generation limit exceeded");
63
+ await store.set(JSON.stringify(record));
64
+ result.initialGrantAllowed = record.generation === 0;
65
+ return record;
66
+ }
67
+ result.sessionStore = {
68
+ async withLock(resource, operation, options) {
69
+ if (store.withLock === undefined)
70
+ throw new Error("OAuth resource identity backend must support transaction locks");
71
+ return store.withLock(async () => {
72
+ options.signal?.throwIfAborted();
73
+ await reconcile(resource);
74
+ options.signal?.throwIfAborted();
75
+ return operation();
76
+ }, options);
77
+ },
78
+ async load(resource) {
79
+ // Unauthorized provenance may peek before acquiring the transaction lock.
80
+ // Only the lock owner may adopt a URL or retire the previous credentials.
81
+ const record = await read();
82
+ return record?.resource === canonicalizeResourceIndicator(resource) ? record.session : null;
83
+ },
84
+ async save(resource, session) {
85
+ const record = await reconcile(resource);
86
+ if (canonicalizeResourceIndicator(session.resource) !== record.resource)
87
+ throw new Error("OAuth session does not match its resource identity");
88
+ record.session = session;
89
+ await store.set(JSON.stringify(record));
90
+ },
91
+ async clear(resource) {
92
+ const record = await reconcile(resource);
93
+ record.session = null;
94
+ // Preserve a tombstone so environment grants cannot revive a cleared family.
95
+ record.generation = Math.max(1, record.generation);
96
+ result.initialGrantAllowed = false;
97
+ await store.set(JSON.stringify(record));
98
+ }
99
+ };
100
+ result.clientStore = {
101
+ async load(issuer) { const record = await read(); return record !== null && Object.hasOwn(record.clients, issuer) ? record.clients[issuer] : null; },
102
+ async save(issuer, client) {
103
+ const record = await read();
104
+ if (record === null)
105
+ throw new Error("OAuth resource identity must be bound before registering a client");
106
+ Object.defineProperty(record.clients, issuer, { value: client, enumerable: true, configurable: true, writable: true });
107
+ await store.set(JSON.stringify(record));
108
+ },
109
+ async clear(issuer) {
110
+ const record = await read();
111
+ if (record === null || !Object.hasOwn(record.clients, issuer))
112
+ return;
113
+ delete record.clients[issuer];
114
+ await store.set(JSON.stringify(record));
115
+ }
116
+ };
117
+ return result;
118
+ }
@@ -147,6 +147,8 @@ export interface DefaultOAuthClientProviderOptions {
147
147
  sessionLockTimeoutMs?: number;
148
148
  /** Isolate native persisted sessions and registrations for a named profile. */
149
149
  persistenceNamespace?: string;
150
+ /** Native-owned durable logical server identity; changing URL retires its credentials. */
151
+ resourceIdentity?: string;
150
152
  /** Import an existing grant for one resource. Persisted sessions take precedence. */
151
153
  initialGrant?: {
152
154
  resource: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.36",
3
+ "version": "0.1.39",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,7 +37,10 @@ by the server. Announced endpoints must remain on the original origin without
37
37
  embedded credentials or fragments; endpoint changes close the connection.
38
38
  Both transports accept the same headers, OAuth provider and response limits.
39
39
  HTTP failures expose `HttpTransportError.status` and `.method`, so callers can
40
- make transport decisions without parsing error messages.
40
+ make transport decisions without parsing error messages. Legacy HTTP/SSE
41
+ `connect()` also waits for the initialized notification POST to complete before
42
+ reporting ready. Completion failures reject the connection and expose
43
+ `rpcMethod: "notifications/initialized"`; they are not setup transport mismatches.
41
44
 
42
45
  ## OAuth HTTP support
43
46
 
@@ -250,6 +250,8 @@ interface DefaultOAuthClientProviderOptions {
250
250
  sessionLockTimeoutMs?: number;
251
251
  /** Isolate native persisted sessions and registrations for a named profile. */
252
252
  persistenceNamespace?: string;
253
+ /** Native-owned durable logical server identity; changing URL retires its credentials. */
254
+ resourceIdentity?: string;
253
255
  /** Import an existing grant for one resource. Persisted sessions take precedence. */
254
256
  initialGrant?: {
255
257
  resource: string;
@@ -639,6 +641,11 @@ interface McpTransport {
639
641
  closed: Promise<McpTransportClosedEvent>;
640
642
  dispose(reason?: Error): void;
641
643
  filterTools?(tools: Tool[], reset?: boolean): Tool[];
644
+ /** Complete a legacy initialization handshake before the client reports ready. */
645
+ completeInitialization?(options: {
646
+ signal?: AbortSignal;
647
+ timeoutMs: number;
648
+ }): Promise<void>;
642
649
  }
643
650
  interface InMemoryServerTransport {
644
651
  readable: Readable;
@@ -674,7 +681,8 @@ type HttpTransportFetch = (input: string | URL, init?: RequestInit) => Promise<R
674
681
  declare class HttpTransportError extends Error {
675
682
  readonly status: number;
676
683
  readonly method: "GET" | "POST" | "DELETE";
677
- constructor(message: string, status: number, method: "GET" | "POST" | "DELETE");
684
+ readonly rpcMethod?: string | undefined;
685
+ constructor(message: string, status: number, method: "GET" | "POST" | "DELETE", rpcMethod?: string | undefined);
678
686
  }
679
687
  interface HttpTransportOptions {
680
688
  url: string;
@@ -731,6 +739,10 @@ declare class HttpTransport implements McpTransport {
731
739
  private readonly toolParameterHeaders;
732
740
  private readonly onWarning;
733
741
  constructor({ url, mode, headers, fetch: fetchImpl, oauth, oauthDiscoveryCache, onWarning, maxResponseBytes, }: HttpTransportOptions);
742
+ completeInitialization(options: {
743
+ signal?: AbortSignal;
744
+ timeoutMs: number;
745
+ }): Promise<void>;
734
746
  filterTools(tools: Tool[], reset?: boolean): Tool[];
735
747
  dispose(reason?: Error): void;
736
748
  private closeWithSessionTermination;
@@ -4709,6 +4709,120 @@ function isNonBlankOwnString(record2, key2) {
4709
4709
  return value !== void 0 && value.trim().length > 0;
4710
4710
  }
4711
4711
 
4712
+ // ../mcp-oauth/dist/client/resource-bound-store.js
4713
+ function createResourceBoundOAuthStores(options, namespace, identity) {
4714
+ assertPersistenceNamespace(identity);
4715
+ const store = createNamedSecretStore(identity, options, {
4716
+ salt: "poe-code:mcp-oauth:resources:v1",
4717
+ directory: ".poe-code/mcp-oauth/resources",
4718
+ service: "poe-code-mcp-oauth-resources",
4719
+ accountPrefix: "resource"
4720
+ }, namespace);
4721
+ const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {} };
4722
+ async function read() {
4723
+ const raw = await store.get();
4724
+ if (raw === null)
4725
+ return null;
4726
+ let value;
4727
+ try {
4728
+ value = JSON.parse(raw);
4729
+ } catch {
4730
+ throw new Error("Stored OAuth resource identity must be valid JSON; reset explicitly to recover");
4731
+ }
4732
+ if (typeof value !== "object" || value === null || Array.isArray(value))
4733
+ throw new Error("Invalid stored OAuth resource identity");
4734
+ const record2 = value;
4735
+ if (!["version", "resource", "generation", "session", "clients"].every((key2) => Object.hasOwn(record2, key2)) || record2.version !== 1 || typeof record2.resource !== "string" || record2.resource !== canonicalizeResourceIndicator(record2.resource) || !Number.isSafeInteger(record2.generation) || record2.generation < 0 || record2.session !== null && (!isStoredOAuthSession(record2.session) || record2.session.resource !== record2.resource) || typeof record2.clients !== "object" || record2.clients === null || Array.isArray(record2.clients))
4736
+ throw new Error("Invalid stored OAuth resource identity; reset explicitly to recover");
4737
+ let resourceUrl;
4738
+ try {
4739
+ resourceUrl = new URL(record2.resource);
4740
+ } catch {
4741
+ throw new Error("Invalid stored OAuth resource identity URL");
4742
+ }
4743
+ if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.hash)
4744
+ throw new Error("Invalid stored OAuth resource identity URL");
4745
+ for (const [issuer, client] of Object.entries(record2.clients)) {
4746
+ const normalized = normalizeStoredOAuthClient(client);
4747
+ let url;
4748
+ try {
4749
+ url = new URL(issuer);
4750
+ } catch {
4751
+ throw new Error("Invalid stored OAuth resource client issuer");
4752
+ }
4753
+ if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
4754
+ throw new Error("Invalid stored OAuth resource client");
4755
+ Object.defineProperty(record2.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
4756
+ }
4757
+ return record2;
4758
+ }
4759
+ async function reconcile(resource) {
4760
+ resource = canonicalizeResourceIndicator(resource);
4761
+ const existing = await read();
4762
+ if (existing?.resource === resource) {
4763
+ result.initialGrantAllowed = existing.generation === 0;
4764
+ return existing;
4765
+ }
4766
+ const record2 = { version: 1, resource, generation: existing === null ? 0 : existing.generation + 1, session: null, clients: {} };
4767
+ if (!Number.isSafeInteger(record2.generation))
4768
+ throw new Error("OAuth resource identity generation limit exceeded");
4769
+ await store.set(JSON.stringify(record2));
4770
+ result.initialGrantAllowed = record2.generation === 0;
4771
+ return record2;
4772
+ }
4773
+ result.sessionStore = {
4774
+ async withLock(resource, operation, options2) {
4775
+ if (store.withLock === void 0)
4776
+ throw new Error("OAuth resource identity backend must support transaction locks");
4777
+ return store.withLock(async () => {
4778
+ options2.signal?.throwIfAborted();
4779
+ await reconcile(resource);
4780
+ options2.signal?.throwIfAborted();
4781
+ return operation();
4782
+ }, options2);
4783
+ },
4784
+ async load(resource) {
4785
+ const record2 = await read();
4786
+ return record2?.resource === canonicalizeResourceIndicator(resource) ? record2.session : null;
4787
+ },
4788
+ async save(resource, session) {
4789
+ const record2 = await reconcile(resource);
4790
+ if (canonicalizeResourceIndicator(session.resource) !== record2.resource)
4791
+ throw new Error("OAuth session does not match its resource identity");
4792
+ record2.session = session;
4793
+ await store.set(JSON.stringify(record2));
4794
+ },
4795
+ async clear(resource) {
4796
+ const record2 = await reconcile(resource);
4797
+ record2.session = null;
4798
+ record2.generation = Math.max(1, record2.generation);
4799
+ result.initialGrantAllowed = false;
4800
+ await store.set(JSON.stringify(record2));
4801
+ }
4802
+ };
4803
+ result.clientStore = {
4804
+ async load(issuer) {
4805
+ const record2 = await read();
4806
+ return record2 !== null && Object.hasOwn(record2.clients, issuer) ? record2.clients[issuer] : null;
4807
+ },
4808
+ async save(issuer, client) {
4809
+ const record2 = await read();
4810
+ if (record2 === null)
4811
+ throw new Error("OAuth resource identity must be bound before registering a client");
4812
+ Object.defineProperty(record2.clients, issuer, { value: client, enumerable: true, configurable: true, writable: true });
4813
+ await store.set(JSON.stringify(record2));
4814
+ },
4815
+ async clear(issuer) {
4816
+ const record2 = await read();
4817
+ if (record2 === null || !Object.hasOwn(record2.clients, issuer))
4818
+ return;
4819
+ delete record2.clients[issuer];
4820
+ await store.set(JSON.stringify(record2));
4821
+ }
4822
+ };
4823
+ return result;
4824
+ }
4825
+
4712
4826
  // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4713
4827
  import { isIP } from "node:net";
4714
4828
 
@@ -5023,8 +5137,11 @@ function createDefaultOAuthClientProvider(options) {
5023
5137
  const configuredClient = normalizeConfiguredClient(options.client);
5024
5138
  const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
5025
5139
  const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
5026
- const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
5027
- const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
5140
+ if (options.resourceIdentity !== void 0 && options.sessionStore !== void 0)
5141
+ throw new Error("OAuth resourceIdentity requires native-owned persistence; custom stores own their resource trust policy");
5142
+ const resourceStores = options.resourceIdentity === void 0 ? void 0 : createResourceBoundOAuthStores(options.authStore ?? {}, options.persistenceNamespace, options.resourceIdentity);
5143
+ const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
5144
+ const clientStore = resourceStores?.clientStore ?? (options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
5028
5145
  const now = options.now ?? Date.now;
5029
5146
  const registeredClients = /* @__PURE__ */ new Map();
5030
5147
  if (options.initialGrant !== void 0) {
@@ -5117,6 +5234,11 @@ function createDefaultOAuthClientProvider(options) {
5117
5234
  const canonicalResource = canonicalizeResourceIndicator(resource);
5118
5235
  return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
5119
5236
  let session = await loadSession(canonicalResource);
5237
+ if (resourceStores !== void 0) {
5238
+ registeredClients.clear();
5239
+ if (!resourceStores.initialGrantAllowed)
5240
+ initialGrantConsumed = true;
5241
+ }
5120
5242
  if (session !== null)
5121
5243
  assertRegistrationIssuer(session.client, session.authorizationServer);
5122
5244
  if (configuredClient !== null && discovery !== void 0)
@@ -6666,7 +6788,8 @@ var McpClient = class {
6666
6788
  async (params, context) => this.options.onElicitationRequest(params, context)
6667
6789
  );
6668
6790
  }
6669
- messageLayer.sendNotification("notifications/initialized");
6791
+ if (transport.completeInitialization === void 0) messageLayer.sendNotification("notifications/initialized");
6792
+ else await transport.completeInitialization({ signal: options.signal, timeoutMs: this.options.requestTimeoutMs ?? 3e4 });
6670
6793
  this.currentState = "ready";
6671
6794
  return initializeResult;
6672
6795
  } catch (error) {
@@ -7166,14 +7289,16 @@ async function createTestPair(server, createClient) {
7166
7289
  return { client, cleanup };
7167
7290
  }
7168
7291
  var HttpTransportError = class extends Error {
7169
- constructor(message, status, method) {
7292
+ constructor(message, status, method, rpcMethod) {
7170
7293
  super(message);
7171
7294
  this.status = status;
7172
7295
  this.method = method;
7296
+ this.rpcMethod = rpcMethod;
7173
7297
  this.name = "HttpTransportError";
7174
7298
  }
7175
7299
  status;
7176
7300
  method;
7301
+ rpcMethod;
7177
7302
  };
7178
7303
  function defaultStdioSpawn(command, args, options) {
7179
7304
  return spawn2(command, args, options);
@@ -7348,6 +7473,21 @@ var HttpTransport = class {
7348
7473
  this.dispose(error instanceof Error ? error : new Error(String(error)));
7349
7474
  });
7350
7475
  }
7476
+ async completeInitialization(options) {
7477
+ const deadline = options.timeoutMs > 0 ? AbortSignal.timeout(Math.ceil(options.timeoutMs)) : void 0;
7478
+ const signals = [options.signal, deadline].filter((signal2) => signal2 !== void 0);
7479
+ const signal = signals.length === 0 ? new AbortController().signal : AbortSignal.any(signals);
7480
+ signal.throwIfAborted();
7481
+ try {
7482
+ await this.sendPost(serializeJsonRpcMessage({ jsonrpc: "2.0", method: "notifications/initialized" }), signal);
7483
+ signal.throwIfAborted();
7484
+ if (this.disposed) throw (await this.closed).reason;
7485
+ } catch (error) {
7486
+ if (error instanceof HttpTransportError)
7487
+ throw new HttpTransportError(error.message, error.status, error.method, "notifications/initialized");
7488
+ throw error;
7489
+ }
7490
+ }
7351
7491
  filterTools(tools, reset = true) {
7352
7492
  if (reset) this.toolParameterHeaders.clear();
7353
7493
  const accepted = [];
@@ -7459,7 +7599,8 @@ var HttpTransport = class {
7459
7599
  }
7460
7600
  }
7461
7601
  }
7462
- async sendPost(line) {
7602
+ async sendPost(line, signal) {
7603
+ signal?.throwIfAborted();
7463
7604
  const parsed = parseJsonRpcMessage(line);
7464
7605
  const message = parsed.type === "request" || parsed.type === "notification" ? parsed.message : void 0;
7465
7606
  const metadata = isObjectRecord5(message?.params) ? message.params._meta : void 0;
@@ -7473,7 +7614,10 @@ var HttpTransport = class {
7473
7614
  this.modernRequests.get(requestId)?.abort();
7474
7615
  return;
7475
7616
  }
7476
- const controller = modern && parsed.type === "request" ? new AbortController() : void 0;
7617
+ const controller = modern && parsed.type === "request" || signal !== void 0 ? new AbortController() : void 0;
7618
+ const aborted = () => controller?.abort(signal?.reason);
7619
+ signal?.addEventListener("abort", aborted, { once: true });
7620
+ if (signal?.aborted) aborted();
7477
7621
  const id = parsed.type === "request" ? parsed.message.id : void 0;
7478
7622
  if (controller !== void 0 && id !== void 0) this.modernRequests.set(id, controller);
7479
7623
  try {
@@ -7482,7 +7626,7 @@ var HttpTransport = class {
7482
7626
  const response = await this.fetchWithOAuthRetry({
7483
7627
  url: postUrl,
7484
7628
  method: "POST",
7485
- createHeaders: (signal) => this.createPostHeaders(message, modern, signal),
7629
+ createHeaders: (signal2) => this.createPostHeaders(message, modern, signal2),
7486
7630
  body: line,
7487
7631
  controller
7488
7632
  });
@@ -7518,6 +7662,7 @@ var HttpTransport = class {
7518
7662
  } catch (error) {
7519
7663
  if (!controller?.signal.aborted) throw error;
7520
7664
  } finally {
7665
+ signal?.removeEventListener("abort", aborted);
7521
7666
  if (id !== void 0 && this.modernRequests.get(id) === controller)
7522
7667
  this.modernRequests.delete(id);
7523
7668
  }
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.316",
11
+ "version": "0.0.318",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.316",
3
+ "version": "0.0.318",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.316",
3
+ "version": "0.0.318",
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.316",
161
+ "toolcraft-schema": "0.0.318",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",