poe-code 4.0.20 → 4.0.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "4.0.20",
3
+ "version": "4.0.22",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,8 @@
1
1
  export { checkAuth } from "./check-auth.js";
2
- export { createOAuthClient } from "./oauth-client.js";
2
+ export { createOAuthAuthorizationUrl, createOAuthClient, exchangeOAuthCode } from "./oauth-client.js";
3
+ export { validateOAuthAuthorizationCallback } from "./loopback-authorization.js";
4
+ export { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
3
5
  export type { OAuthLandingPage } from "./loopback-authorization.js";
4
6
  export type { AuthIdentity, CheckAuthOptions } from "./check-auth.js";
5
- export type { OAuthClient, OAuthClientConfig, OAuthResult, OAuthAuthorization } from "./oauth-client.js";
7
+ export type { OAuthClient, OAuthClientConfig, OAuthResult, OAuthAuthorization, CreateOAuthAuthorizationUrlOptions, ExchangeOAuthCodeOptions } from "./oauth-client.js";
8
+ export type { ValidateOAuthAuthorizationCallbackOptions } from "./loopback-authorization.js";
@@ -1,2 +1,4 @@
1
1
  export { checkAuth } from "./check-auth.js";
2
- export { createOAuthClient } from "./oauth-client.js";
2
+ export { createOAuthAuthorizationUrl, createOAuthClient, exchangeOAuthCode } from "./oauth-client.js";
3
+ export { validateOAuthAuthorizationCallback } from "./loopback-authorization.js";
4
+ export { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
@@ -15,6 +15,13 @@ export interface LoopbackAuthorizationSession {
15
15
  waitForCode(authorizationUrl: string): Promise<string>;
16
16
  close(): void;
17
17
  }
18
+ export interface ValidateOAuthAuthorizationCallbackOptions {
19
+ callbackUrl: string | URL;
20
+ expectedState: string;
21
+ expectedIssuer?: string;
22
+ requireIssuer?: boolean;
23
+ }
24
+ export declare function validateOAuthAuthorizationCallback(options: ValidateOAuthAuthorizationCallbackOptions): string;
18
25
  export declare function createLoopbackAuthorizationSession(options?: LoopbackAuthorizationOptions): Promise<LoopbackAuthorizationSession>;
19
26
  export declare function extractCodeFromInput(input: string): string | null;
20
27
  export declare function buildSuccessPage(landingPage?: OAuthLandingPage): string;
@@ -1,5 +1,39 @@
1
1
  import http from "node:http";
2
2
  import { parseAuthorizationState } from "./authorization-state.js";
3
+ export function validateOAuthAuthorizationCallback(options) {
4
+ let url;
5
+ try {
6
+ url = typeof options.callbackUrl === "string"
7
+ ? new URL(options.callbackUrl)
8
+ : options.callbackUrl;
9
+ }
10
+ catch {
11
+ throw new Error("Poe OAuth callbackUrl must be an absolute URL.");
12
+ }
13
+ const callback = {
14
+ code: url.searchParams.get("code"),
15
+ state: url.searchParams.get("state"),
16
+ iss: url.searchParams.get("iss")
17
+ };
18
+ const expected = {
19
+ state: validateExpectedState(options.expectedState),
20
+ issuer: options.expectedIssuer ?? null,
21
+ requireIssuer: options.requireIssuer ?? false
22
+ };
23
+ validateAuthorizationCallbackBinding(callback, expected);
24
+ const authorizationError = url.searchParams.get("error");
25
+ if (authorizationError !== null) {
26
+ const description = url.searchParams.get("error_description");
27
+ const safeError = sanitizeAuthorizationError(authorizationError, expected.state);
28
+ const safeDescription = description === null
29
+ ? null
30
+ : sanitizeAuthorizationError(description, expected.state);
31
+ throw new Error(safeDescription === null
32
+ ? `OAuth authorization failed: ${safeError}`
33
+ : `OAuth authorization failed: ${safeError} — ${safeDescription}`);
34
+ }
35
+ return validateAuthorizationCallbackParameters(callback, expected);
36
+ }
3
37
  export async function createLoopbackAuthorizationSession(options = {}) {
4
38
  const callbackPath = options.callbackPath ?? "/callback";
5
39
  const server = options.createServer ? options.createServer() : http.createServer();
@@ -46,39 +80,26 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
46
80
  res.end("Not found");
47
81
  return;
48
82
  }
49
- const error = url.searchParams.get("error");
50
- if (error !== null) {
51
- try {
52
- validateAuthorizationCallbackBinding({
53
- state: url.searchParams.get("state"),
54
- iss: url.searchParams.get("iss")
55
- }, expectedAuthorization);
56
- }
57
- catch (validationError) {
58
- res.writeHead(400);
59
- res.end(validationError instanceof Error ? validationError.message : "Invalid OAuth callback");
60
- return;
61
- }
62
- const description = url.searchParams.get("error_description") ?? error;
63
- res.writeHead(400);
64
- res.end(`Authorization failed: ${description}`);
65
- settle(() => reject(new Error(`OAuth authorization failed: ${error} — ${description}`)));
66
- return;
67
- }
68
83
  try {
69
- const code = validateAuthorizationCallbackParameters({
70
- code: url.searchParams.get("code"),
71
- state: url.searchParams.get("state"),
72
- iss: url.searchParams.get("iss")
73
- }, expectedAuthorization);
84
+ const code = validateOAuthAuthorizationCallback({
85
+ callbackUrl: url,
86
+ expectedState: expectedAuthorization.state ?? "",
87
+ expectedIssuer: expectedAuthorization.issuer ?? undefined,
88
+ requireIssuer: expectedAuthorization.requireIssuer
89
+ });
74
90
  res.writeHead(200, { "Content-Type": "text/html" });
75
91
  res.end(buildSuccessPage(options.landingPage));
76
92
  settle(() => resolve(code));
77
93
  }
78
94
  catch (error) {
95
+ const validationError = error instanceof Error ? error : new Error(String(error));
79
96
  res.writeHead(400);
80
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
81
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
97
+ res.end(validationError.message);
98
+ if (url.searchParams.has("error") &&
99
+ !validationError.message.startsWith("OAuth authorization failed:")) {
100
+ return;
101
+ }
102
+ settle(() => reject(validationError));
82
103
  }
83
104
  });
84
105
  if (options.readLine !== undefined) {
@@ -171,6 +192,18 @@ function validateAuthorizationCallbackBinding(callback, expected) {
171
192
  throw new Error("OAuth callback issuer mismatch");
172
193
  }
173
194
  }
195
+ function validateExpectedState(state) {
196
+ if (state.length === 0 || state.trim() !== state) {
197
+ throw new Error("Poe OAuth expectedState must not be blank or contain surrounding whitespace.");
198
+ }
199
+ return state;
200
+ }
201
+ function sanitizeAuthorizationError(value, expectedState) {
202
+ if (value.includes(expectedState)) {
203
+ return "authorization_error";
204
+ }
205
+ return value;
206
+ }
174
207
  function escapeHtml(text) {
175
208
  return text
176
209
  .replaceAll("&", "&amp;")
@@ -21,4 +21,21 @@ export interface OAuthAuthorization {
21
21
  export interface OAuthClient {
22
22
  authorize(): Promise<OAuthAuthorization>;
23
23
  }
24
+ export interface CreateOAuthAuthorizationUrlOptions {
25
+ clientId: string;
26
+ redirectUri: string;
27
+ state: string;
28
+ codeChallenge: string;
29
+ authorizationEndpoint?: string;
30
+ }
31
+ export interface ExchangeOAuthCodeOptions {
32
+ clientId: string;
33
+ redirectUri: string;
34
+ code: string;
35
+ codeVerifier: string;
36
+ tokenEndpoint?: string;
37
+ fetch?: typeof globalThis.fetch;
38
+ }
24
39
  export declare function createOAuthClient(config: OAuthClientConfig): OAuthClient;
40
+ export declare function createOAuthAuthorizationUrl(options: CreateOAuthAuthorizationUrlOptions): string;
41
+ export declare function exchangeOAuthCode(options: ExchangeOAuthCodeOptions): Promise<OAuthResult>;
@@ -1,6 +1,6 @@
1
1
  import { createAuthorizationState } from "./authorization-state.js";
2
2
  import { createLoopbackAuthorizationSession } from "./loopback-authorization.js";
3
- import { generateCodeChallenge as generatePkceCodeChallenge, generateCodeVerifier as generatePkceCodeVerifier } from "./pkce.js";
3
+ import { generateCodeChallenge as generatePkceCodeChallenge, generateCodeVerifier as generatePkceCodeVerifier, validateCodeChallenge, validateCodeVerifier } from "./pkce.js";
4
4
  const DEFAULT_AUTHORIZATION_ENDPOINT = "https://poe.com/oauth/authorize";
5
5
  const DEFAULT_TOKEN_ENDPOINT = "https://api.poe.com/token";
6
6
  const MAX_VALID_EPOCH_MS = 8_640_000_000_000_000;
@@ -9,7 +9,9 @@ export function createOAuthClient(config) {
9
9
  const clientId = validateClientId(config.clientId);
10
10
  const normalizedConfig = {
11
11
  ...config,
12
- clientId
12
+ clientId,
13
+ authorizationEndpoint: validateHttpUrl(config.authorizationEndpoint ?? DEFAULT_AUTHORIZATION_ENDPOINT, "authorizationEndpoint"),
14
+ tokenEndpoint: validateHttpUrl(config.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT, "tokenEndpoint")
13
15
  };
14
16
  return {
15
17
  authorize: () => startAuthorization(normalizedConfig, fetchFn)
@@ -33,8 +35,8 @@ async function startAuthorization(config, fetchFn) {
33
35
  }
34
36
  });
35
37
  const redirectUri = loopbackSession.redirectUri;
36
- const authorizationUrl = buildAuthorizationUrl({
37
- endpoint: authorizationEndpoint,
38
+ const authorizationUrl = createOAuthAuthorizationUrl({
39
+ authorizationEndpoint,
38
40
  clientId: config.clientId,
39
41
  redirectUri,
40
42
  codeChallenge,
@@ -48,13 +50,13 @@ async function startAuthorization(config, fetchFn) {
48
50
  resultPromise ??= (async () => {
49
51
  try {
50
52
  const code = await loopbackSession.waitForCode(authorizationUrl);
51
- return await exchangeCodeForApiKey({
53
+ return await exchangeOAuthCode({
52
54
  tokenEndpoint,
53
55
  code,
54
56
  codeVerifier,
55
57
  clientId: config.clientId,
56
58
  redirectUri,
57
- fetchFn
59
+ fetch: fetchFn
58
60
  });
59
61
  }
60
62
  finally {
@@ -65,41 +67,63 @@ async function startAuthorization(config, fetchFn) {
65
67
  };
66
68
  return { authorizationUrl, waitForResult };
67
69
  }
68
- function buildAuthorizationUrl(params) {
69
- const url = new URL(params.endpoint);
70
+ export function createOAuthAuthorizationUrl(options) {
71
+ const clientId = validateClientId(options.clientId);
72
+ const redirectUri = validateHttpUrl(options.redirectUri, "redirectUri");
73
+ const state = validateOpaqueValue(options.state, "state");
74
+ const codeChallenge = validateCodeChallenge(options.codeChallenge);
75
+ const authorizationEndpoint = validateHttpUrl(options.authorizationEndpoint ?? DEFAULT_AUTHORIZATION_ENDPOINT, "authorizationEndpoint");
76
+ const url = new URL(authorizationEndpoint);
70
77
  url.searchParams.set("response_type", "code");
71
- url.searchParams.set("client_id", params.clientId);
78
+ url.searchParams.set("client_id", clientId);
72
79
  url.searchParams.set("scope", "apikey:create");
73
- url.searchParams.set("code_challenge", params.codeChallenge);
80
+ url.searchParams.set("code_challenge", codeChallenge);
74
81
  url.searchParams.set("code_challenge_method", "S256");
75
- url.searchParams.set("redirect_uri", params.redirectUri);
76
- url.searchParams.set("state", params.state);
82
+ url.searchParams.set("redirect_uri", redirectUri);
83
+ url.searchParams.set("state", state);
77
84
  return url.toString();
78
85
  }
79
- async function exchangeCodeForApiKey(params) {
86
+ export async function exchangeOAuthCode(options) {
87
+ const clientId = validateClientId(options.clientId);
88
+ const redirectUri = validateHttpUrl(options.redirectUri, "redirectUri");
89
+ const code = validateOpaqueValue(options.code, "code");
90
+ const codeVerifier = validateCodeVerifier(options.codeVerifier);
91
+ const tokenEndpoint = validateHttpUrl(options.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT, "tokenEndpoint");
92
+ const fetchFn = options.fetch ?? globalThis.fetch;
80
93
  const body = new URLSearchParams({
81
94
  grant_type: "authorization_code",
82
- code: params.code,
83
- code_verifier: params.codeVerifier,
84
- client_id: params.clientId,
85
- redirect_uri: params.redirectUri
86
- });
87
- const response = await params.fetchFn(params.tokenEndpoint, {
88
- method: "POST",
89
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
90
- body: body.toString()
95
+ code,
96
+ code_verifier: codeVerifier,
97
+ client_id: clientId,
98
+ redirect_uri: redirectUri
91
99
  });
100
+ let response;
101
+ try {
102
+ response = await fetchFn(tokenEndpoint, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
105
+ body: body.toString()
106
+ });
107
+ }
108
+ catch {
109
+ throw new Error("Token exchange request failed");
110
+ }
92
111
  if (!response.ok) {
93
112
  const text = await response.text();
94
113
  const description = parseErrorDescription(text);
95
- throw new Error(description ?? `Token exchange failed (${response.status}): ${text}`);
114
+ if (description !== null &&
115
+ !description.includes(codeVerifier) &&
116
+ !description.includes(code)) {
117
+ throw new Error(description);
118
+ }
119
+ throw new Error(`Token exchange failed (${response.status})`);
96
120
  }
97
121
  let value;
98
122
  try {
99
123
  value = (await response.json());
100
124
  }
101
125
  catch (error) {
102
- throw new Error(`Token exchange failed: invalid JSON response from ${params.tokenEndpoint}`, {
126
+ throw new Error(`Token exchange failed: invalid JSON response from ${tokenEndpoint}`, {
103
127
  cause: error
104
128
  });
105
129
  }
@@ -157,6 +181,28 @@ function validateClientId(clientId) {
157
181
  }
158
182
  return clientId;
159
183
  }
184
+ function validateHttpUrl(value, field) {
185
+ if (value.trim() !== value || value.length === 0) {
186
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL.`);
187
+ }
188
+ let url;
189
+ try {
190
+ url = new URL(value);
191
+ }
192
+ catch {
193
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL.`);
194
+ }
195
+ if ((url.protocol !== "https:" && url.protocol !== "http:") || url.hash.length > 0) {
196
+ throw new Error(`Poe OAuth ${field} must be an absolute HTTP(S) URL without a fragment.`);
197
+ }
198
+ return url.toString();
199
+ }
200
+ function validateOpaqueValue(value, field) {
201
+ if (value.length === 0 || value.trim() !== value) {
202
+ throw new Error(`Poe OAuth ${field} must not be blank or contain surrounding whitespace.`);
203
+ }
204
+ return value;
205
+ }
160
206
  function isValidExpiresIn(value) {
161
207
  if (typeof value !== "number" ||
162
208
  !Number.isFinite(value) ||
@@ -1,2 +1,4 @@
1
1
  export declare function generateCodeVerifier(): string;
2
2
  export declare function generateCodeChallenge(verifier: string): string;
3
+ export declare function validateCodeVerifier(verifier: string): string;
4
+ export declare function validateCodeChallenge(challenge: string): string;
@@ -3,5 +3,31 @@ export function generateCodeVerifier() {
3
3
  return crypto.randomBytes(32).toString("base64url");
4
4
  }
5
5
  export function generateCodeChallenge(verifier) {
6
- return crypto.createHash("sha256").update(verifier).digest("base64url");
6
+ return crypto.createHash("sha256").update(validateCodeVerifier(verifier)).digest("base64url");
7
+ }
8
+ export function validateCodeVerifier(verifier) {
9
+ if (verifier.length < 43 ||
10
+ verifier.length > 128 ||
11
+ !hasOnlyPkceCharacters(verifier)) {
12
+ throw new Error("Poe OAuth codeVerifier must contain 43 to 128 URL-safe PKCE characters.");
13
+ }
14
+ return verifier;
15
+ }
16
+ export function validateCodeChallenge(challenge) {
17
+ if (challenge.length !== 43 || !hasOnlyPkceCharacters(challenge)) {
18
+ throw new Error("Poe OAuth codeChallenge must contain 43 URL-safe PKCE characters.");
19
+ }
20
+ return challenge;
21
+ }
22
+ function hasOnlyPkceCharacters(value) {
23
+ for (const character of value) {
24
+ const code = character.charCodeAt(0);
25
+ const isDigit = code >= 48 && code <= 57;
26
+ const isUppercase = code >= 65 && code <= 90;
27
+ const isLowercase = code >= 97 && code <= 122;
28
+ if (!isDigit && !isUppercase && !isLowercase && character !== "-" && character !== "_") {
29
+ return false;
30
+ }
31
+ }
32
+ return true;
7
33
  }
@@ -14,7 +14,9 @@ export interface TinyHttpMcpServerOAuthOptions extends ProtectedResourceMetadata
14
14
  }
15
15
  export type HttpTransportOptions = ServerOptions & StreamableHttpTransportOptions & {
16
16
  oauth?: TinyHttpMcpServerOAuthOptions;
17
+ requestHandler?: HttpAdditionalRequestHandler;
17
18
  };
19
+ export type HttpAdditionalRequestHandler = (request: IncomingMessage, response: ServerResponse) => boolean | Promise<boolean>;
18
20
  export interface HttpListenOptions {
19
21
  port?: number;
20
22
  hostname?: string;
@@ -157,6 +157,9 @@ export function createHttpServer(options) {
157
157
  const nodeServer = http.createServer(async (req, res) => {
158
158
  const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
159
159
  try {
160
+ if (options.requestHandler !== undefined && (await options.requestHandler(req, res))) {
161
+ return;
162
+ }
160
163
  if (protectedResourceMetadataBody !== undefined &&
161
164
  req.method === "GET" &&
162
165
  getProtectedResourceMetadataPaths(path).includes(requestUrl.pathname)) {
@@ -3,7 +3,7 @@ export type { Server, TypedSchema, ImageContent, AudioContent, EmbeddedResource,
3
3
  export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter } from "./express-middleware.js";
4
4
  export type { CreateExpressOAuthHandlersOptions } from "./express-middleware.js";
5
5
  export { createHttpServer, createProtectedResourceMetadataDocument } from "./http-server.js";
6
- export type { HttpToolContext, HttpToolHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
6
+ export type { HttpToolContext, HttpToolHandler, HttpAdditionalRequestHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
7
7
  export { TokenVerificationError } from "./auth.js";
8
8
  export type { RequestAuthInfo, TokenVerifier, VerifiedAccessToken } from "./auth.js";
9
9
  export { StreamableHttpTransport } from "./http-transport.js";
@@ -1,5 +1,5 @@
1
1
  export { createHttpServer, createProtectedResourceMetadataDocument } from "./http-server.js";
2
- export type { HttpToolContext, HttpToolHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
2
+ export type { HttpToolContext, HttpToolHandler, HttpAdditionalRequestHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
3
3
  export { TokenVerificationError } from "./auth.js";
4
4
  export type { RequestAuthInfo, TokenVerifier, VerifiedAccessToken } from "./auth.js";
5
5
  export { StreamableHttpTransport } from "./http-transport.js";
@@ -0,0 +1,44 @@
1
+ import { hostedOAuth } from "./http-hosted-oauth.js";
2
+ const ignoredHostedOAuth = hostedOAuth({
3
+ publicUrl: "https://calendar.example/mcp",
4
+ storage: ignoredStorage,
5
+ provider: {
6
+ name: "Skylight",
7
+ login: { fields: ["email", "password"] },
8
+ async connect({ email, password, signal }) {
9
+ const ignoredEmail = email;
10
+ const ignoredPassword = password;
11
+ const ignoredSignal = signal;
12
+ return {
13
+ accountId: ignoredEmail,
14
+ credential: `${ignoredPassword}:${ignoredSignal.aborted}`
15
+ };
16
+ },
17
+ services({ credentials }) {
18
+ return { skylight: { credential: () => credentials.read() } };
19
+ }
20
+ }
21
+ });
22
+ void ignoredHostedOAuth;
23
+ const ignoredRedirectHostedOAuth = hostedOAuth({
24
+ publicUrl: "https://calendar.example/mcp",
25
+ storage: ignoredStorage,
26
+ provider: {
27
+ name: "Skylight",
28
+ services: ({ credentials }) => ({
29
+ skylight: { credential: () => credentials.read() }
30
+ })
31
+ },
32
+ advanced: {
33
+ interaction: {
34
+ paths: ["/oauth/skylight/callback"],
35
+ start: () => Response.redirect("https://login.example/authorize"),
36
+ handle: async ({ complete }) => complete({
37
+ transactionId: "transaction-id",
38
+ accountId: "account-id",
39
+ credential: "provider-session"
40
+ })
41
+ }
42
+ }
43
+ });
44
+ void ignoredRedirectHostedOAuth;
@@ -0,0 +1,113 @@
1
+ import { type JWK } from "jose";
2
+ import { type AuthorizationServerStore, type AuthorizationTransactionRecord, type OAuthAuthorizationServerSigningKey } from "../../mcp-oauth-server/dist/index.js";
3
+ import type { HttpAdditionalRequestHandler, TinyHttpMcpServerOAuthOptions } from "../../tiny-http-mcp-server/dist/server.js";
4
+ export type HostedOAuthLoginFieldName = "email" | "password" | "apiKey" | (string & {});
5
+ export interface HostedOAuthLoginField {
6
+ name: HostedOAuthLoginFieldName;
7
+ label?: string;
8
+ type?: "text" | "email" | "password";
9
+ }
10
+ export interface HostedOAuthCredentialStore<TCredential = unknown> {
11
+ get(subject: string): Promise<TCredential | undefined>;
12
+ set(subject: string, credential: TCredential): Promise<void>;
13
+ delete(subject: string): Promise<void>;
14
+ update(subject: string, update: (credential: TCredential) => Promise<TCredential> | TCredential): Promise<TCredential>;
15
+ }
16
+ export interface HostedOAuthStorageCapabilities {
17
+ durable: boolean;
18
+ encryptedCredentials: boolean;
19
+ stableKeys: boolean;
20
+ shared: boolean;
21
+ }
22
+ export interface HostedOAuthInteractionStore {
23
+ set(transaction: AuthorizationTransactionRecord): Promise<void>;
24
+ get(transactionId: string): Promise<AuthorizationTransactionRecord | undefined>;
25
+ delete(transactionId: string): Promise<void>;
26
+ }
27
+ export interface HostedOAuthStorage<TCredential = unknown> {
28
+ authorizationServer: AuthorizationServerStore;
29
+ interactions: HostedOAuthInteractionStore;
30
+ credentials: HostedOAuthCredentialStore<TCredential>;
31
+ capabilities: HostedOAuthStorageCapabilities;
32
+ signingKey(): Promise<OAuthAuthorizationServerSigningKey>;
33
+ resolveSubject(providerName: string, accountId: string): Promise<string>;
34
+ cleanup?(now?: number): Promise<void>;
35
+ }
36
+ export interface HostedOAuthCredentialAccess<TCredential = unknown> {
37
+ read(): Promise<TCredential>;
38
+ update(update: (credential: TCredential) => Promise<TCredential> | TCredential): Promise<TCredential>;
39
+ delete(): Promise<void>;
40
+ }
41
+ export interface HostedOAuthProvider<TCredential = unknown, TServices extends object = object> {
42
+ name: string;
43
+ login?: {
44
+ fields: readonly (HostedOAuthLoginFieldName | HostedOAuthLoginField)[];
45
+ };
46
+ connect?(fields: Readonly<Record<string, string>> & {
47
+ signal: AbortSignal;
48
+ }): Promise<{
49
+ accountId: string;
50
+ credential: TCredential;
51
+ }>;
52
+ services(input: {
53
+ credentials: HostedOAuthCredentialAccess<TCredential>;
54
+ }): Promise<Partial<TServices>> | Partial<TServices>;
55
+ }
56
+ export interface HostedOAuthInteractionAdapter<TCredential = unknown> {
57
+ paths: readonly string[];
58
+ start(context: {
59
+ request: Request;
60
+ transaction: AuthorizationTransactionRecord;
61
+ }): Promise<Response> | Response;
62
+ handle(context: {
63
+ request: Request;
64
+ complete(input: {
65
+ transactionId: string;
66
+ accountId: string;
67
+ credential: TCredential;
68
+ }): Promise<Response>;
69
+ }): Promise<Response> | Response;
70
+ }
71
+ export interface HostedOAuthAdvancedOptions<TCredential = unknown> {
72
+ scopes?: readonly string[];
73
+ branding?: {
74
+ title?: string;
75
+ };
76
+ accessTokenTtlSeconds?: number;
77
+ authorizationCodeTtlSeconds?: number;
78
+ authorizationTransactionTtlSeconds?: number;
79
+ refreshTokenTtlSeconds?: number;
80
+ additionalPublicJwks?: readonly JWK[];
81
+ interaction?: HostedOAuthInteractionAdapter<TCredential>;
82
+ }
83
+ export interface HostedOAuthOptions<TCredential = unknown, TServices extends object = object> {
84
+ publicUrl: string;
85
+ storage: HostedOAuthStorage<TCredential>;
86
+ provider: HostedOAuthProvider<TCredential, TServices>;
87
+ advanced?: HostedOAuthAdvancedOptions<TCredential>;
88
+ }
89
+ export interface PreparedHostedOAuth {
90
+ publicUrl: URL;
91
+ issuer: URL;
92
+ scopes: readonly string[];
93
+ }
94
+ export interface HostedOAuthConfiguration<TCredential = unknown, TServices extends object = object> extends HostedOAuthOptions<TCredential, TServices> {
95
+ readonly kind: "hosted";
96
+ prepare(options?: {
97
+ production?: boolean;
98
+ }): Promise<PreparedHostedOAuth>;
99
+ }
100
+ export declare function isHostedOAuthConfiguration(value: unknown): value is HostedOAuthConfiguration<unknown, object>;
101
+ export declare function hostedOAuth<TCredential = unknown, TServices extends object = object>(options: HostedOAuthOptions<TCredential, TServices>): HostedOAuthConfiguration<TCredential, TServices>;
102
+ export declare class HostedOAuthLoginError extends Error {
103
+ constructor(message: string);
104
+ }
105
+ export interface HostedOAuthRuntime<TServices extends object = object> {
106
+ mcpPath: string;
107
+ oauth: TinyHttpMcpServerOAuthOptions;
108
+ requestHandler: HttpAdditionalRequestHandler;
109
+ requestServices(subject: string): Promise<Partial<TServices>>;
110
+ }
111
+ export declare function createInMemoryHostedOAuthStorage<TCredential = unknown>(options: {
112
+ development: true;
113
+ }): HostedOAuthStorage<TCredential>;