@energyatit/mcp-server 0.1.0 → 0.2.0
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/index.js +90 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,17 +8,85 @@ const BASE_URL = (process.env.ENERGYATIT_BASE_URL ??
|
|
|
8
8
|
"https://energyatit.com").replace(/\/$/, "");
|
|
9
9
|
const API_KEY = process.env.ENERGYATIT_API_KEY ?? "";
|
|
10
10
|
const TOKEN = process.env.ENERGYATIT_TOKEN ?? "";
|
|
11
|
+
// ─── Demo Mode ──────────────────────────────────────────────────────────────
|
|
12
|
+
const demoMode = !API_KEY && !TOKEN;
|
|
13
|
+
let sessionApiKey = "";
|
|
11
14
|
function authHeaders() {
|
|
12
15
|
const h = { "Content-Type": "application/json" };
|
|
13
16
|
if (TOKEN)
|
|
14
17
|
h["Authorization"] = `Bearer ${TOKEN}`;
|
|
15
18
|
else if (API_KEY)
|
|
16
19
|
h["X-API-Key"] = API_KEY;
|
|
20
|
+
else if (sessionApiKey)
|
|
21
|
+
h["X-API-Key"] = sessionApiKey;
|
|
17
22
|
return h;
|
|
18
23
|
}
|
|
24
|
+
// Demo path mapping: read-only tools use public /demo/* routes when in demo mode
|
|
25
|
+
const DEMO_PATH_MAP = {
|
|
26
|
+
"/api/sites": "/api/v1/demo/sites",
|
|
27
|
+
"/api/assets": "/api/v1/demo/assets",
|
|
28
|
+
"/api/grid-connections": "/api/v1/demo/grid-connections",
|
|
29
|
+
"/api/meter-readings": "/api/v1/demo/meter-readings",
|
|
30
|
+
"/api/settlements": "/api/v1/demo/settlements",
|
|
31
|
+
"/api/v1/dr/events": "/api/v1/demo/dr/events",
|
|
32
|
+
"/api/v1/integrations/status": "/api/v1/demo/integrations/status",
|
|
33
|
+
"/api/v1/carbon": "/api/v1/demo/carbon",
|
|
34
|
+
};
|
|
35
|
+
function resolveDemoPath(path) {
|
|
36
|
+
if (!demoMode)
|
|
37
|
+
return path;
|
|
38
|
+
// Check exact match first
|
|
39
|
+
const basePath = path.split("?")[0];
|
|
40
|
+
const qs = path.includes("?") ? path.slice(path.indexOf("?")) : "";
|
|
41
|
+
if (DEMO_PATH_MAP[basePath])
|
|
42
|
+
return DEMO_PATH_MAP[basePath] + qs;
|
|
43
|
+
// Check prefix matches (e.g. /api/sites/123)
|
|
44
|
+
for (const [from, to] of Object.entries(DEMO_PATH_MAP)) {
|
|
45
|
+
if (basePath.startsWith(from + "/")) {
|
|
46
|
+
return to + basePath.slice(from.length) + qs;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return path;
|
|
50
|
+
}
|
|
19
51
|
// ─── HTTP helpers ──────────────────────────────────────────────────────────
|
|
52
|
+
async function autoProvisionSandbox() {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(`${BASE_URL}/api/v1/sandbox/provision`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
});
|
|
58
|
+
const json = await res.json();
|
|
59
|
+
if (json.success && json.data) {
|
|
60
|
+
const data = json.data;
|
|
61
|
+
if (data.apiKey && typeof data.apiKey === "string") {
|
|
62
|
+
sessionApiKey = data.apiKey;
|
|
63
|
+
console.error(`[demo] Auto-provisioned sandbox (key: ${sessionApiKey.slice(0, 20)}...)`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.error("[demo] Sandbox provision returned unexpected response:", json);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error("[demo] Failed to auto-provision sandbox:", err);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
20
75
|
async function apiGet(path) {
|
|
21
|
-
const
|
|
76
|
+
const resolvedPath = resolveDemoPath(path);
|
|
77
|
+
const res = await fetch(`${BASE_URL}${resolvedPath}`, { headers: authHeaders() });
|
|
78
|
+
// Auto-provision on 401 in demo mode
|
|
79
|
+
if (res.status === 401 && demoMode && !sessionApiKey) {
|
|
80
|
+
const provisioned = await autoProvisionSandbox();
|
|
81
|
+
if (provisioned) {
|
|
82
|
+
// Retry with the new key
|
|
83
|
+
const retry = await fetch(`${BASE_URL}${resolvedPath}`, { headers: authHeaders() });
|
|
84
|
+
const retryJson = await retry.json();
|
|
85
|
+
if (retryJson.success === false)
|
|
86
|
+
throw new Error(String(retryJson.error ?? `API error ${retry.status}`));
|
|
87
|
+
return retryJson.data ?? retryJson;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
22
90
|
const json = await res.json();
|
|
23
91
|
if (json.success === false)
|
|
24
92
|
throw new Error(String(json.error ?? `API error ${res.status}`));
|
|
@@ -30,6 +98,21 @@ async function apiPost(path, body) {
|
|
|
30
98
|
headers: authHeaders(),
|
|
31
99
|
body: body ? JSON.stringify(body) : undefined,
|
|
32
100
|
});
|
|
101
|
+
// Auto-provision on 401 in demo mode
|
|
102
|
+
if (res.status === 401 && demoMode && !sessionApiKey) {
|
|
103
|
+
const provisioned = await autoProvisionSandbox();
|
|
104
|
+
if (provisioned) {
|
|
105
|
+
const retry = await fetch(`${BASE_URL}${path}`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: authHeaders(),
|
|
108
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
109
|
+
});
|
|
110
|
+
const retryJson = await retry.json();
|
|
111
|
+
if (retryJson.success === false)
|
|
112
|
+
throw new Error(String(retryJson.error ?? `API error ${retry.status}`));
|
|
113
|
+
return retryJson.data ?? retryJson;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
33
116
|
const json = await res.json();
|
|
34
117
|
if (json.success === false)
|
|
35
118
|
throw new Error(String(json.error ?? `API error ${res.status}`));
|
|
@@ -55,7 +138,7 @@ function errorResult(err) {
|
|
|
55
138
|
// ─── MCP Server ────────────────────────────────────────────────────────────
|
|
56
139
|
const server = new McpServer({
|
|
57
140
|
name: "energyatit",
|
|
58
|
-
version: "0.
|
|
141
|
+
version: "0.2.0",
|
|
59
142
|
});
|
|
60
143
|
// ── Sites ────────────────────────────────────────────────────────────────
|
|
61
144
|
server.tool("list_sites", "List all energy sites in your tenant", {}, async () => {
|
|
@@ -473,17 +556,17 @@ server.resource("platform-overview", "energyatit://overview", async () => ({
|
|
|
473
556
|
" - Integrations: Modbus, OpenADR 2.0b, OCPP 2.0, IEC 61850",
|
|
474
557
|
"",
|
|
475
558
|
`Connected to: ${BASE_URL}`,
|
|
476
|
-
`Auth: ${TOKEN ? "JWT token" : API_KEY ? "API key" : "
|
|
559
|
+
`Auth: ${TOKEN ? "JWT token" : API_KEY ? "API key" : demoMode ? "demo mode (public endpoints + auto-provision)" : "none"}`,
|
|
477
560
|
].join("\n"),
|
|
478
561
|
}],
|
|
479
562
|
}));
|
|
480
563
|
// ─── Start ─────────────────────────────────────────────────────────────────
|
|
481
564
|
async function main() {
|
|
482
|
-
if (
|
|
483
|
-
console.error("
|
|
484
|
-
console.error("Set ENERGYATIT_API_KEY
|
|
565
|
+
if (demoMode) {
|
|
566
|
+
console.error("Running in demo mode — read-only tools use public endpoints, write tools auto-provision sandbox");
|
|
567
|
+
console.error("Set ENERGYATIT_API_KEY or ENERGYATIT_TOKEN for full access.");
|
|
485
568
|
}
|
|
486
|
-
console.error(`EnergyAtIt MCP server v0.
|
|
569
|
+
console.error(`EnergyAtIt MCP server v0.2.0 — connecting to ${BASE_URL}`);
|
|
487
570
|
const transport = new StdioServerTransport();
|
|
488
571
|
await server.connect(transport);
|
|
489
572
|
console.error("EnergyAtIt MCP server running on stdio");
|