@tiangong-lca/mcp-server 0.0.5 → 0.0.7
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.
- package/README.md +4 -4
- package/dist/src/_shared/auth_middleware.js +74 -0
- package/dist/src/_shared/config.js +1 -1
- package/dist/src/_shared/decode_api_key.js +16 -0
- package/dist/src/_shared/init_server.js +5 -5
- package/dist/src/index_server.js +13 -10
- package/dist/src/tools/flow_hybrid_search.js +6 -6
- package/dist/src/tools/process_hybrid_search.js +6 -6
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -32,17 +32,17 @@ npx -y supergateway \
|
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
# Build MCP server image using Dockerfile (optional)
|
|
35
|
-
docker build -t linancn/tiangong-lca-mcp-server:0.0.
|
|
35
|
+
docker build -t linancn/tiangong-lca-mcp-server:0.0.5 .
|
|
36
36
|
|
|
37
37
|
# Pull MCP server image
|
|
38
|
-
docker pull linancn/tiangong-lca-mcp-server:0.0.
|
|
38
|
+
docker pull linancn/tiangong-lca-mcp-server:0.0.5
|
|
39
39
|
|
|
40
40
|
# Start MCP server using Docker
|
|
41
41
|
docker run -d \
|
|
42
42
|
--name tiangong-lca-mcp-server \
|
|
43
43
|
--publish 9278:9278 \
|
|
44
44
|
--env-file .env \
|
|
45
|
-
linancn/tiangong-lca-mcp-server:0.0.
|
|
45
|
+
linancn/tiangong-lca-mcp-server:0.0.5
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
## Development
|
|
@@ -90,7 +90,7 @@ npm install -g supergateway
|
|
|
90
90
|
# Launch the SSE Server (If the parameter --baseUrl is configured, it should be set to a valid IP address or domain name)
|
|
91
91
|
npx dotenv -e .env -- \
|
|
92
92
|
npx -y supergateway \
|
|
93
|
-
--stdio "npx -y -p tiangong-lca-mcp-server-0.0.
|
|
93
|
+
--stdio "npx -y -p tiangong-lca-mcp-server-0.0.5.tgz tiangong-lca-mcp-stdio" \
|
|
94
94
|
--port 3001 \
|
|
95
95
|
--ssePath /sse \
|
|
96
96
|
--messagePath /message
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
import { Redis } from '@upstash/redis';
|
|
3
|
+
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
|
+
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
|
+
const redis = new Redis({
|
|
10
|
+
url: redis_url,
|
|
11
|
+
token: redis_token,
|
|
12
|
+
});
|
|
13
|
+
export async function authenticateRequest(bearerKey) {
|
|
14
|
+
const credentials = decodeApiKey(bearerKey);
|
|
15
|
+
if (credentials) {
|
|
16
|
+
const { email = '', password = '' } = credentials;
|
|
17
|
+
const userIdFromRedis = await redis.get('lca_' + email);
|
|
18
|
+
if (!userIdFromRedis) {
|
|
19
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
20
|
+
email: email,
|
|
21
|
+
password: password,
|
|
22
|
+
});
|
|
23
|
+
console.error(email, password, data, error);
|
|
24
|
+
if (error) {
|
|
25
|
+
return {
|
|
26
|
+
isAuthenticated: false,
|
|
27
|
+
response: 'Unauthorized',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (data.user.role !== 'authenticated') {
|
|
31
|
+
return {
|
|
32
|
+
isAuthenticated: false,
|
|
33
|
+
response: 'You are not an authenticated user.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await redis.setex('lca_' + email, 3600, data.user.id);
|
|
38
|
+
return {
|
|
39
|
+
isAuthenticated: true,
|
|
40
|
+
response: data.user.id,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
isAuthenticated: true,
|
|
46
|
+
response: String(userIdFromRedis),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const { data: authData } = await supabase.auth.getUser(bearerKey);
|
|
50
|
+
if (authData.user?.role === 'authenticated') {
|
|
51
|
+
return {
|
|
52
|
+
isAuthenticated: true,
|
|
53
|
+
response: authData.user?.id,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!authData || !authData.user) {
|
|
57
|
+
return {
|
|
58
|
+
isAuthenticated: false,
|
|
59
|
+
response: 'User Not Found',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (authData.user.role !== 'authenticated') {
|
|
64
|
+
return {
|
|
65
|
+
isAuthenticated: false,
|
|
66
|
+
response: 'Forbidden',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
isAuthenticated: true,
|
|
72
|
+
response: authData.user.id,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const supabase_base_url = process.env.SUPABASE_BASE_URL ?? '';
|
|
2
2
|
export const supabase_anon_key = process.env.SUPABASE_ANON_KEY ?? '';
|
|
3
3
|
export const x_api_key = process.env.X_API_KEY ?? '';
|
|
4
4
|
export const x_region = process.env.X_REGION ?? '';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function decodeApiKey(apiKey) {
|
|
2
|
+
if (!apiKey)
|
|
3
|
+
return null;
|
|
4
|
+
try {
|
|
5
|
+
const jsonString = atob(apiKey);
|
|
6
|
+
const credentials = JSON.parse(jsonString);
|
|
7
|
+
if (!credentials.email || !credentials.password) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return credentials;
|
|
11
|
+
}
|
|
12
|
+
catch (_error) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export default decodeApiKey;
|
|
@@ -5,19 +5,19 @@ import { regOpenLcaLciaTool } from '../tools/openlca_ipc_lcia.js';
|
|
|
5
5
|
import { regOpenLcaListLCIAMethodsTool } from '../tools/openlca_ipc_lcia_methods_list.js';
|
|
6
6
|
import { regOpenLcaListSystemProcessTool } from '../tools/openlca_ipc_process_list.js';
|
|
7
7
|
import { regProcessSearchTool } from '../tools/process_hybrid_search.js';
|
|
8
|
-
export function initializeServer() {
|
|
8
|
+
export function initializeServer(bearerKey) {
|
|
9
9
|
const server = new McpServer({
|
|
10
10
|
name: 'TianGong-MCP-Server',
|
|
11
11
|
version: '1.0.0',
|
|
12
12
|
});
|
|
13
|
-
regFlowSearchTool(server);
|
|
14
|
-
regProcessSearchTool(server);
|
|
13
|
+
regFlowSearchTool(server, bearerKey);
|
|
14
|
+
regProcessSearchTool(server, bearerKey);
|
|
15
15
|
regBomCalculationTool(server);
|
|
16
16
|
regOpenLcaLciaTool(server);
|
|
17
17
|
regOpenLcaListSystemProcessTool(server);
|
|
18
18
|
regOpenLcaListLCIAMethodsTool(server);
|
|
19
19
|
return server;
|
|
20
20
|
}
|
|
21
|
-
export function getServer() {
|
|
22
|
-
return initializeServer();
|
|
21
|
+
export function getServer(bearerKey) {
|
|
22
|
+
return initializeServer(bearerKey);
|
|
23
23
|
}
|
package/dist/src/index_server.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
3
|
import express from 'express';
|
|
4
|
+
import { authenticateRequest } from './_shared/auth_middleware.js';
|
|
4
5
|
import { getServer } from './_shared/init_server.js';
|
|
5
|
-
const
|
|
6
|
-
const authenticateBearer = (req, res, next) => {
|
|
7
|
-
if (!BEARER_KEY) {
|
|
8
|
-
next();
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
6
|
+
const authenticateBearer = async (req, res, next) => {
|
|
11
7
|
const authHeader = req.headers.authorization;
|
|
12
8
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
13
9
|
res.status(401).json({
|
|
@@ -20,25 +16,27 @@ const authenticateBearer = (req, res, next) => {
|
|
|
20
16
|
});
|
|
21
17
|
return;
|
|
22
18
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
19
|
+
const bearerKey = authHeader.substring(7).trim();
|
|
20
|
+
const authResult = await authenticateRequest(bearerKey);
|
|
21
|
+
if (!authResult || !authResult.isAuthenticated) {
|
|
25
22
|
res.status(403).json({
|
|
26
23
|
jsonrpc: '2.0',
|
|
27
24
|
error: {
|
|
28
25
|
code: -32002,
|
|
29
|
-
message:
|
|
26
|
+
message: authResult?.response || 'Forbidden',
|
|
30
27
|
},
|
|
31
28
|
id: null,
|
|
32
29
|
});
|
|
33
30
|
return;
|
|
34
31
|
}
|
|
32
|
+
req.bearerKey = bearerKey;
|
|
35
33
|
next();
|
|
36
34
|
};
|
|
37
35
|
const app = express();
|
|
38
36
|
app.use(express.json());
|
|
39
37
|
app.post('/mcp', authenticateBearer, async (req, res) => {
|
|
40
38
|
try {
|
|
41
|
-
const server = getServer();
|
|
39
|
+
const server = getServer(req.bearerKey);
|
|
42
40
|
const transport = new StreamableHTTPServerTransport({
|
|
43
41
|
sessionIdGenerator: undefined,
|
|
44
42
|
});
|
|
@@ -86,6 +84,11 @@ app.delete('/mcp', async (req, res) => {
|
|
|
86
84
|
id: null,
|
|
87
85
|
}));
|
|
88
86
|
});
|
|
87
|
+
app.get('/health', async (req, res) => {
|
|
88
|
+
res.status(200).json({
|
|
89
|
+
status: 'ok'
|
|
90
|
+
});
|
|
91
|
+
});
|
|
89
92
|
const PORT = Number(process.env.PORT ?? 9278);
|
|
90
93
|
const HOST = process.env.HOST ?? '0.0.0.0';
|
|
91
94
|
app.listen(PORT, HOST, () => {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import cleanObject from '../_shared/clean_object.js';
|
|
3
|
-
import {
|
|
3
|
+
import { supabase_anon_key, supabase_base_url, x_region } from '../_shared/config.js';
|
|
4
4
|
const input_schema = {
|
|
5
5
|
query: z.string().min(1).describe('Queries from user'),
|
|
6
6
|
};
|
|
7
|
-
async function searchFlows({ query }) {
|
|
8
|
-
const url = `${
|
|
7
|
+
async function searchFlows({ query }, bearerKey) {
|
|
8
|
+
const url = `${supabase_base_url}/functions/v1/flow_hybrid_search`;
|
|
9
9
|
try {
|
|
10
10
|
const response = await fetch(url, {
|
|
11
11
|
method: 'POST',
|
|
12
12
|
headers: {
|
|
13
13
|
'Content-Type': 'application/json',
|
|
14
14
|
Authorization: `Bearer ${supabase_anon_key}`,
|
|
15
|
-
'x-api-key':
|
|
15
|
+
...(bearerKey && { 'x-api-key': bearerKey }),
|
|
16
16
|
'x-region': x_region,
|
|
17
17
|
},
|
|
18
18
|
body: JSON.stringify(cleanObject({
|
|
@@ -30,11 +30,11 @@ async function searchFlows({ query }) {
|
|
|
30
30
|
throw error;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
export function regFlowSearchTool(server) {
|
|
33
|
+
export function regFlowSearchTool(server, bearerKey) {
|
|
34
34
|
server.tool('Search_flows_Tool', 'Search LCA flows data.', input_schema, async ({ query }) => {
|
|
35
35
|
const result = await searchFlows({
|
|
36
36
|
query,
|
|
37
|
-
});
|
|
37
|
+
}, bearerKey);
|
|
38
38
|
return {
|
|
39
39
|
content: [
|
|
40
40
|
{
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import cleanObject from '../_shared/clean_object.js';
|
|
3
|
-
import {
|
|
3
|
+
import { supabase_anon_key, supabase_base_url, x_region } from '../_shared/config.js';
|
|
4
4
|
const input_schema = {
|
|
5
5
|
query: z.string().min(1).describe('Queries from user'),
|
|
6
6
|
};
|
|
7
|
-
async function searchProcesses({ query }) {
|
|
8
|
-
const url = `${
|
|
7
|
+
async function searchProcesses({ query }, bearerKey) {
|
|
8
|
+
const url = `${supabase_base_url}/functions/v1/process_hybrid_search`;
|
|
9
9
|
try {
|
|
10
10
|
const response = await fetch(url, {
|
|
11
11
|
method: 'POST',
|
|
12
12
|
headers: {
|
|
13
13
|
'Content-Type': 'application/json',
|
|
14
14
|
Authorization: `Bearer ${supabase_anon_key}`,
|
|
15
|
-
'x-api-key':
|
|
15
|
+
...(bearerKey && { 'x-api-key': bearerKey }),
|
|
16
16
|
'x-region': x_region,
|
|
17
17
|
},
|
|
18
18
|
body: JSON.stringify(cleanObject({
|
|
@@ -30,11 +30,11 @@ async function searchProcesses({ query }) {
|
|
|
30
30
|
throw error;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
export function regProcessSearchTool(server) {
|
|
33
|
+
export function regProcessSearchTool(server, bearerKey) {
|
|
34
34
|
server.tool('Search_processes_Tool', 'Search LCA processes data.', input_schema, async ({ query }) => {
|
|
35
35
|
const result = await searchProcesses({
|
|
36
36
|
query,
|
|
37
|
-
});
|
|
37
|
+
}, bearerKey);
|
|
38
38
|
return {
|
|
39
39
|
content: [
|
|
40
40
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiangong-lca/mcp-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "TianGong LCA MCP Server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nan LI",
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"olca-ipc": "^2.2.1",
|
|
26
|
-
"@types/express": "^5.0.
|
|
26
|
+
"@types/express": "^5.0.3",
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
|
-
"@supabase/supabase-js": "^2.
|
|
29
|
-
"
|
|
28
|
+
"@supabase/supabase-js": "^2.50.0",
|
|
29
|
+
"@upstash/redis": "^1.35.0",
|
|
30
|
+
"zod": "^3.25.57"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@modelcontextprotocol/inspector": "^0.14.0",
|