@platf/bridge 0.0.2

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/gateways/statefulBridge.d.ts +22 -0
  4. package/dist/gateways/statefulBridge.js +211 -0
  5. package/dist/gateways/statefulBridge.js.map +1 -0
  6. package/dist/gateways/statelessBridge.d.ts +22 -0
  7. package/dist/gateways/statelessBridge.js +234 -0
  8. package/dist/gateways/statelessBridge.js.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +117 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/lib/authMiddleware.d.ts +12 -0
  13. package/dist/lib/authMiddleware.js +41 -0
  14. package/dist/lib/authMiddleware.js.map +1 -0
  15. package/dist/lib/cors.d.ts +3 -0
  16. package/dist/lib/cors.js +29 -0
  17. package/dist/lib/cors.js.map +1 -0
  18. package/dist/lib/discoveryRoutes.d.ts +14 -0
  19. package/dist/lib/discoveryRoutes.js +86 -0
  20. package/dist/lib/discoveryRoutes.js.map +1 -0
  21. package/dist/lib/getLogger.d.ts +3 -0
  22. package/dist/lib/getLogger.js +34 -0
  23. package/dist/lib/getLogger.js.map +1 -0
  24. package/dist/lib/headers.d.ts +2 -0
  25. package/dist/lib/headers.js +19 -0
  26. package/dist/lib/headers.js.map +1 -0
  27. package/dist/lib/onSignals.d.ts +6 -0
  28. package/dist/lib/onSignals.js +16 -0
  29. package/dist/lib/onSignals.js.map +1 -0
  30. package/dist/lib/sessionAccessCounter.d.ts +11 -0
  31. package/dist/lib/sessionAccessCounter.js +72 -0
  32. package/dist/lib/sessionAccessCounter.js.map +1 -0
  33. package/dist/types.d.ts +11 -0
  34. package/dist/types.js +2 -0
  35. package/dist/types.js.map +1 -0
  36. package/package.json +41 -0
  37. package/src/gateways/statefulBridge.ts +271 -0
  38. package/src/gateways/statelessBridge.ts +310 -0
  39. package/src/index.ts +121 -0
  40. package/src/lib/authMiddleware.ts +58 -0
  41. package/src/lib/cors.ts +30 -0
  42. package/src/lib/discoveryRoutes.ts +95 -0
  43. package/src/lib/getLogger.ts +49 -0
  44. package/src/lib/headers.ts +26 -0
  45. package/src/lib/onSignals.ts +24 -0
  46. package/src/lib/sessionAccessCounter.ts +98 -0
  47. package/src/types.ts +12 -0
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import yargs from 'yargs';
3
+ import { hideBin } from 'yargs/helpers';
4
+ import { getLogger } from './lib/getLogger.js';
5
+ import { parseCorsOrigin } from './lib/cors.js';
6
+ import { parseHeaders } from './lib/headers.js';
7
+ import { startStatelessBridge } from './gateways/statelessBridge.js';
8
+ import { startStatefulBridge } from './gateways/statefulBridge.js';
9
+ const argv = await yargs(hideBin(process.argv))
10
+ .scriptName('platf-bridge')
11
+ .usage('$0 — Stdio-to-Streamable HTTP bridge for MCP servers')
12
+ .option('stdio', {
13
+ type: 'string',
14
+ demandOption: true,
15
+ describe: 'Shell command that speaks MCP over stdio (stdin/stdout)',
16
+ })
17
+ .option('port', {
18
+ type: 'number',
19
+ default: 8000,
20
+ describe: 'HTTP port to listen on',
21
+ })
22
+ .option('path', {
23
+ type: 'string',
24
+ default: '/mcp',
25
+ describe: 'HTTP path for the Streamable HTTP endpoint',
26
+ })
27
+ .option('stateful', {
28
+ type: 'boolean',
29
+ default: false,
30
+ describe: 'Enable stateful mode (session-based, persistent child process per session)',
31
+ })
32
+ .option('sessionTimeout', {
33
+ type: 'number',
34
+ describe: 'Session inactivity timeout in milliseconds (stateful mode only)',
35
+ })
36
+ .option('protocolVersion', {
37
+ type: 'string',
38
+ default: '2025-03-26',
39
+ describe: 'MCP protocol version for auto-initialization (stateless mode)',
40
+ })
41
+ .option('logLevel', {
42
+ type: 'string',
43
+ choices: ['none', 'info', 'debug'],
44
+ default: 'info',
45
+ describe: 'Log verbosity',
46
+ })
47
+ .option('cors', {
48
+ type: 'array',
49
+ describe: 'CORS origins to allow (omit for no CORS, pass * for all)',
50
+ })
51
+ .option('healthEndpoint', {
52
+ type: 'array',
53
+ string: true,
54
+ default: [],
55
+ describe: 'Path(s) that return 200 "ok" for health checks',
56
+ })
57
+ .option('header', {
58
+ type: 'array',
59
+ default: [],
60
+ describe: 'Additional response headers in "Key: Value" format',
61
+ })
62
+ .option('authIssuer', {
63
+ type: 'string',
64
+ describe: 'OAuth issuer URL (e.g. https://auth.platf.ai). Enables auth when set.',
65
+ })
66
+ .option('authClientId', {
67
+ type: 'string',
68
+ describe: 'OAuth client_id for this bridge instance (pre-registered with auth issuer)',
69
+ })
70
+ .strict()
71
+ .help()
72
+ .parse();
73
+ const logger = getLogger(argv.logLevel);
74
+ const corsOrigin = parseCorsOrigin(argv.cors);
75
+ const headers = parseHeaders(argv.header, logger);
76
+ // Build auth config (only when --authIssuer is provided)
77
+ let auth = null;
78
+ if (argv.authIssuer) {
79
+ if (!argv.authClientId) {
80
+ console.error('Error: --authClientId is required when --authIssuer is set');
81
+ process.exit(1);
82
+ }
83
+ auth = { issuer: argv.authIssuer.replace(/\/$/, ''), clientId: argv.authClientId };
84
+ logger.info(` Auth: enabled (issuer=${auth.issuer}, clientId=${auth.clientId})`);
85
+ }
86
+ else {
87
+ logger.info(' Auth: disabled');
88
+ }
89
+ logger.info('platf-bridge starting...');
90
+ logger.info(` Mode: ${argv.stateful ? 'stateful' : 'stateless'}`);
91
+ if (argv.stateful) {
92
+ await startStatefulBridge({
93
+ stdioCmd: argv.stdio,
94
+ port: argv.port,
95
+ path: argv.path,
96
+ logger,
97
+ corsOrigin,
98
+ healthEndpoints: argv.healthEndpoint,
99
+ headers,
100
+ sessionTimeout: argv.sessionTimeout ?? null,
101
+ auth,
102
+ });
103
+ }
104
+ else {
105
+ await startStatelessBridge({
106
+ stdioCmd: argv.stdio,
107
+ port: argv.port,
108
+ path: argv.path,
109
+ logger,
110
+ corsOrigin,
111
+ healthEndpoints: argv.healthEndpoint,
112
+ headers,
113
+ protocolVersion: argv.protocolVersion,
114
+ auth,
115
+ });
116
+ }
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OAuth 2.0 JWT auth middleware for the bridge.
3
+ *
4
+ * - Fetches JWKS from `{issuer}/jwks` using jose's createRemoteJWKSet (auto-caches).
5
+ * - Validates Bearer token: signature, `iss`, `exp`.
6
+ * - On failure returns 401 with RFC 9728–compliant `WWW-Authenticate` header
7
+ * pointing at the OAuth Protected Resource Metadata document.
8
+ */
9
+ import type { RequestHandler } from 'express';
10
+ import type { AuthConfig, Logger } from '../types.js';
11
+ /** Build a reusable auth middleware for the given auth config */
12
+ export declare function createAuthMiddleware(auth: AuthConfig, logger: Logger): RequestHandler;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * OAuth 2.0 JWT auth middleware for the bridge.
3
+ *
4
+ * - Fetches JWKS from `{issuer}/jwks` using jose's createRemoteJWKSet (auto-caches).
5
+ * - Validates Bearer token: signature, `iss`, `exp`.
6
+ * - On failure returns 401 with RFC 9728–compliant `WWW-Authenticate` header
7
+ * pointing at the OAuth Protected Resource Metadata document.
8
+ */
9
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
10
+ /** Build a reusable auth middleware for the given auth config */
11
+ export function createAuthMiddleware(auth, logger) {
12
+ const jwksUri = new URL('/jwks', auth.issuer);
13
+ const JWKS = createRemoteJWKSet(jwksUri);
14
+ return async (req, res, next) => {
15
+ const authHeader = req.headers.authorization;
16
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
17
+ return unauthorized(req, res, auth, 'missing or malformed Authorization header');
18
+ }
19
+ const token = authHeader.slice(7);
20
+ try {
21
+ const { payload } = await jwtVerify(token, JWKS, {
22
+ issuer: auth.issuer,
23
+ });
24
+ req.tokenPayload = payload;
25
+ next();
26
+ }
27
+ catch (err) {
28
+ logger.error('[auth] JWT verification failed:', err.message ?? err);
29
+ return unauthorized(req, res, auth, 'invalid_token');
30
+ }
31
+ };
32
+ }
33
+ function unauthorized(req, res, auth, error) {
34
+ // RFC 9728: include resource_metadata URI in WWW-Authenticate
35
+ const scheme = req.protocol;
36
+ const host = req.get('host');
37
+ const resourceMetadataUri = `${scheme}://${host}/.well-known/oauth-protected-resource`;
38
+ res.setHeader('WWW-Authenticate', `Bearer realm="mcp", error="${error}", resource_metadata="${resourceMetadataUri}"`);
39
+ res.status(401).json({ error: 'unauthorized', message: error });
40
+ }
41
+ //# sourceMappingURL=authMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/lib/authMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAIpD,iEAAiE;AACjE,MAAM,UAAU,oBAAoB,CAAC,IAAgB,EAAE,MAAc;IACnE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAExC,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAA;QAC5C,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,2CAA2C,CAAC,CAAA;QAClF,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAEjC,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;gBAC/C,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAGD;YAAC,GAAW,CAAC,YAAY,GAAG,OAAO,CAAA;YACpC,IAAI,EAAE,CAAA;QACR,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;YACnE,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;QACtD,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CACnB,GAA8B,EAC9B,GAA+B,EAC/B,IAAgB,EAChB,KAAa;IAEb,8DAA8D;IAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAA;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,mBAAmB,GAAG,GAAG,MAAM,MAAM,IAAI,uCAAuC,CAAA;IAEtF,GAAG,CAAC,SAAS,CACX,kBAAkB,EAClB,8BAA8B,KAAK,yBAAyB,mBAAmB,GAAG,CACnF,CAAA;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;AACjE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CorsOptions } from 'cors';
2
+ export declare function parseCorsOrigin(corsValues: (string | number)[] | undefined): CorsOptions['origin'] | false;
3
+ export declare function serializeCorsOrigin(corsOrigin: CorsOptions['origin']): string;
@@ -0,0 +1,29 @@
1
+ export function parseCorsOrigin(corsValues) {
2
+ if (!corsValues)
3
+ return false;
4
+ if (corsValues.length === 0)
5
+ return '*';
6
+ const origins = corsValues.map((item) => `${item}`);
7
+ if (origins.includes('*'))
8
+ return '*';
9
+ return origins.map((origin) => {
10
+ if (/^\/.*\/$/.test(origin)) {
11
+ const pattern = origin.slice(1, -1);
12
+ try {
13
+ return new RegExp(pattern);
14
+ }
15
+ catch {
16
+ return origin;
17
+ }
18
+ }
19
+ return origin;
20
+ });
21
+ }
22
+ export function serializeCorsOrigin(corsOrigin) {
23
+ return JSON.stringify(corsOrigin, (_key, value) => {
24
+ if (value instanceof RegExp)
25
+ return value.toString();
26
+ return value;
27
+ });
28
+ }
29
+ //# sourceMappingURL=cors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/lib/cors.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,UAA2C;IAE3C,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAA;IAC7B,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAA;IAEvC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;IACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAErC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YACnC,IAAI,CAAC;gBACH,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,CAAA;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,MAAM,CAAA;YACf,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,UAAiC;IACnE,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAChD,IAAI,KAAK,YAAY,MAAM;YAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAA;QACpD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * OAuth 2.0 discovery endpoints for the bridge.
3
+ *
4
+ * When auth is enabled these routes expose:
5
+ * - GET /.well-known/oauth-protected-resource[/*] (RFC 9728)
6
+ * - GET /.well-known/oauth-authorization-server[/*] (RFC 8414 — proxied from issuer)
7
+ * - POST /oauth/register (Pseudo-DCR — RFC 7591)
8
+ *
9
+ * These endpoints are unauthenticated — they must be accessible to
10
+ * any client performing OAuth discovery before obtaining a token.
11
+ */
12
+ import { Router } from 'express';
13
+ import type { AuthConfig, Logger } from '../types.js';
14
+ export declare function createDiscoveryRouter(auth: AuthConfig, logger: Logger): Router;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OAuth 2.0 discovery endpoints for the bridge.
3
+ *
4
+ * When auth is enabled these routes expose:
5
+ * - GET /.well-known/oauth-protected-resource[/*] (RFC 9728)
6
+ * - GET /.well-known/oauth-authorization-server[/*] (RFC 8414 — proxied from issuer)
7
+ * - POST /oauth/register (Pseudo-DCR — RFC 7591)
8
+ *
9
+ * These endpoints are unauthenticated — they must be accessible to
10
+ * any client performing OAuth discovery before obtaining a token.
11
+ */
12
+ import { Router } from 'express';
13
+ export function createDiscoveryRouter(auth, logger) {
14
+ const router = Router();
15
+ /**
16
+ * RFC 9728 — OAuth Protected Resource Metadata
17
+ *
18
+ * Tells the client:
19
+ * - which authorization server protects this resource
20
+ * - which scopes are available
21
+ * - where to find the authorization server metadata
22
+ *
23
+ * Handles both root and path-suffixed variants (e.g. /.well-known/oauth-protected-resource/mcp)
24
+ * as required by the MCP SDK for path-based resource discovery.
25
+ */
26
+ router.get('/.well-known/oauth-protected-resource*', (req, res) => {
27
+ const scheme = req.protocol;
28
+ const host = req.get('host');
29
+ const resourceMetadata = {
30
+ resource: `${scheme}://${host}/`,
31
+ authorization_servers: [auth.issuer],
32
+ scopes_supported: ['openid', 'profile', 'email'],
33
+ bearer_methods_supported: ['header'],
34
+ };
35
+ res.json(resourceMetadata);
36
+ });
37
+ /**
38
+ * RFC 8414 — Authorization Server Metadata (proxied)
39
+ *
40
+ * We proxy the issuer's .well-known/oauth-authorization-server document
41
+ * and patch `registration_endpoint` to point to our local pseudo-DCR.
42
+ *
43
+ * Handles both root and path-suffixed variants.
44
+ */
45
+ router.get('/.well-known/oauth-authorization-server*', async (req, res) => {
46
+ try {
47
+ const metadataUrl = `${auth.issuer}/.well-known/oauth-authorization-server`;
48
+ const upstream = await fetch(metadataUrl);
49
+ if (!upstream.ok) {
50
+ logger.error(`[discovery] Failed to fetch AS metadata: ${upstream.status}`);
51
+ return res.status(502).json({ error: 'upstream_error' });
52
+ }
53
+ const metadata = (await upstream.json());
54
+ // Patch registration_endpoint to point to our pseudo-DCR
55
+ const scheme = req.protocol;
56
+ const host = req.get('host');
57
+ metadata.registration_endpoint = `${scheme}://${host}/oauth/register`;
58
+ res.json(metadata);
59
+ }
60
+ catch (err) {
61
+ logger.error('[discovery] Error proxying AS metadata:', err.message ?? err);
62
+ res.status(502).json({ error: 'upstream_error' });
63
+ }
64
+ });
65
+ /**
66
+ * Pseudo–Dynamic Client Registration (RFC 7591)
67
+ *
68
+ * Always returns the same pre-registered client_id — no new client
69
+ * is actually created. This lets standards-based OAuth clients
70
+ * (e.g., VS Code Copilot) discover the correct client_id through
71
+ * the normal DCR flow without requiring out-of-band configuration.
72
+ */
73
+ router.post('/oauth/register', (_req, res) => {
74
+ res.status(201).json({
75
+ client_id: auth.clientId,
76
+ client_name: 'platf-bridge',
77
+ // No client_secret — public client using PKCE
78
+ token_endpoint_auth_method: 'none',
79
+ grant_types: ['authorization_code'],
80
+ response_types: ['code'],
81
+ redirect_uris: [], // Client provides its own redirect_uri
82
+ });
83
+ });
84
+ return router;
85
+ }
86
+ //# sourceMappingURL=discoveryRoutes.js.map
@@ -0,0 +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;;;;;;;;;;OAUG;IACH,MAAM,CAAC,GAAG,CAAC,wCAAwC,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACnF,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,GAAG;YAChC,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,IAAa,EAAE,GAAa,EAAE,EAAE;QAC9D,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,EAAE,EAAE,uCAAuC;SAC3D,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from '../types.js';
2
+ export type LogLevel = 'none' | 'info' | 'debug';
3
+ export declare function getLogger(logLevel: LogLevel): Logger;
@@ -0,0 +1,34 @@
1
+ import util from 'node:util';
2
+ const defaultFormatArgs = (args) => args;
3
+ const log = ({ formatArgs = defaultFormatArgs } = {}) => (...args) => console.log('[platf-bridge]', ...formatArgs(args));
4
+ const logStderr = ({ formatArgs = defaultFormatArgs } = {}) => (...args) => console.error('[platf-bridge]', ...formatArgs(args));
5
+ const noneLogger = {
6
+ info: () => { },
7
+ error: () => { },
8
+ };
9
+ const infoLogger = {
10
+ info: log(),
11
+ error: logStderr(),
12
+ };
13
+ const debugFormatArgs = (args) => args.map((arg) => {
14
+ if (typeof arg === 'object') {
15
+ return util.inspect(arg, {
16
+ depth: null,
17
+ colors: process.stderr.isTTY,
18
+ compact: false,
19
+ });
20
+ }
21
+ return arg;
22
+ });
23
+ const debugLogger = {
24
+ info: log({ formatArgs: debugFormatArgs }),
25
+ error: logStderr({ formatArgs: debugFormatArgs }),
26
+ };
27
+ export function getLogger(logLevel) {
28
+ if (logLevel === 'none')
29
+ return noneLogger;
30
+ if (logLevel === 'debug')
31
+ return debugLogger;
32
+ return infoLogger;
33
+ }
34
+ //# sourceMappingURL=getLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getLogger.js","sourceRoot":"","sources":["../../src/lib/getLogger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B,MAAM,iBAAiB,GAAG,CAAC,IAAe,EAAE,EAAE,CAAC,IAAI,CAAA;AAEnD,MAAM,GAAG,GACP,CAAC,EAAE,UAAU,GAAG,iBAAiB,KAAgD,EAAE,EAAE,EAAE,CACvF,CAAC,GAAG,IAAe,EAAE,EAAE,CACrB,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAEtD,MAAM,SAAS,GACb,CAAC,EAAE,UAAU,GAAG,iBAAiB,KAAgD,EAAE,EAAE,EAAE,CACvF,CAAC,GAAG,IAAe,EAAE,EAAE,CACrB,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAExD,MAAM,UAAU,GAAW;IACzB,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;CAChB,CAAA;AAED,MAAM,UAAU,GAAW;IACzB,IAAI,EAAE,GAAG,EAAE;IACX,KAAK,EAAE,SAAS,EAAE;CACnB,CAAA;AAED,MAAM,eAAe,GAAG,CAAC,IAAe,EAAE,EAAE,CAC1C,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;IACf,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YACvB,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK;YAC5B,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC,CAAA;AAEJ,MAAM,WAAW,GAAW;IAC1B,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;IAC1C,KAAK,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;CAClD,CAAA;AAID,MAAM,UAAU,SAAS,CAAC,QAAkB;IAC1C,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,UAAU,CAAA;IAC1C,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,WAAW,CAAA;IAC5C,OAAO,UAAU,CAAA;AACnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Logger } from '../types.js';
2
+ export declare function parseHeaders(rawHeaders: (string | number)[], logger: Logger): Record<string, string>;
@@ -0,0 +1,19 @@
1
+ export function parseHeaders(rawHeaders, logger) {
2
+ return rawHeaders.reduce((acc, rawHeader) => {
3
+ const header = `${rawHeader}`;
4
+ const colonIndex = header.indexOf(':');
5
+ if (colonIndex === -1) {
6
+ logger.error(`Invalid header format: ${header}, ignoring`);
7
+ return acc;
8
+ }
9
+ const key = header.slice(0, colonIndex).trim();
10
+ const value = header.slice(colonIndex + 1).trim();
11
+ if (!key || !value) {
12
+ logger.error(`Invalid header format: ${header}, ignoring`);
13
+ return acc;
14
+ }
15
+ acc[key] = value;
16
+ return acc;
17
+ }, {});
18
+ }
19
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/lib/headers.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAC1B,UAA+B,EAC/B,MAAc;IAEd,OAAO,UAAU,CAAC,MAAM,CAAyB,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE;QAClE,MAAM,MAAM,GAAG,GAAG,SAAS,EAAE,CAAA;QAC7B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACtC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,0BAA0B,MAAM,YAAY,CAAC,CAAA;YAC1D,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAA;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAEjD,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,KAAK,CAAC,0BAA0B,MAAM,YAAY,CAAC,CAAA;YAC1D,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QAChB,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,EAAE,CAAC,CAAA;AACR,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Logger } from '../types.js';
2
+ export interface OnSignalsOptions {
3
+ logger: Logger;
4
+ cleanup?: () => void;
5
+ }
6
+ export declare function onSignals({ logger, cleanup }: OnSignalsOptions): void;
@@ -0,0 +1,16 @@
1
+ export function onSignals({ logger, cleanup }) {
2
+ const handleSignal = (signal) => {
3
+ logger.info(`Caught ${signal}. Exiting...`);
4
+ cleanup?.();
5
+ process.exit(0);
6
+ };
7
+ process.on('SIGINT', () => handleSignal('SIGINT'));
8
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
9
+ process.on('SIGHUP', () => handleSignal('SIGHUP'));
10
+ process.stdin.on('close', () => {
11
+ logger.info('stdin closed. Exiting...');
12
+ cleanup?.();
13
+ process.exit(0);
14
+ });
15
+ }
16
+ //# sourceMappingURL=onSignals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onSignals.js","sourceRoot":"","sources":["../../src/lib/onSignals.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAoB;IAC7D,MAAM,YAAY,GAAG,CAAC,MAAc,EAAE,EAAE;QACtC,MAAM,CAAC,IAAI,CAAC,UAAU,MAAM,cAAc,CAAC,CAAA;QAC3C,OAAO,EAAE,EAAE,CAAA;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAA;IAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;IACpD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;IAElD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QACvC,OAAO,EAAE,EAAE,CAAA;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Logger } from '../types.js';
2
+ export declare class SessionAccessCounter {
3
+ private readonly timeoutMs;
4
+ private readonly cleanup;
5
+ private readonly logger;
6
+ private sessions;
7
+ constructor(timeoutMs: number, cleanup: (sessionId: string) => unknown, logger: Logger);
8
+ inc(sessionId: string, reason: string): void;
9
+ dec(sessionId: string, reason: string): void;
10
+ clear(sessionId: string, runCleanup: boolean, reason: string): void;
11
+ }
@@ -0,0 +1,72 @@
1
+ export class SessionAccessCounter {
2
+ timeoutMs;
3
+ cleanup;
4
+ logger;
5
+ sessions = new Map();
6
+ constructor(timeoutMs, cleanup, logger) {
7
+ this.timeoutMs = timeoutMs;
8
+ this.cleanup = cleanup;
9
+ this.logger = logger;
10
+ }
11
+ inc(sessionId, reason) {
12
+ this.logger.info(`SessionAccessCounter.inc() ${sessionId}, caused by ${reason}`);
13
+ const session = this.sessions.get(sessionId);
14
+ if (!session) {
15
+ this.logger.info(`Session access count 0 -> 1 for ${sessionId} (new session)`);
16
+ this.sessions.set(sessionId, { accessCount: 1 });
17
+ return;
18
+ }
19
+ if ('timeout' in session) {
20
+ this.logger.info(`Session access count 0 -> 1, clearing cleanup timeout for ${sessionId}`);
21
+ clearTimeout(session.timeout);
22
+ this.sessions.set(sessionId, { accessCount: 1 });
23
+ }
24
+ else {
25
+ this.logger.info(`Session access count ${session.accessCount} -> ${session.accessCount + 1} for ${sessionId}`);
26
+ session.accessCount++;
27
+ }
28
+ }
29
+ dec(sessionId, reason) {
30
+ this.logger.info(`SessionAccessCounter.dec() ${sessionId}, caused by ${reason}`);
31
+ const session = this.sessions.get(sessionId);
32
+ if (!session) {
33
+ this.logger.error(`Called dec() on non-existent session ${sessionId}, ignoring`);
34
+ return;
35
+ }
36
+ if ('timeout' in session) {
37
+ this.logger.error(`Called dec() on session ${sessionId} that is already pending cleanup, ignoring`);
38
+ return;
39
+ }
40
+ if (session.accessCount <= 0) {
41
+ throw new Error(`Invalid access count ${session.accessCount} for session ${sessionId}`);
42
+ }
43
+ session.accessCount--;
44
+ this.logger.info(`Session access count ${session.accessCount + 1} -> ${session.accessCount} for ${sessionId}`);
45
+ if (session.accessCount === 0) {
46
+ this.logger.info(`Session access count reached 0, setting cleanup timeout for ${sessionId}`);
47
+ this.sessions.set(sessionId, {
48
+ timeout: setTimeout(() => {
49
+ this.logger.info(`Session ${sessionId} timed out, cleaning up`);
50
+ this.sessions.delete(sessionId);
51
+ this.cleanup(sessionId);
52
+ }, this.timeoutMs),
53
+ });
54
+ }
55
+ }
56
+ clear(sessionId, runCleanup, reason) {
57
+ this.logger.info(`SessionAccessCounter.clear() ${sessionId}, caused by ${reason}`);
58
+ const session = this.sessions.get(sessionId);
59
+ if (!session) {
60
+ this.logger.info(`Attempted to clear non-existent session ${sessionId}`);
61
+ return;
62
+ }
63
+ if ('timeout' in session) {
64
+ clearTimeout(session.timeout);
65
+ }
66
+ this.sessions.delete(sessionId);
67
+ if (runCleanup) {
68
+ this.cleanup(sessionId);
69
+ }
70
+ }
71
+ }
72
+ //# sourceMappingURL=sessionAccessCounter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionAccessCounter.js","sourceRoot":"","sources":["../../src/lib/sessionAccessCounter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface Logger {
2
+ info: (...args: unknown[]) => void;
3
+ error: (...args: unknown[]) => void;
4
+ }
5
+ /** OAuth auth config passed when --authIssuer is set */
6
+ export interface AuthConfig {
7
+ /** OAuth issuer URL (e.g. https://auth.platf.ai) */
8
+ issuer: string;
9
+ /** OAuth client_id for this bridge instance */
10
+ clientId: string;
11
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@platf/bridge",
3
+ "version": "0.0.2",
4
+ "description": "Stdio-to-Streamable HTTP bridge for MCP servers — Platf AI Hub",
5
+ "module": "src/index.ts",
6
+ "main": "dist/index.js",
7
+ "type": "module",
8
+ "bin": "dist/index.js",
9
+ "files": [
10
+ "dist",
11
+ "src",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "start": "bun run src/index.ts",
16
+ "dev": "bun run --watch src/index.ts",
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "prepublishOnly": "tsc -p tsconfig.build.json",
19
+ "typecheck": "bunx tsc --noEmit"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.18.2",
23
+ "cors": "^2.8.5",
24
+ "express": "^4.21.2",
25
+ "jose": "^6.1.3",
26
+ "yargs": "^17.7.2"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "@types/cors": "^2.8.17",
31
+ "@types/express": "^5.0.3",
32
+ "@types/yargs": "^17.0.33"
33
+ },
34
+ "peerDependencies": {
35
+ "typescript": "^5"
36
+ },
37
+ "engines": {
38
+ "node": ">=18",
39
+ "bun": ">=1"
40
+ }
41
+ }