dchub 0.1.0__tar.gz

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.
dchub-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: dchub
3
+ Version: 0.1.0
4
+ Summary: DC Hub Python SDK — live data-center, power & gas intelligence over MCP.
5
+ Author: DC Hub
6
+ License: MIT
7
+ Project-URL: Homepage, https://dchub.cloud
8
+ Project-URL: Repository, https://github.com/azmartone67/dchub-mcp-server
9
+ Keywords: data-center,energy,mcp,grid,infrastructure
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest>=7; extra == "test"
14
+
15
+ # dchub — Python SDK
16
+
17
+ Live data-center, power & gas intelligence for AI agents. Hides the MCP JSON-RPC
18
+ handshake (`initialize` → `notifications/initialized` → `tools/call`, SSE
19
+ parsing) behind a thin client. **Zero runtime dependencies** (stdlib only).
20
+
21
+ ## Install
22
+ ```bash
23
+ pip install dchub # from this repo: pip install ./sdk/python
24
+ ```
25
+
26
+ ## Quickstart (5 lines)
27
+ ```python
28
+ from dchub import DCHub
29
+ dc = DCHub() # reads DCHUB_API_KEY from env if set
30
+ print(dc.market("northern-virginia")) # market intel
31
+ print(dc.search(state="VA")) # facility search (canonical slugs)
32
+ print(dc.grid(iso="ERCOT")) # live grid intel
33
+ ```
34
+
35
+ ## API
36
+ | Method | Tool | Returns |
37
+ |--------|------|---------|
38
+ | `dc.market(slug)` | `get_market_intel` | by-status counts, operators, recent facilities |
39
+ | `dc.search(q, state, country, limit)` | `search_facilities` | rows w/ canonical slug, provider, location |
40
+ | `dc.grid(iso)` | `get_grid_data` | live demand / mix / headroom |
41
+ | `dc.call(tool, **args)` | *any of 38* | cleaned data payload |
42
+ | `dc.tools()` | `tools/list` | list of 38 tool names |
43
+
44
+ ## Auth & tiers
45
+ Set `DCHUB_API_KEY` (sent as `X-API-Key`) for full data:
46
+ ```bash
47
+ curl -X POST https://dchub.cloud/api/v1/keys/claim -d '{"client_name":"python-sdk"}'
48
+ export DCHUB_API_KEY=dch_live_...
49
+ ```
50
+ On the **free tier** some fields are masked and `grid` returns a gated preview;
51
+ the SDK strips the upsell wrapper and returns the real embedded payload either
52
+ way. Source/citation: https://dchub.cloud (CC-BY-4.0).
53
+
54
+ ## Tests
55
+ ```bash
56
+ pip install -e ".[test]" && pytest # 5 live, gate-graceful tests
57
+ ```
58
+
59
+ > Packaging is configured but **not published** — the maintainer runs
60
+ > `python -m build && twine upload` to ship to PyPI.
dchub-0.1.0/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # dchub — Python SDK
2
+
3
+ Live data-center, power & gas intelligence for AI agents. Hides the MCP JSON-RPC
4
+ handshake (`initialize` → `notifications/initialized` → `tools/call`, SSE
5
+ parsing) behind a thin client. **Zero runtime dependencies** (stdlib only).
6
+
7
+ ## Install
8
+ ```bash
9
+ pip install dchub # from this repo: pip install ./sdk/python
10
+ ```
11
+
12
+ ## Quickstart (5 lines)
13
+ ```python
14
+ from dchub import DCHub
15
+ dc = DCHub() # reads DCHUB_API_KEY from env if set
16
+ print(dc.market("northern-virginia")) # market intel
17
+ print(dc.search(state="VA")) # facility search (canonical slugs)
18
+ print(dc.grid(iso="ERCOT")) # live grid intel
19
+ ```
20
+
21
+ ## API
22
+ | Method | Tool | Returns |
23
+ |--------|------|---------|
24
+ | `dc.market(slug)` | `get_market_intel` | by-status counts, operators, recent facilities |
25
+ | `dc.search(q, state, country, limit)` | `search_facilities` | rows w/ canonical slug, provider, location |
26
+ | `dc.grid(iso)` | `get_grid_data` | live demand / mix / headroom |
27
+ | `dc.call(tool, **args)` | *any of 38* | cleaned data payload |
28
+ | `dc.tools()` | `tools/list` | list of 38 tool names |
29
+
30
+ ## Auth & tiers
31
+ Set `DCHUB_API_KEY` (sent as `X-API-Key`) for full data:
32
+ ```bash
33
+ curl -X POST https://dchub.cloud/api/v1/keys/claim -d '{"client_name":"python-sdk"}'
34
+ export DCHUB_API_KEY=dch_live_...
35
+ ```
36
+ On the **free tier** some fields are masked and `grid` returns a gated preview;
37
+ the SDK strips the upsell wrapper and returns the real embedded payload either
38
+ way. Source/citation: https://dchub.cloud (CC-BY-4.0).
39
+
40
+ ## Tests
41
+ ```bash
42
+ pip install -e ".[test]" && pytest # 5 live, gate-graceful tests
43
+ ```
44
+
45
+ > Packaging is configured but **not published** — the maintainer runs
46
+ > `python -m build && twine upload` to ship to PyPI.
@@ -0,0 +1,5 @@
1
+ """DC Hub Python SDK — live data-center, power & gas intelligence over MCP."""
2
+ from .client import DCHub
3
+
4
+ __all__ = ["DCHub"]
5
+ __version__ = "0.1.0"
@@ -0,0 +1,154 @@
1
+ """DC Hub Python SDK — hides the MCP JSON-RPC handshake.
2
+
3
+ The MCP transport (initialize -> notifications/initialized -> tools/call, with
4
+ SSE response parsing) is wrapped so you can just write:
5
+
6
+ from dchub import DCHub
7
+ dc = DCHub() # reads DCHUB_API_KEY from env if set
8
+ dc.market("northern-virginia")
9
+ dc.search(state="VA")
10
+ dc.grid(iso="ERCOT")
11
+ dc.call("get_market_intel", market="dallas") # any of the 38 tools
12
+ dc.tools() # list tool names
13
+
14
+ Set DCHUB_API_KEY for full-tier data (sent as the X-API-Key header).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import urllib.request
21
+
22
+ __all__ = ["DCHub"]
23
+
24
+ _DEFAULT_ENDPOINT = "https://dchub.cloud/mcp"
25
+
26
+
27
+ class DCHub:
28
+ def __init__(self, api_key: str | None = None, endpoint: str | None = None,
29
+ timeout: int = 60):
30
+ self.endpoint = endpoint or os.environ.get("DCHUB_ENDPOINT", _DEFAULT_ENDPOINT)
31
+ self.api_key = api_key if api_key is not None else os.environ.get("DCHUB_API_KEY")
32
+ self.timeout = timeout
33
+ self._session_id: str | None = None
34
+
35
+ # --- transport ---------------------------------------------------------
36
+ def _headers(self) -> dict:
37
+ h = {"Content-Type": "application/json",
38
+ "Accept": "application/json, text/event-stream"}
39
+ if self.api_key:
40
+ h["X-API-Key"] = self.api_key
41
+ if self._session_id:
42
+ h["Mcp-Session-Id"] = self._session_id
43
+ return h
44
+
45
+ @staticmethod
46
+ def _parse_body(raw: str):
47
+ raw = raw.strip()
48
+ if not raw:
49
+ return None
50
+ if raw.startswith("{"):
51
+ return json.loads(raw)
52
+ for line in raw.splitlines(): # SSE: 'data: {...}'
53
+ line = line.strip()
54
+ if line.startswith("data:"):
55
+ payload = line[len("data:"):].strip()
56
+ if payload.startswith("{"):
57
+ return json.loads(payload)
58
+ raise ValueError(f"Could not parse MCP response body: {raw[:300]}")
59
+
60
+ def _post(self, payload: dict):
61
+ data = json.dumps(payload).encode()
62
+ req = urllib.request.Request(self.endpoint, data=data,
63
+ headers=self._headers(), method="POST")
64
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
65
+ sid = resp.headers.get("Mcp-Session-Id")
66
+ body = resp.read().decode()
67
+ if sid:
68
+ self._session_id = sid
69
+ return self._parse_body(body)
70
+
71
+ def _ensure_session(self):
72
+ if self._session_id:
73
+ return
74
+ self._post({"jsonrpc": "2.0", "id": 1, "method": "initialize",
75
+ "params": {"protocolVersion": "2024-11-05", "capabilities": {},
76
+ "clientInfo": {"name": "dchub-python-sdk", "version": "1.0"}}})
77
+ try:
78
+ self._post({"jsonrpc": "2.0", "method": "notifications/initialized"})
79
+ except Exception:
80
+ pass
81
+
82
+ # --- payload cleaning --------------------------------------------------
83
+ @staticmethod
84
+ def _clean(result):
85
+ """Return the real data payload, stripping any free-tier upsell wrapper.
86
+
87
+ `search_facilities` already returns a structured dict; the text tools
88
+ (`get_market_intel`, `get_grid_data`) embed the data as a JSON block
89
+ fenced by `---`. Upsell-only objects (agent_action/agent_claim) are
90
+ skipped.
91
+ """
92
+ if isinstance(result, dict):
93
+ return result
94
+ if isinstance(result, str):
95
+ for part in result.split("---"):
96
+ part = part.strip()
97
+ if part.startswith("{"):
98
+ try:
99
+ obj = json.loads(part)
100
+ except Exception:
101
+ continue
102
+ if not ({"agent_action", "agent_claim"} & set(obj)):
103
+ return obj
104
+ return {"text": result}
105
+ return result
106
+
107
+ # --- generic call ------------------------------------------------------
108
+ def call(self, tool: str, **arguments):
109
+ """Call any DC Hub MCP tool; returns the cleaned data payload."""
110
+ self._ensure_session()
111
+ resp = self._post({"jsonrpc": "2.0", "id": 3, "method": "tools/call",
112
+ "params": {"name": tool, "arguments": arguments}})
113
+ if "error" in resp:
114
+ return resp["error"]
115
+ content = resp.get("result", {}).get("content", [])
116
+ parsed = []
117
+ for item in content:
118
+ if item.get("type") == "text":
119
+ txt = item["text"]
120
+ try:
121
+ parsed.append(json.loads(txt))
122
+ except Exception:
123
+ parsed.append(txt)
124
+ else:
125
+ parsed.append(item)
126
+ raw = parsed[0] if len(parsed) == 1 else parsed
127
+ return self._clean(raw)
128
+
129
+ def tools(self) -> list[str]:
130
+ """List all available tool names."""
131
+ self._ensure_session()
132
+ resp = self._post({"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}})
133
+ return [t["name"] for t in resp.get("result", {}).get("tools", [])]
134
+
135
+ # --- convenience methods ----------------------------------------------
136
+ def market(self, slug: str):
137
+ """Market intelligence for a market slug, e.g. 'northern-virginia'."""
138
+ return self.call("get_market_intel", market=slug)
139
+
140
+ def search(self, q: str | None = None, state: str | None = None,
141
+ country: str | None = None, limit: int = 5):
142
+ """Search facilities by free-text / state / country."""
143
+ args = {"limit": limit}
144
+ if q:
145
+ args["q"] = q
146
+ if state:
147
+ args["state"] = state
148
+ if country:
149
+ args["country"] = country
150
+ return self.call("search_facilities", **args)
151
+
152
+ def grid(self, iso: str):
153
+ """Live grid intelligence for an ISO, e.g. 'ERCOT', 'PJM'."""
154
+ return self.call("get_grid_data", iso=iso)
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: dchub
3
+ Version: 0.1.0
4
+ Summary: DC Hub Python SDK — live data-center, power & gas intelligence over MCP.
5
+ Author: DC Hub
6
+ License: MIT
7
+ Project-URL: Homepage, https://dchub.cloud
8
+ Project-URL: Repository, https://github.com/azmartone67/dchub-mcp-server
9
+ Keywords: data-center,energy,mcp,grid,infrastructure
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest>=7; extra == "test"
14
+
15
+ # dchub — Python SDK
16
+
17
+ Live data-center, power & gas intelligence for AI agents. Hides the MCP JSON-RPC
18
+ handshake (`initialize` → `notifications/initialized` → `tools/call`, SSE
19
+ parsing) behind a thin client. **Zero runtime dependencies** (stdlib only).
20
+
21
+ ## Install
22
+ ```bash
23
+ pip install dchub # from this repo: pip install ./sdk/python
24
+ ```
25
+
26
+ ## Quickstart (5 lines)
27
+ ```python
28
+ from dchub import DCHub
29
+ dc = DCHub() # reads DCHUB_API_KEY from env if set
30
+ print(dc.market("northern-virginia")) # market intel
31
+ print(dc.search(state="VA")) # facility search (canonical slugs)
32
+ print(dc.grid(iso="ERCOT")) # live grid intel
33
+ ```
34
+
35
+ ## API
36
+ | Method | Tool | Returns |
37
+ |--------|------|---------|
38
+ | `dc.market(slug)` | `get_market_intel` | by-status counts, operators, recent facilities |
39
+ | `dc.search(q, state, country, limit)` | `search_facilities` | rows w/ canonical slug, provider, location |
40
+ | `dc.grid(iso)` | `get_grid_data` | live demand / mix / headroom |
41
+ | `dc.call(tool, **args)` | *any of 38* | cleaned data payload |
42
+ | `dc.tools()` | `tools/list` | list of 38 tool names |
43
+
44
+ ## Auth & tiers
45
+ Set `DCHUB_API_KEY` (sent as `X-API-Key`) for full data:
46
+ ```bash
47
+ curl -X POST https://dchub.cloud/api/v1/keys/claim -d '{"client_name":"python-sdk"}'
48
+ export DCHUB_API_KEY=dch_live_...
49
+ ```
50
+ On the **free tier** some fields are masked and `grid` returns a gated preview;
51
+ the SDK strips the upsell wrapper and returns the real embedded payload either
52
+ way. Source/citation: https://dchub.cloud (CC-BY-4.0).
53
+
54
+ ## Tests
55
+ ```bash
56
+ pip install -e ".[test]" && pytest # 5 live, gate-graceful tests
57
+ ```
58
+
59
+ > Packaging is configured but **not published** — the maintainer runs
60
+ > `python -m build && twine upload` to ship to PyPI.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ dchub/__init__.py
4
+ dchub/client.py
5
+ dchub.egg-info/PKG-INFO
6
+ dchub.egg-info/SOURCES.txt
7
+ dchub.egg-info/dependency_links.txt
8
+ dchub.egg-info/requires.txt
9
+ dchub.egg-info/top_level.txt
10
+ tests/test_client.py
@@ -0,0 +1,3 @@
1
+
2
+ [test]
3
+ pytest>=7
@@ -0,0 +1 @@
1
+ dchub
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dchub"
7
+ version = "0.1.0"
8
+ description = "DC Hub Python SDK — live data-center, power & gas intelligence over MCP."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "DC Hub" }]
13
+ keywords = ["data-center", "energy", "mcp", "grid", "infrastructure"]
14
+ dependencies = [] # stdlib only (urllib) — zero runtime deps
15
+
16
+ [project.urls]
17
+ Homepage = "https://dchub.cloud"
18
+ Repository = "https://github.com/azmartone67/dchub-mcp-server"
19
+
20
+ [project.optional-dependencies]
21
+ test = ["pytest>=7"]
22
+
23
+ [tool.setuptools]
24
+ packages = ["dchub"]
dchub-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,38 @@
1
+ """Live tests for the DC Hub Python SDK (free tier; gate-graceful)."""
2
+ from dchub import DCHub
3
+
4
+
5
+ def test_tools_list_is_38():
6
+ assert len(DCHub().tools()) == 38
7
+
8
+
9
+ def test_market_returns_real_data():
10
+ d = DCHub().market("northern-virginia")
11
+ assert isinstance(d, dict)
12
+ # Free tier rotates between the full payload and a 1-result preview gate;
13
+ # accept either (gate-graceful), but when data is present it must be real.
14
+ if "market" in d:
15
+ assert "northern virginia" in d["market"]["name"].lower()
16
+ assert sum(d["by_status"].values()) > 0
17
+ else:
18
+ assert "text" in d # gated preview wrapper, stripped to its text
19
+
20
+
21
+ def test_search_returns_canonical_slug():
22
+ d = DCHub().search(state="VA", limit=3)
23
+ rows = d.get("data", [])
24
+ assert rows and all("slug" in r for r in rows)
25
+
26
+
27
+ def test_clean_strips_upsell_wrapper():
28
+ # A text blob with an upsell-only object then the real payload.
29
+ blob = ('marketing...\n---\n{"agent_action":{"x":1}}\n---\n'
30
+ '{"stats":{"facility_count":739},"success":true}')
31
+ out = DCHub._clean(blob)
32
+ assert out == {"stats": {"facility_count": 739}, "success": True}
33
+
34
+
35
+ def test_grid_call_does_not_raise():
36
+ # Free tier gates grid to a preview; SDK should still return cleanly.
37
+ g = DCHub().grid("ERCOT")
38
+ assert isinstance(g, dict)