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