@tiangong-lca/mcp-server 0.0.29 → 0.0.31
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
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
TianGong LCA Model Context Protocol (MCP) Server supports STDIO and Streamable Http protocols.
|
|
6
6
|
|
|
7
|
+
## Environment
|
|
8
|
+
|
|
9
|
+
GLAD dataset search tools require a GLAD API key in the server environment:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
GLAD_API_KEY=your-glad-api-key
|
|
13
|
+
GLAD_API_BASE_URL=https://www.globallcadataaccess.org/api/v1
|
|
14
|
+
```
|
|
15
|
+
|
|
7
16
|
## Starting MCP Server
|
|
8
17
|
|
|
9
18
|
### Client STDIO Server
|
|
@@ -10,3 +10,5 @@ export const supabase_publishable_key = process.env.SUPABASE_PUBLISHABLE_KEY ??
|
|
|
10
10
|
export const x_region = process.env.X_REGION ?? 'us-east-1';
|
|
11
11
|
export const redis_url = process.env.UPSTASH_REDIS_URL ?? '';
|
|
12
12
|
export const redis_token = process.env.UPSTASH_REDIS_TOKEN ?? '';
|
|
13
|
+
export const glad_api_base_url = process.env.GLAD_API_BASE_URL ?? 'https://www.globallcadataaccess.org/api/v1';
|
|
14
|
+
export const glad_api_key = process.env.GLAD_API_KEY ?? '';
|
|
@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { regOpenLcaPrompts } from '../prompts/lca_calculation.js';
|
|
3
3
|
import { regOpenLcaResources } from '../resources/lca_calculation.js';
|
|
4
4
|
import { regFlowSearchTool } from '../tools/flow_hybrid_search.js';
|
|
5
|
+
import { regGladDatasetTools } from '../tools/glad_dataset_search.js';
|
|
5
6
|
import { regLcaCalculationGuidanceTool } from '../tools/lca_calculation_guidance.js';
|
|
6
7
|
import { regLifecycleModelSearchTool } from '../tools/life_cycle_model_hybrid_search.js';
|
|
7
8
|
import { regOpenLcaLciaTool } from '../tools/openlca_ipc_lcia.js';
|
|
@@ -16,6 +17,7 @@ export function initializeServer(bearerKey) {
|
|
|
16
17
|
regFlowSearchTool(server, bearerKey);
|
|
17
18
|
regProcessSearchTool(server, bearerKey);
|
|
18
19
|
regLifecycleModelSearchTool(server, bearerKey);
|
|
20
|
+
regGladDatasetTools(server);
|
|
19
21
|
regOpenLcaLciaTool(server);
|
|
20
22
|
regOpenLcaListSystemProcessesTool(server);
|
|
21
23
|
regOpenLcaListLCIAMethodsTool(server);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { regCrudTool } from '../tools/db_crud.js';
|
|
3
3
|
import { regFlowSearchTool } from '../tools/flow_hybrid_search.js';
|
|
4
|
+
import { regGladDatasetTools } from '../tools/glad_dataset_search.js';
|
|
4
5
|
import { regLifecycleModelSearchTool } from '../tools/life_cycle_model_hybrid_search.js';
|
|
5
6
|
import { regProcessSearchTool } from '../tools/process_hybrid_search.js';
|
|
6
7
|
export function initializeServer(bearerKey, supabaseSession) {
|
|
@@ -11,6 +12,7 @@ export function initializeServer(bearerKey, supabaseSession) {
|
|
|
11
12
|
regFlowSearchTool(server, bearerKey);
|
|
12
13
|
regProcessSearchTool(server, bearerKey);
|
|
13
14
|
regLifecycleModelSearchTool(server, bearerKey);
|
|
15
|
+
regGladDatasetTools(server);
|
|
14
16
|
regCrudTool(server, supabaseSession ?? bearerKey);
|
|
15
17
|
return server;
|
|
16
18
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createClient
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { supabase_base_url, supabase_publishable_key } from '../_shared/config.js';
|
|
4
4
|
import { resolveSupabaseAccessToken } from '../_shared/supabase_session.js';
|
|
5
5
|
import { prepareLifecycleModelFile } from './life_cycle_model_file_tools.js';
|
|
6
6
|
const allowedTables = ['contacts', 'flows', 'lifecyclemodels', 'processes', 'sources'];
|
|
7
7
|
const tableSchema = z.enum(allowedTables);
|
|
8
|
-
const UPDATE_FUNCTION_NAME = 'update_data';
|
|
9
8
|
const MAX_VALIDATION_ERROR_LENGTH = 4_000;
|
|
10
9
|
const tablePrimaryKey = {
|
|
11
10
|
contacts: 'id',
|
|
@@ -321,35 +320,24 @@ async function handleUpdate(supabase, accessToken, input, bearerKey) {
|
|
|
321
320
|
}
|
|
322
321
|
const jsonOrderedValue = jsonOrdered;
|
|
323
322
|
const preparedWrite = await prepareWritePayload(table, jsonOrderedValue, id, version, bearerKey);
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
},
|
|
335
|
-
region: FunctionRegion.UsEast1,
|
|
336
|
-
});
|
|
323
|
+
requireAccessToken(accessToken);
|
|
324
|
+
const keyColumn = getPrimaryKeyColumn(table);
|
|
325
|
+
const resolvedId = preparedWrite.resolvedId ?? id;
|
|
326
|
+
const resolvedVersion = preparedWrite.resolvedVersion ?? version;
|
|
327
|
+
const { data, error } = await supabase
|
|
328
|
+
.from(table)
|
|
329
|
+
.update(preparedWrite.payload)
|
|
330
|
+
.eq(keyColumn, resolvedId)
|
|
331
|
+
.eq('version', resolvedVersion)
|
|
332
|
+
.select();
|
|
337
333
|
if (error) {
|
|
338
|
-
console.error('Error
|
|
334
|
+
console.error('Error updating the database:', error);
|
|
339
335
|
throw error;
|
|
340
336
|
}
|
|
341
|
-
const
|
|
342
|
-
{});
|
|
343
|
-
if (functionError) {
|
|
344
|
-
console.error('Supabase update_data returned an error:', functionError);
|
|
345
|
-
const message = functionError.message ?? 'Supabase update_data function rejected the request.';
|
|
346
|
-
throw new Error(message);
|
|
347
|
-
}
|
|
348
|
-
const keyColumn = getPrimaryKeyColumn(table);
|
|
349
|
-
const rows = ensureRows(updatedRows, `Update affected 0 rows for table "${table}"; verify the provided ${keyColumn} (${preparedWrite.resolvedId ?? id}) and version (${preparedWrite.resolvedVersion ?? version}) exist and are accessible.`);
|
|
337
|
+
const rows = ensureRows(data, `Update affected 0 rows for table "${table}"; verify the provided ${keyColumn} (${resolvedId}) and version (${resolvedVersion}) exist and are accessible.`);
|
|
350
338
|
return JSON.stringify({
|
|
351
|
-
id:
|
|
352
|
-
version:
|
|
339
|
+
id: resolvedId,
|
|
340
|
+
version: resolvedVersion,
|
|
353
341
|
data: sanitizeRowsForOutput(table, rows),
|
|
354
342
|
});
|
|
355
343
|
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { glad_api_base_url, glad_api_key } from '../_shared/config.js';
|
|
3
|
+
const sectorSchema = z.enum([
|
|
4
|
+
'Agriculture',
|
|
5
|
+
'Chemicals',
|
|
6
|
+
'Construction',
|
|
7
|
+
'Electronics',
|
|
8
|
+
'Energy',
|
|
9
|
+
'Food',
|
|
10
|
+
'Metals',
|
|
11
|
+
'Mining',
|
|
12
|
+
'Textiles',
|
|
13
|
+
'Transport',
|
|
14
|
+
'Other',
|
|
15
|
+
]);
|
|
16
|
+
const formatSchema = z.enum(['ECOSPOLD1', 'ECOSPOLD2', 'ILCD', 'JSON-LD', 'OTHER', 'UNKNOWN']);
|
|
17
|
+
const processTypeSchema = z.enum([
|
|
18
|
+
'UNIT',
|
|
19
|
+
'PARTIALLY_AGGREGATED',
|
|
20
|
+
'FULLY_AGGREGATED',
|
|
21
|
+
'BRIDGE',
|
|
22
|
+
'UNKNOWN',
|
|
23
|
+
]);
|
|
24
|
+
const modelingTypeSchema = z.enum(['ATTRIBUTIONAL', 'CONSEQUENTIAL', 'BEFORE_MODELING', 'UNKNOWN']);
|
|
25
|
+
const reviewTypeSchema = z.enum(['INTERNAL', 'EXTERNAL', 'PANEL', 'UNKNOWN', 'NONE']);
|
|
26
|
+
const reviewSystemSchema = z.enum([
|
|
27
|
+
'ILCD',
|
|
28
|
+
'PEF',
|
|
29
|
+
'GHG',
|
|
30
|
+
'LCA_UN',
|
|
31
|
+
'OTHER',
|
|
32
|
+
'UNKNOWN',
|
|
33
|
+
'NOT_APPLICABLE',
|
|
34
|
+
]);
|
|
35
|
+
const extraFilterValueSchema = z.union([
|
|
36
|
+
z.string(),
|
|
37
|
+
z.number(),
|
|
38
|
+
z.boolean(),
|
|
39
|
+
z.array(z.union([z.string(), z.number(), z.boolean()])),
|
|
40
|
+
]);
|
|
41
|
+
const gladSearchInputSchema = z.object({
|
|
42
|
+
query: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Full-text search query. GLAD searches name, description, category, and technology; multiple words are combined with AND.'),
|
|
47
|
+
page: z.number().int().min(0).default(0).describe('Zero-based GLAD result page. Default: 0.'),
|
|
48
|
+
pageSize: z
|
|
49
|
+
.number()
|
|
50
|
+
.int()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(100)
|
|
53
|
+
.default(10)
|
|
54
|
+
.describe('Number of datasets to return. Capped at 100 to keep MCP responses usable.'),
|
|
55
|
+
sortBy: z.string().min(1).optional().describe('Field name to sort by, for example name.'),
|
|
56
|
+
sortOrder: z.enum(['ASC', 'DESC']).default('ASC').describe('Sort order used when sortBy is set.'),
|
|
57
|
+
sectors: z.array(sectorSchema).optional().describe('Filter by one or more GLAD sectors.'),
|
|
58
|
+
format: z.array(formatSchema).optional().describe('Filter by one or more dataset formats.'),
|
|
59
|
+
location: z
|
|
60
|
+
.array(z.string().min(1))
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Filter by one or more location codes, for example DE or ZA.'),
|
|
63
|
+
dataprovider: z
|
|
64
|
+
.array(z.string().min(1))
|
|
65
|
+
.optional()
|
|
66
|
+
.describe('Filter by one or more data provider names.'),
|
|
67
|
+
supportedNomenclatures: z
|
|
68
|
+
.array(z.string().min(1))
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Filter by supported nomenclature names, for example ecoinvent 3.5.'),
|
|
71
|
+
lciaMethods: z.array(z.string().min(1)).optional().describe('Filter by LCIA method names.'),
|
|
72
|
+
category: z
|
|
73
|
+
.array(z.string().min(1))
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Filter by category name or ISIC4 category code.'),
|
|
76
|
+
categoryPaths: z.array(z.string().min(1)).optional().describe('Filter by GLAD category paths.'),
|
|
77
|
+
unspscPaths: z.array(z.string().min(1)).optional().describe('Filter by UNSPSC path values.'),
|
|
78
|
+
co2pePaths: z.array(z.string().min(1)).optional().describe('Filter by CO2PE path values.'),
|
|
79
|
+
processType: z.array(processTypeSchema).optional().describe('Filter by GLAD process type.'),
|
|
80
|
+
modelingType: z.array(modelingTypeSchema).optional().describe('Filter by GLAD modeling type.'),
|
|
81
|
+
reviewType: z.array(reviewTypeSchema).optional().describe('Filter by GLAD review type.'),
|
|
82
|
+
reviewSystem: z.array(reviewSystemSchema).optional().describe('Filter by GLAD review system.'),
|
|
83
|
+
copyrightHolder: z.array(z.string().min(1)).optional().describe('Filter by copyright holder.'),
|
|
84
|
+
contact: z.string().min(1).optional().describe('Filter by contact value.'),
|
|
85
|
+
validFromYear: z.number().int().optional().describe('Filter by validity start year.'),
|
|
86
|
+
validUntilYear: z.number().int().optional().describe('Filter by validity end year.'),
|
|
87
|
+
copyrightProtected: z.boolean().optional().describe('Filter by copyright-protected status.'),
|
|
88
|
+
free: z.boolean().optional().describe('Filter by whether the dataset is available for free.'),
|
|
89
|
+
publiclyAccessible: z
|
|
90
|
+
.boolean()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Filter by whether the dataset URL is publicly accessible without further login.'),
|
|
93
|
+
extraFilters: z
|
|
94
|
+
.record(z.string(), extraFilterValueSchema)
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Advanced GLAD query parameters not modeled explicitly. Use official parameter names; arrays are sent as repeated query parameters.'),
|
|
97
|
+
});
|
|
98
|
+
const gladGetDatasetInputSchema = z.object({
|
|
99
|
+
refId: z.string().min(1).describe('GLAD dataset refId.'),
|
|
100
|
+
dataProvider: z.string().min(1).describe('GLAD data provider name for the dataset.'),
|
|
101
|
+
});
|
|
102
|
+
const resultInfoSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
currentPage: z.number().optional(),
|
|
105
|
+
pageSize: z.number().optional(),
|
|
106
|
+
pageCount: z.number().optional(),
|
|
107
|
+
count: z.number().optional(),
|
|
108
|
+
totalCount: z.number().optional(),
|
|
109
|
+
})
|
|
110
|
+
.passthrough();
|
|
111
|
+
const passthroughObjectSchema = z.object({}).passthrough();
|
|
112
|
+
const gladSearchOutputSchema = z
|
|
113
|
+
.object({
|
|
114
|
+
request: z
|
|
115
|
+
.object({
|
|
116
|
+
endpoint: z.string(),
|
|
117
|
+
page: z.number(),
|
|
118
|
+
pageSize: z.number(),
|
|
119
|
+
})
|
|
120
|
+
.passthrough(),
|
|
121
|
+
resultInfo: resultInfoSchema.optional(),
|
|
122
|
+
data: z.array(passthroughObjectSchema),
|
|
123
|
+
aggregations: z.array(passthroughObjectSchema),
|
|
124
|
+
})
|
|
125
|
+
.passthrough();
|
|
126
|
+
const gladGetDatasetOutputSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
request: z
|
|
129
|
+
.object({
|
|
130
|
+
endpoint: z.string(),
|
|
131
|
+
refId: z.string(),
|
|
132
|
+
dataProvider: z.string(),
|
|
133
|
+
})
|
|
134
|
+
.passthrough(),
|
|
135
|
+
dataSet: passthroughObjectSchema,
|
|
136
|
+
})
|
|
137
|
+
.passthrough();
|
|
138
|
+
const ARRAY_PARAM_MAP = {
|
|
139
|
+
'sectors[]': 'sectors',
|
|
140
|
+
'format[]': 'format',
|
|
141
|
+
'location[]': 'location',
|
|
142
|
+
'dataprovider[]': 'dataprovider',
|
|
143
|
+
'supportedNomenclatures[]': 'supportedNomenclatures',
|
|
144
|
+
'lciaMethods[]': 'lciaMethods',
|
|
145
|
+
'category[]': 'category',
|
|
146
|
+
'categoryPaths[]': 'categoryPaths',
|
|
147
|
+
'unspscPaths[]': 'unspscPaths',
|
|
148
|
+
'co2pePaths[]': 'co2pePaths',
|
|
149
|
+
'processType[]': 'processType',
|
|
150
|
+
'modelingType[]': 'modelingType',
|
|
151
|
+
'reviewType[]': 'reviewType',
|
|
152
|
+
'reviewSystem[]': 'reviewSystem',
|
|
153
|
+
'copyrightHolder[]': 'copyrightHolder',
|
|
154
|
+
};
|
|
155
|
+
const SCALAR_PARAM_NAMES = [
|
|
156
|
+
'query',
|
|
157
|
+
'sortBy',
|
|
158
|
+
'sortOrder',
|
|
159
|
+
'page',
|
|
160
|
+
'pageSize',
|
|
161
|
+
'contact',
|
|
162
|
+
'validFromYear',
|
|
163
|
+
'validUntilYear',
|
|
164
|
+
'copyrightProtected',
|
|
165
|
+
'free',
|
|
166
|
+
'publiclyAccessible',
|
|
167
|
+
];
|
|
168
|
+
function createGladUrl(pathname) {
|
|
169
|
+
const baseUrl = glad_api_base_url.replace(/\/+$/, '');
|
|
170
|
+
return new URL(`${baseUrl}${pathname}`);
|
|
171
|
+
}
|
|
172
|
+
function appendParam(params, key, value) {
|
|
173
|
+
if (value === undefined || value === null || value === '') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
for (const item of value) {
|
|
178
|
+
appendParam(params, key, item);
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
params.append(key, String(value));
|
|
183
|
+
}
|
|
184
|
+
function appendSearchParams(url, input) {
|
|
185
|
+
for (const key of SCALAR_PARAM_NAMES) {
|
|
186
|
+
appendParam(url.searchParams, key, input[key]);
|
|
187
|
+
}
|
|
188
|
+
for (const [wireName, inputKey] of Object.entries(ARRAY_PARAM_MAP)) {
|
|
189
|
+
appendParam(url.searchParams, wireName, input[inputKey]);
|
|
190
|
+
}
|
|
191
|
+
if (input.extraFilters) {
|
|
192
|
+
for (const [key, value] of Object.entries(input.extraFilters)) {
|
|
193
|
+
appendParam(url.searchParams, key, value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function assertGladApiKey() {
|
|
198
|
+
if (!glad_api_key) {
|
|
199
|
+
throw new Error('Missing GLAD_API_KEY. Add it to the MCP server environment before using GLAD tools.');
|
|
200
|
+
}
|
|
201
|
+
return glad_api_key;
|
|
202
|
+
}
|
|
203
|
+
function buildGladErrorMessage(status, statusText, body) {
|
|
204
|
+
const trimmedBody = body.trim();
|
|
205
|
+
if (trimmedBody.startsWith('<!DOCTYPE html') || trimmedBody.startsWith('<html')) {
|
|
206
|
+
return `GLAD API returned an HTML page instead of JSON (HTTP ${status} ${statusText}). This usually means Cloudflare or browser verification blocked this Node.js runtime before the GLAD API processed the api-key. Verify this runtime can reach GLAD directly, or point GLAD_API_BASE_URL at an accessible GLAD-compatible API endpoint.`;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(trimmedBody);
|
|
210
|
+
if (typeof parsed === 'object' && parsed !== null && 'message' in parsed) {
|
|
211
|
+
return `GLAD API request failed (HTTP ${status} ${statusText}): ${String(parsed.message)}`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
}
|
|
216
|
+
const preview = trimmedBody.slice(0, 500);
|
|
217
|
+
return `GLAD API request failed (HTTP ${status} ${statusText})${preview ? `: ${preview}` : ''}`;
|
|
218
|
+
}
|
|
219
|
+
async function requestGladJson(url) {
|
|
220
|
+
const response = await fetch(url, {
|
|
221
|
+
method: 'GET',
|
|
222
|
+
headers: {
|
|
223
|
+
Accept: 'application/json',
|
|
224
|
+
'User-Agent': 'TianGong-LCA-MCP/1.0',
|
|
225
|
+
'api-key': assertGladApiKey(),
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
const text = await response.text();
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
throw new Error(buildGladErrorMessage(response.status, response.statusText, text));
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
return JSON.parse(text);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw new Error(`GLAD API returned a non-JSON success response from ${url.pathname}: ${error.message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function searchGladDatasets(input) {
|
|
240
|
+
const url = createGladUrl('/search');
|
|
241
|
+
appendSearchParams(url, input);
|
|
242
|
+
const result = await requestGladJson(url);
|
|
243
|
+
return {
|
|
244
|
+
request: {
|
|
245
|
+
endpoint: `${url.origin}${url.pathname}`,
|
|
246
|
+
page: input.page,
|
|
247
|
+
pageSize: input.pageSize,
|
|
248
|
+
},
|
|
249
|
+
resultInfo: result.resultInfo,
|
|
250
|
+
data: result.data ?? [],
|
|
251
|
+
aggregations: result.aggregations ?? [],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function getGladDataset(input) {
|
|
255
|
+
const url = createGladUrl(`/search/index/${encodeURIComponent(input.refId)}/${encodeURIComponent(input.dataProvider)}`);
|
|
256
|
+
const dataSet = await requestGladJson(url);
|
|
257
|
+
return {
|
|
258
|
+
request: {
|
|
259
|
+
endpoint: `${url.origin}${url.pathname}`,
|
|
260
|
+
refId: input.refId,
|
|
261
|
+
dataProvider: input.dataProvider,
|
|
262
|
+
},
|
|
263
|
+
dataSet,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function toSuccessResult(output) {
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: 'text',
|
|
271
|
+
text: JSON.stringify(output, null, 2),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
structuredContent: output,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function toErrorResult(error) {
|
|
278
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
279
|
+
return {
|
|
280
|
+
isError: true,
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: 'text',
|
|
284
|
+
text: message,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
export function regGladDatasetTools(server) {
|
|
290
|
+
server.registerTool('Search_GLAD_Datasets_Tool', {
|
|
291
|
+
title: 'Search GLAD Datasets',
|
|
292
|
+
description: 'Search Global LCA Data Access (GLAD) dataset descriptors through the official GLAD /search API. Requires GLAD_API_KEY in the MCP server environment.',
|
|
293
|
+
inputSchema: gladSearchInputSchema,
|
|
294
|
+
outputSchema: gladSearchOutputSchema,
|
|
295
|
+
annotations: {
|
|
296
|
+
readOnlyHint: true,
|
|
297
|
+
destructiveHint: false,
|
|
298
|
+
idempotentHint: true,
|
|
299
|
+
openWorldHint: true,
|
|
300
|
+
},
|
|
301
|
+
}, async (rawInput) => {
|
|
302
|
+
try {
|
|
303
|
+
const input = gladSearchInputSchema.parse(rawInput);
|
|
304
|
+
const output = await searchGladDatasets(input);
|
|
305
|
+
return toSuccessResult(output);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return toErrorResult(error);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
server.registerTool('Get_GLAD_Dataset_Tool', {
|
|
312
|
+
title: 'Get GLAD Dataset',
|
|
313
|
+
description: 'Fetch one GLAD dataset descriptor by refId and dataProvider through the official GLAD /search/index/{refId}/{dataProvider} API. Requires GLAD_API_KEY in the MCP server environment.',
|
|
314
|
+
inputSchema: gladGetDatasetInputSchema,
|
|
315
|
+
outputSchema: gladGetDatasetOutputSchema,
|
|
316
|
+
annotations: {
|
|
317
|
+
readOnlyHint: true,
|
|
318
|
+
destructiveHint: false,
|
|
319
|
+
idempotentHint: true,
|
|
320
|
+
openWorldHint: true,
|
|
321
|
+
},
|
|
322
|
+
}, async (rawInput) => {
|
|
323
|
+
try {
|
|
324
|
+
const input = gladGetDatasetInputSchema.parse(rawInput);
|
|
325
|
+
const output = await getGladDataset(input);
|
|
326
|
+
return toSuccessResult(output);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
return toErrorResult(error);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiangong-lca/mcp-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"description": "TianGong LCA MCP Server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nan LI",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/linancn/tiangong-lca-mcp"
|
|
10
|
+
},
|
|
7
11
|
"type": "module",
|
|
8
12
|
"bin": {
|
|
9
13
|
"tiangong-lca-mcp-stdio": "dist/src/index.js",
|
|
@@ -23,29 +27,30 @@
|
|
|
23
27
|
"start:server": "npm run build && concurrently \"npx dotenv -e .env -- node dist/src/index_server.js\" \"DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector\"",
|
|
24
28
|
"start:server-local": "npm run build && concurrently \"npx dotenv -e .env -- node dist/src/index_server_local.js\" \"DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector\"",
|
|
25
29
|
"test": "node test/tidas_validation_test.js",
|
|
30
|
+
"prepush:gate": "npm run build && npm run lint && npm test",
|
|
26
31
|
"lint": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
|
|
27
32
|
"ncu": "npx npm-check-updates",
|
|
28
33
|
"ncu:update": "npx npm-check-updates -u"
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
|
-
"@dagrejs/dagre": "^
|
|
32
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
33
|
-
"@supabase/supabase-js": "^2.
|
|
34
|
-
"@tiangong-lca/tidas-sdk": "^0.1.
|
|
36
|
+
"@dagrejs/dagre": "^3.0.0",
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
|
+
"@supabase/supabase-js": "^2.106.1",
|
|
39
|
+
"@tiangong-lca/tidas-sdk": "^0.1.40",
|
|
35
40
|
"@types/express": "^5.0.6",
|
|
36
|
-
"@upstash/redis": "^1.
|
|
41
|
+
"@upstash/redis": "^1.38.0",
|
|
37
42
|
"aws-jwt-verify": "^5.1.1",
|
|
38
|
-
"olca-ipc": "^2.2
|
|
39
|
-
"zod": "^4.3
|
|
43
|
+
"olca-ipc": "^2.6.2",
|
|
44
|
+
"zod": "^4.4.3"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
42
|
-
"@modelcontextprotocol/inspector": "^0.21.
|
|
47
|
+
"@modelcontextprotocol/inspector": "^0.21.2",
|
|
43
48
|
"dotenv-cli": "^11.0.0",
|
|
44
|
-
"npm-check-updates": "^
|
|
45
|
-
"prettier": "^3.8.
|
|
49
|
+
"npm-check-updates": "^22.2.1",
|
|
50
|
+
"prettier": "^3.8.3",
|
|
46
51
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
47
52
|
"shx": "^0.4.0",
|
|
48
|
-
"tsx": "^4.
|
|
49
|
-
"typescript": "^
|
|
53
|
+
"tsx": "^4.22.3",
|
|
54
|
+
"typescript": "^6.0.3"
|
|
50
55
|
}
|
|
51
56
|
}
|