@platf/bridge 0.0.16 → 0.0.18

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 (40) hide show
  1. package/dist/gateways/statefulBridge.d.ts +10 -7
  2. package/dist/gateways/statefulBridge.js +79 -139
  3. package/dist/gateways/statefulBridge.js.map +1 -1
  4. package/dist/gateways/statelessBridge.d.ts +10 -7
  5. package/dist/gateways/statelessBridge.js +85 -166
  6. package/dist/gateways/statelessBridge.js.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/childProcess.d.ts +37 -0
  9. package/dist/lib/childProcess.js +80 -0
  10. package/dist/lib/childProcess.js.map +1 -0
  11. package/dist/lib/config.d.ts +6 -0
  12. package/dist/lib/config.js +7 -0
  13. package/dist/lib/config.js.map +1 -0
  14. package/dist/lib/discoveryRoutes.d.ts +3 -1
  15. package/dist/lib/discoveryRoutes.js +7 -68
  16. package/dist/lib/discoveryRoutes.js.map +1 -1
  17. package/dist/lib/express.d.ts +33 -0
  18. package/dist/lib/express.js +65 -0
  19. package/dist/lib/express.js.map +1 -0
  20. package/dist/lib/getLogger.d.ts +2 -2
  21. package/dist/lib/getLogger.js.map +1 -1
  22. package/dist/lib/mcpMessages.d.ts +10 -0
  23. package/dist/lib/mcpMessages.js +35 -0
  24. package/dist/lib/mcpMessages.js.map +1 -0
  25. package/dist/lib/oauthProxy.d.ts +14 -0
  26. package/dist/lib/oauthProxy.js +80 -0
  27. package/dist/lib/oauthProxy.js.map +1 -0
  28. package/dist/types.d.ts +3 -0
  29. package/package.json +1 -1
  30. package/src/gateways/statefulBridge.ts +87 -167
  31. package/src/gateways/statelessBridge.ts +100 -218
  32. package/src/index.ts +2 -2
  33. package/src/lib/childProcess.ts +120 -0
  34. package/src/lib/config.ts +9 -0
  35. package/src/lib/discoveryRoutes.ts +7 -71
  36. package/src/lib/express.ts +82 -0
  37. package/src/lib/getLogger.ts +4 -3
  38. package/src/lib/mcpMessages.ts +42 -0
  39. package/src/lib/oauthProxy.ts +86 -0
  40. package/src/types.ts +4 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared Express application factory.
3
+ *
4
+ * Creates and configures the common Express setup used by both
5
+ * stateful and stateless bridges.
6
+ */
7
+
8
+ import express, { type Express, type Response } from 'express'
9
+ import cors, { type CorsOptions } from 'cors'
10
+ import type { AuthConfig, Logger } from '../types.js'
11
+ import { serializeCorsOrigin } from './cors.js'
12
+ import { createDiscoveryRouter } from './discoveryRoutes.js'
13
+ import { createOAuthProxyRouter } from './oauthProxy.js'
14
+ import { createAuthMiddleware } from './authMiddleware.js'
15
+
16
+ export interface CreateAppOptions {
17
+ logger: Logger
18
+ corsOrigin: CorsOptions['origin']
19
+ healthEndpoints: string[]
20
+ headers: Record<string, string>
21
+ auth: AuthConfig | null
22
+ /** Path for the MCP endpoint (used to apply auth middleware) */
23
+ mcpPath: string
24
+ }
25
+
26
+ /** Set custom response headers */
27
+ export const setResponseHeaders = (res: Response, headers: Record<string, string>) =>
28
+ Object.entries(headers).forEach(([key, value]) => res.setHeader(key, value))
29
+
30
+ /**
31
+ * Create and configure an Express application with shared middleware.
32
+ *
33
+ * Sets up:
34
+ * - JSON body parser
35
+ * - Trust proxy (for X-Forwarded-* headers)
36
+ * - CORS (if configured)
37
+ * - Health endpoints
38
+ * - OAuth discovery routes (if auth enabled)
39
+ * - OAuth proxy routes (if auth enabled)
40
+ * - Auth middleware on mcpPath (if auth enabled)
41
+ */
42
+ export function createApp(options: CreateAppOptions): Express {
43
+ const { logger, corsOrigin, healthEndpoints, headers, auth, mcpPath } = options
44
+
45
+ const app = express()
46
+ app.set('trust proxy', true)
47
+ app.use(express.json())
48
+
49
+ // CORS
50
+ if (corsOrigin) {
51
+ app.use(cors({ origin: corsOrigin, exposedHeaders: ['Mcp-Session-Id'] }))
52
+ logger.info(` - CORS: enabled (${serializeCorsOrigin(corsOrigin)})`)
53
+ } else {
54
+ logger.info(' - CORS: disabled')
55
+ }
56
+
57
+ // Health endpoints
58
+ for (const ep of healthEndpoints) {
59
+ app.get(ep, (_req, res) => {
60
+ setResponseHeaders(res, headers)
61
+ res.send('ok')
62
+ })
63
+ }
64
+ if (healthEndpoints.length) {
65
+ logger.info(` - Health endpoints: ${healthEndpoints.join(', ')}`)
66
+ }
67
+
68
+ // OAuth (when auth is enabled)
69
+ if (auth) {
70
+ // Discovery routes (PRM, AS metadata, pseudo-DCR)
71
+ app.use(createDiscoveryRouter(auth, logger))
72
+ // OAuth proxy routes (authorize redirect, token proxy, JWKS proxy)
73
+ app.use(createOAuthProxyRouter(auth, logger))
74
+ // Auth middleware on MCP path
75
+ app.use(mcpPath, createAuthMiddleware(auth, logger))
76
+ logger.info(` - Auth: enabled (issuer=${auth.issuer})`)
77
+ } else {
78
+ logger.info(' - Auth: disabled')
79
+ }
80
+
81
+ return app
82
+ }
@@ -1,5 +1,5 @@
1
1
  import util from 'node:util'
2
- import type { Logger } from '../types.js'
2
+ import type { Logger, LogLevel } from '../types.js'
3
3
 
4
4
  const defaultFormatArgs = (args: unknown[]) => args
5
5
 
@@ -40,10 +40,11 @@ const debugLogger: Logger = {
40
40
  error: logStderr({ formatArgs: debugFormatArgs }),
41
41
  }
42
42
 
43
- export type LogLevel = 'none' | 'info' | 'debug'
44
-
45
43
  export function getLogger(logLevel: LogLevel): Logger {
46
44
  if (logLevel === 'none') return noneLogger
47
45
  if (logLevel === 'debug') return debugLogger
48
46
  return infoLogger
49
47
  }
48
+
49
+ // Re-export LogLevel for backward compatibility
50
+ export type { LogLevel }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * MCP message helpers for auto-initialization and protocol handling.
3
+ */
4
+
5
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'
6
+ import { VERSION, SERVER_NAME } from './config.js'
7
+
8
+ /** Create a synthetic MCP initialize request */
9
+ export function createInitializeRequest(
10
+ id: string | number,
11
+ protocolVersion: string,
12
+ ): JSONRPCMessage {
13
+ return {
14
+ jsonrpc: '2.0',
15
+ id,
16
+ method: 'initialize',
17
+ params: {
18
+ protocolVersion,
19
+ capabilities: {
20
+ roots: { listChanged: true },
21
+ sampling: {},
22
+ },
23
+ clientInfo: {
24
+ name: SERVER_NAME,
25
+ version: VERSION,
26
+ },
27
+ },
28
+ }
29
+ }
30
+
31
+ /** Create an initialized notification */
32
+ export function createInitializedNotification(): JSONRPCMessage {
33
+ return {
34
+ jsonrpc: '2.0',
35
+ method: 'notifications/initialized',
36
+ }
37
+ }
38
+
39
+ /** Generate a unique ID for auto-init requests */
40
+ export function generateAutoInitId(): string {
41
+ return `init_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`
42
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OAuth 2.0 proxy routes for the bridge.
3
+ *
4
+ * These routes proxy OAuth endpoints to the upstream authorization server:
5
+ * - GET /authorize → Redirect to upstream (preserves query params)
6
+ * - POST /token → Proxy to upstream
7
+ * - GET /jwks → Proxy JWKS for token verification
8
+ *
9
+ * This separation allows the bridge to advertise itself as the authorization
10
+ * server while delegating actual auth operations to the upstream issuer.
11
+ */
12
+
13
+ import { Router, type Request, type Response } from 'express'
14
+ import type { AuthConfig, Logger } from '../types.js'
15
+
16
+ export function createOAuthProxyRouter(auth: AuthConfig, logger: Logger): Router {
17
+ const router = Router()
18
+
19
+ /**
20
+ * OAuth Authorization Endpoint — Redirect to upstream
21
+ *
22
+ * Since the bridge advertises itself as the authorization_server,
23
+ * clients will attempt to call /authorize here. We redirect
24
+ * to the upstream auth server, preserving all query parameters.
25
+ */
26
+ router.get('/authorize', (req: Request, res: Response) => {
27
+ const upstreamUrl = new URL(`${auth.issuer}/oauth/authorize`)
28
+ // Copy all query params to upstream
29
+ for (const [key, value] of Object.entries(req.query)) {
30
+ if (typeof value === 'string') {
31
+ upstreamUrl.searchParams.set(key, value)
32
+ }
33
+ }
34
+ logger.info(`[oauth-proxy] Redirecting /authorize to upstream`)
35
+ res.redirect(upstreamUrl.toString())
36
+ })
37
+
38
+ /**
39
+ * OAuth Token Endpoint — Proxy to upstream
40
+ *
41
+ * Proxies token exchange requests to the upstream auth server.
42
+ */
43
+ router.post('/token', async (req: Request, res: Response) => {
44
+ try {
45
+ const upstreamUrl = `${auth.issuer}/oauth/token`
46
+ logger.info('[oauth-proxy] Proxying /token to upstream')
47
+
48
+ const upstreamRes = await fetch(upstreamUrl, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': req.get('Content-Type') || 'application/x-www-form-urlencoded',
52
+ },
53
+ body: req.get('Content-Type')?.includes('application/json')
54
+ ? JSON.stringify(req.body)
55
+ : new URLSearchParams(req.body as Record<string, string>).toString(),
56
+ })
57
+
58
+ const data = await upstreamRes.text()
59
+ res.status(upstreamRes.status)
60
+ res.set('Content-Type', upstreamRes.headers.get('Content-Type') || 'application/json')
61
+ res.send(data)
62
+ } catch (err: any) {
63
+ logger.error('[oauth-proxy] Error proxying /token:', err.message ?? err)
64
+ res.status(502).json({ error: 'upstream_error' })
65
+ }
66
+ })
67
+
68
+ /**
69
+ * JWKS Endpoint — Proxy to upstream
70
+ *
71
+ * Proxies JSON Web Key Set requests for token verification.
72
+ */
73
+ router.get('/jwks', async (_req: Request, res: Response) => {
74
+ try {
75
+ const upstreamUrl = `${auth.issuer}/jwks`
76
+ const upstreamRes = await fetch(upstreamUrl)
77
+ const data = await upstreamRes.json()
78
+ res.json(data)
79
+ } catch (err: any) {
80
+ logger.error('[oauth-proxy] Error proxying /jwks:', err.message ?? err)
81
+ res.status(502).json({ error: 'upstream_error' })
82
+ }
83
+ })
84
+
85
+ return router
86
+ }
package/src/types.ts CHANGED
@@ -1,8 +1,12 @@
1
+ /** Logger interface for structured logging */
1
2
  export interface Logger {
2
3
  info: (...args: unknown[]) => void
3
4
  error: (...args: unknown[]) => void
4
5
  }
5
6
 
7
+ /** Log level for the logger */
8
+ export type LogLevel = 'none' | 'info' | 'debug'
9
+
6
10
  /** OAuth auth config passed when --authIssuer is set */
7
11
  export interface AuthConfig {
8
12
  /** OAuth issuer URL (e.g. https://auth.platf.ai) */