@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.
Files changed (2) hide show
  1. package/dist/index.js +90 -7
  2. 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 res = await fetch(`${BASE_URL}${path}`, { headers: authHeaders() });
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.1.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" : "none (set ENERGYATIT_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 (!API_KEY && !TOKEN) {
483
- console.error("Warning: No ENERGYATIT_API_KEY or ENERGYATIT_TOKEN set. Requests will be unauthenticated.");
484
- console.error("Set ENERGYATIT_API_KEY=eat_live_xxx or ENERGYATIT_TOKEN=jwt_xxx in your environment.");
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.1.0 — connecting to ${BASE_URL}`);
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energyatit/mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for EnergyAtIt — connect Claude, GPT, or any MCP client to energy grid data",
5
5
  "type": "module",
6
6
  "bin": {