@burdenoff/mcp-libs 2026.627.2 → 2026.627.4
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/dist/modules/artistrybase/index.cjs +2112 -0
- package/dist/modules/artistrybase/index.cjs.map +1 -0
- package/dist/modules/artistrybase/index.d.cts +8 -0
- package/dist/modules/artistrybase/index.d.ts +8 -0
- package/dist/modules/artistrybase/index.js +2051 -0
- package/dist/modules/artistrybase/index.js.map +1 -0
- package/dist/modules/brainyrich/index.cjs +2148 -0
- package/dist/modules/brainyrich/index.cjs.map +1 -0
- package/dist/modules/brainyrich/index.d.cts +8 -0
- package/dist/modules/brainyrich/index.d.ts +8 -0
- package/dist/modules/brainyrich/index.js +2087 -0
- package/dist/modules/brainyrich/index.js.map +1 -0
- package/dist/modules/buildmyiq/index.cjs +1562 -0
- package/dist/modules/buildmyiq/index.cjs.map +1 -0
- package/dist/modules/buildmyiq/index.d.cts +8 -0
- package/dist/modules/buildmyiq/index.d.ts +8 -0
- package/dist/modules/buildmyiq/index.js +1501 -0
- package/dist/modules/buildmyiq/index.js.map +1 -0
- package/dist/modules/collabkin/index.cjs +1577 -0
- package/dist/modules/collabkin/index.cjs.map +1 -0
- package/dist/modules/collabkin/index.d.cts +8 -0
- package/dist/modules/collabkin/index.d.ts +8 -0
- package/dist/modules/collabkin/index.js +1516 -0
- package/dist/modules/collabkin/index.js.map +1 -0
- package/dist/modules/comradecircle/index.cjs +1817 -0
- package/dist/modules/comradecircle/index.cjs.map +1 -0
- package/dist/modules/comradecircle/index.d.cts +8 -0
- package/dist/modules/comradecircle/index.d.ts +8 -0
- package/dist/modules/comradecircle/index.js +1756 -0
- package/dist/modules/comradecircle/index.js.map +1 -0
- package/dist/modules/cosmicintersection/index.cjs +1984 -0
- package/dist/modules/cosmicintersection/index.cjs.map +1 -0
- package/dist/modules/cosmicintersection/index.d.cts +8 -0
- package/dist/modules/cosmicintersection/index.d.ts +8 -0
- package/dist/modules/cosmicintersection/index.js +1923 -0
- package/dist/modules/cosmicintersection/index.js.map +1 -0
- package/dist/modules/ecoimpacthub/index.cjs +2042 -0
- package/dist/modules/ecoimpacthub/index.cjs.map +1 -0
- package/dist/modules/ecoimpacthub/index.d.cts +8 -0
- package/dist/modules/ecoimpacthub/index.d.ts +8 -0
- package/dist/modules/ecoimpacthub/index.js +1981 -0
- package/dist/modules/ecoimpacthub/index.js.map +1 -0
- package/dist/modules/govcitizenhub/index.cjs +1686 -0
- package/dist/modules/govcitizenhub/index.cjs.map +1 -0
- package/dist/modules/govcitizenhub/index.d.cts +8 -0
- package/dist/modules/govcitizenhub/index.d.ts +8 -0
- package/dist/modules/govcitizenhub/index.js +1625 -0
- package/dist/modules/govcitizenhub/index.js.map +1 -0
- package/dist/modules/intelwatchtower/index.cjs +1919 -0
- package/dist/modules/intelwatchtower/index.cjs.map +1 -0
- package/dist/modules/intelwatchtower/index.d.cts +8 -0
- package/dist/modules/intelwatchtower/index.d.ts +8 -0
- package/dist/modules/intelwatchtower/index.js +1858 -0
- package/dist/modules/intelwatchtower/index.js.map +1 -0
- package/dist/modules/manufacturedops/index.cjs +1884 -0
- package/dist/modules/manufacturedops/index.cjs.map +1 -0
- package/dist/modules/manufacturedops/index.d.cts +8 -0
- package/dist/modules/manufacturedops/index.d.ts +8 -0
- package/dist/modules/manufacturedops/index.js +1823 -0
- package/dist/modules/manufacturedops/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2042 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
require('graphql-request');
|
|
5
|
+
require('cross-fetch');
|
|
6
|
+
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
+
require('@modelcontextprotocol/sdk/types.js');
|
|
8
|
+
|
|
9
|
+
// src/modules/ecoimpacthub/tools/sites.ts
|
|
10
|
+
|
|
11
|
+
// src/core/errors.ts
|
|
12
|
+
var McpInternalError = class extends Error {
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "McpInternalError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/core/result.ts
|
|
22
|
+
function jsonResult(data, structured) {
|
|
23
|
+
const result = {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
25
|
+
};
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function errorResult(message, hint) {
|
|
29
|
+
const text = hint ? `${message}
|
|
30
|
+
|
|
31
|
+
Hint: ${hint}` : message;
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/tool-name.ts
|
|
36
|
+
var MAX_TOOL_NAME_LENGTH = 63;
|
|
37
|
+
function assertSafeName(name, kind) {
|
|
38
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
39
|
+
throw new McpInternalError(
|
|
40
|
+
`Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function mcpToolName(productName, ...parts) {
|
|
46
|
+
if (!productName) {
|
|
47
|
+
throw new McpInternalError("mcpToolName: productName is required");
|
|
48
|
+
}
|
|
49
|
+
const name = [productName, ...parts].filter(Boolean).join("_");
|
|
50
|
+
return assertSafeName(name);
|
|
51
|
+
}
|
|
52
|
+
function mcpResourceUri(productName, ...parts) {
|
|
53
|
+
if (!productName) {
|
|
54
|
+
throw new McpInternalError("mcpResourceUri: productName is required");
|
|
55
|
+
}
|
|
56
|
+
return `${productName}://${parts.filter(Boolean).join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function mcpPromptName(productName, ...parts) {
|
|
59
|
+
if (!productName) {
|
|
60
|
+
throw new McpInternalError("mcpPromptName: productName is required");
|
|
61
|
+
}
|
|
62
|
+
return [productName, ...parts].filter(Boolean).join("-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/modules/ecoimpacthub/operations.ts
|
|
66
|
+
var SITE_FIELDS = `
|
|
67
|
+
id name code description type status region country latitude longitude
|
|
68
|
+
areaHectares workspaceId createdAt updatedAt
|
|
69
|
+
`;
|
|
70
|
+
var SENSOR_FIELDS = `
|
|
71
|
+
id name siteId kind status unit hardwareId installedAt lastReadingAt
|
|
72
|
+
workspaceId createdAt updatedAt
|
|
73
|
+
`;
|
|
74
|
+
var THRESHOLD_FIELDS = `
|
|
75
|
+
id siteId parameter direction severity minValue maxValue limitValue unit
|
|
76
|
+
isActive workspaceId createdAt updatedAt
|
|
77
|
+
`;
|
|
78
|
+
var MEASUREMENT_FIELDS = `
|
|
79
|
+
id siteId sensorId parameter value unit recordedAt source isBreach notes
|
|
80
|
+
workspaceId createdAt updatedAt
|
|
81
|
+
`;
|
|
82
|
+
var SPECIES_FIELDS = `
|
|
83
|
+
id commonName scientificName taxonGroup conservationStatus isPriority
|
|
84
|
+
description workspaceId createdAt updatedAt
|
|
85
|
+
`;
|
|
86
|
+
var OBSERVATION_FIELDS = `
|
|
87
|
+
id speciesId siteId observerId observedAt method count latitude longitude
|
|
88
|
+
notes evidence { id } workspaceId createdAt updatedAt
|
|
89
|
+
`;
|
|
90
|
+
var RESTORATION_ACTIVITY_FIELDS = `
|
|
91
|
+
id projectId type summary details performedAt quantity unit workspaceId
|
|
92
|
+
createdAt updatedAt
|
|
93
|
+
`;
|
|
94
|
+
var HABITAT_PROJECT_FIELDS = `
|
|
95
|
+
id name description siteId managerId status targetHectares budgetAmount
|
|
96
|
+
currency startDate endDate completedAt workspaceId createdAt updatedAt
|
|
97
|
+
`;
|
|
98
|
+
var EMISSION_FACTOR_FIELDS = `
|
|
99
|
+
id name scope activityUnit factorKgco2e source validYear isActive
|
|
100
|
+
workspaceId createdAt updatedAt
|
|
101
|
+
`;
|
|
102
|
+
var CARBON_ENTRY_FIELDS = `
|
|
103
|
+
id category scope activityValue activityUnit co2eKg factorId siteId
|
|
104
|
+
periodStart status notes workspaceId createdAt updatedAt
|
|
105
|
+
`;
|
|
106
|
+
var REDUCTION_TARGET_FIELDS = `
|
|
107
|
+
id name metric description baselineValue baselineYear targetValue targetYear
|
|
108
|
+
unit status workspaceId createdAt updatedAt
|
|
109
|
+
`;
|
|
110
|
+
var REPORT_LINE_ITEM_FIELDS = `
|
|
111
|
+
id reportId label category value unit carbonEntryId workspaceId
|
|
112
|
+
createdAt updatedAt
|
|
113
|
+
`;
|
|
114
|
+
var REPORT_FIELDS = `
|
|
115
|
+
id title summary framework status periodStart periodEnd publishedAt
|
|
116
|
+
file { id } workspaceId createdAt updatedAt
|
|
117
|
+
lineItems { ${REPORT_LINE_ITEM_FIELDS} }
|
|
118
|
+
`;
|
|
119
|
+
var GET_SITE = `
|
|
120
|
+
query EcoGetSite($id: ID!) {
|
|
121
|
+
ecoSite(id: $id) {
|
|
122
|
+
${SITE_FIELDS}
|
|
123
|
+
sensors { id name kind status unit }
|
|
124
|
+
thresholds { id parameter direction severity isActive }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
var LIST_SITES = `
|
|
129
|
+
query EcoListSites(
|
|
130
|
+
$status: EcoSiteStatus
|
|
131
|
+
$type: EcoSiteType
|
|
132
|
+
$pagination: PaginationInput
|
|
133
|
+
) {
|
|
134
|
+
ecoListSites(status: $status, type: $type, pagination: $pagination) {
|
|
135
|
+
items { ${SITE_FIELDS} }
|
|
136
|
+
pageInfo { cursor hasMore }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
var CREATE_SITE = `
|
|
141
|
+
mutation EcoCreateSite($input: EcoCreateSiteInput!) {
|
|
142
|
+
ecoCreateSite(input: $input) {
|
|
143
|
+
${SITE_FIELDS}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
var UPDATE_SITE = `
|
|
148
|
+
mutation EcoUpdateSite($id: ID!, $input: EcoUpdateSiteInput!) {
|
|
149
|
+
ecoUpdateSite(id: $id, input: $input) {
|
|
150
|
+
${SITE_FIELDS}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
var ARCHIVE_SITE = `
|
|
155
|
+
mutation EcoArchiveSite($id: ID!) {
|
|
156
|
+
ecoArchiveSite(id: $id)
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
var LIST_SENSORS = `
|
|
160
|
+
query EcoListSensors($siteId: ID, $kind: EcoSensorKind, $status: EcoSensorStatus) {
|
|
161
|
+
ecoListSensors(siteId: $siteId, kind: $kind, status: $status) {
|
|
162
|
+
${SENSOR_FIELDS}
|
|
163
|
+
site { id name }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
var CREATE_SENSOR = `
|
|
168
|
+
mutation EcoCreateSensor($input: EcoCreateSensorInput!) {
|
|
169
|
+
ecoCreateSensor(input: $input) {
|
|
170
|
+
${SENSOR_FIELDS}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
var SET_SENSOR_STATUS = `
|
|
175
|
+
mutation EcoSetSensorStatus($id: ID!, $status: EcoSensorStatus!) {
|
|
176
|
+
ecoSetSensorStatus(id: $id, status: $status) {
|
|
177
|
+
${SENSOR_FIELDS}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
var LIST_THRESHOLDS = `
|
|
182
|
+
query EcoListThresholds($siteId: ID) {
|
|
183
|
+
ecoListThresholds(siteId: $siteId) {
|
|
184
|
+
${THRESHOLD_FIELDS}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
var CREATE_THRESHOLD = `
|
|
189
|
+
mutation EcoCreateThreshold($input: EcoCreateThresholdInput!) {
|
|
190
|
+
ecoCreateThreshold(input: $input) {
|
|
191
|
+
${THRESHOLD_FIELDS}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
var SET_THRESHOLD_ACTIVE = `
|
|
196
|
+
mutation EcoSetThresholdActive($id: ID!, $isActive: Boolean!) {
|
|
197
|
+
ecoSetThresholdActive(id: $id, isActive: $isActive) {
|
|
198
|
+
${THRESHOLD_FIELDS}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
var LIST_MEASUREMENTS = `
|
|
203
|
+
query EcoListMeasurements(
|
|
204
|
+
$siteId: ID
|
|
205
|
+
$sensorId: ID
|
|
206
|
+
$parameter: String
|
|
207
|
+
$breachesOnly: Boolean
|
|
208
|
+
$pagination: PaginationInput
|
|
209
|
+
) {
|
|
210
|
+
ecoListMeasurements(
|
|
211
|
+
siteId: $siteId
|
|
212
|
+
sensorId: $sensorId
|
|
213
|
+
parameter: $parameter
|
|
214
|
+
breachesOnly: $breachesOnly
|
|
215
|
+
pagination: $pagination
|
|
216
|
+
) {
|
|
217
|
+
items { ${MEASUREMENT_FIELDS} }
|
|
218
|
+
pageInfo { cursor hasMore }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
var RECORD_MEASUREMENT = `
|
|
223
|
+
mutation EcoRecordMeasurement($input: EcoRecordMeasurementInput!) {
|
|
224
|
+
ecoRecordMeasurement(input: $input) {
|
|
225
|
+
${MEASUREMENT_FIELDS}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
var GET_SPECIES = `
|
|
230
|
+
query EcoGetSpecies($id: ID!) {
|
|
231
|
+
ecoSpecies(id: $id) {
|
|
232
|
+
${SPECIES_FIELDS}
|
|
233
|
+
observations { id observedAt method count siteId }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
`;
|
|
237
|
+
var LIST_SPECIES = `
|
|
238
|
+
query EcoListSpecies(
|
|
239
|
+
$taxonGroup: EcoTaxonGroup
|
|
240
|
+
$conservationStatus: EcoConservationStatus
|
|
241
|
+
$priorityOnly: Boolean
|
|
242
|
+
$pagination: PaginationInput
|
|
243
|
+
) {
|
|
244
|
+
ecoListSpecies(
|
|
245
|
+
taxonGroup: $taxonGroup
|
|
246
|
+
conservationStatus: $conservationStatus
|
|
247
|
+
priorityOnly: $priorityOnly
|
|
248
|
+
pagination: $pagination
|
|
249
|
+
) {
|
|
250
|
+
items { ${SPECIES_FIELDS} }
|
|
251
|
+
pageInfo { cursor hasMore }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
`;
|
|
255
|
+
var CREATE_SPECIES = `
|
|
256
|
+
mutation EcoCreateSpecies($input: EcoCreateSpeciesInput!) {
|
|
257
|
+
ecoCreateSpecies(input: $input) {
|
|
258
|
+
${SPECIES_FIELDS}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
var UPDATE_SPECIES = `
|
|
263
|
+
mutation EcoUpdateSpecies($id: ID!, $input: EcoUpdateSpeciesInput!) {
|
|
264
|
+
ecoUpdateSpecies(id: $id, input: $input) {
|
|
265
|
+
${SPECIES_FIELDS}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
var ARCHIVE_SPECIES = `
|
|
270
|
+
mutation EcoArchiveSpecies($id: ID!) {
|
|
271
|
+
ecoArchiveSpecies(id: $id)
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
var LIST_OBSERVATIONS = `
|
|
275
|
+
query EcoListObservations(
|
|
276
|
+
$speciesId: ID
|
|
277
|
+
$siteId: ID
|
|
278
|
+
$pagination: PaginationInput
|
|
279
|
+
) {
|
|
280
|
+
ecoListObservations(speciesId: $speciesId, siteId: $siteId, pagination: $pagination) {
|
|
281
|
+
items {
|
|
282
|
+
${OBSERVATION_FIELDS}
|
|
283
|
+
species { id commonName }
|
|
284
|
+
}
|
|
285
|
+
pageInfo { cursor hasMore }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
289
|
+
var RECORD_OBSERVATION = `
|
|
290
|
+
mutation EcoRecordObservation($input: EcoRecordObservationInput!) {
|
|
291
|
+
ecoRecordObservation(input: $input) {
|
|
292
|
+
${OBSERVATION_FIELDS}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
var GET_HABITAT_PROJECT = `
|
|
297
|
+
query EcoGetHabitatProject($id: ID!) {
|
|
298
|
+
ecoHabitatProject(id: $id) {
|
|
299
|
+
${HABITAT_PROJECT_FIELDS}
|
|
300
|
+
activities { ${RESTORATION_ACTIVITY_FIELDS} }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
var LIST_HABITAT_PROJECTS = `
|
|
305
|
+
query EcoListHabitatProjects($status: EcoHabitatProjectStatus) {
|
|
306
|
+
ecoListHabitatProjects(status: $status) {
|
|
307
|
+
${HABITAT_PROJECT_FIELDS}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
`;
|
|
311
|
+
var CREATE_HABITAT_PROJECT = `
|
|
312
|
+
mutation EcoCreateHabitatProject($input: EcoCreateHabitatProjectInput!) {
|
|
313
|
+
ecoCreateHabitatProject(input: $input) {
|
|
314
|
+
${HABITAT_PROJECT_FIELDS}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
`;
|
|
318
|
+
var TRANSITION_HABITAT_PROJECT = `
|
|
319
|
+
mutation EcoTransitionHabitatProject($id: ID!, $status: EcoHabitatProjectStatus!) {
|
|
320
|
+
ecoTransitionHabitatProject(id: $id, status: $status) {
|
|
321
|
+
${HABITAT_PROJECT_FIELDS}
|
|
322
|
+
activities { ${RESTORATION_ACTIVITY_FIELDS} }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
var LOG_RESTORATION_ACTIVITY = `
|
|
327
|
+
mutation EcoLogRestorationActivity($input: EcoLogRestorationActivityInput!) {
|
|
328
|
+
ecoLogRestorationActivity(input: $input) {
|
|
329
|
+
${RESTORATION_ACTIVITY_FIELDS}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
var LIST_CARBON_ENTRIES = `
|
|
334
|
+
query EcoListCarbonEntries(
|
|
335
|
+
$scope: EcoEmissionScope
|
|
336
|
+
$siteId: ID
|
|
337
|
+
$status: EcoCarbonEntryStatus
|
|
338
|
+
$pagination: PaginationInput
|
|
339
|
+
) {
|
|
340
|
+
ecoListCarbonEntries(
|
|
341
|
+
scope: $scope
|
|
342
|
+
siteId: $siteId
|
|
343
|
+
status: $status
|
|
344
|
+
pagination: $pagination
|
|
345
|
+
) {
|
|
346
|
+
items {
|
|
347
|
+
${CARBON_ENTRY_FIELDS}
|
|
348
|
+
factor { id name factorKgco2e }
|
|
349
|
+
}
|
|
350
|
+
pageInfo { cursor hasMore }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
var CARBON_TOTAL_KG = `
|
|
355
|
+
query EcoCarbonTotalKg($scope: EcoEmissionScope, $siteId: ID) {
|
|
356
|
+
ecoCarbonTotalKg(scope: $scope, siteId: $siteId)
|
|
357
|
+
}
|
|
358
|
+
`;
|
|
359
|
+
var RECORD_CARBON_ENTRY = `
|
|
360
|
+
mutation EcoRecordCarbonEntry($input: EcoRecordCarbonEntryInput!) {
|
|
361
|
+
ecoRecordCarbonEntry(input: $input) {
|
|
362
|
+
${CARBON_ENTRY_FIELDS}
|
|
363
|
+
factor { id name factorKgco2e }
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
`;
|
|
367
|
+
var VERIFY_CARBON_ENTRY = `
|
|
368
|
+
mutation EcoVerifyCarbonEntry($id: ID!) {
|
|
369
|
+
ecoVerifyCarbonEntry(id: $id) {
|
|
370
|
+
${CARBON_ENTRY_FIELDS}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
`;
|
|
374
|
+
var LIST_EMISSION_FACTORS = `
|
|
375
|
+
query EcoListEmissionFactors($scope: EcoEmissionScope, $activeOnly: Boolean) {
|
|
376
|
+
ecoListEmissionFactors(scope: $scope, activeOnly: $activeOnly) {
|
|
377
|
+
${EMISSION_FACTOR_FIELDS}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
`;
|
|
381
|
+
var CREATE_EMISSION_FACTOR = `
|
|
382
|
+
mutation EcoCreateEmissionFactor($input: EcoCreateEmissionFactorInput!) {
|
|
383
|
+
ecoCreateEmissionFactor(input: $input) {
|
|
384
|
+
${EMISSION_FACTOR_FIELDS}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
`;
|
|
388
|
+
var LIST_REDUCTION_TARGETS = `
|
|
389
|
+
query EcoListReductionTargets($status: EcoReductionTargetStatus) {
|
|
390
|
+
ecoListReductionTargets(status: $status) {
|
|
391
|
+
${REDUCTION_TARGET_FIELDS}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
`;
|
|
395
|
+
var CREATE_REDUCTION_TARGET = `
|
|
396
|
+
mutation EcoCreateReductionTarget($input: EcoCreateReductionTargetInput!) {
|
|
397
|
+
ecoCreateReductionTarget(input: $input) {
|
|
398
|
+
${REDUCTION_TARGET_FIELDS}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
`;
|
|
402
|
+
var SET_REDUCTION_TARGET_STATUS = `
|
|
403
|
+
mutation EcoSetReductionTargetStatus($id: ID!, $status: EcoReductionTargetStatus!) {
|
|
404
|
+
ecoSetReductionTargetStatus(id: $id, status: $status) {
|
|
405
|
+
${REDUCTION_TARGET_FIELDS}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
`;
|
|
409
|
+
var GET_REPORT = `
|
|
410
|
+
query EcoGetReport($id: ID!) {
|
|
411
|
+
ecoReport(id: $id) {
|
|
412
|
+
${REPORT_FIELDS}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
`;
|
|
416
|
+
var LIST_REPORTS = `
|
|
417
|
+
query EcoListReports($status: EcoReportStatus, $pagination: PaginationInput) {
|
|
418
|
+
ecoListReports(status: $status, pagination: $pagination) {
|
|
419
|
+
items { ${REPORT_FIELDS} }
|
|
420
|
+
pageInfo { cursor hasMore }
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
var CREATE_REPORT = `
|
|
425
|
+
mutation EcoCreateReport($input: EcoCreateReportInput!) {
|
|
426
|
+
ecoCreateReport(input: $input) {
|
|
427
|
+
${REPORT_FIELDS}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
`;
|
|
431
|
+
var ADD_REPORT_LINE_ITEM = `
|
|
432
|
+
mutation EcoAddReportLineItem($input: EcoAddReportLineItemInput!) {
|
|
433
|
+
ecoAddReportLineItem(input: $input) {
|
|
434
|
+
${REPORT_LINE_ITEM_FIELDS}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
`;
|
|
438
|
+
var PUBLISH_REPORT = `
|
|
439
|
+
mutation EcoPublishReport($id: ID!, $fileId: ID) {
|
|
440
|
+
ecoPublishReport(id: $id, fileId: $fileId) {
|
|
441
|
+
${REPORT_FIELDS}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
`;
|
|
445
|
+
var HEALTH = `
|
|
446
|
+
query EcoHealth {
|
|
447
|
+
ecoHealth
|
|
448
|
+
}
|
|
449
|
+
`;
|
|
450
|
+
var SiteStatusEnum = zod.z.enum(["ACTIVE", "ARCHIVED", "MONITORING_PAUSED"]).describe("Lifecycle/monitoring status of an environmental site");
|
|
451
|
+
var SiteTypeEnum = zod.z.enum([
|
|
452
|
+
"AIRSHED",
|
|
453
|
+
"COASTAL",
|
|
454
|
+
"FACILITY",
|
|
455
|
+
"FARM",
|
|
456
|
+
"FOREST",
|
|
457
|
+
"OTHER",
|
|
458
|
+
"PROTECTED_AREA",
|
|
459
|
+
"WATERSHED",
|
|
460
|
+
"WETLAND"
|
|
461
|
+
]).describe("Classification of an environmental site");
|
|
462
|
+
var SensorKindEnum = zod.z.enum([
|
|
463
|
+
"ACOUSTIC",
|
|
464
|
+
"AIR_QUALITY",
|
|
465
|
+
"CAMERA_TRAP",
|
|
466
|
+
"ENERGY_METER",
|
|
467
|
+
"FLOW",
|
|
468
|
+
"MANUAL",
|
|
469
|
+
"SOIL",
|
|
470
|
+
"WATER_QUALITY",
|
|
471
|
+
"WEATHER"
|
|
472
|
+
]).describe("Kind of monitoring sensor");
|
|
473
|
+
var SensorStatusEnum = zod.z.enum(["DEGRADED", "MAINTENANCE", "OFFLINE", "ONLINE", "RETIRED"]).describe("Operational status of a sensor");
|
|
474
|
+
var ThresholdDirectionEnum = zod.z.enum(["ABOVE", "BELOW", "OUTSIDE_RANGE"]).describe("Direction a measurement must drift to breach a threshold");
|
|
475
|
+
var ThresholdSeverityEnum = zod.z.enum(["CRITICAL", "INFO", "WARNING"]).describe("Severity classification of an alert threshold");
|
|
476
|
+
var MeasurementSourceEnum = zod.z.enum(["CALCULATED", "IMPORT", "MANUAL", "SATELLITE", "SENSOR"]).describe("Provenance of a measurement value");
|
|
477
|
+
var ConservationStatusEnum = zod.z.enum([
|
|
478
|
+
"CRITICALLY_ENDANGERED",
|
|
479
|
+
"DATA_DEFICIENT",
|
|
480
|
+
"ENDANGERED",
|
|
481
|
+
"EXTINCT_IN_WILD",
|
|
482
|
+
"LEAST_CONCERN",
|
|
483
|
+
"NEAR_THREATENED",
|
|
484
|
+
"NOT_EVALUATED",
|
|
485
|
+
"VULNERABLE"
|
|
486
|
+
]).describe("IUCN-style conservation status of a species");
|
|
487
|
+
var TaxonGroupEnum = zod.z.enum(["AMPHIBIAN", "BIRD", "FISH", "FUNGI", "INSECT", "MAMMAL", "OTHER", "PLANT", "REPTILE"]).describe("Broad taxonomic group of a species");
|
|
488
|
+
var ObservationMethodEnum = zod.z.enum([
|
|
489
|
+
"ACOUSTIC",
|
|
490
|
+
"CAMERA_TRAP",
|
|
491
|
+
"COMMUNITY_REPORT",
|
|
492
|
+
"DIRECT_SIGHTING",
|
|
493
|
+
"EDNA",
|
|
494
|
+
"TELEMETRY",
|
|
495
|
+
"TRACKS_SIGNS"
|
|
496
|
+
]).describe("Method by which a species observation was recorded");
|
|
497
|
+
var HabitatProjectStatusEnum = zod.z.enum(["CANCELLED", "COMPLETED", "IN_PROGRESS", "MONITORING", "ON_HOLD", "PLANNED"]).describe("Lifecycle status of a habitat restoration project");
|
|
498
|
+
var RestorationActivityTypeEnum = zod.z.enum([
|
|
499
|
+
"COMMUNITY_ENGAGEMENT",
|
|
500
|
+
"INVASIVE_REMOVAL",
|
|
501
|
+
"PLANTING",
|
|
502
|
+
"REWILDING",
|
|
503
|
+
"SOIL_REMEDIATION",
|
|
504
|
+
"SURVEY",
|
|
505
|
+
"WATER_MANAGEMENT"
|
|
506
|
+
]).describe("Type of restoration activity logged against a habitat project");
|
|
507
|
+
var EmissionScopeEnum = zod.z.enum(["SCOPE_1", "SCOPE_2", "SCOPE_3"]).describe("GHG Protocol emission scope");
|
|
508
|
+
var CarbonEntryStatusEnum = zod.z.enum(["ADJUSTED", "DRAFT", "RECORDED", "VERIFIED"]).describe("Lifecycle status of a carbon-ledger entry");
|
|
509
|
+
var ReductionTargetStatusEnum = zod.z.enum(["ACHIEVED", "ACTIVE", "MISSED", "PROPOSED", "RETIRED"]).describe("Status of an emissions-reduction target");
|
|
510
|
+
var ReportFrameworkEnum = zod.z.enum(["CDP", "CUSTOM", "GHG_PROTOCOL", "GRI", "SASB", "TCFD"]).describe("Sustainability-reporting framework");
|
|
511
|
+
var ReportStatusEnum = zod.z.enum(["ARCHIVED", "DRAFT", "IN_REVIEW", "PUBLISHED"]).describe("Lifecycle status of a sustainability report");
|
|
512
|
+
function buildPagination(args) {
|
|
513
|
+
const pagination = {};
|
|
514
|
+
if (args.limit !== void 0) pagination.limit = args.limit;
|
|
515
|
+
if (args.offset !== void 0) pagination.offset = args.offset;
|
|
516
|
+
if (args.cursor !== void 0) pagination.cursor = args.cursor;
|
|
517
|
+
return Object.keys(pagination).length > 0 ? pagination : void 0;
|
|
518
|
+
}
|
|
519
|
+
var paginationInputShape = {
|
|
520
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
521
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
522
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/modules/ecoimpacthub/tools/sites.ts
|
|
526
|
+
function registerSiteTools(server, ctx, track) {
|
|
527
|
+
server.registerTool(
|
|
528
|
+
track(mcpToolName(ctx.productName, "sites_get")),
|
|
529
|
+
{
|
|
530
|
+
title: "Get Site",
|
|
531
|
+
description: "Fetch a single monitored environmental site by ID, including its sensors and alert thresholds.",
|
|
532
|
+
inputSchema: { id: zod.z.string().describe("Site ID") }
|
|
533
|
+
},
|
|
534
|
+
async ({ id }) => {
|
|
535
|
+
try {
|
|
536
|
+
const data = await ctx.gql.workspace.request(GET_SITE, { id });
|
|
537
|
+
if (!data.ecoSite) return errorResult(`Site ${id} not found`);
|
|
538
|
+
return jsonResult(data.ecoSite);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
ctx.logger.error("ecoimpacthub sites_get failed", { error: String(error) });
|
|
541
|
+
return errorResult(
|
|
542
|
+
`Failed to get site: ${error instanceof Error ? error.message : String(error)}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
server.registerTool(
|
|
548
|
+
track(mcpToolName(ctx.productName, "sites_list")),
|
|
549
|
+
{
|
|
550
|
+
title: "List Sites",
|
|
551
|
+
description: "List monitored sites in the bound workspace with optional status/type filters and cursor pagination.",
|
|
552
|
+
inputSchema: {
|
|
553
|
+
status: SiteStatusEnum.optional().describe("Filter by monitoring status"),
|
|
554
|
+
type: SiteTypeEnum.optional().describe("Filter by site type"),
|
|
555
|
+
...paginationInputShape
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
async ({ status, type, limit, offset, cursor }) => {
|
|
559
|
+
try {
|
|
560
|
+
const data = await ctx.gql.workspace.request(LIST_SITES, {
|
|
561
|
+
status,
|
|
562
|
+
type,
|
|
563
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
564
|
+
});
|
|
565
|
+
return jsonResult(data.ecoListSites);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
ctx.logger.error("ecoimpacthub sites_list failed", { error: String(error) });
|
|
568
|
+
return errorResult(
|
|
569
|
+
`Failed to list sites: ${error instanceof Error ? error.message : String(error)}`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
server.registerTool(
|
|
575
|
+
track(mcpToolName(ctx.productName, "sites_create")),
|
|
576
|
+
{
|
|
577
|
+
title: "Create Site",
|
|
578
|
+
description: "Register a new monitored environmental site (facility, forest, watershed, etc.).",
|
|
579
|
+
inputSchema: {
|
|
580
|
+
name: zod.z.string().min(1).describe("Site display name"),
|
|
581
|
+
type: SiteTypeEnum.optional().describe("Site type (default FACILITY)"),
|
|
582
|
+
status: SiteStatusEnum.optional().describe("Initial status (default ACTIVE)"),
|
|
583
|
+
code: zod.z.string().optional().describe("Short site code"),
|
|
584
|
+
description: zod.z.string().optional().describe("Site description"),
|
|
585
|
+
region: zod.z.string().optional().describe("Region / sub-national area"),
|
|
586
|
+
country: zod.z.string().optional().describe("Country"),
|
|
587
|
+
latitude: zod.z.number().optional().describe("Latitude (decimal degrees)"),
|
|
588
|
+
longitude: zod.z.number().optional().describe("Longitude (decimal degrees)"),
|
|
589
|
+
areaHectares: zod.z.number().optional().describe("Site area in hectares")
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
async ({
|
|
593
|
+
name,
|
|
594
|
+
type,
|
|
595
|
+
status,
|
|
596
|
+
code,
|
|
597
|
+
description,
|
|
598
|
+
region,
|
|
599
|
+
country,
|
|
600
|
+
latitude,
|
|
601
|
+
longitude,
|
|
602
|
+
areaHectares
|
|
603
|
+
}) => {
|
|
604
|
+
try {
|
|
605
|
+
const input = { name };
|
|
606
|
+
if (type !== void 0) input.type = type;
|
|
607
|
+
if (status !== void 0) input.status = status;
|
|
608
|
+
if (code !== void 0) input.code = code;
|
|
609
|
+
if (description !== void 0) input.description = description;
|
|
610
|
+
if (region !== void 0) input.region = region;
|
|
611
|
+
if (country !== void 0) input.country = country;
|
|
612
|
+
if (latitude !== void 0) input.latitude = latitude;
|
|
613
|
+
if (longitude !== void 0) input.longitude = longitude;
|
|
614
|
+
if (areaHectares !== void 0) input.areaHectares = areaHectares;
|
|
615
|
+
const data = await ctx.gql.workspace.request(CREATE_SITE, { input });
|
|
616
|
+
return jsonResult(data.ecoCreateSite);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
ctx.logger.error("ecoimpacthub sites_create failed", { error: String(error) });
|
|
619
|
+
return errorResult(
|
|
620
|
+
`Failed to create site: ${error instanceof Error ? error.message : String(error)}`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
server.registerTool(
|
|
626
|
+
track(mcpToolName(ctx.productName, "sites_update")),
|
|
627
|
+
{
|
|
628
|
+
title: "Update Site",
|
|
629
|
+
description: "Update a monitored site. At least one mutable field must be provided.",
|
|
630
|
+
inputSchema: {
|
|
631
|
+
id: zod.z.string().describe("Site ID"),
|
|
632
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
633
|
+
type: SiteTypeEnum.optional().describe("Updated site type"),
|
|
634
|
+
status: SiteStatusEnum.optional().describe("Updated monitoring status"),
|
|
635
|
+
code: zod.z.string().optional().describe("Updated site code"),
|
|
636
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
637
|
+
region: zod.z.string().optional().describe("Updated region"),
|
|
638
|
+
country: zod.z.string().optional().describe("Updated country"),
|
|
639
|
+
latitude: zod.z.number().optional().describe("Updated latitude"),
|
|
640
|
+
longitude: zod.z.number().optional().describe("Updated longitude"),
|
|
641
|
+
areaHectares: zod.z.number().optional().describe("Updated area in hectares")
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
async ({
|
|
645
|
+
id,
|
|
646
|
+
name,
|
|
647
|
+
type,
|
|
648
|
+
status,
|
|
649
|
+
code,
|
|
650
|
+
description,
|
|
651
|
+
region,
|
|
652
|
+
country,
|
|
653
|
+
latitude,
|
|
654
|
+
longitude,
|
|
655
|
+
areaHectares
|
|
656
|
+
}) => {
|
|
657
|
+
try {
|
|
658
|
+
const input = {};
|
|
659
|
+
if (name !== void 0) input.name = name;
|
|
660
|
+
if (type !== void 0) input.type = type;
|
|
661
|
+
if (status !== void 0) input.status = status;
|
|
662
|
+
if (code !== void 0) input.code = code;
|
|
663
|
+
if (description !== void 0) input.description = description;
|
|
664
|
+
if (region !== void 0) input.region = region;
|
|
665
|
+
if (country !== void 0) input.country = country;
|
|
666
|
+
if (latitude !== void 0) input.latitude = latitude;
|
|
667
|
+
if (longitude !== void 0) input.longitude = longitude;
|
|
668
|
+
if (areaHectares !== void 0) input.areaHectares = areaHectares;
|
|
669
|
+
if (Object.keys(input).length === 0) {
|
|
670
|
+
return errorResult("No updates specified \u2014 provide at least one mutable site field");
|
|
671
|
+
}
|
|
672
|
+
const data = await ctx.gql.workspace.request(UPDATE_SITE, { id, input });
|
|
673
|
+
return jsonResult(data.ecoUpdateSite);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
ctx.logger.error("ecoimpacthub sites_update failed", { error: String(error) });
|
|
676
|
+
return errorResult(
|
|
677
|
+
`Failed to update site: ${error instanceof Error ? error.message : String(error)}`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
server.registerTool(
|
|
683
|
+
track(mcpToolName(ctx.productName, "sites_archive")),
|
|
684
|
+
{
|
|
685
|
+
title: "Archive Site",
|
|
686
|
+
description: "Archive a monitored site (soft delete). Returns true on success.",
|
|
687
|
+
inputSchema: { id: zod.z.string().describe("Site ID") }
|
|
688
|
+
},
|
|
689
|
+
async ({ id }) => {
|
|
690
|
+
try {
|
|
691
|
+
const data = await ctx.gql.workspace.request(
|
|
692
|
+
ARCHIVE_SITE,
|
|
693
|
+
{
|
|
694
|
+
id
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
return jsonResult({ ok: data.ecoArchiveSite, id });
|
|
698
|
+
} catch (error) {
|
|
699
|
+
ctx.logger.error("ecoimpacthub sites_archive failed", { error: String(error) });
|
|
700
|
+
return errorResult(
|
|
701
|
+
`Failed to archive site: ${error instanceof Error ? error.message : String(error)}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
function registerSensorTools(server, ctx, track) {
|
|
708
|
+
server.registerTool(
|
|
709
|
+
track(mcpToolName(ctx.productName, "sensors_list")),
|
|
710
|
+
{
|
|
711
|
+
title: "List Sensors",
|
|
712
|
+
description: "List monitoring sensors, optionally filtered by site, kind, and/or operational status.",
|
|
713
|
+
inputSchema: {
|
|
714
|
+
siteId: zod.z.string().optional().describe("Filter by site ID"),
|
|
715
|
+
kind: SensorKindEnum.optional().describe("Filter by sensor kind"),
|
|
716
|
+
status: SensorStatusEnum.optional().describe("Filter by operational status")
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
async ({ siteId, kind, status }) => {
|
|
720
|
+
try {
|
|
721
|
+
const data = await ctx.gql.workspace.request(LIST_SENSORS, { siteId, kind, status });
|
|
722
|
+
return jsonResult(data.ecoListSensors);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
ctx.logger.error("ecoimpacthub sensors_list failed", { error: String(error) });
|
|
725
|
+
return errorResult(
|
|
726
|
+
`Failed to list sensors: ${error instanceof Error ? error.message : String(error)}`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
server.registerTool(
|
|
732
|
+
track(mcpToolName(ctx.productName, "sensors_create")),
|
|
733
|
+
{
|
|
734
|
+
title: "Create Sensor",
|
|
735
|
+
description: "Provision a new sensor on a site. `kind` defaults to MANUAL and `status` to ONLINE.",
|
|
736
|
+
inputSchema: {
|
|
737
|
+
siteId: zod.z.string().min(1).describe("Owning site ID"),
|
|
738
|
+
name: zod.z.string().min(1).describe("Sensor display name"),
|
|
739
|
+
kind: SensorKindEnum.optional().describe("Sensor kind (default MANUAL)"),
|
|
740
|
+
status: SensorStatusEnum.optional().describe("Initial status (default ONLINE)"),
|
|
741
|
+
unit: zod.z.string().optional().describe("Default measurement unit"),
|
|
742
|
+
hardwareId: zod.z.string().optional().describe("Physical hardware identifier"),
|
|
743
|
+
installedAt: zod.z.string().optional().describe("Installation timestamp (ISO-8601 DateTime)")
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
async ({ siteId, name, kind, status, unit, hardwareId, installedAt }) => {
|
|
747
|
+
try {
|
|
748
|
+
const input = { siteId, name };
|
|
749
|
+
if (kind !== void 0) input.kind = kind;
|
|
750
|
+
if (status !== void 0) input.status = status;
|
|
751
|
+
if (unit !== void 0) input.unit = unit;
|
|
752
|
+
if (hardwareId !== void 0) input.hardwareId = hardwareId;
|
|
753
|
+
if (installedAt !== void 0) input.installedAt = installedAt;
|
|
754
|
+
const data = await ctx.gql.workspace.request(CREATE_SENSOR, { input });
|
|
755
|
+
return jsonResult(data.ecoCreateSensor);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
ctx.logger.error("ecoimpacthub sensors_create failed", { error: String(error) });
|
|
758
|
+
return errorResult(
|
|
759
|
+
`Failed to create sensor: ${error instanceof Error ? error.message : String(error)}`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
server.registerTool(
|
|
765
|
+
track(mcpToolName(ctx.productName, "sensors_set_status")),
|
|
766
|
+
{
|
|
767
|
+
title: "Set Sensor Status",
|
|
768
|
+
description: "Update a sensor's operational status (e.g. flag it OFFLINE / MAINTENANCE / DEGRADED).",
|
|
769
|
+
inputSchema: {
|
|
770
|
+
id: zod.z.string().describe("Sensor ID"),
|
|
771
|
+
status: SensorStatusEnum.describe("New operational status")
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
async ({ id, status }) => {
|
|
775
|
+
try {
|
|
776
|
+
const data = await ctx.gql.workspace.request(SET_SENSOR_STATUS, { id, status });
|
|
777
|
+
return jsonResult(data.ecoSetSensorStatus);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
ctx.logger.error("ecoimpacthub sensors_set_status failed", { error: String(error) });
|
|
780
|
+
return errorResult(
|
|
781
|
+
`Failed to set sensor status: ${error instanceof Error ? error.message : String(error)}`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
function registerThresholdTools(server, ctx, track) {
|
|
788
|
+
server.registerTool(
|
|
789
|
+
track(mcpToolName(ctx.productName, "thresholds_list")),
|
|
790
|
+
{
|
|
791
|
+
title: "List Alert Thresholds",
|
|
792
|
+
description: "List alert thresholds, optionally filtered by site.",
|
|
793
|
+
inputSchema: {
|
|
794
|
+
siteId: zod.z.string().optional().describe("Filter by site ID")
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
async ({ siteId }) => {
|
|
798
|
+
try {
|
|
799
|
+
const data = await ctx.gql.workspace.request(LIST_THRESHOLDS, { siteId });
|
|
800
|
+
return jsonResult(data.ecoListThresholds);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
ctx.logger.error("ecoimpacthub thresholds_list failed", { error: String(error) });
|
|
803
|
+
return errorResult(
|
|
804
|
+
`Failed to list thresholds: ${error instanceof Error ? error.message : String(error)}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
server.registerTool(
|
|
810
|
+
track(mcpToolName(ctx.productName, "thresholds_create")),
|
|
811
|
+
{
|
|
812
|
+
title: "Create Alert Threshold",
|
|
813
|
+
description: "Create an alert threshold for a site + parameter. `direction` defaults to ABOVE and `severity` to WARNING. Provide min/max for OUTSIDE_RANGE or limitValue for ABOVE/BELOW.",
|
|
814
|
+
inputSchema: {
|
|
815
|
+
siteId: zod.z.string().min(1).describe("Owning site ID"),
|
|
816
|
+
parameter: zod.z.string().min(1).describe('Monitored parameter (e.g. "pm2_5", "ph", "turbidity")'),
|
|
817
|
+
unit: zod.z.string().min(1).describe("Unit of the threshold value"),
|
|
818
|
+
direction: ThresholdDirectionEnum.optional().describe("Breach direction (default ABOVE)"),
|
|
819
|
+
severity: ThresholdSeverityEnum.optional().describe("Alert severity (default WARNING)"),
|
|
820
|
+
limitValue: zod.z.number().optional().describe("Single limit value (ABOVE/BELOW)"),
|
|
821
|
+
minValue: zod.z.number().optional().describe("Lower bound (OUTSIDE_RANGE)"),
|
|
822
|
+
maxValue: zod.z.number().optional().describe("Upper bound (OUTSIDE_RANGE)")
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
async ({ siteId, parameter, unit, direction, severity, limitValue, minValue, maxValue }) => {
|
|
826
|
+
try {
|
|
827
|
+
const input = { siteId, parameter, unit };
|
|
828
|
+
if (direction !== void 0) input.direction = direction;
|
|
829
|
+
if (severity !== void 0) input.severity = severity;
|
|
830
|
+
if (limitValue !== void 0) input.limitValue = limitValue;
|
|
831
|
+
if (minValue !== void 0) input.minValue = minValue;
|
|
832
|
+
if (maxValue !== void 0) input.maxValue = maxValue;
|
|
833
|
+
const data = await ctx.gql.workspace.request(CREATE_THRESHOLD, { input });
|
|
834
|
+
return jsonResult(data.ecoCreateThreshold);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
ctx.logger.error("ecoimpacthub thresholds_create failed", { error: String(error) });
|
|
837
|
+
return errorResult(
|
|
838
|
+
`Failed to create threshold: ${error instanceof Error ? error.message : String(error)}`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
server.registerTool(
|
|
844
|
+
track(mcpToolName(ctx.productName, "thresholds_set_active")),
|
|
845
|
+
{
|
|
846
|
+
title: "Set Threshold Active",
|
|
847
|
+
description: "Enable or disable an alert threshold.",
|
|
848
|
+
inputSchema: {
|
|
849
|
+
id: zod.z.string().describe("Threshold ID"),
|
|
850
|
+
isActive: zod.z.boolean().describe("Whether the threshold should be active")
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
async ({ id, isActive }) => {
|
|
854
|
+
try {
|
|
855
|
+
const data = await ctx.gql.workspace.request(SET_THRESHOLD_ACTIVE, { id, isActive });
|
|
856
|
+
return jsonResult(data.ecoSetThresholdActive);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
ctx.logger.error("ecoimpacthub thresholds_set_active failed", { error: String(error) });
|
|
859
|
+
return errorResult(
|
|
860
|
+
`Failed to set threshold active state: ${error instanceof Error ? error.message : String(error)}`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
function registerMeasurementTools(server, ctx, track) {
|
|
867
|
+
server.registerTool(
|
|
868
|
+
track(mcpToolName(ctx.productName, "measurements_list")),
|
|
869
|
+
{
|
|
870
|
+
title: "List Measurements",
|
|
871
|
+
description: "List telemetry measurements with optional site/sensor/parameter filters, a breaches-only flag, and cursor pagination.",
|
|
872
|
+
inputSchema: {
|
|
873
|
+
siteId: zod.z.string().optional().describe("Filter by site ID"),
|
|
874
|
+
sensorId: zod.z.string().optional().describe("Filter by sensor ID"),
|
|
875
|
+
parameter: zod.z.string().optional().describe("Filter by measured parameter"),
|
|
876
|
+
breachesOnly: zod.z.boolean().optional().describe("Only return measurements flagged as breaches"),
|
|
877
|
+
...paginationInputShape
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
async ({ siteId, sensorId, parameter, breachesOnly, limit, offset, cursor }) => {
|
|
881
|
+
try {
|
|
882
|
+
const data = await ctx.gql.workspace.request(LIST_MEASUREMENTS, {
|
|
883
|
+
siteId,
|
|
884
|
+
sensorId,
|
|
885
|
+
parameter,
|
|
886
|
+
breachesOnly,
|
|
887
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
888
|
+
});
|
|
889
|
+
return jsonResult(data.ecoListMeasurements);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
ctx.logger.error("ecoimpacthub measurements_list failed", { error: String(error) });
|
|
892
|
+
return errorResult(
|
|
893
|
+
`Failed to list measurements: ${error instanceof Error ? error.message : String(error)}`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
server.registerTool(
|
|
899
|
+
track(mcpToolName(ctx.productName, "measurements_record")),
|
|
900
|
+
{
|
|
901
|
+
title: "Record Measurement",
|
|
902
|
+
description: "Record a measurement for a site + parameter; the value is evaluated against active thresholds and flagged as a breach when out of range. `source` defaults to SENSOR.",
|
|
903
|
+
inputSchema: {
|
|
904
|
+
siteId: zod.z.string().min(1).describe("Site the measurement belongs to"),
|
|
905
|
+
parameter: zod.z.string().min(1).describe('Measured parameter (e.g. "pm2_5", "ph")'),
|
|
906
|
+
value: zod.z.number().describe("Measured value"),
|
|
907
|
+
unit: zod.z.string().min(1).describe("Unit of the value"),
|
|
908
|
+
recordedAt: zod.z.string().describe("When the measurement was taken (ISO-8601 DateTime)"),
|
|
909
|
+
source: MeasurementSourceEnum.optional().describe("Provenance (default SENSOR)"),
|
|
910
|
+
sensorId: zod.z.string().optional().describe("Source sensor ID (if from a sensor)"),
|
|
911
|
+
notes: zod.z.string().optional().describe("Free-text notes")
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
async ({ siteId, parameter, value, unit, recordedAt, source, sensorId, notes }) => {
|
|
915
|
+
try {
|
|
916
|
+
const input = { siteId, parameter, value, unit, recordedAt };
|
|
917
|
+
if (source !== void 0) input.source = source;
|
|
918
|
+
if (sensorId !== void 0) input.sensorId = sensorId;
|
|
919
|
+
if (notes !== void 0) input.notes = notes;
|
|
920
|
+
const data = await ctx.gql.workspace.request(RECORD_MEASUREMENT, { input });
|
|
921
|
+
return jsonResult(data.ecoRecordMeasurement);
|
|
922
|
+
} catch (error) {
|
|
923
|
+
ctx.logger.error("ecoimpacthub measurements_record failed", { error: String(error) });
|
|
924
|
+
return errorResult(
|
|
925
|
+
`Failed to record measurement: ${error instanceof Error ? error.message : String(error)}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
function registerSpeciesTools(server, ctx, track) {
|
|
932
|
+
server.registerTool(
|
|
933
|
+
track(mcpToolName(ctx.productName, "species_get")),
|
|
934
|
+
{
|
|
935
|
+
title: "Get Species",
|
|
936
|
+
description: "Fetch a single catalogued species by ID, including its observations.",
|
|
937
|
+
inputSchema: { id: zod.z.string().describe("Species ID") }
|
|
938
|
+
},
|
|
939
|
+
async ({ id }) => {
|
|
940
|
+
try {
|
|
941
|
+
const data = await ctx.gql.workspace.request(GET_SPECIES, { id });
|
|
942
|
+
if (!data.ecoSpecies) return errorResult(`Species ${id} not found`);
|
|
943
|
+
return jsonResult(data.ecoSpecies);
|
|
944
|
+
} catch (error) {
|
|
945
|
+
ctx.logger.error("ecoimpacthub species_get failed", { error: String(error) });
|
|
946
|
+
return errorResult(
|
|
947
|
+
`Failed to get species: ${error instanceof Error ? error.message : String(error)}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
server.registerTool(
|
|
953
|
+
track(mcpToolName(ctx.productName, "species_list")),
|
|
954
|
+
{
|
|
955
|
+
title: "List Species",
|
|
956
|
+
description: "List catalogued species with optional taxon-group/conservation-status/priority filters and cursor pagination.",
|
|
957
|
+
inputSchema: {
|
|
958
|
+
taxonGroup: TaxonGroupEnum.optional().describe("Filter by taxonomic group"),
|
|
959
|
+
conservationStatus: ConservationStatusEnum.optional().describe(
|
|
960
|
+
"Filter by conservation status"
|
|
961
|
+
),
|
|
962
|
+
priorityOnly: zod.z.boolean().optional().describe("Only priority species"),
|
|
963
|
+
...paginationInputShape
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
async ({ taxonGroup, conservationStatus, priorityOnly, limit, offset, cursor }) => {
|
|
967
|
+
try {
|
|
968
|
+
const data = await ctx.gql.workspace.request(LIST_SPECIES, {
|
|
969
|
+
taxonGroup,
|
|
970
|
+
conservationStatus,
|
|
971
|
+
priorityOnly,
|
|
972
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
973
|
+
});
|
|
974
|
+
return jsonResult(data.ecoListSpecies);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
ctx.logger.error("ecoimpacthub species_list failed", { error: String(error) });
|
|
977
|
+
return errorResult(
|
|
978
|
+
`Failed to list species: ${error instanceof Error ? error.message : String(error)}`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
);
|
|
983
|
+
server.registerTool(
|
|
984
|
+
track(mcpToolName(ctx.productName, "species_create")),
|
|
985
|
+
{
|
|
986
|
+
title: "Create Species",
|
|
987
|
+
description: "Add a species to the biodiversity catalog. `taxonGroup` defaults to OTHER and `conservationStatus` to NOT_EVALUATED.",
|
|
988
|
+
inputSchema: {
|
|
989
|
+
commonName: zod.z.string().min(1).describe("Common (vernacular) name"),
|
|
990
|
+
scientificName: zod.z.string().optional().describe("Binomial scientific name"),
|
|
991
|
+
taxonGroup: TaxonGroupEnum.optional().describe("Taxonomic group (default OTHER)"),
|
|
992
|
+
conservationStatus: ConservationStatusEnum.optional().describe(
|
|
993
|
+
"Conservation status (default NOT_EVALUATED)"
|
|
994
|
+
),
|
|
995
|
+
isPriority: zod.z.boolean().optional().describe("Flag as a priority species (default false)"),
|
|
996
|
+
description: zod.z.string().optional().describe("Species description / notes")
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
async ({
|
|
1000
|
+
commonName,
|
|
1001
|
+
scientificName,
|
|
1002
|
+
taxonGroup,
|
|
1003
|
+
conservationStatus,
|
|
1004
|
+
isPriority,
|
|
1005
|
+
description
|
|
1006
|
+
}) => {
|
|
1007
|
+
try {
|
|
1008
|
+
const input = { commonName };
|
|
1009
|
+
if (scientificName !== void 0) input.scientificName = scientificName;
|
|
1010
|
+
if (taxonGroup !== void 0) input.taxonGroup = taxonGroup;
|
|
1011
|
+
if (conservationStatus !== void 0) input.conservationStatus = conservationStatus;
|
|
1012
|
+
if (isPriority !== void 0) input.isPriority = isPriority;
|
|
1013
|
+
if (description !== void 0) input.description = description;
|
|
1014
|
+
const data = await ctx.gql.workspace.request(CREATE_SPECIES, { input });
|
|
1015
|
+
return jsonResult(data.ecoCreateSpecies);
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
ctx.logger.error("ecoimpacthub species_create failed", { error: String(error) });
|
|
1018
|
+
return errorResult(
|
|
1019
|
+
`Failed to create species: ${error instanceof Error ? error.message : String(error)}`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
server.registerTool(
|
|
1025
|
+
track(mcpToolName(ctx.productName, "species_update")),
|
|
1026
|
+
{
|
|
1027
|
+
title: "Update Species",
|
|
1028
|
+
description: "Update a catalogued species. At least one mutable field must be provided.",
|
|
1029
|
+
inputSchema: {
|
|
1030
|
+
id: zod.z.string().describe("Species ID"),
|
|
1031
|
+
commonName: zod.z.string().optional().describe("Updated common name"),
|
|
1032
|
+
scientificName: zod.z.string().optional().describe("Updated scientific name"),
|
|
1033
|
+
taxonGroup: TaxonGroupEnum.optional().describe("Updated taxonomic group"),
|
|
1034
|
+
conservationStatus: ConservationStatusEnum.optional().describe(
|
|
1035
|
+
"Updated conservation status"
|
|
1036
|
+
),
|
|
1037
|
+
isPriority: zod.z.boolean().optional().describe("Updated priority flag"),
|
|
1038
|
+
description: zod.z.string().optional().describe("Updated description")
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
async ({
|
|
1042
|
+
id,
|
|
1043
|
+
commonName,
|
|
1044
|
+
scientificName,
|
|
1045
|
+
taxonGroup,
|
|
1046
|
+
conservationStatus,
|
|
1047
|
+
isPriority,
|
|
1048
|
+
description
|
|
1049
|
+
}) => {
|
|
1050
|
+
try {
|
|
1051
|
+
const input = {};
|
|
1052
|
+
if (commonName !== void 0) input.commonName = commonName;
|
|
1053
|
+
if (scientificName !== void 0) input.scientificName = scientificName;
|
|
1054
|
+
if (taxonGroup !== void 0) input.taxonGroup = taxonGroup;
|
|
1055
|
+
if (conservationStatus !== void 0) input.conservationStatus = conservationStatus;
|
|
1056
|
+
if (isPriority !== void 0) input.isPriority = isPriority;
|
|
1057
|
+
if (description !== void 0) input.description = description;
|
|
1058
|
+
if (Object.keys(input).length === 0) {
|
|
1059
|
+
return errorResult("No updates specified \u2014 provide at least one mutable species field");
|
|
1060
|
+
}
|
|
1061
|
+
const data = await ctx.gql.workspace.request(UPDATE_SPECIES, { id, input });
|
|
1062
|
+
return jsonResult(data.ecoUpdateSpecies);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
ctx.logger.error("ecoimpacthub species_update failed", { error: String(error) });
|
|
1065
|
+
return errorResult(
|
|
1066
|
+
`Failed to update species: ${error instanceof Error ? error.message : String(error)}`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
server.registerTool(
|
|
1072
|
+
track(mcpToolName(ctx.productName, "species_archive")),
|
|
1073
|
+
{
|
|
1074
|
+
title: "Archive Species",
|
|
1075
|
+
description: "Archive a species (soft delete). Returns true on success.",
|
|
1076
|
+
inputSchema: { id: zod.z.string().describe("Species ID") }
|
|
1077
|
+
},
|
|
1078
|
+
async ({ id }) => {
|
|
1079
|
+
try {
|
|
1080
|
+
const data = await ctx.gql.workspace.request(
|
|
1081
|
+
ARCHIVE_SPECIES,
|
|
1082
|
+
{ id }
|
|
1083
|
+
);
|
|
1084
|
+
return jsonResult({ ok: data.ecoArchiveSpecies, id });
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
ctx.logger.error("ecoimpacthub species_archive failed", { error: String(error) });
|
|
1087
|
+
return errorResult(
|
|
1088
|
+
`Failed to archive species: ${error instanceof Error ? error.message : String(error)}`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
function registerObservationTools(server, ctx, track) {
|
|
1095
|
+
server.registerTool(
|
|
1096
|
+
track(mcpToolName(ctx.productName, "observations_list")),
|
|
1097
|
+
{
|
|
1098
|
+
title: "List Observations",
|
|
1099
|
+
description: "List species observations with optional species/site filters and cursor pagination.",
|
|
1100
|
+
inputSchema: {
|
|
1101
|
+
speciesId: zod.z.string().optional().describe("Filter by species ID"),
|
|
1102
|
+
siteId: zod.z.string().optional().describe("Filter by site ID"),
|
|
1103
|
+
...paginationInputShape
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
async ({ speciesId, siteId, limit, offset, cursor }) => {
|
|
1107
|
+
try {
|
|
1108
|
+
const data = await ctx.gql.workspace.request(LIST_OBSERVATIONS, {
|
|
1109
|
+
speciesId,
|
|
1110
|
+
siteId,
|
|
1111
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
1112
|
+
});
|
|
1113
|
+
return jsonResult(data.ecoListObservations);
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
ctx.logger.error("ecoimpacthub observations_list failed", { error: String(error) });
|
|
1116
|
+
return errorResult(
|
|
1117
|
+
`Failed to list observations: ${error instanceof Error ? error.message : String(error)}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
server.registerTool(
|
|
1123
|
+
track(mcpToolName(ctx.productName, "observations_record")),
|
|
1124
|
+
{
|
|
1125
|
+
title: "Record Observation",
|
|
1126
|
+
description: "Record a species observation. `count` defaults to 1 and `method` to DIRECT_SIGHTING. Attach a wspace-files File via `evidenceFileId` for photo/audio evidence.",
|
|
1127
|
+
inputSchema: {
|
|
1128
|
+
speciesId: zod.z.string().min(1).describe("Observed species ID"),
|
|
1129
|
+
observedAt: zod.z.string().describe("When the observation was made (ISO-8601 DateTime)"),
|
|
1130
|
+
siteId: zod.z.string().optional().describe("Site where it was observed"),
|
|
1131
|
+
method: ObservationMethodEnum.optional().describe(
|
|
1132
|
+
"Observation method (default DIRECT_SIGHTING)"
|
|
1133
|
+
),
|
|
1134
|
+
count: zod.z.number().int().optional().describe("Number of individuals (default 1)"),
|
|
1135
|
+
latitude: zod.z.number().optional().describe("Latitude (decimal degrees)"),
|
|
1136
|
+
longitude: zod.z.number().optional().describe("Longitude (decimal degrees)"),
|
|
1137
|
+
evidenceFileId: zod.z.string().optional().describe("wspace-files File ID for photo/audio evidence"),
|
|
1138
|
+
notes: zod.z.string().optional().describe("Free-text notes")
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
async ({
|
|
1142
|
+
speciesId,
|
|
1143
|
+
observedAt,
|
|
1144
|
+
siteId,
|
|
1145
|
+
method,
|
|
1146
|
+
count,
|
|
1147
|
+
latitude,
|
|
1148
|
+
longitude,
|
|
1149
|
+
evidenceFileId,
|
|
1150
|
+
notes
|
|
1151
|
+
}) => {
|
|
1152
|
+
try {
|
|
1153
|
+
const input = { speciesId, observedAt };
|
|
1154
|
+
if (siteId !== void 0) input.siteId = siteId;
|
|
1155
|
+
if (method !== void 0) input.method = method;
|
|
1156
|
+
if (count !== void 0) input.count = count;
|
|
1157
|
+
if (latitude !== void 0) input.latitude = latitude;
|
|
1158
|
+
if (longitude !== void 0) input.longitude = longitude;
|
|
1159
|
+
if (evidenceFileId !== void 0) input.evidenceFileId = evidenceFileId;
|
|
1160
|
+
if (notes !== void 0) input.notes = notes;
|
|
1161
|
+
const data = await ctx.gql.workspace.request(RECORD_OBSERVATION, { input });
|
|
1162
|
+
return jsonResult(data.ecoRecordObservation);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
ctx.logger.error("ecoimpacthub observations_record failed", { error: String(error) });
|
|
1165
|
+
return errorResult(
|
|
1166
|
+
`Failed to record observation: ${error instanceof Error ? error.message : String(error)}`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
function registerHabitatTools(server, ctx, track) {
|
|
1173
|
+
server.registerTool(
|
|
1174
|
+
track(mcpToolName(ctx.productName, "habitat_projects_get")),
|
|
1175
|
+
{
|
|
1176
|
+
title: "Get Habitat Project",
|
|
1177
|
+
description: "Fetch a single habitat restoration project by ID, including its logged restoration activities.",
|
|
1178
|
+
inputSchema: { id: zod.z.string().describe("Habitat project ID") }
|
|
1179
|
+
},
|
|
1180
|
+
async ({ id }) => {
|
|
1181
|
+
try {
|
|
1182
|
+
const data = await ctx.gql.workspace.request(GET_HABITAT_PROJECT, { id });
|
|
1183
|
+
if (!data.ecoHabitatProject) return errorResult(`Habitat project ${id} not found`);
|
|
1184
|
+
return jsonResult(data.ecoHabitatProject);
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
ctx.logger.error("ecoimpacthub habitat_projects_get failed", { error: String(error) });
|
|
1187
|
+
return errorResult(
|
|
1188
|
+
`Failed to get habitat project: ${error instanceof Error ? error.message : String(error)}`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
server.registerTool(
|
|
1194
|
+
track(mcpToolName(ctx.productName, "habitat_projects_list")),
|
|
1195
|
+
{
|
|
1196
|
+
title: "List Habitat Projects",
|
|
1197
|
+
description: "List habitat restoration projects, optionally filtered by status.",
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
status: HabitatProjectStatusEnum.optional().describe("Filter by project status")
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
async ({ status }) => {
|
|
1203
|
+
try {
|
|
1204
|
+
const data = await ctx.gql.workspace.request(LIST_HABITAT_PROJECTS, { status });
|
|
1205
|
+
return jsonResult(data.ecoListHabitatProjects);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
ctx.logger.error("ecoimpacthub habitat_projects_list failed", { error: String(error) });
|
|
1208
|
+
return errorResult(
|
|
1209
|
+
`Failed to list habitat projects: ${error instanceof Error ? error.message : String(error)}`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
);
|
|
1214
|
+
server.registerTool(
|
|
1215
|
+
track(mcpToolName(ctx.productName, "habitat_projects_create")),
|
|
1216
|
+
{
|
|
1217
|
+
title: "Create Habitat Project",
|
|
1218
|
+
description: "Create a habitat restoration project (target area, budget, timeline, manager).",
|
|
1219
|
+
inputSchema: {
|
|
1220
|
+
name: zod.z.string().min(1).describe("Project name"),
|
|
1221
|
+
description: zod.z.string().optional().describe("Project description"),
|
|
1222
|
+
siteId: zod.z.string().optional().describe("Associated site ID"),
|
|
1223
|
+
managerId: zod.z.string().optional().describe("Project manager user ID"),
|
|
1224
|
+
targetHectares: zod.z.number().optional().describe("Target restoration area in hectares"),
|
|
1225
|
+
budgetAmount: zod.z.number().optional().describe("Budget amount"),
|
|
1226
|
+
currency: zod.z.string().optional().describe("Budget currency code"),
|
|
1227
|
+
startDate: zod.z.string().optional().describe("Planned start (ISO-8601 DateTime)"),
|
|
1228
|
+
endDate: zod.z.string().optional().describe("Planned end (ISO-8601 DateTime)")
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
async ({
|
|
1232
|
+
name,
|
|
1233
|
+
description,
|
|
1234
|
+
siteId,
|
|
1235
|
+
managerId,
|
|
1236
|
+
targetHectares,
|
|
1237
|
+
budgetAmount,
|
|
1238
|
+
currency,
|
|
1239
|
+
startDate,
|
|
1240
|
+
endDate
|
|
1241
|
+
}) => {
|
|
1242
|
+
try {
|
|
1243
|
+
const input = { name };
|
|
1244
|
+
if (description !== void 0) input.description = description;
|
|
1245
|
+
if (siteId !== void 0) input.siteId = siteId;
|
|
1246
|
+
if (managerId !== void 0) input.managerId = managerId;
|
|
1247
|
+
if (targetHectares !== void 0) input.targetHectares = targetHectares;
|
|
1248
|
+
if (budgetAmount !== void 0) input.budgetAmount = budgetAmount;
|
|
1249
|
+
if (currency !== void 0) input.currency = currency;
|
|
1250
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
1251
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
1252
|
+
const data = await ctx.gql.workspace.request(CREATE_HABITAT_PROJECT, { input });
|
|
1253
|
+
return jsonResult(data.ecoCreateHabitatProject);
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
ctx.logger.error("ecoimpacthub habitat_projects_create failed", { error: String(error) });
|
|
1256
|
+
return errorResult(
|
|
1257
|
+
`Failed to create habitat project: ${error instanceof Error ? error.message : String(error)}`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
server.registerTool(
|
|
1263
|
+
track(mcpToolName(ctx.productName, "habitat_projects_transition")),
|
|
1264
|
+
{
|
|
1265
|
+
title: "Transition Habitat Project",
|
|
1266
|
+
description: "Advance a habitat project through its lifecycle (validated transitions, e.g. PLANNED \u2192 IN_PROGRESS \u2192 MONITORING \u2192 COMPLETED, or ON_HOLD / CANCELLED).",
|
|
1267
|
+
inputSchema: {
|
|
1268
|
+
id: zod.z.string().describe("Habitat project ID"),
|
|
1269
|
+
status: HabitatProjectStatusEnum.describe("Target lifecycle status")
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
async ({ id, status }) => {
|
|
1273
|
+
try {
|
|
1274
|
+
const data = await ctx.gql.workspace.request(TRANSITION_HABITAT_PROJECT, { id, status });
|
|
1275
|
+
return jsonResult(data.ecoTransitionHabitatProject);
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
ctx.logger.error("ecoimpacthub habitat_projects_transition failed", {
|
|
1278
|
+
error: String(error)
|
|
1279
|
+
});
|
|
1280
|
+
return errorResult(
|
|
1281
|
+
`Failed to transition habitat project: ${error instanceof Error ? error.message : String(error)}`
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
);
|
|
1286
|
+
server.registerTool(
|
|
1287
|
+
track(mcpToolName(ctx.productName, "habitat_projects_log_activity")),
|
|
1288
|
+
{
|
|
1289
|
+
title: "Log Restoration Activity",
|
|
1290
|
+
description: "Log a restoration activity against a habitat project (planting, invasive removal, survey, etc.). `type` defaults to PLANTING.",
|
|
1291
|
+
inputSchema: {
|
|
1292
|
+
projectId: zod.z.string().min(1).describe("Owning habitat project ID"),
|
|
1293
|
+
summary: zod.z.string().min(1).describe("Short activity summary"),
|
|
1294
|
+
performedAt: zod.z.string().describe("When the activity was performed (ISO-8601 DateTime)"),
|
|
1295
|
+
type: RestorationActivityTypeEnum.optional().describe("Activity type (default PLANTING)"),
|
|
1296
|
+
quantity: zod.z.number().optional().describe("Quantity (e.g. seedlings planted, hectares cleared)"),
|
|
1297
|
+
unit: zod.z.string().optional().describe("Unit of the quantity"),
|
|
1298
|
+
details: zod.z.string().optional().describe("Detailed notes")
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
async ({ projectId, summary, performedAt, type, quantity, unit, details }) => {
|
|
1302
|
+
try {
|
|
1303
|
+
const input = { projectId, summary, performedAt };
|
|
1304
|
+
if (type !== void 0) input.type = type;
|
|
1305
|
+
if (quantity !== void 0) input.quantity = quantity;
|
|
1306
|
+
if (unit !== void 0) input.unit = unit;
|
|
1307
|
+
if (details !== void 0) input.details = details;
|
|
1308
|
+
const data = await ctx.gql.workspace.request(LOG_RESTORATION_ACTIVITY, { input });
|
|
1309
|
+
return jsonResult(data.ecoLogRestorationActivity);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
ctx.logger.error("ecoimpacthub habitat_projects_log_activity failed", {
|
|
1312
|
+
error: String(error)
|
|
1313
|
+
});
|
|
1314
|
+
return errorResult(
|
|
1315
|
+
`Failed to log restoration activity: ${error instanceof Error ? error.message : String(error)}`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
function registerCarbonTools(server, ctx, track) {
|
|
1322
|
+
server.registerTool(
|
|
1323
|
+
track(mcpToolName(ctx.productName, "carbon_list_entries")),
|
|
1324
|
+
{
|
|
1325
|
+
title: "List Carbon Entries",
|
|
1326
|
+
description: "List carbon-ledger entries with optional scope/site/status filters and cursor pagination.",
|
|
1327
|
+
inputSchema: {
|
|
1328
|
+
scope: EmissionScopeEnum.optional().describe("Filter by emission scope"),
|
|
1329
|
+
siteId: zod.z.string().optional().describe("Filter by site ID"),
|
|
1330
|
+
status: CarbonEntryStatusEnum.optional().describe("Filter by entry status"),
|
|
1331
|
+
...paginationInputShape
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
async ({ scope, siteId, status, limit, offset, cursor }) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const data = await ctx.gql.workspace.request(LIST_CARBON_ENTRIES, {
|
|
1337
|
+
scope,
|
|
1338
|
+
siteId,
|
|
1339
|
+
status,
|
|
1340
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
1341
|
+
});
|
|
1342
|
+
return jsonResult(data.ecoListCarbonEntries);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
ctx.logger.error("ecoimpacthub carbon_list_entries failed", { error: String(error) });
|
|
1345
|
+
return errorResult(
|
|
1346
|
+
`Failed to list carbon entries: ${error instanceof Error ? error.message : String(error)}`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
server.registerTool(
|
|
1352
|
+
track(mcpToolName(ctx.productName, "carbon_total_kg")),
|
|
1353
|
+
{
|
|
1354
|
+
title: "Carbon Total (kg CO2e)",
|
|
1355
|
+
description: "Total CO2e (kg) recorded in the workspace, optionally narrowed by emission scope and/or site.",
|
|
1356
|
+
inputSchema: {
|
|
1357
|
+
scope: EmissionScopeEnum.optional().describe("Narrow the total to a single emission scope"),
|
|
1358
|
+
siteId: zod.z.string().optional().describe("Narrow the total to a single site")
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
async ({ scope, siteId }) => {
|
|
1362
|
+
try {
|
|
1363
|
+
const data = await ctx.gql.workspace.request(
|
|
1364
|
+
CARBON_TOTAL_KG,
|
|
1365
|
+
{ scope, siteId }
|
|
1366
|
+
);
|
|
1367
|
+
return jsonResult({
|
|
1368
|
+
totalKgCo2e: data.ecoCarbonTotalKg,
|
|
1369
|
+
scope: scope ?? null,
|
|
1370
|
+
siteId: siteId ?? null
|
|
1371
|
+
});
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
ctx.logger.error("ecoimpacthub carbon_total failed", { error: String(error) });
|
|
1374
|
+
return errorResult(
|
|
1375
|
+
`Failed to get carbon total: ${error instanceof Error ? error.message : String(error)}`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
);
|
|
1380
|
+
server.registerTool(
|
|
1381
|
+
track(mcpToolName(ctx.productName, "carbon_record_entry")),
|
|
1382
|
+
{
|
|
1383
|
+
title: "Record Carbon Entry",
|
|
1384
|
+
description: "Record a carbon-ledger entry; CO2e is computed from the activity value and the referenced emission factor (when one is provided). `scope` defaults to SCOPE_1.",
|
|
1385
|
+
inputSchema: {
|
|
1386
|
+
category: zod.z.string().min(1).describe('Activity category (e.g. "electricity", "fleet-diesel")'),
|
|
1387
|
+
activityValue: zod.z.number().describe("Activity quantity (e.g. kWh, litres)"),
|
|
1388
|
+
activityUnit: zod.z.string().min(1).describe("Unit of the activity value"),
|
|
1389
|
+
periodStart: zod.z.string().describe("Start of the activity period (ISO-8601 DateTime)"),
|
|
1390
|
+
scope: EmissionScopeEnum.optional().describe("Emission scope (default SCOPE_1)"),
|
|
1391
|
+
factorId: zod.z.string().optional().describe("Emission factor ID used to compute CO2e"),
|
|
1392
|
+
siteId: zod.z.string().optional().describe("Associated site ID"),
|
|
1393
|
+
notes: zod.z.string().optional().describe("Free-text notes")
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
async ({
|
|
1397
|
+
category,
|
|
1398
|
+
activityValue,
|
|
1399
|
+
activityUnit,
|
|
1400
|
+
periodStart,
|
|
1401
|
+
scope,
|
|
1402
|
+
factorId,
|
|
1403
|
+
siteId,
|
|
1404
|
+
notes
|
|
1405
|
+
}) => {
|
|
1406
|
+
try {
|
|
1407
|
+
const input = {
|
|
1408
|
+
category,
|
|
1409
|
+
activityValue,
|
|
1410
|
+
activityUnit,
|
|
1411
|
+
periodStart
|
|
1412
|
+
};
|
|
1413
|
+
if (scope !== void 0) input.scope = scope;
|
|
1414
|
+
if (factorId !== void 0) input.factorId = factorId;
|
|
1415
|
+
if (siteId !== void 0) input.siteId = siteId;
|
|
1416
|
+
if (notes !== void 0) input.notes = notes;
|
|
1417
|
+
const data = await ctx.gql.workspace.request(RECORD_CARBON_ENTRY, { input });
|
|
1418
|
+
return jsonResult(data.ecoRecordCarbonEntry);
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
ctx.logger.error("ecoimpacthub carbon_record_entry failed", { error: String(error) });
|
|
1421
|
+
return errorResult(
|
|
1422
|
+
`Failed to record carbon entry: ${error instanceof Error ? error.message : String(error)}`
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
);
|
|
1427
|
+
server.registerTool(
|
|
1428
|
+
track(mcpToolName(ctx.productName, "carbon_verify_entry")),
|
|
1429
|
+
{
|
|
1430
|
+
title: "Verify Carbon Entry",
|
|
1431
|
+
description: "Mark a carbon-ledger entry as VERIFIED.",
|
|
1432
|
+
inputSchema: { id: zod.z.string().describe("Carbon entry ID") }
|
|
1433
|
+
},
|
|
1434
|
+
async ({ id }) => {
|
|
1435
|
+
try {
|
|
1436
|
+
const data = await ctx.gql.workspace.request(VERIFY_CARBON_ENTRY, { id });
|
|
1437
|
+
return jsonResult(data.ecoVerifyCarbonEntry);
|
|
1438
|
+
} catch (error) {
|
|
1439
|
+
ctx.logger.error("ecoimpacthub carbon_verify_entry failed", { error: String(error) });
|
|
1440
|
+
return errorResult(
|
|
1441
|
+
`Failed to verify carbon entry: ${error instanceof Error ? error.message : String(error)}`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
function registerEmissionFactorTools(server, ctx, track) {
|
|
1448
|
+
server.registerTool(
|
|
1449
|
+
track(mcpToolName(ctx.productName, "emission_factors_list")),
|
|
1450
|
+
{
|
|
1451
|
+
title: "List Emission Factors",
|
|
1452
|
+
description: "List emission factors, optionally filtered by scope and/or limited to active factors.",
|
|
1453
|
+
inputSchema: {
|
|
1454
|
+
scope: EmissionScopeEnum.optional().describe("Filter by emission scope"),
|
|
1455
|
+
activeOnly: zod.z.boolean().optional().describe("Only return active factors")
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
async ({ scope, activeOnly }) => {
|
|
1459
|
+
try {
|
|
1460
|
+
const data = await ctx.gql.workspace.request(LIST_EMISSION_FACTORS, { scope, activeOnly });
|
|
1461
|
+
return jsonResult(data.ecoListEmissionFactors);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
ctx.logger.error("ecoimpacthub emission_factors_list failed", { error: String(error) });
|
|
1464
|
+
return errorResult(
|
|
1465
|
+
`Failed to list emission factors: ${error instanceof Error ? error.message : String(error)}`
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
1470
|
+
server.registerTool(
|
|
1471
|
+
track(mcpToolName(ctx.productName, "emission_factors_create")),
|
|
1472
|
+
{
|
|
1473
|
+
title: "Create Emission Factor",
|
|
1474
|
+
description: "Create an emission factor (kg CO2e per activity unit) for use in carbon-entry calculations. `scope` defaults to SCOPE_1.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
name: zod.z.string().min(1).describe('Factor name (e.g. "Grid electricity \u2013 IN")'),
|
|
1477
|
+
activityUnit: zod.z.string().min(1).describe('Activity unit the factor applies to (e.g. "kWh")'),
|
|
1478
|
+
factorKgco2e: zod.z.number().describe("Kilograms CO2e per activity unit"),
|
|
1479
|
+
scope: EmissionScopeEnum.optional().describe("Emission scope (default SCOPE_1)"),
|
|
1480
|
+
source: zod.z.string().optional().describe("Citation / source of the factor"),
|
|
1481
|
+
validYear: zod.z.number().int().optional().describe("Year the factor is valid for")
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1484
|
+
async ({ name, activityUnit, factorKgco2e, scope, source, validYear }) => {
|
|
1485
|
+
try {
|
|
1486
|
+
const input = { name, activityUnit, factorKgco2e };
|
|
1487
|
+
if (scope !== void 0) input.scope = scope;
|
|
1488
|
+
if (source !== void 0) input.source = source;
|
|
1489
|
+
if (validYear !== void 0) input.validYear = validYear;
|
|
1490
|
+
const data = await ctx.gql.workspace.request(CREATE_EMISSION_FACTOR, { input });
|
|
1491
|
+
return jsonResult(data.ecoCreateEmissionFactor);
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
ctx.logger.error("ecoimpacthub emission_factors_create failed", { error: String(error) });
|
|
1494
|
+
return errorResult(
|
|
1495
|
+
`Failed to create emission factor: ${error instanceof Error ? error.message : String(error)}`
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
function registerTargetTools(server, ctx, track) {
|
|
1502
|
+
server.registerTool(
|
|
1503
|
+
track(mcpToolName(ctx.productName, "reduction_targets_list")),
|
|
1504
|
+
{
|
|
1505
|
+
title: "List Reduction Targets",
|
|
1506
|
+
description: "List emissions/impact reduction targets, optionally filtered by status.",
|
|
1507
|
+
inputSchema: {
|
|
1508
|
+
status: ReductionTargetStatusEnum.optional().describe("Filter by target status")
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
async ({ status }) => {
|
|
1512
|
+
try {
|
|
1513
|
+
const data = await ctx.gql.workspace.request(LIST_REDUCTION_TARGETS, { status });
|
|
1514
|
+
return jsonResult(data.ecoListReductionTargets);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
ctx.logger.error("ecoimpacthub reduction_targets_list failed", { error: String(error) });
|
|
1517
|
+
return errorResult(
|
|
1518
|
+
`Failed to list reduction targets: ${error instanceof Error ? error.message : String(error)}`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
server.registerTool(
|
|
1524
|
+
track(mcpToolName(ctx.productName, "reduction_targets_create")),
|
|
1525
|
+
{
|
|
1526
|
+
title: "Create Reduction Target",
|
|
1527
|
+
description: "Create an emissions/impact reduction target (baseline \u2192 target over a metric, with baseline + target years).",
|
|
1528
|
+
inputSchema: {
|
|
1529
|
+
name: zod.z.string().min(1).describe("Target name"),
|
|
1530
|
+
metric: zod.z.string().min(1).describe('Metric being reduced (e.g. "Scope 1+2 CO2e")'),
|
|
1531
|
+
baselineValue: zod.z.number().describe("Baseline metric value"),
|
|
1532
|
+
baselineYear: zod.z.number().int().describe("Baseline year"),
|
|
1533
|
+
targetValue: zod.z.number().describe("Target metric value"),
|
|
1534
|
+
targetYear: zod.z.number().int().describe("Target year"),
|
|
1535
|
+
unit: zod.z.string().min(1).describe("Unit of the metric values"),
|
|
1536
|
+
description: zod.z.string().optional().describe("Target description")
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
async ({
|
|
1540
|
+
name,
|
|
1541
|
+
metric,
|
|
1542
|
+
baselineValue,
|
|
1543
|
+
baselineYear,
|
|
1544
|
+
targetValue,
|
|
1545
|
+
targetYear,
|
|
1546
|
+
unit,
|
|
1547
|
+
description
|
|
1548
|
+
}) => {
|
|
1549
|
+
try {
|
|
1550
|
+
const input = {
|
|
1551
|
+
name,
|
|
1552
|
+
metric,
|
|
1553
|
+
baselineValue,
|
|
1554
|
+
baselineYear,
|
|
1555
|
+
targetValue,
|
|
1556
|
+
targetYear,
|
|
1557
|
+
unit
|
|
1558
|
+
};
|
|
1559
|
+
if (description !== void 0) input.description = description;
|
|
1560
|
+
const data = await ctx.gql.workspace.request(CREATE_REDUCTION_TARGET, { input });
|
|
1561
|
+
return jsonResult(data.ecoCreateReductionTarget);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
ctx.logger.error("ecoimpacthub reduction_targets_create failed", { error: String(error) });
|
|
1564
|
+
return errorResult(
|
|
1565
|
+
`Failed to create reduction target: ${error instanceof Error ? error.message : String(error)}`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
);
|
|
1570
|
+
server.registerTool(
|
|
1571
|
+
track(mcpToolName(ctx.productName, "reduction_targets_set_status")),
|
|
1572
|
+
{
|
|
1573
|
+
title: "Set Reduction Target Status",
|
|
1574
|
+
description: "Update the status of a reduction target (e.g. PROPOSED \u2192 ACTIVE \u2192 ACHIEVED / MISSED / RETIRED).",
|
|
1575
|
+
inputSchema: {
|
|
1576
|
+
id: zod.z.string().describe("Reduction target ID"),
|
|
1577
|
+
status: ReductionTargetStatusEnum.describe("New target status")
|
|
1578
|
+
}
|
|
1579
|
+
},
|
|
1580
|
+
async ({ id, status }) => {
|
|
1581
|
+
try {
|
|
1582
|
+
const data = await ctx.gql.workspace.request(SET_REDUCTION_TARGET_STATUS, { id, status });
|
|
1583
|
+
return jsonResult(data.ecoSetReductionTargetStatus);
|
|
1584
|
+
} catch (error) {
|
|
1585
|
+
ctx.logger.error("ecoimpacthub reduction_targets_set_status failed", {
|
|
1586
|
+
error: String(error)
|
|
1587
|
+
});
|
|
1588
|
+
return errorResult(
|
|
1589
|
+
`Failed to set reduction target status: ${error instanceof Error ? error.message : String(error)}`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
function registerReportTools(server, ctx, track) {
|
|
1596
|
+
server.registerTool(
|
|
1597
|
+
track(mcpToolName(ctx.productName, "reports_get")),
|
|
1598
|
+
{
|
|
1599
|
+
title: "Get Report",
|
|
1600
|
+
description: "Fetch a single sustainability report by ID, including its line items.",
|
|
1601
|
+
inputSchema: { id: zod.z.string().describe("Report ID") }
|
|
1602
|
+
},
|
|
1603
|
+
async ({ id }) => {
|
|
1604
|
+
try {
|
|
1605
|
+
const data = await ctx.gql.workspace.request(GET_REPORT, { id });
|
|
1606
|
+
if (!data.ecoReport) return errorResult(`Report ${id} not found`);
|
|
1607
|
+
return jsonResult(data.ecoReport);
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
ctx.logger.error("ecoimpacthub reports_get failed", { error: String(error) });
|
|
1610
|
+
return errorResult(
|
|
1611
|
+
`Failed to get report: ${error instanceof Error ? error.message : String(error)}`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
);
|
|
1616
|
+
server.registerTool(
|
|
1617
|
+
track(mcpToolName(ctx.productName, "reports_list")),
|
|
1618
|
+
{
|
|
1619
|
+
title: "List Reports",
|
|
1620
|
+
description: "List sustainability reports, optionally filtered by status, with cursor pagination.",
|
|
1621
|
+
inputSchema: {
|
|
1622
|
+
status: ReportStatusEnum.optional().describe("Filter by report status"),
|
|
1623
|
+
...paginationInputShape
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
async ({ status, limit, offset, cursor }) => {
|
|
1627
|
+
try {
|
|
1628
|
+
const data = await ctx.gql.workspace.request(LIST_REPORTS, {
|
|
1629
|
+
status,
|
|
1630
|
+
pagination: buildPagination({ limit, offset, cursor })
|
|
1631
|
+
});
|
|
1632
|
+
return jsonResult(data.ecoListReports);
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
ctx.logger.error("ecoimpacthub reports_list failed", { error: String(error) });
|
|
1635
|
+
return errorResult(
|
|
1636
|
+
`Failed to list reports: ${error instanceof Error ? error.message : String(error)}`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
);
|
|
1641
|
+
server.registerTool(
|
|
1642
|
+
track(mcpToolName(ctx.productName, "reports_create")),
|
|
1643
|
+
{
|
|
1644
|
+
title: "Create Report",
|
|
1645
|
+
description: "Create a sustainability report for a reporting period. `framework` defaults to GHG_PROTOCOL.",
|
|
1646
|
+
inputSchema: {
|
|
1647
|
+
title: zod.z.string().min(1).describe("Report title"),
|
|
1648
|
+
periodStart: zod.z.string().describe("Reporting period start (ISO-8601 DateTime)"),
|
|
1649
|
+
periodEnd: zod.z.string().describe("Reporting period end (ISO-8601 DateTime)"),
|
|
1650
|
+
framework: ReportFrameworkEnum.optional().describe(
|
|
1651
|
+
"Reporting framework (default GHG_PROTOCOL)"
|
|
1652
|
+
),
|
|
1653
|
+
summary: zod.z.string().optional().describe("Report summary")
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
async ({ title, periodStart, periodEnd, framework, summary }) => {
|
|
1657
|
+
try {
|
|
1658
|
+
const input = { title, periodStart, periodEnd };
|
|
1659
|
+
if (framework !== void 0) input.framework = framework;
|
|
1660
|
+
if (summary !== void 0) input.summary = summary;
|
|
1661
|
+
const data = await ctx.gql.workspace.request(CREATE_REPORT, { input });
|
|
1662
|
+
return jsonResult(data.ecoCreateReport);
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
ctx.logger.error("ecoimpacthub reports_create failed", { error: String(error) });
|
|
1665
|
+
return errorResult(
|
|
1666
|
+
`Failed to create report: ${error instanceof Error ? error.message : String(error)}`
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
);
|
|
1671
|
+
server.registerTool(
|
|
1672
|
+
track(mcpToolName(ctx.productName, "reports_add_line_item")),
|
|
1673
|
+
{
|
|
1674
|
+
title: "Add Report Line Item",
|
|
1675
|
+
description: "Add a line item to a sustainability report, optionally linked to a carbon-ledger entry.",
|
|
1676
|
+
inputSchema: {
|
|
1677
|
+
reportId: zod.z.string().min(1).describe("Owning report ID"),
|
|
1678
|
+
label: zod.z.string().min(1).describe("Line-item label"),
|
|
1679
|
+
value: zod.z.number().describe("Line-item value"),
|
|
1680
|
+
unit: zod.z.string().min(1).describe("Unit of the value"),
|
|
1681
|
+
category: zod.z.string().optional().describe("Line-item category"),
|
|
1682
|
+
carbonEntryId: zod.z.string().optional().describe("Linked carbon-entry ID")
|
|
1683
|
+
}
|
|
1684
|
+
},
|
|
1685
|
+
async ({ reportId, label, value, unit, category, carbonEntryId }) => {
|
|
1686
|
+
try {
|
|
1687
|
+
const input = { reportId, label, value, unit };
|
|
1688
|
+
if (category !== void 0) input.category = category;
|
|
1689
|
+
if (carbonEntryId !== void 0) input.carbonEntryId = carbonEntryId;
|
|
1690
|
+
const data = await ctx.gql.workspace.request(ADD_REPORT_LINE_ITEM, { input });
|
|
1691
|
+
return jsonResult(data.ecoAddReportLineItem);
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
ctx.logger.error("ecoimpacthub reports_add_line_item failed", { error: String(error) });
|
|
1694
|
+
return errorResult(
|
|
1695
|
+
`Failed to add report line item: ${error instanceof Error ? error.message : String(error)}`
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
);
|
|
1700
|
+
server.registerTool(
|
|
1701
|
+
track(mcpToolName(ctx.productName, "reports_publish")),
|
|
1702
|
+
{
|
|
1703
|
+
title: "Publish Report",
|
|
1704
|
+
description: "Publish a sustainability report, optionally attaching an exported wspace-files File as the published artifact.",
|
|
1705
|
+
inputSchema: {
|
|
1706
|
+
id: zod.z.string().describe("Report ID"),
|
|
1707
|
+
fileId: zod.z.string().optional().describe("wspace-files File ID of the exported artifact")
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
async ({ id, fileId }) => {
|
|
1711
|
+
try {
|
|
1712
|
+
const data = await ctx.gql.workspace.request(PUBLISH_REPORT, { id, fileId });
|
|
1713
|
+
return jsonResult(data.ecoPublishReport);
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
ctx.logger.error("ecoimpacthub reports_publish failed", { error: String(error) });
|
|
1716
|
+
return errorResult(
|
|
1717
|
+
`Failed to publish report: ${error instanceof Error ? error.message : String(error)}`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/modules/ecoimpacthub/tools/health.ts
|
|
1725
|
+
function registerHealthTools(server, ctx, track) {
|
|
1726
|
+
server.registerTool(
|
|
1727
|
+
track(mcpToolName(ctx.productName, "health")),
|
|
1728
|
+
{
|
|
1729
|
+
title: "EcoImpactHub Health Check",
|
|
1730
|
+
description: "Public liveness/identity probe for the EcoImpactHub subgraph (ecoHealth). Requires no authentication.",
|
|
1731
|
+
inputSchema: {}
|
|
1732
|
+
},
|
|
1733
|
+
async () => {
|
|
1734
|
+
try {
|
|
1735
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1736
|
+
return jsonResult({ ok: true, status: data.ecoHealth });
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
ctx.logger.error("ecoimpacthub health failed", { error: String(error) });
|
|
1739
|
+
return errorResult(
|
|
1740
|
+
`EcoImpactHub health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// src/modules/ecoimpacthub/resources.ts
|
|
1748
|
+
function registerEcoimpacthubResources(server, ctx, track) {
|
|
1749
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1750
|
+
const sitesUri = track(mcpResourceUri(ctx.productName, "sites"));
|
|
1751
|
+
const targetsUri = track(mcpResourceUri(ctx.productName, "targets"));
|
|
1752
|
+
const carbonUri = track(mcpResourceUri(ctx.productName, "carbon"));
|
|
1753
|
+
server.registerResource(
|
|
1754
|
+
"ecoimpacthub-schema",
|
|
1755
|
+
schemaUri,
|
|
1756
|
+
{
|
|
1757
|
+
title: "EcoImpactHub Domain Schema",
|
|
1758
|
+
description: "Overview of the EcoImpactHub environmental-impact domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1759
|
+
mimeType: "text/plain"
|
|
1760
|
+
},
|
|
1761
|
+
async () => {
|
|
1762
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1763
|
+
const schema = `# EcoImpactHub Domain (Overview)
|
|
1764
|
+
|
|
1765
|
+
Environmental-impact, sustainability + biodiversity monitoring platform.
|
|
1766
|
+
|
|
1767
|
+
## Entities
|
|
1768
|
+
- EcoSite \u2014 a monitored environmental site (sensors, thresholds)
|
|
1769
|
+
- EcoSensor \u2014 monitoring hardware attached to a site
|
|
1770
|
+
- EcoAlertThreshold \u2014 per-site/parameter breach rule
|
|
1771
|
+
- EcoMeasurement \u2014 a telemetry reading (auto breach-flagged)
|
|
1772
|
+
- EcoSpecies \u2014 biodiversity catalog entry (observations)
|
|
1773
|
+
- EcoObservation \u2014 a species sighting (+ optional file evidence)
|
|
1774
|
+
- EcoHabitatProject (+EcoRestorationActivity) \u2014 restoration project + activity log
|
|
1775
|
+
- EcoCarbonEntry \u2014 a carbon-ledger entry (CO2e)
|
|
1776
|
+
- EcoEmissionFactor \u2014 kg CO2e per activity unit
|
|
1777
|
+
- EcoReductionTarget \u2014 emissions/impact reduction goal
|
|
1778
|
+
- EcoReport (+EcoReportLineItem) \u2014 sustainability disclosure report
|
|
1779
|
+
|
|
1780
|
+
## Enums
|
|
1781
|
+
- EcoSiteStatus: ACTIVE, ARCHIVED, MONITORING_PAUSED
|
|
1782
|
+
- EcoSiteType: AIRSHED, COASTAL, FACILITY, FARM, FOREST, OTHER, PROTECTED_AREA, WATERSHED, WETLAND
|
|
1783
|
+
- EcoSensorKind: ACOUSTIC, AIR_QUALITY, CAMERA_TRAP, ENERGY_METER, FLOW, MANUAL, SOIL, WATER_QUALITY, WEATHER
|
|
1784
|
+
- EcoSensorStatus: DEGRADED, MAINTENANCE, OFFLINE, ONLINE, RETIRED
|
|
1785
|
+
- EcoThresholdDirection: ABOVE, BELOW, OUTSIDE_RANGE | EcoThresholdSeverity: CRITICAL, INFO, WARNING
|
|
1786
|
+
- EcoMeasurementSource: CALCULATED, IMPORT, MANUAL, SATELLITE, SENSOR
|
|
1787
|
+
- EcoConservationStatus: CRITICALLY_ENDANGERED, DATA_DEFICIENT, ENDANGERED, EXTINCT_IN_WILD, LEAST_CONCERN, NEAR_THREATENED, NOT_EVALUATED, VULNERABLE
|
|
1788
|
+
- EcoTaxonGroup: AMPHIBIAN, BIRD, FISH, FUNGI, INSECT, MAMMAL, OTHER, PLANT, REPTILE
|
|
1789
|
+
- EcoObservationMethod: ACOUSTIC, CAMERA_TRAP, COMMUNITY_REPORT, DIRECT_SIGHTING, EDNA, TELEMETRY, TRACKS_SIGNS
|
|
1790
|
+
- EcoHabitatProjectStatus: CANCELLED, COMPLETED, IN_PROGRESS, MONITORING, ON_HOLD, PLANNED
|
|
1791
|
+
- EcoRestorationActivityType: COMMUNITY_ENGAGEMENT, INVASIVE_REMOVAL, PLANTING, REWILDING, SOIL_REMEDIATION, SURVEY, WATER_MANAGEMENT
|
|
1792
|
+
- EcoEmissionScope: SCOPE_1, SCOPE_2, SCOPE_3 | EcoCarbonEntryStatus: ADJUSTED, DRAFT, RECORDED, VERIFIED
|
|
1793
|
+
- EcoReductionTargetStatus: ACHIEVED, ACTIVE, MISSED, PROPOSED, RETIRED
|
|
1794
|
+
- EcoReportFramework: CDP, CUSTOM, GHG_PROTOCOL, GRI, SASB, TCFD | EcoReportStatus: ARCHIVED, DRAFT, IN_REVIEW, PUBLISHED
|
|
1795
|
+
|
|
1796
|
+
## Tool groups
|
|
1797
|
+
sites, sensors, thresholds, measurements, species, observations, habitat,
|
|
1798
|
+
carbon, emissions, targets, reports, health.
|
|
1799
|
+
All run against the WORKSPACE gateway (workspace-scoped) except the public health probe.
|
|
1800
|
+
`;
|
|
1801
|
+
return {
|
|
1802
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
);
|
|
1806
|
+
server.registerResource(
|
|
1807
|
+
"ecoimpacthub-sites",
|
|
1808
|
+
sitesUri,
|
|
1809
|
+
{
|
|
1810
|
+
title: "EcoImpactHub Site Directory",
|
|
1811
|
+
description: "The first 50 monitored sites in the bound workspace.",
|
|
1812
|
+
mimeType: "application/json"
|
|
1813
|
+
},
|
|
1814
|
+
async () => {
|
|
1815
|
+
ctx.logger.debug(`Resource ${sitesUri} requested`);
|
|
1816
|
+
try {
|
|
1817
|
+
const data = await ctx.gql.workspace.request(LIST_SITES, {
|
|
1818
|
+
pagination: { limit: 50, offset: 0 }
|
|
1819
|
+
});
|
|
1820
|
+
return {
|
|
1821
|
+
contents: [
|
|
1822
|
+
{
|
|
1823
|
+
uri: sitesUri,
|
|
1824
|
+
text: JSON.stringify(data.ecoListSites, null, 2),
|
|
1825
|
+
mimeType: "application/json"
|
|
1826
|
+
}
|
|
1827
|
+
]
|
|
1828
|
+
};
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
return {
|
|
1831
|
+
contents: [
|
|
1832
|
+
{
|
|
1833
|
+
uri: sitesUri,
|
|
1834
|
+
text: JSON.stringify({
|
|
1835
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1836
|
+
}),
|
|
1837
|
+
mimeType: "application/json"
|
|
1838
|
+
}
|
|
1839
|
+
]
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
);
|
|
1844
|
+
server.registerResource(
|
|
1845
|
+
"ecoimpacthub-targets",
|
|
1846
|
+
targetsUri,
|
|
1847
|
+
{
|
|
1848
|
+
title: "EcoImpactHub Reduction Targets",
|
|
1849
|
+
description: "All emissions/impact reduction targets in the bound workspace.",
|
|
1850
|
+
mimeType: "application/json"
|
|
1851
|
+
},
|
|
1852
|
+
async () => {
|
|
1853
|
+
ctx.logger.debug(`Resource ${targetsUri} requested`);
|
|
1854
|
+
try {
|
|
1855
|
+
const data = await ctx.gql.workspace.request(
|
|
1856
|
+
LIST_REDUCTION_TARGETS,
|
|
1857
|
+
{}
|
|
1858
|
+
);
|
|
1859
|
+
return {
|
|
1860
|
+
contents: [
|
|
1861
|
+
{
|
|
1862
|
+
uri: targetsUri,
|
|
1863
|
+
text: JSON.stringify(data.ecoListReductionTargets, null, 2),
|
|
1864
|
+
mimeType: "application/json"
|
|
1865
|
+
}
|
|
1866
|
+
]
|
|
1867
|
+
};
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
return {
|
|
1870
|
+
contents: [
|
|
1871
|
+
{
|
|
1872
|
+
uri: targetsUri,
|
|
1873
|
+
text: JSON.stringify({
|
|
1874
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1875
|
+
}),
|
|
1876
|
+
mimeType: "application/json"
|
|
1877
|
+
}
|
|
1878
|
+
]
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
);
|
|
1883
|
+
server.registerResource(
|
|
1884
|
+
"ecoimpacthub-carbon",
|
|
1885
|
+
carbonUri,
|
|
1886
|
+
{
|
|
1887
|
+
title: "EcoImpactHub Carbon Total",
|
|
1888
|
+
description: "Total CO2e (kg) recorded across the bound workspace.",
|
|
1889
|
+
mimeType: "application/json"
|
|
1890
|
+
},
|
|
1891
|
+
async () => {
|
|
1892
|
+
ctx.logger.debug(`Resource ${carbonUri} requested`);
|
|
1893
|
+
try {
|
|
1894
|
+
const data = await ctx.gql.workspace.request(
|
|
1895
|
+
CARBON_TOTAL_KG,
|
|
1896
|
+
{}
|
|
1897
|
+
);
|
|
1898
|
+
return {
|
|
1899
|
+
contents: [
|
|
1900
|
+
{
|
|
1901
|
+
uri: carbonUri,
|
|
1902
|
+
text: JSON.stringify({ totalKgCo2e: data.ecoCarbonTotalKg }, null, 2),
|
|
1903
|
+
mimeType: "application/json"
|
|
1904
|
+
}
|
|
1905
|
+
]
|
|
1906
|
+
};
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
return {
|
|
1909
|
+
contents: [
|
|
1910
|
+
{
|
|
1911
|
+
uri: carbonUri,
|
|
1912
|
+
text: JSON.stringify({
|
|
1913
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1914
|
+
}),
|
|
1915
|
+
mimeType: "application/json"
|
|
1916
|
+
}
|
|
1917
|
+
]
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
function registerEcoimpacthubPrompts(server, ctx, track) {
|
|
1924
|
+
const p = ctx.productName;
|
|
1925
|
+
const siteOnboardingName = track(mcpPromptName(p, "site-onboarding"));
|
|
1926
|
+
const reportingName = track(mcpPromptName(p, "sustainability-reporting"));
|
|
1927
|
+
server.registerPrompt(
|
|
1928
|
+
siteOnboardingName,
|
|
1929
|
+
{
|
|
1930
|
+
title: "Site Monitoring Onboarding",
|
|
1931
|
+
description: "Guide for standing up a monitored EcoImpactHub site end to end: create the site, attach sensors, define alert thresholds, and record a first measurement.",
|
|
1932
|
+
argsSchema: {
|
|
1933
|
+
name: zod.z.string().optional().describe("Site name"),
|
|
1934
|
+
type: zod.z.string().optional().describe("Site type")
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
({ name, type }) => {
|
|
1938
|
+
ctx.logger.debug(`Prompt ${siteOnboardingName} called`, { name, type });
|
|
1939
|
+
const nameNote = name ? `Use the site name "${name}".` : "Ask the user for the site name.";
|
|
1940
|
+
const typeNote = type ? `Use type ${type}.` : "Help the user pick an EcoSiteType.";
|
|
1941
|
+
return {
|
|
1942
|
+
messages: [
|
|
1943
|
+
{
|
|
1944
|
+
role: "user",
|
|
1945
|
+
content: {
|
|
1946
|
+
type: "text",
|
|
1947
|
+
text: `Help me onboard a new EcoImpactHub monitored site.
|
|
1948
|
+
|
|
1949
|
+
1. **Create Site**: Use ${p}_sites_create. ${nameNote} ${typeNote} Optionally set region/country and latitude/longitude.
|
|
1950
|
+
2. **Attach Sensors**: Use ${p}_sensors_create for each monitoring device \u2014 set siteId, name, kind (e.g. AIR_QUALITY, WATER_QUALITY), and unit.
|
|
1951
|
+
3. **Define Thresholds**: Use ${p}_thresholds_create per monitored parameter \u2014 set direction (ABOVE/BELOW/OUTSIDE_RANGE), severity, and limit/min/max values.
|
|
1952
|
+
4. **Record a Measurement**: Use ${p}_measurements_record to log a first reading; it is auto-evaluated against the active thresholds.
|
|
1953
|
+
5. **Verify**: Use ${p}_sites_get to confirm the site shows its sensors + thresholds, and ${p}_measurements_list with breachesOnly=true to spot any breaches.
|
|
1954
|
+
|
|
1955
|
+
Show results at each step and confirm before moving on.`
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
);
|
|
1962
|
+
server.registerPrompt(
|
|
1963
|
+
reportingName,
|
|
1964
|
+
{
|
|
1965
|
+
title: "Sustainability Reporting",
|
|
1966
|
+
description: "Workflow for compiling a sustainability disclosure: review the carbon ledger and targets, draft a report, add line items, and publish.",
|
|
1967
|
+
argsSchema: {
|
|
1968
|
+
framework: zod.z.string().optional().describe("Reporting framework (e.g. GHG_PROTOCOL, GRI)")
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
({ framework }) => {
|
|
1972
|
+
ctx.logger.debug(`Prompt ${reportingName} called`, { framework });
|
|
1973
|
+
const frameworkNote = framework ? `Use the ${framework} framework.` : "Help the user pick an EcoReportFramework (default GHG_PROTOCOL).";
|
|
1974
|
+
return {
|
|
1975
|
+
messages: [
|
|
1976
|
+
{
|
|
1977
|
+
role: "user",
|
|
1978
|
+
content: {
|
|
1979
|
+
type: "text",
|
|
1980
|
+
text: `Compile an EcoImpactHub sustainability report. ${frameworkNote}
|
|
1981
|
+
|
|
1982
|
+
1. **Carbon Ledger**: Use ${p}_carbon_total for the headline CO2e and ${p}_carbon_entries_list to review entries by scope; verify any pending entries with ${p}_carbon_entries_verify.
|
|
1983
|
+
2. **Emission Factors**: Use ${p}_emission_factors_list to confirm the factors backing the entries are current.
|
|
1984
|
+
3. **Targets**: Use ${p}_targets_list to check progress against reduction targets.
|
|
1985
|
+
4. **Create Report**: Use ${p}_reports_create for the reporting period (periodStart/periodEnd, framework).
|
|
1986
|
+
5. **Line Items**: Use ${p}_reports_add_line_item for each disclosure figure, linking carbonEntryId where applicable.
|
|
1987
|
+
6. **Publish**: Once reviewed, use ${p}_reports_publish (optionally attaching an exported file).
|
|
1988
|
+
|
|
1989
|
+
Summarize totals and any targets at risk after each step.`
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
]
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// src/modules/ecoimpacthub/index.ts
|
|
1999
|
+
var ecoimpacthubModule = {
|
|
2000
|
+
id: "ecoimpacthub",
|
|
2001
|
+
version: "0.0.1",
|
|
2002
|
+
displayName: "EcoImpactHub",
|
|
2003
|
+
dependsOn: ["auth"],
|
|
2004
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
2005
|
+
register(server, ctx) {
|
|
2006
|
+
const tools = [];
|
|
2007
|
+
const resources = [];
|
|
2008
|
+
const prompts = [];
|
|
2009
|
+
const trackTool = (n) => {
|
|
2010
|
+
tools.push(n);
|
|
2011
|
+
return n;
|
|
2012
|
+
};
|
|
2013
|
+
const trackResource = (uri) => {
|
|
2014
|
+
resources.push(uri);
|
|
2015
|
+
return uri;
|
|
2016
|
+
};
|
|
2017
|
+
const trackPrompt = (n) => {
|
|
2018
|
+
prompts.push(n);
|
|
2019
|
+
return n;
|
|
2020
|
+
};
|
|
2021
|
+
registerSiteTools(server, ctx, trackTool);
|
|
2022
|
+
registerSensorTools(server, ctx, trackTool);
|
|
2023
|
+
registerThresholdTools(server, ctx, trackTool);
|
|
2024
|
+
registerMeasurementTools(server, ctx, trackTool);
|
|
2025
|
+
registerSpeciesTools(server, ctx, trackTool);
|
|
2026
|
+
registerObservationTools(server, ctx, trackTool);
|
|
2027
|
+
registerHabitatTools(server, ctx, trackTool);
|
|
2028
|
+
registerCarbonTools(server, ctx, trackTool);
|
|
2029
|
+
registerEmissionFactorTools(server, ctx, trackTool);
|
|
2030
|
+
registerTargetTools(server, ctx, trackTool);
|
|
2031
|
+
registerReportTools(server, ctx, trackTool);
|
|
2032
|
+
registerHealthTools(server, ctx, trackTool);
|
|
2033
|
+
registerEcoimpacthubResources(server, ctx, trackResource);
|
|
2034
|
+
registerEcoimpacthubPrompts(server, ctx, trackPrompt);
|
|
2035
|
+
return { tools, resources, prompts };
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
var ecoimpacthub_default = ecoimpacthubModule;
|
|
2039
|
+
|
|
2040
|
+
module.exports = ecoimpacthub_default;
|
|
2041
|
+
//# sourceMappingURL=index.cjs.map
|
|
2042
|
+
//# sourceMappingURL=index.cjs.map
|