@@ -10,36 +10,31 @@ import { oauthMonitoringRoutes } from './routes/oauth-monitoring'
1010import { oauthWebSocket } from './routes/oauth-websocket'
1111import { adminRoutes } from './routes/admin'
1212import { authRoutes } from './routes/auth'
13- import { aiRoutes , aiPublicRoutes } from './routes/admin/ai'
13+ import { aiRoutes , aiPublicRoutes } from './routes/admin/ai-external '
1414import { writeFileSync , mkdirSync } from 'fs'
1515import { join } from 'path'
1616
17- // Minimal config for OpenAPI export - provides defaults for required environment variables
17+ /**
18+ * Export configuration - uses values from config module (which reads from .env and package.json)
19+ * No hardcoded values - everything comes from environment or package.json
20+ */
1821const exportConfig = {
19- name : 'proxy-smart' ,
20- displayName : 'Proxy Smart' ,
21- version : process . env . npm_package_version || '1.0.0' ,
22- baseUrl : 'http://localhost:3001' ,
23- port : 3001 ,
22+ name : config . name ,
23+ displayName : config . displayName ,
24+ version : config . version ,
25+ baseUrl : config . baseUrl ,
26+ port : config . port ,
2427 keycloak : {
25- serverUrl : process . env . KEYCLOAK_SERVER_URL || 'http://localhost:8080' ,
26- realm : process . env . KEYCLOAK_REALM || 'proxy-smart' ,
27- clientId : process . env . KEYCLOAK_CLIENT_ID || 'proxy-smart-admin' ,
28- clientSecret : process . env . KEYCLOAK_CLIENT_SECRET || 'mock-secret' ,
28+ serverUrl : config . keycloak . publicUrl || config . keycloak . baseUrl || 'http://localhost:8080' ,
29+ realm : config . keycloak . realm || 'proxy-smart' ,
30+ jwksUri : config . keycloak . jwksUri ,
2931 } ,
3032 fhir : {
31- serverBases : ( process . env . FHIR_SERVER_BASE ?? 'http://localhost:8081/fhir' )
32- . split ( ',' )
33- . map ( s => s . trim ( ) ) ,
34- } ,
35- logging : {
36- level : 'info' as const ,
37- oauthMetrics : false ,
33+ serverBases : config . fhir . serverBases ,
3834 } ,
3935 cors : {
40- allowedOrigins : [ 'http://localhost:5173' , 'http://localhost:3000' ] ,
36+ allowedOrigins : config . cors . origins ,
4137 } ,
42- enableMutualTLS : false ,
4338}
4439
4540// Create the same app configuration as the main server
@@ -95,13 +90,57 @@ const app = new Elysia({
9590 bearerFormat : 'JWT' ,
9691 description : 'JWT Bearer token from OAuth2 flow'
9792 } ,
93+ OAuth2 : {
94+ type : 'oauth2' ,
95+ description : 'OAuth2 authentication via Keycloak with SMART on FHIR support' ,
96+ flows : {
97+ authorizationCode : {
98+ authorizationUrl : `${ exportConfig . baseUrl } /auth/authorize` ,
99+ tokenUrl : `${ exportConfig . baseUrl } /auth/token` ,
100+ refreshUrl : `${ exportConfig . baseUrl } /auth/token` ,
101+ scopes : {
102+ 'openid' : 'OpenID Connect authentication' ,
103+ 'profile' : 'User profile information' ,
104+ 'email' : 'User email address' ,
105+ 'patient/*.read' : 'Read all patient data' ,
106+ 'patient/*.write' : 'Write all patient data' ,
107+ 'user/*.read' : 'Read all data for current user' ,
108+ 'user/*.write' : 'Write all data for current user' ,
109+ 'launch' : 'SMART launch context' ,
110+ 'launch/patient' : 'SMART launch with patient context' ,
111+ 'launch/encounter' : 'SMART launch with encounter context' ,
112+ 'offline_access' : 'Offline access via refresh token'
113+ }
114+ } ,
115+ password : {
116+ tokenUrl : `${ exportConfig . baseUrl } /auth/token` ,
117+ refreshUrl : `${ exportConfig . baseUrl } /auth/token` ,
118+ scopes : {
119+ 'openid' : 'OpenID Connect authentication' ,
120+ 'profile' : 'User profile information' ,
121+ 'email' : 'User email address'
122+ }
123+ } ,
124+ clientCredentials : {
125+ tokenUrl : `${ exportConfig . baseUrl } /auth/token` ,
126+ scopes : {
127+ 'system/*.read' : 'System-level read access to FHIR resources' ,
128+ 'system/*.write' : 'System-level write access to FHIR resources'
129+ }
130+ }
131+ }
132+ } ,
98133 MutualTLS : {
99134 type : 'http' ,
100135 scheme : 'mutual-tls' ,
101136 description : 'Mutual TLS authentication for secure API communication between proxy and FHIR servers. Submit a request to the infrastructure team with full information about your application to obtain a client certificate.'
102137 }
103138 }
104139 } ,
140+ security : [
141+ { OAuth2 : [ 'openid' , 'profile' , 'email' ] } ,
142+ { BearerAuth : [ ] }
143+ ] ,
105144 servers : [
106145 {
107146 url : exportConfig . baseUrl ,
@@ -148,15 +187,40 @@ const exportSpec = async () => {
148187
149188 const spec = await response . json ( )
150189
190+ // Add custom OpenAPI extensions for MCP server generation
191+ // These help the MCP generator extract authentication configuration
192+ spec [ 'x-jwks-uri' ] = exportConfig . keycloak . jwksUri || `${ exportConfig . baseUrl } /.well-known/jwks.json`
193+ spec [ 'x-issuer' ] = exportConfig . keycloak . serverUrl ?
194+ `${ exportConfig . keycloak . serverUrl } /realms/${ exportConfig . keycloak . realm } ` :
195+ exportConfig . baseUrl
196+ spec [ 'x-audience' ] = config . mcp . audience
197+ spec [ 'x-token-endpoint' ] = `${ exportConfig . baseUrl } /auth/token`
198+ spec [ 'x-authorization-endpoint' ] = `${ exportConfig . baseUrl } /auth/authorize`
199+ spec [ 'x-userinfo-endpoint' ] = `${ exportConfig . baseUrl } /auth/userinfo`
200+
201+ console . log ( '📝 Added custom OpenAPI extensions:' )
202+ console . log ( ` x-jwks-uri: ${ spec [ 'x-jwks-uri' ] } ` )
203+ console . log ( ` x-issuer: ${ spec [ 'x-issuer' ] } ` )
204+ console . log ( ` x-audience: ${ spec [ 'x-audience' ] } ` )
205+
151206 // Ensure dist directory exists
152207 const distDir = join ( process . cwd ( ) , 'dist' )
153208 mkdirSync ( distDir , { recursive : true } )
154209
155- // Write to file
210+ // Write to backend dist
156211 const outputPath = join ( distDir , 'openapi.json' )
157- writeFileSync ( outputPath , JSON . stringify ( spec , null , 2 ) )
158-
212+ const specJson = JSON . stringify ( spec , null , 2 )
213+ writeFileSync ( outputPath , specJson )
159214 console . log ( `✅ OpenAPI spec exported to: ${ outputPath } ` )
215+
216+ // Also copy to mcp-server folder for the Python generator
217+ const mcpServerPath = join ( process . cwd ( ) , '..' , 'mcp-server' , 'openapi.json' )
218+ try {
219+ writeFileSync ( mcpServerPath , specJson )
220+ console . log ( `✅ OpenAPI spec copied to: ${ mcpServerPath } ` )
221+ } catch ( copyError ) {
222+ console . warn ( `⚠️ Failed to copy to mcp-server folder: ${ copyError } ` )
223+ }
160224
161225 serverInstance ?. stop ( )
162226 process . exit ( 0 )
0 commit comments