tiny-http-mcp-server 0.1.64 → 0.1.65

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.
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "name": "tiny-http-mcp-server",
21
- "version": "0.1.64",
21
+ "version": "0.1.65",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -94,6 +94,13 @@ accept the same `redirectUri`, `signal` and `timeoutMs` options. Cancellation,
94
94
  timeout and explicit close settle pending code waits and release listeners.
95
95
  Always close a successful standalone session in `finally`.
96
96
 
97
+ Native provider calls capture request option handles before host work. Unauthorized
98
+ handling also owns complete discovery metadata, presented grant values, rejected
99
+ request headers and the selected challenge error before reading persistence.
100
+ A browser callback cannot redirect a later code exchange by changing caller
101
+ metadata. Explicit authentication owns metadata when lazy discovery returns;
102
+ the selected discovery method retains its original receiver and live host state.
103
+
97
104
  Provider request inputs accept an optional `signal`. It reaches callback waits,
98
105
  registration, token requests and bounded token-body reads. Cancellation retains
99
106
  its original reason and does not retry authorization. Native provider calls
@@ -73,6 +73,7 @@ export function createDefaultOAuthClientProvider(options) {
73
73
  let initialGrantConsumed = false;
74
74
  return {
75
75
  async authenticate(input) {
76
+ input = { ...input, ...(input.discover === undefined ? {} : { discover: input.discover.bind(input) }) };
76
77
  input.signal?.throwIfAborted();
77
78
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
78
79
  const resource = canonicalizeResourceIndicator(input.requestUrl);
@@ -84,7 +85,7 @@ export function createDefaultOAuthClientProvider(options) {
84
85
  return { ...initialGrant.tokens };
85
86
  if (input.discover === undefined)
86
87
  return;
87
- const discovery = await waitForOAuthOperation(input.discover(), input.signal);
88
+ const discovery = structuredClone(await waitForOAuthOperation(input.discover(), input.signal));
88
89
  input.signal?.throwIfAborted();
89
90
  assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
90
91
  session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
@@ -93,6 +94,7 @@ export function createDefaultOAuthClientProvider(options) {
93
94
  return { ...session.tokens };
94
95
  },
95
96
  async authorizeRequest(input) {
97
+ input = { ...input };
96
98
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
97
99
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
98
100
  const session = await ensureAuthorizedSession(requestUrl, undefined, input.fetch, false, false, input.signal);
@@ -113,8 +115,16 @@ export function createDefaultOAuthClientProvider(options) {
113
115
  return { ...session.tokens };
114
116
  },
115
117
  async handleUnauthorized(input) {
118
+ input = { ...input };
116
119
  try {
117
120
  input.signal?.throwIfAborted();
121
+ const presented = input.presentedTokens == null ? input.presentedTokens : normalizeStoredTokens(input.presentedTokens);
122
+ if (input.presentedTokens != null && presented === undefined)
123
+ throw new Error("OAuth rejected-request provenance does not match its authorization header");
124
+ const challengeError = input.challenge?.params.error;
125
+ input = { ...input, discovery: structuredClone(input.discovery),
126
+ ...(input.requestHeaders === undefined ? {} : { requestHeaders: new Headers(input.requestHeaders) }),
127
+ ...(presented === undefined ? {} : { presentedTokens: presented }) };
118
128
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
119
129
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
120
130
  const resource = canonicalizeResourceIndicator(input.discovery.resource);
@@ -126,16 +136,15 @@ export function createDefaultOAuthClientProvider(options) {
126
136
  if (input.presentedTokens !== undefined) {
127
137
  rejectedCurrentGrant = false;
128
138
  if (input.presentedTokens !== null) {
129
- const presented = normalizeStoredTokens(input.presentedTokens);
139
+ const presented = input.presentedTokens;
130
140
  const header = input.requestHeaders?.get("Authorization") ?? "";
131
141
  const separator = header.indexOf(" ");
132
- if (presented === undefined || header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented.accessToken)
142
+ if (header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented.accessToken)
133
143
  throw new Error("OAuth rejected-request provenance does not match its authorization header");
134
144
  presentedTokens = presented;
135
145
  rejectedCurrentGrant = currentTokens !== undefined && sameTokenGrant(currentTokens, presented);
136
146
  }
137
147
  }
138
- const challengeError = input.challenge?.params.error;
139
148
  const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || (input.presentedTokens !== undefined && challengeError === undefined));
140
149
  const session = await ensureAuthorizedSession(resource, {
141
150
  ...input.discovery,
@@ -5335,6 +5335,7 @@ function createDefaultOAuthClientProvider(options) {
5335
5335
  let initialGrantConsumed = false;
5336
5336
  return {
5337
5337
  async authenticate(input) {
5338
+ input = { ...input, ...input.discover === void 0 ? {} : { discover: input.discover.bind(input) } };
5338
5339
  input.signal?.throwIfAborted();
5339
5340
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
5340
5341
  const resource = canonicalizeResourceIndicator(input.requestUrl);
@@ -5345,7 +5346,7 @@ function createDefaultOAuthClientProvider(options) {
5345
5346
  return { ...initialGrant.tokens };
5346
5347
  if (input.discover === void 0)
5347
5348
  return;
5348
- const discovery = await waitForOAuthOperation(input.discover(), input.signal);
5349
+ const discovery = structuredClone(await waitForOAuthOperation(input.discover(), input.signal));
5349
5350
  input.signal?.throwIfAborted();
5350
5351
  assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
5351
5352
  session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
@@ -5354,6 +5355,7 @@ function createDefaultOAuthClientProvider(options) {
5354
5355
  return { ...session.tokens };
5355
5356
  },
5356
5357
  async authorizeRequest(input) {
5358
+ input = { ...input };
5357
5359
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
5358
5360
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
5359
5361
  const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
@@ -5370,8 +5372,19 @@ function createDefaultOAuthClientProvider(options) {
5370
5372
  return { ...session.tokens };
5371
5373
  },
5372
5374
  async handleUnauthorized(input) {
5375
+ input = { ...input };
5373
5376
  try {
5374
5377
  input.signal?.throwIfAborted();
5378
+ const presented = input.presentedTokens == null ? input.presentedTokens : normalizeStoredTokens(input.presentedTokens);
5379
+ if (input.presentedTokens != null && presented === void 0)
5380
+ throw new Error("OAuth rejected-request provenance does not match its authorization header");
5381
+ const challengeError = input.challenge?.params.error;
5382
+ input = {
5383
+ ...input,
5384
+ discovery: structuredClone(input.discovery),
5385
+ ...input.requestHeaders === void 0 ? {} : { requestHeaders: new Headers(input.requestHeaders) },
5386
+ ...presented === void 0 ? {} : { presentedTokens: presented }
5387
+ };
5375
5388
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
5376
5389
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
5377
5390
  const resource = canonicalizeResourceIndicator(input.discovery.resource);
@@ -5383,16 +5396,15 @@ function createDefaultOAuthClientProvider(options) {
5383
5396
  if (input.presentedTokens !== void 0) {
5384
5397
  rejectedCurrentGrant = false;
5385
5398
  if (input.presentedTokens !== null) {
5386
- const presented = normalizeStoredTokens(input.presentedTokens);
5399
+ const presented2 = input.presentedTokens;
5387
5400
  const header = input.requestHeaders?.get("Authorization") ?? "";
5388
5401
  const separator = header.indexOf(" ");
5389
- if (presented === void 0 || header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented.accessToken)
5402
+ if (header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented2.accessToken)
5390
5403
  throw new Error("OAuth rejected-request provenance does not match its authorization header");
5391
- presentedTokens = presented;
5392
- rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens, presented);
5404
+ presentedTokens = presented2;
5405
+ rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens, presented2);
5393
5406
  }
5394
5407
  }
5395
- const challengeError = input.challenge?.params.error;
5396
5408
  const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || input.presentedTokens !== void 0 && challengeError === void 0);
5397
5409
  const session = await ensureAuthorizedSession(resource, {
5398
5410
  ...input.discovery,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",