@slashfi/agents-sdk 0.90.4 → 0.90.5

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/src/mcp-client.ts CHANGED
@@ -13,9 +13,9 @@
13
13
  * by registry-consumer — this module only provides auth primitives.
14
14
  */
15
15
 
16
- import { generatePkcePair } from "./pkce.js";
17
16
  import type { RegistryAuthRequirement } from "./define-config.js";
18
17
  import type { FetchFn } from "./fetch-types.js";
18
+ import { generatePkcePair } from "./pkce.js";
19
19
 
20
20
  // ============================================
21
21
  // Types
@@ -82,8 +82,7 @@ export async function dynamicClientRegistration(
82
82
  client_name: params.clientName,
83
83
  redirect_uris: params.redirectUris,
84
84
  grant_types: params.grantTypes ?? ["authorization_code"],
85
- token_endpoint_auth_method:
86
- params.tokenEndpointAuthMethod ?? "none",
85
+ token_endpoint_auth_method: params.tokenEndpointAuthMethod ?? "none",
87
86
  }),
88
87
  });
89
88
  if (!res.ok) {
@@ -148,6 +147,69 @@ export async function buildOAuthAuthorizeUrl(params: {
148
147
  // Token Exchange
149
148
  // ============================================
150
149
 
150
+ export type OAuthClientAuthMethod =
151
+ | "client_secret_post"
152
+ | "client_secret_basic";
153
+
154
+ const TOKEN_EXCHANGE_BODY_PARAMS: Record<OAuthClientAuthMethod, string[]> = {
155
+ client_secret_post: [
156
+ "grant_type",
157
+ "code",
158
+ "code_verifier",
159
+ "redirect_uri",
160
+ "client_id",
161
+ "client_secret",
162
+ ],
163
+ client_secret_basic: ["grant_type", "code", "code_verifier", "redirect_uri"],
164
+ };
165
+
166
+ const TOKEN_REFRESH_BODY_PARAMS: Record<OAuthClientAuthMethod, string[]> = {
167
+ client_secret_post: [
168
+ "grant_type",
169
+ "refresh_token",
170
+ "client_id",
171
+ "client_secret",
172
+ ],
173
+ client_secret_basic: ["grant_type", "refresh_token"],
174
+ };
175
+
176
+ function buildBasicAuth(clientId: string, clientSecret: string): string {
177
+ return `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
178
+ }
179
+
180
+ function buildOAuthTokenRequest(params: {
181
+ allParams: Record<string, string>;
182
+ bodyParamKeys: string[];
183
+ clientId: string;
184
+ clientSecret?: string;
185
+ clientAuthMethod: OAuthClientAuthMethod;
186
+ }): { headers: Record<string, string>; body: string } {
187
+ const body = new URLSearchParams();
188
+ for (const key of params.bodyParamKeys) {
189
+ const value = params.allParams[key];
190
+ if (value) body.set(key, value);
191
+ }
192
+
193
+ const headers: Record<string, string> = {
194
+ "Content-Type": "application/x-www-form-urlencoded",
195
+ Accept: "application/json",
196
+ };
197
+
198
+ if (
199
+ params.clientAuthMethod === "client_secret_basic" &&
200
+ params.clientSecret
201
+ ) {
202
+ headers.Authorization = buildBasicAuth(
203
+ params.clientId,
204
+ params.clientSecret,
205
+ );
206
+ } else if (params.clientSecret) {
207
+ body.set("client_secret", params.clientSecret);
208
+ }
209
+
210
+ return { headers, body: body.toString() };
211
+ }
212
+
151
213
  /**
152
214
  * Exchange an authorization code for tokens (with PKCE).
153
215
  */
@@ -159,29 +221,35 @@ export async function exchangeCodeForTokens(
159
221
  clientId: string;
160
222
  clientSecret?: string;
161
223
  redirectUri: string;
224
+ clientAuthMethod?: OAuthClientAuthMethod;
162
225
  },
163
- fetchFn: typeof globalThis.fetch = globalThis.fetch,
226
+ fetchFn: FetchFn = globalThis.fetch,
164
227
  ): Promise<{
165
228
  accessToken: string;
166
229
  refreshToken?: string;
167
230
  expiresIn?: number;
168
231
  tokenType?: string;
169
232
  }> {
170
- const body = new URLSearchParams({
171
- grant_type: "authorization_code",
172
- code: params.code,
173
- code_verifier: params.codeVerifier,
174
- client_id: params.clientId,
175
- redirect_uri: params.redirectUri,
233
+ const method = params.clientAuthMethod ?? "client_secret_post";
234
+ const { headers, body } = buildOAuthTokenRequest({
235
+ allParams: {
236
+ grant_type: "authorization_code",
237
+ code: params.code,
238
+ code_verifier: params.codeVerifier,
239
+ redirect_uri: params.redirectUri,
240
+ client_id: params.clientId,
241
+ ...(params.clientSecret && { client_secret: params.clientSecret }),
242
+ },
243
+ bodyParamKeys: TOKEN_EXCHANGE_BODY_PARAMS[method],
244
+ clientId: params.clientId,
245
+ clientSecret: params.clientSecret,
246
+ clientAuthMethod: method,
176
247
  });
177
- if (params.clientSecret) {
178
- body.set("client_secret", params.clientSecret);
179
- }
180
248
 
181
249
  const res = await fetchFn(tokenEndpoint, {
182
250
  method: "POST",
183
- headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" },
184
- body: body.toString(),
251
+ headers,
252
+ body,
185
253
  });
186
254
  if (!res.ok) {
187
255
  const text = await res.text().catch(() => "unknown");
@@ -209,26 +277,32 @@ export async function refreshAccessToken(
209
277
  refreshToken: string;
210
278
  clientId: string;
211
279
  clientSecret?: string;
280
+ clientAuthMethod?: OAuthClientAuthMethod;
212
281
  },
213
- fetchFn: typeof globalThis.fetch = globalThis.fetch,
282
+ fetchFn: FetchFn = globalThis.fetch,
214
283
  ): Promise<{
215
284
  accessToken: string;
216
285
  refreshToken?: string;
217
286
  expiresIn?: number;
218
287
  }> {
219
- const body = new URLSearchParams({
220
- grant_type: "refresh_token",
221
- refresh_token: params.refreshToken,
222
- client_id: params.clientId,
288
+ const method = params.clientAuthMethod ?? "client_secret_post";
289
+ const { headers, body } = buildOAuthTokenRequest({
290
+ allParams: {
291
+ grant_type: "refresh_token",
292
+ refresh_token: params.refreshToken,
293
+ client_id: params.clientId,
294
+ ...(params.clientSecret && { client_secret: params.clientSecret }),
295
+ },
296
+ bodyParamKeys: TOKEN_REFRESH_BODY_PARAMS[method],
297
+ clientId: params.clientId,
298
+ clientSecret: params.clientSecret,
299
+ clientAuthMethod: method,
223
300
  });
224
- if (params.clientSecret) {
225
- body.set("client_secret", params.clientSecret);
226
- }
227
301
 
228
302
  const res = await fetchFn(tokenEndpoint, {
229
303
  method: "POST",
230
- headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" },
231
- body: body.toString(),
304
+ headers,
305
+ body,
232
306
  });
233
307
  if (!res.ok) {
234
308
  const text = await res.text().catch(() => "unknown");
@@ -252,14 +326,17 @@ export async function refreshAccessToken(
252
326
  * Returns the scheme and any `key="value"` params. Tolerant of
253
327
  * single-value headers and missing params.
254
328
  */
255
- export function parseWwwAuthenticate(
256
- header: string,
257
- ): { scheme: string; params: Record<string, string> } {
329
+ export function parseWwwAuthenticate(header: string): {
330
+ scheme: string;
331
+ params: Record<string, string>;
332
+ } {
258
333
  const spaceIdx = header.indexOf(" ");
259
334
  const scheme = (spaceIdx === -1 ? header : header.slice(0, spaceIdx)).trim();
260
335
  const rest = spaceIdx === -1 ? "" : header.slice(spaceIdx + 1);
261
336
  const params: Record<string, string> = {};
262
- for (const match of rest.matchAll(/([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g)) {
337
+ for (const match of rest.matchAll(
338
+ /([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g,
339
+ )) {
263
340
  params[match[1]!.toLowerCase()] = match[2]!;
264
341
  }
265
342
  return { scheme, params };
@@ -315,7 +392,10 @@ export async function probeRegistryAuth(
315
392
  try {
316
393
  res = await fetchFn(url, {
317
394
  method: "POST",
318
- headers: { "Content-Type": "application/json", Accept: "application/json" },
395
+ headers: {
396
+ "Content-Type": "application/json",
397
+ Accept: "application/json",
398
+ },
319
399
  body: JSON.stringify({
320
400
  jsonrpc: "2.0",
321
401
  id: 1,
@@ -344,7 +424,10 @@ export async function probeRegistryAuth(
344
424
  const metadataUrl = params.resource_metadata;
345
425
  if (metadataUrl) {
346
426
  requirement.resourceMetadataUrl = metadataUrl;
347
- const metadata = await discoverProtectedResourceMetadata(metadataUrl, fetchFn);
427
+ const metadata = await discoverProtectedResourceMetadata(
428
+ metadataUrl,
429
+ fetchFn,
430
+ );
348
431
  if (metadata) {
349
432
  if (metadata.authorization_servers?.length) {
350
433
  requirement.authorizationServers = metadata.authorization_servers;