@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.
Files changed (2) hide show
  1. package/dist/index.js +96 -7
  2. 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 res = await fetch(`${BASE_URL}${path}`, { headers: authHeaders() });
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.0",
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" : "none (set ENERGYATIT_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 (!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.");
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.0 — connecting to ${BASE_URL}`);
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");
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.1",
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": {