mcp-oauth-provider 0.0.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.
Files changed (44) hide show
  1. package/README.md +668 -0
  2. package/dist/__tests__/config.test.js +56 -0
  3. package/dist/__tests__/config.test.js.map +1 -0
  4. package/dist/__tests__/integration.test.js +341 -0
  5. package/dist/__tests__/integration.test.js.map +1 -0
  6. package/dist/__tests__/oauth-flow.test.js +201 -0
  7. package/dist/__tests__/oauth-flow.test.js.map +1 -0
  8. package/dist/__tests__/server.test.js +271 -0
  9. package/dist/__tests__/server.test.js.map +1 -0
  10. package/dist/__tests__/storage.test.js +256 -0
  11. package/dist/__tests__/storage.test.js.map +1 -0
  12. package/dist/client/config.js +30 -0
  13. package/dist/client/config.js.map +1 -0
  14. package/dist/client/factory.js +16 -0
  15. package/dist/client/factory.js.map +1 -0
  16. package/dist/client/index.js +237 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/client/oauth-flow.js +73 -0
  19. package/dist/client/oauth-flow.js.map +1 -0
  20. package/dist/client/storage.js +237 -0
  21. package/dist/client/storage.js.map +1 -0
  22. package/dist/index.js +12 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/server/callback.js +164 -0
  25. package/dist/server/callback.js.map +1 -0
  26. package/dist/server/index.js +8 -0
  27. package/dist/server/index.js.map +1 -0
  28. package/dist/server/templates.js +245 -0
  29. package/dist/server/templates.js.map +1 -0
  30. package/package.json +66 -0
  31. package/src/__tests__/config.test.ts +78 -0
  32. package/src/__tests__/integration.test.ts +398 -0
  33. package/src/__tests__/oauth-flow.test.ts +276 -0
  34. package/src/__tests__/server.test.ts +391 -0
  35. package/src/__tests__/storage.test.ts +329 -0
  36. package/src/client/config.ts +134 -0
  37. package/src/client/factory.ts +19 -0
  38. package/src/client/index.ts +361 -0
  39. package/src/client/oauth-flow.ts +115 -0
  40. package/src/client/storage.ts +335 -0
  41. package/src/index.ts +31 -0
  42. package/src/server/callback.ts +257 -0
  43. package/src/server/index.ts +21 -0
  44. package/src/server/templates.ts +271 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/storage.ts"],"sourcesContent":["import type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthTokens,\n StorageAdapter,\n} from './config.js';\n\nexport type { StorageAdapter };\n\n/**\n * Internal token storage format with absolute expiry timestamp\n */\ninterface StoredTokens extends Omit<OAuthTokens, 'expires_in'> {\n /**\n * Absolute timestamp (milliseconds) when the token expires\n * This is derived from expires_in when tokens are saved\n */\n expires_at?: number;\n}\n\n/**\n * Calculate expires_in from expires_at timestamp\n */\nfunction calculateExpiresIn(expiresAt: number | undefined): number | undefined {\n if (!expiresAt) {\n return undefined;\n }\n\n const now = Date.now();\n const expiresInMs = expiresAt - now;\n const expiresInSeconds = Math.floor(expiresInMs / 1000);\n\n // Return 0 if already expired, otherwise return remaining seconds\n return Math.max(0, expiresInSeconds);\n}\n\n/**\n * Calculate expires_at from expires_in\n */\nfunction calculateExpiresAt(expiresIn: number | undefined): number {\n return Date.now() + (expiresIn || 0) * 1000;\n}\n\n/**\n * In-memory storage adapter for OAuth data\n * Suitable for development and testing, but data is lost when process exits\n */\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | undefined> {\n return this.data.get(key);\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n\n /**\n * Clear all data (useful for testing)\n */\n clear(): void {\n this.data.clear();\n }\n}\n\n/**\n * File-based storage adapter for OAuth data\n * Persists data to the filesystem for longer-term storage\n */\nexport class FileStorage implements StorageAdapter {\n private basePath: string;\n\n constructor(basePath = './oauth-data') {\n this.basePath = basePath;\n }\n\n private getFilePath(key: string): string {\n // Sanitize key for filename\n const sanitized = key.replace(/[^a-zA-Z0-9-_]/g, '_');\n\n return `${this.basePath}/${sanitized}.json`;\n }\n\n private async ensureDirectory(): Promise<void> {\n try {\n await Bun.write(`${this.basePath}/.gitkeep`, '');\n } catch {\n // Directory creation will happen automatically with Bun.write\n }\n }\n\n async get(key: string): Promise<string | undefined> {\n try {\n const file = Bun.file(this.getFilePath(key));\n const exists = await file.exists();\n\n if (!exists) {\n return undefined;\n }\n\n return await file.text();\n } catch {\n return undefined;\n }\n }\n\n async set(key: string, value: string): Promise<void> {\n await this.ensureDirectory();\n await Bun.write(this.getFilePath(key), value);\n }\n\n async delete(key: string): Promise<void> {\n try {\n await Bun.$`rm -f ${this.getFilePath(key)}`;\n } catch {\n // File might not exist, ignore error\n }\n }\n\n /**\n * Clear all data (useful for testing)\n */\n async clear(): Promise<void> {\n try {\n await Bun.$`rm -rf ${this.basePath}`;\n } catch {\n // Directory might not exist, ignore error\n }\n }\n}\n\n/**\n * Create a storage adapter based on the provided configuration\n */\nexport function createStorageAdapter(\n type?: 'memory' | 'file',\n options?: { path?: string }\n): StorageAdapter {\n switch (type) {\n case 'file':\n return new FileStorage(options?.path);\n case 'memory':\n default:\n return new MemoryStorage();\n }\n}\n\n/**\n * Options for initializing OAuthStorage\n */\nexport interface OAuthStorageOptions {\n /**\n * Static client information (from config)\n * If provided, will ALWAYS be returned (takes precedence over storage)\n * This is because client credentials are like API keys - they don't change\n */\n staticClientInfo?: OAuthClientInformation;\n\n /**\n * Initial tokens (from config)\n * If provided, will be stored ONCE on first access if storage is empty\n * After that, storage takes precedence (tokens change over time)\n */\n initialTokens?: OAuthTokens;\n}\n\n/**\n * Helper class to manage OAuth data with the simplified storage adapter\n * Handles initialization from config and provides a unified storage interface\n */\nexport class OAuthStorage {\n private initialized = false;\n\n constructor(\n private storage: StorageAdapter,\n private sessionId: string,\n private options?: OAuthStorageOptions\n ) {}\n\n /**\n * Initialize storage with config values if provided\n * This ensures config tokens are written to storage on first use\n */\n private async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.initialized = true;\n\n // Initialize tokens if provided and not already in storage\n // (tokens are one-time initialization, storage takes over after that)\n if (this.options?.initialTokens) {\n const existing = await this.storage.get(`tokens:${this.sessionId}`);\n\n if (!existing) {\n // Convert expires_in to expires_at before storing\n const storedTokens: StoredTokens = {\n access_token: this.options.initialTokens.access_token,\n token_type: this.options.initialTokens.token_type,\n refresh_token: this.options.initialTokens.refresh_token,\n scope: this.options.initialTokens.scope,\n };\n\n // Only set expires_at if expires_in is provided\n if (this.options.initialTokens.expires_in !== undefined) {\n storedTokens.expires_at = calculateExpiresAt(\n this.options.initialTokens.expires_in\n );\n }\n\n await this.storage.set(\n `tokens:${this.sessionId}`,\n JSON.stringify(storedTokens)\n );\n }\n }\n }\n\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n await this.initialize();\n\n // Convert expires_in to expires_at for storage\n const storedTokens: StoredTokens = {\n access_token: tokens.access_token,\n token_type: tokens.token_type,\n refresh_token: tokens.refresh_token,\n scope: tokens.scope,\n };\n\n // Only set expires_at if expires_in is provided\n if (tokens.expires_in !== undefined) {\n storedTokens.expires_at = calculateExpiresAt(tokens.expires_in);\n }\n\n await this.storage.set(\n `tokens:${this.sessionId}`,\n JSON.stringify(storedTokens)\n );\n }\n\n async getTokens(): Promise<OAuthTokens | undefined> {\n await this.initialize();\n const data = await this.storage.get(`tokens:${this.sessionId}`);\n\n if (!data) {\n return undefined;\n }\n\n const storedTokens: StoredTokens = JSON.parse(data);\n\n // Convert expires_at back to expires_in for the OAuthTokens interface\n const tokens: OAuthTokens = {\n access_token: storedTokens.access_token,\n token_type: storedTokens.token_type,\n };\n\n // Only include optional fields if they exist\n if (storedTokens.refresh_token !== undefined) {\n tokens.refresh_token = storedTokens.refresh_token;\n }\n if (storedTokens.scope !== undefined) {\n tokens.scope = storedTokens.scope;\n }\n if (storedTokens.expires_at !== undefined) {\n tokens.expires_in = calculateExpiresIn(storedTokens.expires_at);\n }\n\n return tokens;\n }\n\n async clearTokens(): Promise<void> {\n await this.initialize();\n await this.storage.delete(`tokens:${this.sessionId}`);\n }\n\n async saveClientInfo(clientInfo: OAuthClientInformationFull): Promise<void> {\n await this.initialize();\n await this.storage.set('client_info', JSON.stringify(clientInfo));\n }\n\n async getClientInfo(): Promise<OAuthClientInformation | undefined> {\n await this.initialize();\n\n // Static client info from config ALWAYS takes precedence\n // (client credentials are like API keys - they don't change)\n if (this.options?.staticClientInfo?.client_id) {\n return this.options.staticClientInfo;\n }\n\n const data = await this.storage.get('client_info');\n\n return data ? JSON.parse(data) : undefined;\n }\n\n async clearClientInfo(): Promise<void> {\n await this.initialize();\n await this.storage.delete('client_info');\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.initialize();\n await this.storage.set(`verifier:${this.sessionId}`, verifier);\n }\n\n async getCodeVerifier(): Promise<string | undefined> {\n await this.initialize();\n\n return this.storage.get(`verifier:${this.sessionId}`);\n }\n\n async clearCodeVerifier(): Promise<void> {\n await this.initialize();\n await this.storage.delete(`verifier:${this.sessionId}`);\n }\n\n async clearSession(): Promise<void> {\n await this.initialize();\n await Promise.all([this.clearTokens(), this.clearCodeVerifier()]);\n }\n\n async clearAll(): Promise<void> {\n await this.initialize();\n await Promise.all([\n this.clearTokens(),\n this.clearCodeVerifier(),\n this.clearClientInfo(),\n ]);\n }\n}\n"],"names":["calculateExpiresIn","expiresAt","undefined","now","Date","expiresInMs","expiresInSeconds","Math","floor","max","calculateExpiresAt","expiresIn","MemoryStorage","get","key","data","set","value","delete","clear","Map","FileStorage","getFilePath","sanitized","replace","basePath","ensureDirectory","Bun","write","file","exists","text","$","createStorageAdapter","type","options","path","OAuthStorage","initialize","initialized","initialTokens","existing","storage","sessionId","storedTokens","access_token","token_type","refresh_token","scope","expires_in","expires_at","JSON","stringify","saveTokens","tokens","getTokens","parse","clearTokens","saveClientInfo","clientInfo","getClientInfo","staticClientInfo","client_id","clearClientInfo","saveCodeVerifier","verifier","getCodeVerifier","clearCodeVerifier","clearSession","Promise","all","clearAll"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP OAuth Provider
3
+ *
4
+ * OAuth client provider implementation for the Model Context Protocol (MCP)
5
+ */ export { MCPOAuthClientProvider } from './client/index.js';
6
+ export { createStorageAdapter, FileStorage, MemoryStorage, OAuthStorage } from './client/storage.js';
7
+ export { DEFAULT_CLIENT_METADATA, generateSessionId, generateState } from './client/config.js';
8
+ /**
9
+ * Factory function to create an OAuth client provider
10
+ */ export { createOAuthProvider } from './client/factory';
11
+
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * MCP OAuth Provider\n *\n * OAuth client provider implementation for the Model Context Protocol (MCP)\n */\n\nexport { MCPOAuthClientProvider } from './client/index.js';\n\nexport {\n createStorageAdapter,\n FileStorage,\n MemoryStorage,\n OAuthStorage,\n type StorageAdapter,\n} from './client/storage.js';\n\nexport {\n DEFAULT_CLIENT_METADATA,\n generateSessionId,\n generateState,\n type OAuthClientInformation,\n type OAuthClientInformationFull,\n type OAuthClientMetadata,\n type OAuthConfig,\n type OAuthTokens,\n} from './client/config.js';\n\n/**\n * Factory function to create an OAuth client provider\n */\nexport { createOAuthProvider } from './client/factory';\n"],"names":["MCPOAuthClientProvider","createStorageAdapter","FileStorage","MemoryStorage","OAuthStorage","DEFAULT_CLIENT_METADATA","generateSessionId","generateState","createOAuthProvider"],"mappings":"AAAA;;;;CAIC,GAED,SAASA,sBAAsB,QAAQ,oBAAoB;AAE3D,SACEC,oBAAoB,EACpBC,WAAW,EACXC,aAAa,EACbC,YAAY,QAEP,sBAAsB;AAE7B,SACEC,uBAAuB,EACvBC,iBAAiB,EACjBC,aAAa,QAMR,qBAAqB;AAE5B;;CAEC,GACD,SAASC,mBAAmB,QAAQ,mBAAmB"}
@@ -0,0 +1,164 @@
1
+ import { _ as _define_property } from "@swc/helpers/_/_define_property";
2
+ import { renderErrorPage, renderSuccessPage } from './templates.js';
3
+ /**
4
+ * OAuth callback server implementation using Bun
5
+ */ export class OAuthCallbackServer {
6
+ /**
7
+ * Start the HTTP server
8
+ */ async start(options) {
9
+ this.options = options;
10
+ if (this.server) {
11
+ throw new Error('Server is already running');
12
+ }
13
+ // Handle abort signal
14
+ if (options.signal?.aborted) {
15
+ throw new Error('Operation aborted');
16
+ }
17
+ const abortHandler = ()=>{
18
+ this.stop().catch(()=>{
19
+ // Ignore errors during cleanup
20
+ });
21
+ };
22
+ if (options.signal) {
23
+ options.signal.addEventListener('abort', abortHandler);
24
+ }
25
+ try {
26
+ this.server = Bun.serve({
27
+ port: options.port,
28
+ hostname: options.hostname || 'localhost',
29
+ fetch: (request)=>this.handleRequest(request),
30
+ error: (error)=>{
31
+ return new Response(`Server error: ${error.message}`, {
32
+ status: 500
33
+ });
34
+ }
35
+ });
36
+ // eslint-disable-next-line no-console
37
+ console.log(`OAuth callback server running on http://${options.hostname || 'localhost'}:${options.port}`);
38
+ } catch (error) {
39
+ if (options.signal) {
40
+ options.signal.removeEventListener('abort', abortHandler);
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+ /**
46
+ * Handle incoming HTTP requests
47
+ */ handleRequest(request) {
48
+ this.options?.onRequest?.(request);
49
+ const url = new URL(request.url);
50
+ const listener = this.callbackListeners.get(url.pathname);
51
+ if (!listener) {
52
+ return new Response('Not Found', {
53
+ status: 404
54
+ });
55
+ }
56
+ // Extract OAuth callback parameters from URL
57
+ const params = {};
58
+ for (const [key, value] of url.searchParams.entries()){
59
+ params[key] = value;
60
+ }
61
+ // Generate appropriate response
62
+ const hasError = Boolean(params.error);
63
+ const html = hasError ? renderErrorPage(params.error, params.error_description, params.error_uri, this.options?.errorHtml) : renderSuccessPage(this.options?.successHtml);
64
+ // Resolve the waiting promise with params (including error params)
65
+ // The caller can check for params.error to determine if it was an OAuth error
66
+ listener.resolve(params);
67
+ // Return HTML response with appropriate status code
68
+ return new Response(html, {
69
+ status: hasError ? 400 : 200,
70
+ headers: {
71
+ 'Content-Type': 'text/html; charset=utf-8',
72
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
73
+ Pragma: 'no-cache',
74
+ Expires: '0'
75
+ }
76
+ });
77
+ }
78
+ /**
79
+ * Wait for OAuth callback on the specified path
80
+ */ async waitForCallback(path, timeout) {
81
+ if (!this.server) {
82
+ throw new Error('Server is not running');
83
+ }
84
+ try {
85
+ return await Promise.race([
86
+ // Promise resolved by handleRequest method
87
+ new Promise((resolve, reject)=>{
88
+ this.callbackListeners.set(path, {
89
+ resolve,
90
+ reject
91
+ });
92
+ }),
93
+ // Timeout promise
94
+ new Promise((_, reject)=>{
95
+ setTimeout(()=>{
96
+ reject(new Error(`OAuth callback timeout after ${timeout}ms waiting for ${path}`));
97
+ }, timeout);
98
+ })
99
+ ]);
100
+ } finally{
101
+ // Always clean up the listener
102
+ this.callbackListeners.delete(path);
103
+ }
104
+ }
105
+ /**
106
+ * Stop the server and clean up resources
107
+ */ async stop() {
108
+ if (this.server) {
109
+ // Reject any pending promises
110
+ const error = new Error('Server stopped');
111
+ for (const listener of this.callbackListeners.values()){
112
+ // Reject in a try-catch to prevent unhandled promise rejections
113
+ try {
114
+ listener.reject(error);
115
+ } catch {
116
+ // Ignore - the caller may have already handled or abandoned this promise
117
+ }
118
+ }
119
+ this.callbackListeners.clear();
120
+ // Stop the server
121
+ await this.server.stop();
122
+ this.server = undefined;
123
+ }
124
+ }
125
+ /**
126
+ * Check if server is running
127
+ */ isRunning() {
128
+ return Boolean(this.server);
129
+ }
130
+ /**
131
+ * Get server URL if running
132
+ */ getServerUrl() {
133
+ if (!this.server || !this.options) {
134
+ return undefined;
135
+ }
136
+ const { hostname = 'localhost', port } = this.options;
137
+ return `http://${hostname}:${port}`;
138
+ }
139
+ constructor(){
140
+ _define_property(this, "server", void 0);
141
+ _define_property(this, "callbackListeners", new Map());
142
+ _define_property(this, "options", void 0);
143
+ }
144
+ }
145
+ /**
146
+ * Create and start a temporary OAuth callback server
147
+ */ export async function createCallbackServer(options) {
148
+ const server = new OAuthCallbackServer();
149
+ await server.start(options);
150
+ return server;
151
+ }
152
+ /**
153
+ * Convenience function to start server, wait for callback, and stop server
154
+ */ export async function waitForOAuthCallback(path, options) {
155
+ const server = await createCallbackServer(options);
156
+ const timeout = options.timeout ?? 30000; // 30 second default
157
+ try {
158
+ return await server.waitForCallback(path, timeout);
159
+ } finally{
160
+ await server.stop();
161
+ }
162
+ }
163
+
164
+ //# sourceMappingURL=callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/callback.ts"],"sourcesContent":["import { renderErrorPage, renderSuccessPage } from './templates.js';\n\n/**\n * OAuth callback result interface\n */\nexport interface CallbackResult {\n /** Authorization code returned by OAuth provider */\n code?: string;\n /** State parameter for CSRF protection */\n state?: string;\n /** OAuth error code (e.g., 'access_denied', 'invalid_request') */\n error?: string;\n /** Human-readable error description */\n error_description?: string;\n /** URI with additional error information */\n error_uri?: string;\n /** Additional query parameters from OAuth provider */\n [key: string]: string | undefined;\n}\n\n/**\n * Configuration options for the OAuth callback server\n */\nexport interface CallbackServerOptions {\n /** Port number to bind the server to */\n port: number;\n /** Hostname to bind the server to (default: \"localhost\") */\n hostname?: string;\n /** Custom HTML content for successful authorization */\n successHtml?: string;\n /** Custom HTML template for error pages */\n errorHtml?: string;\n /** AbortSignal for cancelling the server operation */\n signal?: AbortSignal;\n /** Callback function called for each HTTP request */\n onRequest?: (req: Request) => void;\n}\n\n/**\n * OAuth callback server implementation using Bun\n */\nexport class OAuthCallbackServer {\n private server?: { stop: () => void };\n private callbackListeners = new Map<\n string,\n {\n resolve: (result: CallbackResult) => void;\n reject: (error: Error) => void;\n }\n >();\n private options: CallbackServerOptions | undefined;\n\n /**\n * Start the HTTP server\n */\n async start(options: CallbackServerOptions): Promise<void> {\n this.options = options;\n\n if (this.server) {\n throw new Error('Server is already running');\n }\n\n // Handle abort signal\n if (options.signal?.aborted) {\n throw new Error('Operation aborted');\n }\n\n const abortHandler = () => {\n this.stop().catch(() => {\n // Ignore errors during cleanup\n });\n };\n\n if (options.signal) {\n options.signal.addEventListener('abort', abortHandler);\n }\n\n try {\n this.server = Bun.serve({\n port: options.port,\n hostname: options.hostname || 'localhost',\n fetch: request => this.handleRequest(request),\n error: error => {\n return new Response(`Server error: ${error.message}`, {\n status: 500,\n });\n },\n });\n\n // eslint-disable-next-line no-console\n console.log(\n `OAuth callback server running on http://${options.hostname || 'localhost'}:${options.port}`\n );\n } catch (error) {\n if (options.signal) {\n options.signal.removeEventListener('abort', abortHandler);\n }\n throw error;\n }\n }\n\n /**\n * Handle incoming HTTP requests\n */\n private handleRequest(request: Request): Response {\n this.options?.onRequest?.(request);\n\n const url = new URL(request.url);\n const listener = this.callbackListeners.get(url.pathname);\n\n if (!listener) {\n return new Response('Not Found', { status: 404 });\n }\n\n // Extract OAuth callback parameters from URL\n const params: CallbackResult = {};\n\n for (const [key, value] of url.searchParams.entries()) {\n params[key] = value;\n }\n\n // Generate appropriate response\n const hasError = Boolean(params.error);\n const html = hasError\n ? renderErrorPage(\n params.error,\n params.error_description,\n params.error_uri,\n this.options?.errorHtml\n )\n : renderSuccessPage(this.options?.successHtml);\n\n // Resolve the waiting promise with params (including error params)\n // The caller can check for params.error to determine if it was an OAuth error\n listener.resolve(params);\n\n // Return HTML response with appropriate status code\n return new Response(html, {\n status: hasError ? 400 : 200,\n headers: {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Cache-Control': 'no-cache, no-store, must-revalidate',\n Pragma: 'no-cache',\n Expires: '0',\n },\n });\n }\n\n /**\n * Wait for OAuth callback on the specified path\n */\n async waitForCallback(\n path: string,\n timeout: number\n ): Promise<CallbackResult> {\n if (!this.server) {\n throw new Error('Server is not running');\n }\n\n try {\n return await Promise.race([\n // Promise resolved by handleRequest method\n new Promise<CallbackResult>((resolve, reject) => {\n this.callbackListeners.set(path, { resolve, reject });\n }),\n // Timeout promise\n new Promise<CallbackResult>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `OAuth callback timeout after ${timeout}ms waiting for ${path}`\n )\n );\n }, timeout);\n }),\n ]);\n } finally {\n // Always clean up the listener\n this.callbackListeners.delete(path);\n }\n }\n\n /**\n * Stop the server and clean up resources\n */\n async stop(): Promise<void> {\n if (this.server) {\n // Reject any pending promises\n const error = new Error('Server stopped');\n\n for (const listener of this.callbackListeners.values()) {\n // Reject in a try-catch to prevent unhandled promise rejections\n try {\n listener.reject(error);\n } catch {\n // Ignore - the caller may have already handled or abandoned this promise\n }\n }\n\n this.callbackListeners.clear();\n\n // Stop the server\n await this.server.stop();\n this.server = undefined;\n }\n }\n\n /**\n * Check if server is running\n */\n isRunning(): boolean {\n return Boolean(this.server);\n }\n\n /**\n * Get server URL if running\n */\n getServerUrl(): string | undefined {\n if (!this.server || !this.options) {\n return undefined;\n }\n\n const { hostname = 'localhost', port } = this.options;\n\n return `http://${hostname}:${port}`;\n }\n}\n\n/**\n * Create and start a temporary OAuth callback server\n */\nexport async function createCallbackServer(\n options: CallbackServerOptions\n): Promise<OAuthCallbackServer> {\n const server = new OAuthCallbackServer();\n\n await server.start(options);\n\n return server;\n}\n\n/**\n * Convenience function to start server, wait for callback, and stop server\n */\nexport async function waitForOAuthCallback(\n path: string,\n options: CallbackServerOptions & { timeout?: number }\n): Promise<CallbackResult> {\n const server = await createCallbackServer(options);\n const timeout = options.timeout ?? 30000; // 30 second default\n\n try {\n return await server.waitForCallback(path, timeout);\n } finally {\n await server.stop();\n }\n}\n"],"names":["renderErrorPage","renderSuccessPage","OAuthCallbackServer","start","options","server","Error","signal","aborted","abortHandler","stop","catch","addEventListener","Bun","serve","port","hostname","fetch","request","handleRequest","error","Response","message","status","console","log","removeEventListener","onRequest","url","URL","listener","callbackListeners","get","pathname","params","key","value","searchParams","entries","hasError","Boolean","html","error_description","error_uri","errorHtml","successHtml","resolve","headers","Pragma","Expires","waitForCallback","path","timeout","Promise","race","reject","set","_","setTimeout","delete","values","clear","undefined","isRunning","getServerUrl","Map","createCallbackServer","waitForOAuthCallback"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * OAuth Callback Server Module
3
+ *
4
+ * Provides HTTP server functionality for handling OAuth authorization callbacks
5
+ */ export { createCallbackServer, OAuthCallbackServer, waitForOAuthCallback } from './callback.js';
6
+ export { ERROR_TEMPLATE, renderErrorPage, renderSuccessPage, renderTemplate, SUCCESS_TEMPLATE } from './templates.js';
7
+
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * OAuth Callback Server Module\n *\n * Provides HTTP server functionality for handling OAuth authorization callbacks\n */\n\nexport {\n createCallbackServer,\n OAuthCallbackServer,\n waitForOAuthCallback,\n type CallbackResult,\n type CallbackServerOptions,\n} from './callback.js';\n\nexport {\n ERROR_TEMPLATE,\n renderErrorPage,\n renderSuccessPage,\n renderTemplate,\n SUCCESS_TEMPLATE,\n} from './templates.js';\n"],"names":["createCallbackServer","OAuthCallbackServer","waitForOAuthCallback","ERROR_TEMPLATE","renderErrorPage","renderSuccessPage","renderTemplate","SUCCESS_TEMPLATE"],"mappings":"AAAA;;;;CAIC,GAED,SACEA,oBAAoB,EACpBC,mBAAmB,EACnBC,oBAAoB,QAGf,gBAAgB;AAEvB,SACEC,cAAc,EACdC,eAAe,EACfC,iBAAiB,EACjBC,cAAc,EACdC,gBAAgB,QACX,iBAAiB"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Default success HTML template for OAuth authorization
3
+ */ export const SUCCESS_TEMPLATE = `<!DOCTYPE html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Authorization Successful</title>
9
+ <style>
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
12
+ margin: 0;
13
+ padding: 0;
14
+ background: hsl(0deg, 0%, 7%);
15
+ min-height: 100vh;
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ }
20
+ .container {
21
+ background: hsl(0deg, 0%, 13%);
22
+ padding: 2rem;
23
+ border-radius: 8px;
24
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
25
+ text-align: center;
26
+ max-width: 400px;
27
+ width: 90%;
28
+ border: 1px solid hsl(0deg, 0%, 19%);
29
+ }
30
+ .success-icon {
31
+ width: 64px;
32
+ height: 64px;
33
+ background: #0d1f06;
34
+ border-radius: 50%;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ margin: 0 auto 1rem;
39
+ border: 2px solid #16a34a;
40
+ }
41
+ .checkmark {
42
+ color: #16a34a;
43
+ font-size: 2rem;
44
+ font-weight: bold;
45
+ }
46
+ h1 {
47
+ color: #ebebec;
48
+ margin: 0 0 1rem;
49
+ font-size: 1.5rem;
50
+ }
51
+ p {
52
+ color: #c4c4c5;
53
+ margin: 0 0 1.5rem;
54
+ line-height: 1.5;
55
+ }
56
+ .close-button {
57
+ background: #114eac;
58
+ color: #ebebec;
59
+ border: none;
60
+ padding: 0.75rem 1.5rem;
61
+ border-radius: 6px;
62
+ font-size: 1rem;
63
+ cursor: pointer;
64
+ transition: background-color 0.2s;
65
+ }
66
+ .close-button:hover {
67
+ background: #186ff6;
68
+ }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <div class="container">
73
+ <div class="success-icon">
74
+ <span class="checkmark">✓</span>
75
+ </div>
76
+ <h1>Authorization Successful!</h1>
77
+ <p>You have successfully authorized the application. You can close this window and return to the application.</p>
78
+ <button class="close-button" onclick="window.close()">Close Window</button>
79
+ </div>
80
+ </body>
81
+ </html>`;
82
+ /**
83
+ * Default error HTML template for OAuth authorization
84
+ * Supports placeholder replacement: {{error}}, {{error_description}}, {{error_uri}}
85
+ */ export const ERROR_TEMPLATE = `<!DOCTYPE html>
86
+ <html lang="en">
87
+ <head>
88
+ <meta charset="UTF-8">
89
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
90
+ <title>Authorization Error</title>
91
+ <style>
92
+ body {
93
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
94
+ margin: 0;
95
+ padding: 0;
96
+ background: hsl(0deg, 0%, 7%);
97
+ color: #c4c4c5;
98
+ min-height: 100vh;
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ }
103
+ .container {
104
+ background: hsl(0deg, 0%, 13%);
105
+ padding: 2rem;
106
+ border-radius: 8px;
107
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
108
+ text-align: center;
109
+ max-width: 400px;
110
+ width: 90%;
111
+ border: 1px solid hsl(0deg, 0%, 19%);
112
+ }
113
+ .error-icon {
114
+ width: 64px;
115
+ height: 64px;
116
+ background: rgba(240, 77, 107, 15%);
117
+ border-radius: 50%;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ margin: 0 auto 1rem;
122
+ border: 2px solid #f87171;
123
+ }
124
+ .x-mark {
125
+ color: #f87171;
126
+ font-size: 2rem;
127
+ font-weight: bold;
128
+ }
129
+ h1 {
130
+ color: #ebebec;
131
+ margin: 0 0 1rem;
132
+ font-size: 1.5rem;
133
+ }
134
+ .error-code {
135
+ background: rgba(240, 77, 107, 15%);
136
+ color: #f87171;
137
+ padding: 0.5rem 1rem;
138
+ border-radius: 6px;
139
+ margin: 0 0 1rem;
140
+ font-family: monospace;
141
+ font-size: 0.9rem;
142
+ border: 1px solid #4f4f51;
143
+ }
144
+ p {
145
+ color: #c4c4c5;
146
+ margin: 0 0 1.5rem;
147
+ line-height: 1.5;
148
+ }
149
+ .error-details {
150
+ text-align: left;
151
+ background: hsl(0deg, 0%, 19%);
152
+ padding: 1rem;
153
+ border-radius: 6px;
154
+ margin: 1rem 0;
155
+ border-left: 4px solid #f87171;
156
+ }
157
+ .error-details strong {
158
+ display: block;
159
+ color: #ebebec;
160
+ margin-bottom: 0.5rem;
161
+ }
162
+ .close-button {
163
+ background: #636365;
164
+ color: #ebebec;
165
+ border: none;
166
+ padding: 0.75rem 1.5rem;
167
+ border-radius: 6px;
168
+ font-size: 1rem;
169
+ cursor: pointer;
170
+ transition: background-color 0.2s;
171
+ }
172
+ .close-button:hover {
173
+ background: #767678;
174
+ }
175
+ .retry-link {
176
+ color: #60a5fa;
177
+ text-decoration: none;
178
+ margin-left: 1rem;
179
+ }
180
+ .retry-link:hover {
181
+ text-decoration: underline;
182
+ }
183
+ </style>
184
+ </head>
185
+ <body>
186
+ <div class="container">
187
+ <div class="error-icon">
188
+ <span class="x-mark">✕</span>
189
+ </div>
190
+ <h1>Authorization Failed</h1>
191
+ <div class="error-code">{{error}}</div>
192
+ <p>The authorization request could not be completed.</p>
193
+
194
+ {{#if error_description}}
195
+ <div class="error-details">
196
+ <strong>Error Details:</strong>
197
+ {{error_description}}
198
+ </div>
199
+ {{/if}}
200
+
201
+ {{#if error_uri}}
202
+ <p>
203
+ <a href="{{error_uri}}" target="_blank" class="retry-link">
204
+ More information about this error
205
+ </a>
206
+ </p>
207
+ {{/if}}
208
+
209
+ <button class="close-button" onclick="window.close()">Close Window</button>
210
+ <a href="javascript:history.back()" class="retry-link">Try Again</a>
211
+ </div>
212
+ </body>
213
+ </html>`;
214
+ /**
215
+ * Render HTML template with parameter substitution
216
+ */ export function renderTemplate(template, params) {
217
+ let rendered = template;
218
+ // Simple placeholder replacement
219
+ for (const [key, value] of Object.entries(params)){
220
+ const placeholder = `{{${key}}}`;
221
+ rendered = rendered.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), value || '');
222
+ }
223
+ // Handle conditional blocks
224
+ rendered = rendered.replace(/{{#if (\w+)}}(.*?){{\/if}}/gs, (match, condition, content)=>{
225
+ return params[condition] ? content : '';
226
+ });
227
+ return rendered;
228
+ }
229
+ /**
230
+ * Generate success page HTML
231
+ */ export function renderSuccessPage(customTemplate) {
232
+ return customTemplate || SUCCESS_TEMPLATE;
233
+ }
234
+ /**
235
+ * Generate error page HTML with error details
236
+ */ export function renderErrorPage(error, errorDescription, errorUri, customTemplate) {
237
+ const template = customTemplate || ERROR_TEMPLATE;
238
+ return renderTemplate(template, {
239
+ error: error || 'unknown_error',
240
+ error_description: errorDescription,
241
+ error_uri: errorUri
242
+ });
243
+ }
244
+
245
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/templates.ts"],"sourcesContent":["/**\n * Default success HTML template for OAuth authorization\n */\nexport const SUCCESS_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n margin: 0;\n padding: 0;\n background: hsl(0deg, 0%, 7%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .container {\n background: hsl(0deg, 0%, 13%);\n padding: 2rem;\n border-radius: 8px;\n box-shadow: 0 10px 30px rgba(0,0,0,0.5);\n text-align: center;\n max-width: 400px;\n width: 90%;\n border: 1px solid hsl(0deg, 0%, 19%);\n }\n .success-icon {\n width: 64px;\n height: 64px;\n background: #0d1f06;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 1rem;\n border: 2px solid #16a34a;\n }\n .checkmark {\n color: #16a34a;\n font-size: 2rem;\n font-weight: bold;\n }\n h1 {\n color: #ebebec;\n margin: 0 0 1rem;\n font-size: 1.5rem;\n }\n p {\n color: #c4c4c5;\n margin: 0 0 1.5rem;\n line-height: 1.5;\n }\n .close-button {\n background: #114eac;\n color: #ebebec;\n border: none;\n padding: 0.75rem 1.5rem;\n border-radius: 6px;\n font-size: 1rem;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n .close-button:hover {\n background: #186ff6;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"success-icon\">\n <span class=\"checkmark\">✓</span>\n </div>\n <h1>Authorization Successful!</h1>\n <p>You have successfully authorized the application. You can close this window and return to the application.</p>\n <button class=\"close-button\" onclick=\"window.close()\">Close Window</button>\n </div>\n</body>\n</html>`;\n\n/**\n * Default error HTML template for OAuth authorization\n * Supports placeholder replacement: {{error}}, {{error_description}}, {{error_uri}}\n */\nexport const ERROR_TEMPLATE = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Authorization Error</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n margin: 0;\n padding: 0;\n background: hsl(0deg, 0%, 7%);\n color: #c4c4c5;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .container {\n background: hsl(0deg, 0%, 13%);\n padding: 2rem;\n border-radius: 8px;\n box-shadow: 0 10px 30px rgba(0,0,0,0.5);\n text-align: center;\n max-width: 400px;\n width: 90%;\n border: 1px solid hsl(0deg, 0%, 19%);\n }\n .error-icon {\n width: 64px;\n height: 64px;\n background: rgba(240, 77, 107, 15%);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 1rem;\n border: 2px solid #f87171;\n }\n .x-mark {\n color: #f87171;\n font-size: 2rem;\n font-weight: bold;\n }\n h1 {\n color: #ebebec;\n margin: 0 0 1rem;\n font-size: 1.5rem;\n }\n .error-code {\n background: rgba(240, 77, 107, 15%);\n color: #f87171;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n margin: 0 0 1rem;\n font-family: monospace;\n font-size: 0.9rem;\n border: 1px solid #4f4f51;\n }\n p {\n color: #c4c4c5;\n margin: 0 0 1.5rem;\n line-height: 1.5;\n }\n .error-details {\n text-align: left;\n background: hsl(0deg, 0%, 19%);\n padding: 1rem;\n border-radius: 6px;\n margin: 1rem 0;\n border-left: 4px solid #f87171;\n }\n .error-details strong {\n display: block;\n color: #ebebec;\n margin-bottom: 0.5rem;\n }\n .close-button {\n background: #636365;\n color: #ebebec;\n border: none;\n padding: 0.75rem 1.5rem;\n border-radius: 6px;\n font-size: 1rem;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n .close-button:hover {\n background: #767678;\n }\n .retry-link {\n color: #60a5fa;\n text-decoration: none;\n margin-left: 1rem;\n }\n .retry-link:hover {\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"error-icon\">\n <span class=\"x-mark\">✕</span>\n </div>\n <h1>Authorization Failed</h1>\n <div class=\"error-code\">{{error}}</div>\n <p>The authorization request could not be completed.</p>\n \n {{#if error_description}}\n <div class=\"error-details\">\n <strong>Error Details:</strong>\n {{error_description}}\n </div>\n {{/if}}\n \n {{#if error_uri}}\n <p>\n <a href=\"{{error_uri}}\" target=\"_blank\" class=\"retry-link\">\n More information about this error\n </a>\n </p>\n {{/if}}\n \n <button class=\"close-button\" onclick=\"window.close()\">Close Window</button>\n <a href=\"javascript:history.back()\" class=\"retry-link\">Try Again</a>\n </div>\n</body>\n</html>`;\n\n/**\n * Render HTML template with parameter substitution\n */\nexport function renderTemplate(\n template: string,\n params: Record<string, string | undefined>\n): string {\n let rendered = template;\n\n // Simple placeholder replacement\n for (const [key, value] of Object.entries(params)) {\n const placeholder = `{{${key}}}`;\n\n rendered = rendered.replace(\n new RegExp(placeholder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n value || ''\n );\n }\n\n // Handle conditional blocks\n rendered = rendered.replace(\n /{{#if (\\w+)}}(.*?){{\\/if}}/gs,\n (match, condition, content) => {\n return params[condition] ? content : '';\n }\n );\n\n return rendered;\n}\n\n/**\n * Generate success page HTML\n */\nexport function renderSuccessPage(customTemplate?: string): string {\n return customTemplate || SUCCESS_TEMPLATE;\n}\n\n/**\n * Generate error page HTML with error details\n */\nexport function renderErrorPage(\n error?: string,\n errorDescription?: string,\n errorUri?: string,\n customTemplate?: string\n): string {\n const template = customTemplate || ERROR_TEMPLATE;\n\n return renderTemplate(template, {\n error: error || 'unknown_error',\n error_description: errorDescription,\n error_uri: errorUri,\n });\n}\n"],"names":["SUCCESS_TEMPLATE","ERROR_TEMPLATE","renderTemplate","template","params","rendered","key","value","Object","entries","placeholder","replace","RegExp","match","condition","content","renderSuccessPage","customTemplate","renderErrorPage","error","errorDescription","errorUri","error_description","error_uri"],"mappings":"AAAA;;CAEC,GACD,OAAO,MAAMA,mBAAmB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8E1B,CAAC,CAAC;AAET;;;CAGC,GACD,OAAO,MAAMC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgIxB,CAAC,CAAC;AAET;;CAEC,GACD,OAAO,SAASC,eACdC,QAAgB,EAChBC,MAA0C;IAE1C,IAAIC,WAAWF;IAEf,iCAAiC;IACjC,KAAK,MAAM,CAACG,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACL,QAAS;QACjD,MAAMM,cAAc,CAAC,EAAE,EAAEJ,IAAI,EAAE,CAAC;QAEhCD,WAAWA,SAASM,OAAO,CACzB,IAAIC,OAAOF,YAAYC,OAAO,CAAC,uBAAuB,SAAS,MAC/DJ,SAAS;IAEb;IAEA,4BAA4B;IAC5BF,WAAWA,SAASM,OAAO,CACzB,gCACA,CAACE,OAAOC,WAAWC;QACjB,OAAOX,MAAM,CAACU,UAAU,GAAGC,UAAU;IACvC;IAGF,OAAOV;AACT;AAEA;;CAEC,GACD,OAAO,SAASW,kBAAkBC,cAAuB;IACvD,OAAOA,kBAAkBjB;AAC3B;AAEA;;CAEC,GACD,OAAO,SAASkB,gBACdC,KAAc,EACdC,gBAAyB,EACzBC,QAAiB,EACjBJ,cAAuB;IAEvB,MAAMd,WAAWc,kBAAkBhB;IAEnC,OAAOC,eAAeC,UAAU;QAC9BgB,OAAOA,SAAS;QAChBG,mBAAmBF;QACnBG,WAAWF;IACb;AACF"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "mcp-oauth-provider",
3
+ "version": "0.0.1",
4
+ "description": "MCP OAuth client provider for streamable HTTP clients",
5
+ "author": "Clutch Creator",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "packageManager": "bun@1.2.14",
11
+ "keywords": [
12
+ "oauth",
13
+ "mcp",
14
+ "modelcontextprotocol"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./src/index.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./server": {
22
+ "types": "./src/server/index.ts",
23
+ "default": "./dist/server/index.js"
24
+ }
25
+ },
26
+ "type": "module",
27
+ "files": [
28
+ "dist",
29
+ "src",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "swc src --out-dir dist --config-file ./.swcrc --delete-dir-on-start --strip-leading-paths",
34
+ "dev": "bun run build --watch",
35
+ "lint": "eslint .",
36
+ "format": "prettier --write .",
37
+ "format:check": "prettier --check .",
38
+ "check-types": "tsc --noEmit",
39
+ "check-types:watch": "tsc --noEmit --watch",
40
+ "changeset": "changeset",
41
+ "version-packages": "changeset version",
42
+ "release": "bun run build && changeset publish",
43
+ "test": "bun test",
44
+ "test:watch": "bun test --watch",
45
+ "server:success": "bun run scripts/server-success.ts",
46
+ "server:error": "bun run scripts/server-error.ts"
47
+ },
48
+ "dependencies": {
49
+ "@swc/helpers": "^0.5.17",
50
+ "@modelcontextprotocol/sdk": "^1.20.1"
51
+ },
52
+ "devDependencies": {
53
+ "@changesets/changelog-git": "^0.2.1",
54
+ "@changesets/cli": "^2.29.7",
55
+ "@eslint/js": "^9.38.0",
56
+ "@swc/cli": "^0.7.8",
57
+ "@swc/core": "^1.13.5",
58
+ "@types/bun": "^1.3.0",
59
+ "chokidar": "^4.0.3",
60
+ "eslint": "^9.38.0",
61
+ "eslint-config-prettier": "^10.1.8",
62
+ "prettier": "^3.6.2",
63
+ "typescript": "^5.9.3",
64
+ "typescript-eslint": "^8.46.1"
65
+ }
66
+ }