@yagni-app/code-staging 1.2.3-staging.1607.1 → 1.2.3-staging.1621.1

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.
@@ -202,11 +202,25 @@ export class YagniAuthProvider {
202
202
  return {
203
203
  client_id: clientId,
204
204
  ...(entry?.clientSecret ? { client_secret: entry.clientSecret } : {}),
205
+ ...(entry?.clientIssuer ? { issuer: entry.clientIssuer } : {}),
205
206
  };
206
207
  }
207
208
  async saveClientInformation(info) {
208
209
  updateStoredOAuthEntry(this.serverName, this.config, (entry) => {
210
+ // The SDK also calls this to bind the client we already use (stored, or
211
+ // pre-registered in the config) to the authorization server that just
212
+ // accepted it. That is not a registration: record the server only, so
213
+ // a pre-registered id never lands in the auth store.
214
+ if (info.client_id === (entry.clientId ?? this.config.oauth?.clientId)) {
215
+ if (info.issuer)
216
+ entry.clientIssuer = info.issuer;
217
+ return;
218
+ }
209
219
  entry.clientId = info.client_id;
220
+ if (info.issuer)
221
+ entry.clientIssuer = info.issuer;
222
+ else
223
+ delete entry.clientIssuer;
210
224
  if (info.client_secret != null) {
211
225
  entry.clientSecret = info.client_secret;
212
226
  entry.clientSecretSource = "dcr";
@@ -216,7 +230,7 @@ export class YagniAuthProvider {
216
230
  delete entry.clientSecret;
217
231
  delete entry.clientSecretSource;
218
232
  }
219
- // The SDK only calls this from dynamic client registration, so the
233
+ // A new client id only comes from dynamic client registration, so the
220
234
  // redirect this provider advertised is exactly what the server pinned.
221
235
  entry.redirectUri = this.redirectUri;
222
236
  });
@@ -232,6 +246,7 @@ export class YagniAuthProvider {
232
246
  ...(entry.refreshToken ? { refresh_token: entry.refreshToken } : {}),
233
247
  ...(expiresIn != null ? { expires_in: expiresIn } : {}),
234
248
  ...(entry.scopes ? { scope: entry.scopes.join(" ") } : {}),
249
+ ...(entry.tokensIssuer ? { issuer: entry.tokensIssuer } : {}),
235
250
  };
236
251
  }
237
252
  async saveTokens(tokens) {
@@ -243,6 +258,8 @@ export class YagniAuthProvider {
243
258
  entry.expiresAt = Date.now() + expiresIn * 1000;
244
259
  if (tokens.scope)
245
260
  entry.scopes = tokens.scope.split(/\s+/).filter(Boolean);
261
+ if (tokens.issuer)
262
+ entry.tokensIssuer = tokens.issuer;
246
263
  });
247
264
  }
248
265
  async saveCodeVerifier(codeVerifier) {
@@ -266,11 +283,13 @@ export class YagniAuthProvider {
266
283
  delete entry.clientId;
267
284
  delete entry.clientSecret;
268
285
  delete entry.redirectUri;
286
+ delete entry.clientIssuer;
269
287
  }
270
288
  if (scope === "tokens" || scope === "all") {
271
289
  delete entry.accessToken;
272
290
  delete entry.refreshToken;
273
291
  delete entry.expiresAt;
292
+ delete entry.tokensIssuer;
274
293
  }
275
294
  });
276
295
  if (scope === "verifier" || scope === "all")
@@ -430,6 +449,7 @@ function dropPinnedClientIfPortChanged(serverName, config, stored, listener, red
430
449
  const { errors } = updateStoredOAuthEntry(serverName, config, (entry) => {
431
450
  delete entry.clientId;
432
451
  delete entry.redirectUri;
452
+ delete entry.clientIssuer;
433
453
  // The secret goes with the DCR client unless it is the operator's
434
454
  // (`--client-secret`), which belongs to the config's pre-registered
435
455
  // client and lives nowhere else. Entries from before provenance was
@@ -44,11 +44,20 @@ export interface StoredOAuthEntry {
44
44
  * client ids (their redirect is whatever the operator registered).
45
45
  */
46
46
  redirectUri?: string;
47
+ /**
48
+ * The authorization server the client in use (`clientId`, or the config's
49
+ * pre-registered one) is bound to. The MCP SDK stamps it on what it saves
50
+ * and refuses a stored client stamped for a different server, so the
51
+ * client is never sent to an authorization server it was not issued by.
52
+ */
53
+ clientIssuer?: string;
47
54
  accessToken?: string;
48
55
  refreshToken?: string;
49
56
  /** Epoch ms when the access token expires (refresh happens before this). */
50
57
  expiresAt?: number;
51
58
  scopes?: string[];
59
+ /** The authorization server that issued the tokens (see `clientIssuer`). */
60
+ tokensIssuer?: string;
52
61
  }
53
62
  export interface McpAuthFile {
54
63
  servers?: Record<string, StoredOAuthEntry>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.2.3-staging.1607.1",
3
+ "version": "1.2.3-staging.1621.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -38,7 +38,7 @@
38
38
  "@earendil-works/pi-coding-agent": "0.84.1",
39
39
  "@earendil-works/pi-tui": "0.84.1",
40
40
  "@grpc/grpc-js": "^1.14.5",
41
- "@modelcontextprotocol/sdk": "^1.30.0",
41
+ "@modelcontextprotocol/sdk": "^1.31.0",
42
42
  "@opentelemetry/api": "^1.9.1",
43
43
  "@opentelemetry/api-logs": "^0.222.0",
44
44
  "@opentelemetry/exporter-logs-otlp-grpc": "^0.222.0",
@@ -58,5 +58,5 @@
58
58
  "turndown": "^7.2.4",
59
59
  "typebox": "^1.3.15"
60
60
  },
61
- "yagniSourceSha": "edbcdfaf5b4700317fb1930f5390b8ad9012c4cb"
61
+ "yagniSourceSha": "7d2396135b54ce5b4461bd57dcdb99292eb52115"
62
62
  }