@platf/bridge 0.0.13 → 0.0.15

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.
@@ -26,15 +26,21 @@ export function createDiscoveryRouter(auth, logger) {
26
26
  * The `resource` field MUST match the URL the client is accessing (RFC 9728 §2).
27
27
  * For path-suffixed requests like /.well-known/oauth-protected-resource/mcp,
28
28
  * the resource is the path after the well-known prefix.
29
+ *
30
+ * IMPORTANT: We advertise the BRIDGE as the authorization_server (not the upstream issuer).
31
+ * This ensures clients use our proxied AS metadata (which patches registration_endpoint),
32
+ * rather than going directly to the upstream auth server's DCR endpoint.
29
33
  */
30
34
  router.get('/.well-known/oauth-protected-resource', (req, res) => {
31
35
  // Exact match — client is looking for the root resource
32
36
  // Return /mcp as that's our protected endpoint
33
37
  const scheme = req.protocol;
34
38
  const host = req.get('host');
39
+ const bridgeOrigin = `${scheme}://${host}`;
35
40
  const resourceMetadata = {
36
- resource: `${scheme}://${host}/mcp`,
37
- authorization_servers: [auth.issuer],
41
+ resource: `${bridgeOrigin}/mcp`,
42
+ // Point to OURSELVES so clients use our proxied AS metadata
43
+ authorization_servers: [bridgeOrigin],
38
44
  scopes_supported: ['openid', 'profile', 'email'],
39
45
  bearer_methods_supported: ['header'],
40
46
  };
@@ -44,10 +50,12 @@ export function createDiscoveryRouter(auth, logger) {
44
50
  // Path-suffixed request — the suffix IS the protected resource path
45
51
  const scheme = req.protocol;
46
52
  const host = req.get('host');
53
+ const bridgeOrigin = `${scheme}://${host}`;
47
54
  const resourcePath = '/' + req.params[0];
48
55
  const resourceMetadata = {
49
- resource: `${scheme}://${host}${resourcePath}`,
50
- authorization_servers: [auth.issuer],
56
+ resource: `${bridgeOrigin}${resourcePath}`,
57
+ // Point to OURSELVES so clients use our proxied AS metadata
58
+ authorization_servers: [bridgeOrigin],
51
59
  scopes_supported: ['openid', 'profile', 'email'],
52
60
  bearer_methods_supported: ['header'],
53
61
  };
@@ -101,6 +109,69 @@ export function createDiscoveryRouter(auth, logger) {
101
109
  redirect_uris: Array.isArray(body.redirect_uris) ? body.redirect_uris : [],
102
110
  });
103
111
  });
112
+ /**
113
+ * OAuth Authorization Endpoint — Redirect to upstream
114
+ *
115
+ * Since the bridge advertises itself as the authorization_server,
116
+ * clients will attempt to call /oauth/authorize here. We redirect
117
+ * to the upstream auth server, preserving all query parameters.
118
+ */
119
+ router.get('/oauth/authorize', (req, res) => {
120
+ const upstreamUrl = new URL(`${auth.issuer}/oauth/authorize`);
121
+ // Copy all query params to upstream
122
+ for (const [key, value] of Object.entries(req.query)) {
123
+ if (typeof value === 'string') {
124
+ upstreamUrl.searchParams.set(key, value);
125
+ }
126
+ }
127
+ logger.info(`[discovery] Redirecting /oauth/authorize to ${upstreamUrl.toString().slice(0, 100)}...`);
128
+ res.redirect(upstreamUrl.toString());
129
+ });
130
+ /**
131
+ * OAuth Token Endpoint — Proxy to upstream
132
+ *
133
+ * Proxies token exchange requests to the upstream auth server.
134
+ */
135
+ router.post('/oauth/token', async (req, res) => {
136
+ try {
137
+ const upstreamUrl = `${auth.issuer}/oauth/token`;
138
+ logger.info('[discovery] Proxying /oauth/token to upstream');
139
+ const upstreamRes = await fetch(upstreamUrl, {
140
+ method: 'POST',
141
+ headers: {
142
+ 'Content-Type': req.get('Content-Type') || 'application/x-www-form-urlencoded',
143
+ },
144
+ body: req.get('Content-Type')?.includes('application/json')
145
+ ? JSON.stringify(req.body)
146
+ : new URLSearchParams(req.body).toString(),
147
+ });
148
+ const data = await upstreamRes.text();
149
+ res.status(upstreamRes.status);
150
+ res.set('Content-Type', upstreamRes.headers.get('Content-Type') || 'application/json');
151
+ res.send(data);
152
+ }
153
+ catch (err) {
154
+ logger.error('[discovery] Error proxying /oauth/token:', err.message ?? err);
155
+ res.status(502).json({ error: 'upstream_error' });
156
+ }
157
+ });
158
+ /**
159
+ * JWKS Endpoint — Proxy to upstream
160
+ *
161
+ * Proxies JSON Web Key Set requests for token verification.
162
+ */
163
+ router.get('/jwks', async (req, res) => {
164
+ try {
165
+ const upstreamUrl = `${auth.issuer}/jwks`;
166
+ const upstreamRes = await fetch(upstreamUrl);
167
+ const data = await upstreamRes.json();
168
+ res.json(data);
169
+ }
170
+ catch (err) {
171
+ logger.error('[discovery] Error proxying /jwks:', err.message ?? err);
172
+ res.status(502).json({ error: 'upstream_error' });
173
+ }
174
+ });
104
175
  return router;
105
176
  }
106
177
  //# sourceMappingURL=discoveryRoutes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discoveryRoutes.js","sourceRoot":"","sources":["../../src/lib/discoveryRoutes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAA+B,MAAM,SAAS,CAAA;AAG7D,MAAM,UAAU,qBAAqB,CAAC,IAAgB,EAAE,MAAc;IACpE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA;IAEvB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,GAAG,CAAC,uCAAuC,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAClF,wDAAwD;QACxD,+CAA+C;QAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAA;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC5B,MAAM,gBAAgB,GAAG;YACvB,QAAQ,EAAE,GAAG,MAAM,MAAM,IAAI,MAAM;YACnC,qBAAqB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;YACpC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;YAChD,wBAAwB,EAAE,CAAC,QAAQ,CAAC;SACrC,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,GAAG,CAAC,yCAAyC,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACpF,oEAAoE;QACpE,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAA;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC5B,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACxC,MAAM,gBAAgB,GAAG;YACvB,QAAQ,EAAE,GAAG,MAAM,MAAM,IAAI,GAAG,YAAY,EAAE;YAC9C,qBAAqB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;YACpC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;YAChD,wBAAwB,EAAE,CAAC,QAAQ,CAAC;SACrC,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,CAAC,0CAA0C,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC3F,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,MAAM,yCAAyC,CAAA;YAC3E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAA;YAEzC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,CAAC,KAAK,CAAC,4CAA4C,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;gBAC3E,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAA;YAC1D,CAAC;YAED,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAA;YAEnE,yDAAyD;YACzD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAA;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC5B,QAAQ,CAAC,qBAAqB,GAAG,GAAG,MAAM,MAAM,IAAI,iBAAiB,CAAA;YAErE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACpB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;YAC3E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAA;QACnD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAA;QAC3B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,SAAS,EAAE,IAAI,CAAC,QAAQ;YACxB,WAAW,EAAE,cAAc;YAC3B,8CAA8C;YAC9C,0BAA0B,EAAE,MAAM;YAClC,WAAW,EAAE,CAAC,oBAAoB,CAAC;YACnC,cAAc,EAAE,CAAC,MAAM,CAAC;YACxB,aAAa,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE;SAC3E,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"discoveryRoutes.js","sourceRoot":"","sources":["../../src/lib/discoveryRoutes.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platf/bridge",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Stdio-to-Streamable HTTP bridge for MCP servers — Platf AI Hub",
5
5
  "module": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -30,15 +30,21 @@ export function createDiscoveryRouter(auth: AuthConfig, logger: Logger): Router
30
30
  * The `resource` field MUST match the URL the client is accessing (RFC 9728 §2).
31
31
  * For path-suffixed requests like /.well-known/oauth-protected-resource/mcp,
32
32
  * the resource is the path after the well-known prefix.
33
+ *
34
+ * IMPORTANT: We advertise the BRIDGE as the authorization_server (not the upstream issuer).
35
+ * This ensures clients use our proxied AS metadata (which patches registration_endpoint),
36
+ * rather than going directly to the upstream auth server's DCR endpoint.
33
37
  */
34
38
  router.get('/.well-known/oauth-protected-resource', (req: Request, res: Response) => {
35
39
  // Exact match — client is looking for the root resource
36
40
  // Return /mcp as that's our protected endpoint
37
41
  const scheme = req.protocol
38
42
  const host = req.get('host')
43
+ const bridgeOrigin = `${scheme}://${host}`
39
44
  const resourceMetadata = {
40
- resource: `${scheme}://${host}/mcp`,
41
- authorization_servers: [auth.issuer],
45
+ resource: `${bridgeOrigin}/mcp`,
46
+ // Point to OURSELVES so clients use our proxied AS metadata
47
+ authorization_servers: [bridgeOrigin],
42
48
  scopes_supported: ['openid', 'profile', 'email'],
43
49
  bearer_methods_supported: ['header'],
44
50
  }
@@ -49,10 +55,12 @@ export function createDiscoveryRouter(auth: AuthConfig, logger: Logger): Router
49
55
  // Path-suffixed request — the suffix IS the protected resource path
50
56
  const scheme = req.protocol
51
57
  const host = req.get('host')
58
+ const bridgeOrigin = `${scheme}://${host}`
52
59
  const resourcePath = '/' + req.params[0]
53
60
  const resourceMetadata = {
54
- resource: `${scheme}://${host}${resourcePath}`,
55
- authorization_servers: [auth.issuer],
61
+ resource: `${bridgeOrigin}${resourcePath}`,
62
+ // Point to OURSELVES so clients use our proxied AS metadata
63
+ authorization_servers: [bridgeOrigin],
56
64
  scopes_supported: ['openid', 'profile', 'email'],
57
65
  bearer_methods_supported: ['header'],
58
66
  }
@@ -112,5 +120,71 @@ export function createDiscoveryRouter(auth: AuthConfig, logger: Logger): Router
112
120
  })
113
121
  })
114
122
 
123
+ /**
124
+ * OAuth Authorization Endpoint — Redirect to upstream
125
+ *
126
+ * Since the bridge advertises itself as the authorization_server,
127
+ * clients will attempt to call /oauth/authorize here. We redirect
128
+ * to the upstream auth server, preserving all query parameters.
129
+ */
130
+ router.get('/oauth/authorize', (req: Request, res: Response) => {
131
+ const upstreamUrl = new URL(`${auth.issuer}/oauth/authorize`)
132
+ // Copy all query params to upstream
133
+ for (const [key, value] of Object.entries(req.query)) {
134
+ if (typeof value === 'string') {
135
+ upstreamUrl.searchParams.set(key, value)
136
+ }
137
+ }
138
+ logger.info(`[discovery] Redirecting /oauth/authorize to ${upstreamUrl.toString().slice(0, 100)}...`)
139
+ res.redirect(upstreamUrl.toString())
140
+ })
141
+
142
+ /**
143
+ * OAuth Token Endpoint — Proxy to upstream
144
+ *
145
+ * Proxies token exchange requests to the upstream auth server.
146
+ */
147
+ router.post('/oauth/token', async (req: Request, res: Response) => {
148
+ try {
149
+ const upstreamUrl = `${auth.issuer}/oauth/token`
150
+ logger.info('[discovery] Proxying /oauth/token to upstream')
151
+
152
+ const upstreamRes = await fetch(upstreamUrl, {
153
+ method: 'POST',
154
+ headers: {
155
+ 'Content-Type': req.get('Content-Type') || 'application/x-www-form-urlencoded',
156
+ },
157
+ body: req.get('Content-Type')?.includes('application/json')
158
+ ? JSON.stringify(req.body)
159
+ : new URLSearchParams(req.body as Record<string, string>).toString(),
160
+ })
161
+
162
+ const data = await upstreamRes.text()
163
+ res.status(upstreamRes.status)
164
+ res.set('Content-Type', upstreamRes.headers.get('Content-Type') || 'application/json')
165
+ res.send(data)
166
+ } catch (err: any) {
167
+ logger.error('[discovery] Error proxying /oauth/token:', err.message ?? err)
168
+ res.status(502).json({ error: 'upstream_error' })
169
+ }
170
+ })
171
+
172
+ /**
173
+ * JWKS Endpoint — Proxy to upstream
174
+ *
175
+ * Proxies JSON Web Key Set requests for token verification.
176
+ */
177
+ router.get('/jwks', async (req: Request, res: Response) => {
178
+ try {
179
+ const upstreamUrl = `${auth.issuer}/jwks`
180
+ const upstreamRes = await fetch(upstreamUrl)
181
+ const data = await upstreamRes.json()
182
+ res.json(data)
183
+ } catch (err: any) {
184
+ logger.error('[discovery] Error proxying /jwks:', err.message ?? err)
185
+ res.status(502).json({ error: 'upstream_error' })
186
+ }
187
+ })
188
+
115
189
  return router
116
190
  }