@tiangong-lca/mcp-server 0.0.7 → 0.0.9

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.
@@ -1,16 +1,53 @@
1
1
  import { createClient } from '@supabase/supabase-js';
2
2
  import { Redis } from '@upstash/redis';
3
+ import { authenticateCognitoToken } from './cognito_auth.js';
4
+ import { redis_token, redis_url, supabase_anon_key, supabase_base_url } from './config.js';
3
5
  import decodeApiKey from './decode_api_key.js';
4
- const supabase_base_url = process.env.SUPABASE_BASE_URL ?? '';
5
- const supabase_anon_key = process.env.SUPABASE_ANON_KEY ?? '';
6
6
  const supabase = createClient(supabase_base_url, supabase_anon_key);
7
- const redis_url = process.env.UPSTASH_REDIS_URL ?? '';
8
- const redis_token = process.env.UPSTASH_REDIS_TOKEN ?? '';
9
7
  const redis = new Redis({
10
8
  url: redis_url,
11
9
  token: redis_token,
12
10
  });
11
+ function getTokenType(bearerKey) {
12
+ const jwtPattern = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
13
+ if (jwtPattern.test(bearerKey)) {
14
+ try {
15
+ const payload = JSON.parse(atob(bearerKey.split('.')[1]));
16
+ if (payload.iss && payload.iss.includes('cognito')) {
17
+ return 'cognito';
18
+ }
19
+ }
20
+ catch (error) {
21
+ }
22
+ }
23
+ const credentials = decodeApiKey(bearerKey);
24
+ if (credentials) {
25
+ return 'api_key';
26
+ }
27
+ return 'supabase';
28
+ }
13
29
  export async function authenticateRequest(bearerKey) {
30
+ const tokenType = getTokenType(bearerKey);
31
+ switch (tokenType) {
32
+ case 'cognito':
33
+ return await authenticateCognitoRequest(bearerKey);
34
+ case 'api_key':
35
+ return await authenticateApiKeyRequest(bearerKey);
36
+ case 'supabase':
37
+ default:
38
+ return await authenticateSupabaseRequest(bearerKey);
39
+ }
40
+ }
41
+ async function authenticateCognitoRequest(bearerKey) {
42
+ const cognitoResult = await authenticateCognitoToken(bearerKey);
43
+ return {
44
+ isAuthenticated: cognitoResult.isAuthenticated,
45
+ response: cognitoResult.response,
46
+ userId: cognitoResult.userId,
47
+ email: cognitoResult.email,
48
+ };
49
+ }
50
+ async function authenticateApiKeyRequest(bearerKey) {
14
51
  const credentials = decodeApiKey(bearerKey);
15
52
  if (credentials) {
16
53
  const { email = '', password = '' } = credentials;
@@ -20,7 +57,6 @@ export async function authenticateRequest(bearerKey) {
20
57
  email: email,
21
58
  password: password,
22
59
  });
23
- console.error(email, password, data, error);
24
60
  if (error) {
25
61
  return {
26
62
  isAuthenticated: false,
@@ -38,19 +74,31 @@ export async function authenticateRequest(bearerKey) {
38
74
  return {
39
75
  isAuthenticated: true,
40
76
  response: data.user.id,
77
+ userId: data.user.id,
78
+ email: data.user.email,
41
79
  };
42
80
  }
43
81
  }
44
82
  return {
45
83
  isAuthenticated: true,
46
84
  response: String(userIdFromRedis),
85
+ userId: String(userIdFromRedis),
47
86
  };
48
87
  }
88
+ return {
89
+ isAuthenticated: false,
90
+ response: 'Invalid API key',
91
+ };
92
+ }
93
+ async function authenticateSupabaseRequest(bearerKey) {
94
+ console.log(bearerKey);
49
95
  const { data: authData } = await supabase.auth.getUser(bearerKey);
50
96
  if (authData.user?.role === 'authenticated') {
51
97
  return {
52
98
  isAuthenticated: true,
53
99
  response: authData.user?.id,
100
+ userId: authData.user?.id,
101
+ email: authData.user?.email,
54
102
  };
55
103
  }
56
104
  if (!authData || !authData.user) {
@@ -70,5 +118,7 @@ export async function authenticateRequest(bearerKey) {
70
118
  return {
71
119
  isAuthenticated: true,
72
120
  response: authData.user.id,
121
+ userId: authData.user.id,
122
+ email: authData.user.email,
73
123
  };
74
124
  }
@@ -0,0 +1,33 @@
1
+ import { CognitoJwtVerifier } from 'aws-jwt-verify';
2
+ import { COGNITO_CLIENT_ID, COGNITO_USER_POOL_ID } from './config.js';
3
+ const verifier = CognitoJwtVerifier.create({
4
+ userPoolId: COGNITO_USER_POOL_ID,
5
+ tokenUse: 'access',
6
+ clientId: COGNITO_CLIENT_ID,
7
+ });
8
+ export async function authenticateCognitoToken(token) {
9
+ try {
10
+ const payload = await verifier.verify(token);
11
+ const userId = payload.sub || payload['cognito:username'];
12
+ const email = payload.email || payload['cognito:email'];
13
+ if (!userId) {
14
+ return {
15
+ isAuthenticated: false,
16
+ response: 'Invalid token: missing user ID',
17
+ };
18
+ }
19
+ return {
20
+ isAuthenticated: true,
21
+ response: userId,
22
+ userId,
23
+ email,
24
+ };
25
+ }
26
+ catch (error) {
27
+ console.error('Cognito token verification failed:', error);
28
+ return {
29
+ isAuthenticated: false,
30
+ response: error instanceof Error ? error.message : 'Token verification failed',
31
+ };
32
+ }
33
+ }
@@ -1,4 +1,13 @@
1
- export const supabase_base_url = process.env.SUPABASE_BASE_URL ?? '';
2
- export const supabase_anon_key = process.env.SUPABASE_ANON_KEY ?? '';
3
- export const x_api_key = process.env.X_API_KEY ?? '';
4
- export const x_region = process.env.X_REGION ?? '';
1
+ export const COGNITO_REGION = process.env.COGNITO_REGION ?? 'us-east-1';
2
+ export const COGNITO_USER_POOL_ID = process.env.COGNITO_USER_POOL_ID ?? 'us-east-1_SnSYiMoND';
3
+ export const COGNITO_CLIENT_ID = process.env.COGNITO_CLIENT_ID ?? '3p182unuqch7rahbp0trs1sprv';
4
+ export const COGNITO_CLIENT_SECRET = process.env.COGNITO_CLIENT_SECRET;
5
+ export const COGNITO_ISSUER = `https://cognito-idp.${COGNITO_REGION}.amazonaws.com/${COGNITO_USER_POOL_ID}`;
6
+ export const COGNITO_JWKS_URL = `${COGNITO_ISSUER}/.well-known/jwks.json`;
7
+ export const COGNITO_BASE_URL = 'https://us-east-1snsyimond.auth.us-east-1.amazoncognito.com';
8
+ export const supabase_base_url = process.env.SUPABASE_BASE_URL ?? 'https://qgzvkongdjqiiamzbbts.supabase.co';
9
+ export const supabase_anon_key = process.env.SUPABASE_ANON_KEY ??
10
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFnenZrb25nZGpxaWlhbXpiYnRzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAzNjUyMzQsImV4cCI6MjA1NTk0MTIzNH0.PsZIcjAqexpqIg-91twpKjALyw9big6Bn4WRLLoCzTo';
11
+ export const x_region = process.env.X_REGION ?? 'us-east-1';
12
+ export const redis_url = process.env.UPSTASH_REDIS_URL ?? '';
13
+ export const redis_token = process.env.UPSTASH_REDIS_TOKEN ?? '';
@@ -1,6 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { regOpenLcaPrompts } from '../prompts/lca_calculation.js';
3
+ import { regOpenLcaResources } from '../resources/lca_calculation.js';
2
4
  import { regBomCalculationTool } from '../tools/bom_calculation.js';
3
5
  import { regFlowSearchTool } from '../tools/flow_hybrid_search.js';
6
+ import { regLcaCalculationGuidanceTool } from '../tools/lca_calculation_guidance.js';
4
7
  import { regOpenLcaLciaTool } from '../tools/openlca_ipc_lcia.js';
5
8
  import { regOpenLcaListLCIAMethodsTool } from '../tools/openlca_ipc_lcia_methods_list.js';
6
9
  import { regOpenLcaListSystemProcessTool } from '../tools/openlca_ipc_process_list.js';
@@ -9,13 +12,16 @@ export function initializeServer(bearerKey) {
9
12
  const server = new McpServer({
10
13
  name: 'TianGong-MCP-Server',
11
14
  version: '1.0.0',
12
- });
15
+ }, { capabilities: { prompts: {}, resources: {} } });
13
16
  regFlowSearchTool(server, bearerKey);
14
17
  regProcessSearchTool(server, bearerKey);
15
18
  regBomCalculationTool(server);
16
19
  regOpenLcaLciaTool(server);
17
20
  regOpenLcaListSystemProcessTool(server);
18
21
  regOpenLcaListLCIAMethodsTool(server);
22
+ regOpenLcaPrompts(server);
23
+ regOpenLcaResources(server);
24
+ regLcaCalculationGuidanceTool(server);
19
25
  return server;
20
26
  }
21
27
  export function getServer(bearerKey) {
@@ -0,0 +1,17 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { regBomCalculationTool } from '../tools/bom_calculation.js';
3
+ import { regFlowSearchTool } from '../tools/flow_hybrid_search.js';
4
+ import { regProcessSearchTool } from '../tools/process_hybrid_search.js';
5
+ export function initializeServer(bearerKey) {
6
+ const server = new McpServer({
7
+ name: 'TianGong-LCA-MCP-Server',
8
+ version: '1.0.0',
9
+ });
10
+ regFlowSearchTool(server, bearerKey);
11
+ regProcessSearchTool(server, bearerKey);
12
+ regBomCalculationTool(server);
13
+ return server;
14
+ }
15
+ export function getServer(bearerKey) {
16
+ return initializeServer(bearerKey);
17
+ }
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
3
+ import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
4
+ import express from 'express';
5
+ import { authenticateCognitoToken } from './_shared/cognito_auth.js';
6
+ import { COGNITO_BASE_URL, COGNITO_CLIENT_ID, COGNITO_CLIENT_SECRET } from './_shared/config.js';
7
+ const proxyProvider = new ProxyOAuthServerProvider({
8
+ endpoints: {
9
+ authorizationUrl: `${COGNITO_BASE_URL}/oauth2/authorize`,
10
+ tokenUrl: `${COGNITO_BASE_URL}/oauth2/token`,
11
+ revocationUrl: `${COGNITO_BASE_URL}/oauth2/revoke`,
12
+ },
13
+ verifyAccessToken: async (token) => {
14
+ const authResult = await authenticateCognitoToken(token);
15
+ if (!authResult.isAuthenticated) {
16
+ throw new Error('Invalid token');
17
+ }
18
+ return {
19
+ token,
20
+ clientId: COGNITO_CLIENT_ID,
21
+ scopes: ['openid', 'email', 'profile'],
22
+ audience: COGNITO_CLIENT_ID,
23
+ };
24
+ },
25
+ getClient: async (client_id) => {
26
+ return {
27
+ client_id,
28
+ redirect_uris: ['https://lcamcp.tiangong.earth/oauth/callback'],
29
+ response_types: ['code'],
30
+ grant_types: ['authorization_code', 'refresh_token'],
31
+ token_endpoint_auth_method: 'client_secret_post',
32
+ code_challenge_methods_supported: ['S256'],
33
+ scope: 'openid email profile',
34
+ };
35
+ },
36
+ });
37
+ const authApp = express();
38
+ authApp.set('trust proxy', 1);
39
+ authApp.use(express.json());
40
+ authApp.use(express.urlencoded({ extended: true }));
41
+ authApp.use((req, res, next) => {
42
+ res.header('Access-Control-Allow-Origin', '*');
43
+ res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
44
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
45
+ if (req.method === 'OPTIONS') {
46
+ res.sendStatus(200);
47
+ return;
48
+ }
49
+ next();
50
+ });
51
+ authApp.get('/callback', async (req, res) => {
52
+ const { code, state, error, error_description } = req.query;
53
+ if (error) {
54
+ console.error('OAuth error:', error, error_description);
55
+ return res.status(400).send(`OAuth Error: ${error} - ${error_description}`);
56
+ }
57
+ if (!code) {
58
+ console.error('Missing authorization code in callback');
59
+ return res.status(400).send('Missing authorization code');
60
+ }
61
+ res.send(`
62
+ <!DOCTYPE html>
63
+ <html>
64
+ <head>
65
+ <meta charset="utf-8">
66
+ <title>Authentication Successful - Tiangong LCA MCP</title>
67
+ <style>
68
+ body {
69
+ font-family: Arial, sans-serif;
70
+ margin: 40px;
71
+ background-color: #f5f5f5;
72
+ }
73
+ .container {
74
+ max-width: 800px;
75
+ margin: 0 auto;
76
+ background: white;
77
+ padding: 30px;
78
+ border-radius: 8px;
79
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
80
+ }
81
+ .header {
82
+ text-align: center;
83
+ margin-bottom: 40px;
84
+ color: #333;
85
+ }
86
+ .section {
87
+ margin: 30px 0;
88
+ padding: 20px;
89
+ border: 1px solid #ddd;
90
+ border-radius: 6px;
91
+ background-color: #fafafa;
92
+ }
93
+ .success {
94
+ color: #2e7d32;
95
+ background-color: #e8f5e8;
96
+ padding: 15px;
97
+ border-radius: 4px;
98
+ border-left: 4px solid #2e7d32;
99
+ margin: 15px 0;
100
+ }
101
+ .code-display {
102
+ background: #f5f5f5;
103
+ padding: 15px;
104
+ border-radius: 4px;
105
+ border-left: 4px solid #1976d2;
106
+ margin: 15px 0;
107
+ font-family: monospace;
108
+ word-break: break-all;
109
+ }
110
+ .link {
111
+ color: #1976d2;
112
+ text-decoration: none;
113
+ font-weight: bold;
114
+ background: #1976d2;
115
+ color: white;
116
+ padding: 12px 24px;
117
+ border-radius: 4px;
118
+ display: inline-block;
119
+ margin: 10px 10px 0 0;
120
+ transition: background-color 0.3s;
121
+ }
122
+ .link:hover {
123
+ background: #1565c0;
124
+ text-decoration: none;
125
+ }
126
+ button {
127
+ background: #1976d2;
128
+ color: white;
129
+ border: none;
130
+ padding: 12px 24px;
131
+ border-radius: 4px;
132
+ cursor: pointer;
133
+ font-size: 14px;
134
+ font-weight: bold;
135
+ margin: 10px 10px 0 0;
136
+ transition: background-color 0.3s;
137
+ text-decoration: none;
138
+ display: inline-block;
139
+ }
140
+ button:hover {
141
+ background: #1565c0;
142
+ }
143
+ .copy-button {
144
+ background: #28a745;
145
+ color: white;
146
+ border: none;
147
+ padding: 8px 16px;
148
+ border-radius: 4px;
149
+ cursor: pointer;
150
+ font-size: 12px;
151
+ font-weight: bold;
152
+ margin-left: 10px;
153
+ transition: background-color 0.3s;
154
+ }
155
+ .copy-button:hover {
156
+ background: #218838;
157
+ }
158
+ .copy-button:active {
159
+ background: #1e7e34;
160
+ }
161
+ .code-container {
162
+ position: relative;
163
+ }
164
+ .code-header {
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: space-between;
168
+ margin-bottom: 10px;
169
+ }
170
+ </style>
171
+ </head>
172
+ <body>
173
+ <div class="container">
174
+ <div class="header">
175
+ <h1>🎉 Authentication Successful!</h1>
176
+ <p>Your OAuth authorization has been completed successfully</p>
177
+ </div>
178
+
179
+ <div class="success">
180
+ <p><strong>✅ Authorization Code Received</strong></p>
181
+ <p>You can now exchange this code for an access token using PKCE.</p>
182
+ </div>
183
+
184
+ <div class="section">
185
+ <div class="code-container">
186
+ <div class="code-header">
187
+ <h3>🔑 Your Authorization Code</h3>
188
+ <button class="copy-button" onclick="copyAuthCode()">📋 Copy Code</button>
189
+ </div>
190
+ <div class="code-display" id="auth-code">${code}</div>
191
+ <p>Use this code along with your stored code verifier to exchange for an access token in the demo interface.</p>
192
+ </div>
193
+ </div>
194
+
195
+ <div class="section">
196
+ <h3>🚀 Next Steps</h3>
197
+ <ol>
198
+ <li>Copy the authorization code above</li>
199
+ <li>Return to the OAuth demo page</li>
200
+ <li>Paste the code in the "Exchange Authorization Code" section</li>
201
+ <li>Click "Exchange for Token" to get your access token</li>
202
+ </ol>
203
+ </div>
204
+
205
+ <div class="section">
206
+ <button onclick="window.close()">Close Window</button>
207
+ </div>
208
+ </div>
209
+
210
+ <script>
211
+ // Copy authorization code to clipboard
212
+ function copyAuthCode() {
213
+ const codeElement = document.getElementById('auth-code');
214
+ const code = codeElement.textContent;
215
+
216
+ if (navigator.clipboard && window.isSecureContext) {
217
+ // Use modern clipboard API
218
+ navigator.clipboard.writeText(code).then(() => {
219
+ showCopySuccess();
220
+ }).catch(() => {
221
+ fallbackCopy(code);
222
+ });
223
+ } else {
224
+ // Fallback for older browsers or non-secure contexts
225
+ fallbackCopy(code);
226
+ }
227
+ }
228
+
229
+ function fallbackCopy(text) {
230
+ // Create a temporary textarea element
231
+ const textArea = document.createElement('textarea');
232
+ textArea.value = text;
233
+ textArea.style.position = 'fixed';
234
+ textArea.style.left = '-999999px';
235
+ textArea.style.top = '-999999px';
236
+ document.body.appendChild(textArea);
237
+ textArea.focus();
238
+ textArea.select();
239
+
240
+ try {
241
+ document.execCommand('copy');
242
+ showCopySuccess();
243
+ } catch (err) {
244
+ console.error('Failed to copy text: ', err);
245
+ alert('Failed to copy code to clipboard. Please copy manually.');
246
+ }
247
+
248
+ document.body.removeChild(textArea);
249
+ }
250
+
251
+ function showCopySuccess() {
252
+ const button = document.querySelector('.copy-button');
253
+ const originalText = button.textContent;
254
+ button.textContent = '✅ Copied!';
255
+ button.style.background = '#28a745';
256
+
257
+ setTimeout(() => {
258
+ button.textContent = originalText;
259
+ button.style.background = '#28a745';
260
+ }, 2000);
261
+ }
262
+
263
+ // Try to communicate with parent window if in popup
264
+ if (window.opener) {
265
+ window.opener.postMessage({
266
+ type: 'oauth_success',
267
+ code: '${code}',
268
+ state: '${state || ''}'
269
+ }, '*');
270
+ }
271
+ </script>
272
+ </body>
273
+ </html>
274
+ `);
275
+ });
276
+ authApp.post('/token', async (req, res) => {
277
+ const { grant_type, client_id, code, redirect_uri, code_verifier } = req.body;
278
+ if (!grant_type || grant_type !== 'authorization_code') {
279
+ return res
280
+ .status(400)
281
+ .json({ error: 'invalid_request', error_description: 'Invalid or missing grant_type' });
282
+ }
283
+ if (!client_id || client_id !== COGNITO_CLIENT_ID) {
284
+ return res
285
+ .status(400)
286
+ .json({ error: 'invalid_client', error_description: 'Invalid or missing client_id' });
287
+ }
288
+ if (!code) {
289
+ return res
290
+ .status(400)
291
+ .json({ error: 'invalid_request', error_description: 'Missing authorization code' });
292
+ }
293
+ if (!redirect_uri || redirect_uri !== 'https://lcamcp.tiangong.earth/oauth/callback') {
294
+ return res
295
+ .status(400)
296
+ .json({ error: 'invalid_request', error_description: 'Invalid redirect_uri' });
297
+ }
298
+ if (!code_verifier) {
299
+ return res
300
+ .status(400)
301
+ .json({ error: 'invalid_request', error_description: 'Missing code_verifier for PKCE' });
302
+ }
303
+ try {
304
+ const tokenParams = new URLSearchParams({
305
+ grant_type: 'authorization_code',
306
+ client_id: COGNITO_CLIENT_ID,
307
+ code: code,
308
+ redirect_uri: redirect_uri,
309
+ code_verifier: code_verifier,
310
+ });
311
+ const headers = {
312
+ 'Content-Type': 'application/x-www-form-urlencoded',
313
+ Accept: 'application/json',
314
+ };
315
+ if (COGNITO_CLIENT_SECRET) {
316
+ const credentials = Buffer.from(`${COGNITO_CLIENT_ID}:${COGNITO_CLIENT_SECRET}`).toString('base64');
317
+ headers['Authorization'] = `Basic ${credentials}`;
318
+ }
319
+ const tokenResponse = await fetch(`${COGNITO_BASE_URL}/oauth2/token`, {
320
+ method: 'POST',
321
+ headers,
322
+ body: tokenParams,
323
+ });
324
+ const responseText = await tokenResponse.text();
325
+ if (!tokenResponse.ok) {
326
+ console.error('Token exchange failed:', responseText);
327
+ return res.status(tokenResponse.status).json({
328
+ error: 'invalid_grant',
329
+ error_description: `Token exchange failed: ${responseText}`,
330
+ });
331
+ }
332
+ const tokens = JSON.parse(responseText);
333
+ const response = {
334
+ access_token: tokens.access_token,
335
+ token_type: tokens.token_type,
336
+ expires_in: tokens.expires_in,
337
+ ...(tokens.refresh_token && { refresh_token: tokens.refresh_token }),
338
+ ...(tokens.id_token && { id_token: tokens.id_token }),
339
+ };
340
+ res.json(response);
341
+ }
342
+ catch (error) {
343
+ console.error('Token exchange error:', error);
344
+ res.status(500).json({
345
+ error: 'server_error',
346
+ error_description: 'Internal server error during token exchange',
347
+ });
348
+ }
349
+ });
350
+ authApp.use(mcpAuthRouter({
351
+ provider: proxyProvider,
352
+ issuerUrl: new URL('https://lcamcp.tiangong.earth/oauth'),
353
+ baseUrl: new URL('https://lcamcp.tiangong.earth/oauth'),
354
+ serviceDocumentationUrl: new URL('https://docs.aws.amazon.com/cognito/'),
355
+ }));
356
+ export default authApp;
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
3
  import express from 'express';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
4
6
  import { authenticateRequest } from './_shared/auth_middleware.js';
5
- import { getServer } from './_shared/init_server.js';
7
+ import { getServer } from './_shared/init_server_http.js';
8
+ import authApp from './auth_app.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
6
11
  const authenticateBearer = async (req, res, next) => {
7
12
  const authHeader = req.headers.authorization;
8
13
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
@@ -33,7 +38,19 @@ const authenticateBearer = async (req, res, next) => {
33
38
  next();
34
39
  };
35
40
  const app = express();
41
+ app.set('trust proxy', 1);
36
42
  app.use(express.json());
43
+ const publicPath = path.join(__dirname, '..', 'public');
44
+ app.use((req, res, next) => {
45
+ res.header('Access-Control-Allow-Origin', '*');
46
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
47
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
48
+ if (req.method === 'OPTIONS') {
49
+ res.sendStatus(200);
50
+ return;
51
+ }
52
+ next();
53
+ });
37
54
  app.post('/mcp', authenticateBearer, async (req, res) => {
38
55
  try {
39
56
  const server = getServer(req.bearerKey);
@@ -85,10 +102,22 @@ app.delete('/mcp', async (req, res) => {
85
102
  }));
86
103
  });
87
104
  app.get('/health', async (req, res) => {
105
+ console.log('Health check requested');
88
106
  res.status(200).json({
89
- status: 'ok'
107
+ status: 'ok',
108
+ timestamp: new Date().toISOString(),
90
109
  });
91
110
  });
111
+ app.use('/oauth', authApp);
112
+ app.get('/', async (req, res) => {
113
+ res.redirect('/oauth/index');
114
+ });
115
+ app.get('/oauth/index', async (req, res) => {
116
+ res.sendFile(path.join(publicPath, 'oauth-index.html'));
117
+ });
118
+ app.get('/oauth/demo', async (req, res) => {
119
+ res.sendFile(path.join(publicPath, 'oauth-demo.html'));
120
+ });
92
121
  const PORT = Number(process.env.PORT ?? 9278);
93
122
  const HOST = process.env.HOST ?? '0.0.0.0';
94
123
  app.listen(PORT, HOST, () => {
@@ -0,0 +1,19 @@
1
+ export function regOpenLcaPrompts(server) {
2
+ server.prompt('openlca', `The workflow for LCA calculation`, () => {
3
+ return {
4
+ messages: [
5
+ {
6
+ role: 'assistant',
7
+ content: {
8
+ type: 'text',
9
+ text: `I am an expert in Life Cycle Assessment (LCA) and use the MCP tool to perform LCA calculations.
10
+ The workflow is as follows:
11
+ 1. Use the OpenLCA_Impact_Assessment_Tool to list all LCIA (Life Cycle Impact Assessment) method UUIDs and their corresponding names.
12
+ 2. Use the OpenLCA_List_System_Processes_Tool to list all system process UUIDs and their corresponding names.
13
+ 3. Use the OpenLCA_Impact_Assessment_Tool to perform LCA calculations.`,
14
+ },
15
+ },
16
+ ],
17
+ };
18
+ });
19
+ }
@@ -0,0 +1,16 @@
1
+ export function regOpenLcaResources(server) {
2
+ server.resource('Guidence for LCA calculation', `resource://openlca`, (request) => {
3
+ return {
4
+ contents: [
5
+ {
6
+ uri: 'resource://openlca',
7
+ mimeType: 'text/plain',
8
+ text: `The workflow to perform LCA calculations using the MCP tool is as follows:
9
+ 1. Use the OpenLCA_Impact_Assessment_Tool to list all LCIA (Life Cycle Impact Assessment) method UUIDs and their corresponding names.
10
+ 2. Use the OpenLCA_List_System_Processes_Tool to list all system process UUIDs and their corresponding names.
11
+ 3. Use the OpenLCA_Impact_Assessment_Tool to perform LCA calculations.`,
12
+ },
13
+ ],
14
+ };
15
+ });
16
+ }
@@ -0,0 +1,19 @@
1
+ async function getLcaCalculationGuidance() {
2
+ const prompt = `The workflow to perform LCA calculations using the MCP tool is as follows:
3
+ 1. Use the OpenLCA_Impact_Assessment_Tool to list all LCIA (Life Cycle Impact Assessment) method UUIDs and their corresponding names.
4
+ 2. Use the OpenLCA_List_System_Processes_Tool to list all system process UUIDs and their corresponding names.
5
+ 3. Use the OpenLCA_Impact_Assessment_Tool to perform LCA calculations.`;
6
+ return prompt;
7
+ }
8
+ export function regLcaCalculationGuidanceTool(server) {
9
+ server.tool('LCA_Calculation_Guidance_Tool', 'Get the workflow, which should be followed for Life Cycle Assessment (LCA) Calculations to Obtain Life Cycle Impact Assessment (LCIA) Results', async () => {
10
+ return {
11
+ content: [
12
+ {
13
+ type: 'text',
14
+ text: await getLcaCalculationGuidance(),
15
+ },
16
+ ],
17
+ };
18
+ });
19
+ }