muriel 0.2.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.
- muriel-0.2.0/LICENSE +21 -0
- muriel-0.2.0/MANIFEST.in +6 -0
- muriel-0.2.0/PKG-INFO +341 -0
- muriel-0.2.0/README.md +303 -0
- muriel-0.2.0/muriel/__init__.py +75 -0
- muriel-0.2.0/muriel/agent.py +355 -0
- muriel-0.2.0/muriel/client.py +164 -0
- muriel-0.2.0/muriel/clients.py +94 -0
- muriel-0.2.0/muriel/config.py +46 -0
- muriel-0.2.0/muriel/endpoints/__init__.py +1 -0
- muriel-0.2.0/muriel/endpoints/contaminants.py +437 -0
- muriel-0.2.0/muriel/endpoints/facilities.py +82 -0
- muriel-0.2.0/muriel/endpoints/requirements.py +39 -0
- muriel-0.2.0/muriel/endpoints/results.py +142 -0
- muriel-0.2.0/muriel/endpoints/sampling_points.py +86 -0
- muriel-0.2.0/muriel/endpoints/systems.py +34 -0
- muriel-0.2.0/muriel/endpoints/violations.py +45 -0
- muriel-0.2.0/muriel/exceptions.py +65 -0
- muriel-0.2.0/muriel/models.py +133 -0
- muriel-0.2.0/muriel/py.typed +0 -0
- muriel-0.2.0/muriel/utils.py +111 -0
- muriel-0.2.0/muriel.egg-info/PKG-INFO +341 -0
- muriel-0.2.0/muriel.egg-info/SOURCES.txt +30 -0
- muriel-0.2.0/muriel.egg-info/dependency_links.txt +1 -0
- muriel-0.2.0/muriel.egg-info/requires.txt +10 -0
- muriel-0.2.0/muriel.egg-info/top_level.txt +1 -0
- muriel-0.2.0/pyproject.toml +71 -0
- muriel-0.2.0/requirements.txt +2 -0
- muriel-0.2.0/setup.cfg +4 -0
- muriel-0.2.0/setup.py +4 -0
- muriel-0.2.0/tests/test_agent_query.py +164 -0
- muriel-0.2.0/tests/test_traditional.py +186 -0
muriel-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Muriel AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
muriel-0.2.0/MANIFEST.in
ADDED
muriel-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: muriel
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Muriel: ask the U.S. drinking water record in plain English (agent-first SDK)
|
|
5
|
+
Author-email: "Muriel AI, Inc." <info@murielai.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://murielai.io/developers
|
|
8
|
+
Project-URL: Documentation, https://docs.murielai.io
|
|
9
|
+
Project-URL: API reference, https://api.murielai.io/docs
|
|
10
|
+
Project-URL: Repository, https://github.com/Muriel-AI-Inc/muriel-sdk-python
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/Muriel-AI-Inc/muriel-sdk-python/issues
|
|
12
|
+
Keywords: water,quality,api,pfas,lead,contaminants,environmental
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests>=2.31.0
|
|
29
|
+
Requires-Dist: pydantic>=2.0.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Muriel Python SDK
|
|
40
|
+
|
|
41
|
+
[Muriel](https://murielai.io) is the AI agent for U.S. drinking water data. It
|
|
42
|
+
answers plain-English questions about any public water system from the
|
|
43
|
+
monitoring results states publish, checks them against federal limits, and
|
|
44
|
+
shows the records behind every answer.
|
|
45
|
+
|
|
46
|
+
There are three ways to use it, and this README covers them in the order most
|
|
47
|
+
people need them:
|
|
48
|
+
|
|
49
|
+
1. **[In your AI assistant](#1-use-muriel-in-your-ai-assistant-mcp-connector)**:
|
|
50
|
+
add the Muriel connector to Claude, ChatGPT, Cursor, VS Code or any MCP
|
|
51
|
+
client and ask in the chat you already use. No code, no key.
|
|
52
|
+
2. **[The agent, from code](#2-ask-the-agent-from-code)**: `muriel.query("...")`
|
|
53
|
+
sends a question to the same agent and returns the answer, the records it
|
|
54
|
+
used, and any CSV it produced, shaped the way your program needs.
|
|
55
|
+
3. **[The REST API](#3-query-the-rest-api-directly)**: exact, parameter-driven
|
|
56
|
+
queries for one system's history, one contaminant nationwide, every system
|
|
57
|
+
in a state, or the system serving an address. Use this package's functions or
|
|
58
|
+
call it over HTTP from any language.
|
|
59
|
+
|
|
60
|
+
All three draw on the same data and the same Muriel account and credits.
|
|
61
|
+
|
|
62
|
+
## What Muriel covers
|
|
63
|
+
|
|
64
|
+
- **88 million+ results** from **32 states** and **63,000+ water systems**
|
|
65
|
+
- **Current:** many states update daily, often within days of sampling
|
|
66
|
+
- **Deep:** some states go back to the 1970s
|
|
67
|
+
- **Every contaminant states test for:** lead and copper, PFAS, nitrate,
|
|
68
|
+
disinfection byproducts, arsenic, coliform, VOCs and more
|
|
69
|
+
|
|
70
|
+
Every answer is built from the sample results themselves, not from summaries or
|
|
71
|
+
annual reports, and says how current each state's data is. A gap is never
|
|
72
|
+
passed off as a clean record. See exactly which states, contaminants and years
|
|
73
|
+
are covered, live, at [murielai.io/overview](https://murielai.io/overview).
|
|
74
|
+
|
|
75
|
+
Muriel is built for people who work with drinking water: utilities and the
|
|
76
|
+
regulators who oversee them, and the labs, engineering firms and vendors who
|
|
77
|
+
serve them.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 1. Use Muriel in your AI assistant (MCP connector)
|
|
82
|
+
|
|
83
|
+
The quickest way to put Muriel to work. Add one URL to your assistant, sign in
|
|
84
|
+
to your Muriel account and click **Allow**. There is no API key to copy and
|
|
85
|
+
nothing to install.
|
|
86
|
+
|
|
87
|
+
**Connector URL:** `https://mcp.murielai.io/mcp`
|
|
88
|
+
|
|
89
|
+
| Assistant | How to add it |
|
|
90
|
+
|---|---|
|
|
91
|
+
| **Claude** (claude.ai and desktop) | Settings → Connectors → *Add custom connector* → name it Muriel, paste the URL → *Connect* |
|
|
92
|
+
| **Claude Code** | `claude mcp add --transport http --scope user muriel https://mcp.murielai.io/mcp`, then `/mcp` → muriel → *Authenticate* |
|
|
93
|
+
| **Cursor** | In `~/.cursor/mcp.json`: `{"mcpServers": {"muriel": {"url": "https://mcp.murielai.io/mcp"}}}` |
|
|
94
|
+
| **VS Code** (Copilot agent mode) | In `.vscode/mcp.json`: `{"servers": {"muriel": {"type": "http", "url": "https://mcp.murielai.io/mcp"}}}` |
|
|
95
|
+
| **ChatGPT** | Where custom connectors are enabled: Settings → Connectors → create one with the URL and OAuth |
|
|
96
|
+
| **Anything else** | Any client that supports remote MCP servers with OAuth takes the URL. For local-only (stdio) clients: `npx -y mcp-remote https://mcp.murielai.io/mcp` |
|
|
97
|
+
|
|
98
|
+
The first time your assistant uses Muriel it opens a Muriel page: sign in if
|
|
99
|
+
you need to, see what it is asking for, and click **Allow**. To disconnect,
|
|
100
|
+
use *Connected apps* on your [account page](https://murielai.io/account).
|
|
101
|
+
|
|
102
|
+
Then just ask, in the chat you already use:
|
|
103
|
+
|
|
104
|
+
> Anything new with Chicago's water this week?
|
|
105
|
+
>
|
|
106
|
+
> Which systems in Georgia had a nitrate result above the MCL in the last year?
|
|
107
|
+
>
|
|
108
|
+
> Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania?
|
|
109
|
+
|
|
110
|
+
Your assistant calls Muriel whenever a question needs drinking water data and
|
|
111
|
+
answers from what Muriel returns. The connector gives it 16 read-only tools,
|
|
112
|
+
the same ones Muriel's own agent uses:
|
|
113
|
+
|
|
114
|
+
| Tools | What they answer |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `find_water_system` | A utility or town name → its water system ID (PWSID) |
|
|
117
|
+
| `get_sample_results`, `summarize_sample_results`, `deep_history_search` | Individual results and summaries, current and archived |
|
|
118
|
+
| `get_notable_events`, `get_record_changes` | What changed in the latest updates |
|
|
119
|
+
| `analyte_trend` | Whether a contaminant is going up or down |
|
|
120
|
+
| `check_compliance_from_data`, `check_monitoring_gaps` | Running annual averages, 90th percentiles and missing samples |
|
|
121
|
+
| `rank_systems_by_size` | State-wide questions with no system named |
|
|
122
|
+
| `data_coverage`, `historical_depth` | What data exists for a state or system, and how far back |
|
|
123
|
+
| `lookup_standard`, `evaluate_result`, `resolve_analyte`, `search_regulations` | The federal standards and rules |
|
|
124
|
+
|
|
125
|
+
The connector can't see your Muriel conversations or documents, change your
|
|
126
|
+
account or buy anything. Full reference:
|
|
127
|
+
[murielai.io/developers](https://murielai.io/developers#connector).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 2. Ask the agent from code
|
|
132
|
+
|
|
133
|
+
Install the package and set your key. Create a key on your
|
|
134
|
+
[account page](https://murielai.io/account).
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install muriel
|
|
138
|
+
export MURIEL_API_KEY="muriel_live_..."
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Every function reads `MURIEL_API_KEY`; you can also pass `api_key=` directly.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import muriel
|
|
145
|
+
|
|
146
|
+
answer = muriel.query("Is Chicago's water meeting federal limits for lead and copper?")
|
|
147
|
+
|
|
148
|
+
print(answer.summary) # the answer, in plain English
|
|
149
|
+
for r in answer.results[:5]: # the records it rests on
|
|
150
|
+
print(r["sample_collection_date"], r["system_name"],
|
|
151
|
+
r["result_analyte_name"], r["result_value"], r["result_unit"])
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`muriel.query()` asks the same agent that answers at
|
|
155
|
+
[murielai.io](https://murielai.io). It works out which systems, contaminants and
|
|
156
|
+
dates the question is about, pulls the results, compares them with MCLs, action
|
|
157
|
+
levels and treatment rules, and tells you when a state's data is out of date.
|
|
158
|
+
Each answer carries:
|
|
159
|
+
|
|
160
|
+
| Field | What it holds |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `summary` | The answer in plain English |
|
|
163
|
+
| `results`, `count` | The records the answer rests on |
|
|
164
|
+
| `documents` | Any CSV or PDF the agent produced, with a download URL |
|
|
165
|
+
| `warning` | A caveat, when the data needs one |
|
|
166
|
+
| `execution_log` | The steps the agent took |
|
|
167
|
+
| `credits` | What the question used, and your balance after it |
|
|
168
|
+
|
|
169
|
+
### What you can ask
|
|
170
|
+
|
|
171
|
+
| | |
|
|
172
|
+
|---|---|
|
|
173
|
+
| **Compliance** | Which systems in Georgia had a nitrate result above the MCL in the last year? |
|
|
174
|
+
| **Trends** | Which systems are within 20% of an MCL and trending up? |
|
|
175
|
+
| **PFAS** | Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania? |
|
|
176
|
+
| **What changed** | What new results were published in California this week? |
|
|
177
|
+
| **Market research** | Which Illinois systems serving over 50,000 people have detected PFAS? |
|
|
178
|
+
|
|
179
|
+
### Scope it
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
answer = muriel.query("Any new detections?", pwsid="IL0316000", since="2026-01-01")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Get exactly the fields you need
|
|
186
|
+
|
|
187
|
+
Pass a list of field names, a JSON schema or a Pydantic model, and the answer
|
|
188
|
+
comes back in that shape, ready for a database, a dashboard or another program:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
latest = muriel.query(
|
|
192
|
+
"What was the most recent lead result for Chicago?",
|
|
193
|
+
pwsid="IL0316000",
|
|
194
|
+
schema=["sample_date", "result", "units", "above_action_level"],
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from typing import List
|
|
200
|
+
from pydantic import BaseModel
|
|
201
|
+
from muriel import Agent
|
|
202
|
+
|
|
203
|
+
class SystemSummary(BaseModel):
|
|
204
|
+
pwsid: str
|
|
205
|
+
system_name: str
|
|
206
|
+
total_samples: int
|
|
207
|
+
contaminants_found: List[str]
|
|
208
|
+
|
|
209
|
+
summary = Agent().query("Summarize water quality for IL0316000.", schema=SystemSummary)
|
|
210
|
+
print(summary.system_name, summary.total_samples)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### From question to CSV
|
|
214
|
+
|
|
215
|
+
Ask for an export and the file comes back with the answer:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
answer = muriel.query("Which systems in Oregon have a lead 90th percentile above 10 ppb? "
|
|
219
|
+
"Export it to a CSV, largest systems first.")
|
|
220
|
+
for doc in answer.documents:
|
|
221
|
+
print(doc["filename"], doc["url"])
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 3. Query the REST API directly
|
|
227
|
+
|
|
228
|
+
When you know exactly what you want, such as one system's full history or one
|
|
229
|
+
contaminant across the country, query the data directly. Same key as above.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from muriel import get_results, get_lead, get_systems
|
|
233
|
+
|
|
234
|
+
# One water system, history included
|
|
235
|
+
rows = get_results(pwsid="IL0316000", analyte="Lead", limit=100)
|
|
236
|
+
|
|
237
|
+
# A whole state, or one group of contaminants
|
|
238
|
+
rows = get_results(state="IL", analyte_group="pfas", limit=500)
|
|
239
|
+
|
|
240
|
+
# The water system that serves an address, then systems nearby
|
|
241
|
+
rows = get_results(address="121 N LaSalle St, Chicago, IL", radius_miles=10)
|
|
242
|
+
|
|
243
|
+
# The latest lead results nationwide
|
|
244
|
+
rows = get_lead(limit=50, detections_only=True)
|
|
245
|
+
|
|
246
|
+
# Every water system in a state, with size and latest sample
|
|
247
|
+
systems = get_systems(state="IL")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The same calls work from any language over HTTP:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
curl "https://api.murielai.io/api/v1/records/system/IL0316000?result_analyte_name=Lead&limit=12" \
|
|
254
|
+
-H "X-API-Key: $MURIEL_API_KEY"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Every record has the same fields whichever state it came from: the system, the
|
|
258
|
+
contaminant, the value as published and converted to mg/L, the sample date, the
|
|
259
|
+
place where known, and `era`: `"recent"` for the window states currently
|
|
260
|
+
publish, `"historical"` for the archive. Pass `era="all"` to include history in
|
|
261
|
+
the national feeds, or `era="recent"` to limit a system or state query to
|
|
262
|
+
current results.
|
|
263
|
+
|
|
264
|
+
| Function | What it returns |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `get_results()` | Results by system, state, analyte group or address, with filters for dates, detections, concentration and analyte |
|
|
267
|
+
| `get_latest()`, `get_lead()`, `get_pfas()`, `get_coliform()`, `get_nitrate()`, `get_dbps()`, `get_arsenic()`, `get_manganese()`, `get_vocs()` | The latest results for one contaminant, nationwide |
|
|
268
|
+
| `get_systems()` | Water systems in a state |
|
|
269
|
+
| `get_state_sampling_points()`, `get_sampling_point_geocoding()` | Sampling points with a location |
|
|
270
|
+
| `get_state_facilities()`, `get_facility_geocoding()` | Facilities with a location, where states publish them |
|
|
271
|
+
| `SamplesClient`, `SystemsClient` | The same calls as objects |
|
|
272
|
+
|
|
273
|
+
Up to 1,000 records per call; page with `offset`. Monitoring requirements and
|
|
274
|
+
violation records (`get_requirements`, `get_violations`) are not available yet;
|
|
275
|
+
ask the agent about monitoring gaps and exceedances instead.
|
|
276
|
+
|
|
277
|
+
Interactive reference for every route and field:
|
|
278
|
+
[api.murielai.io/docs](https://api.murielai.io/docs).
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Pricing
|
|
283
|
+
|
|
284
|
+
One balance of credits on your Muriel account covers all three:
|
|
285
|
+
|
|
286
|
+
- **An agent question**, in the app or from code, is metered on the work it
|
|
287
|
+
takes: typically 1–3 credits, never more than 8.
|
|
288
|
+
- **Data calls**, through the connector's tools or the REST API, cost 1 credit
|
|
289
|
+
per 1,000 records returned, added up across calls. A call that returns
|
|
290
|
+
nothing is free.
|
|
291
|
+
|
|
292
|
+
Every account gets 8 free credits a month, usable on any of them. For more,
|
|
293
|
+
email [info@murielai.io](mailto:info@murielai.io).
|
|
294
|
+
|
|
295
|
+
## Errors
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from muriel import MurielAPIError, MurielCreditsError, get_results
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
rows = get_results(pwsid="IL0316000")
|
|
302
|
+
except MurielCreditsError:
|
|
303
|
+
print("Out of credits.")
|
|
304
|
+
except MurielAPIError as e:
|
|
305
|
+
print(e.status_code, e.message)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Exception | When |
|
|
309
|
+
|---|---|
|
|
310
|
+
| `MurielAuthenticationError` | Missing or invalid API key (401) |
|
|
311
|
+
| `MurielValidationError` | A bad parameter (400) |
|
|
312
|
+
| `MurielCreditsError` | Out of credits (402); nothing was charged |
|
|
313
|
+
| `MurielNotFoundError` | Unknown system or resource (404) |
|
|
314
|
+
| `MurielRateLimitError` | Too many requests (429); wait and retry |
|
|
315
|
+
| `MurielServerError` | Not available (501), or a server error worth retrying (5xx) |
|
|
316
|
+
|
|
317
|
+
All of them inherit from `MurielAPIError`. Keys are only ever sent over HTTPS,
|
|
318
|
+
and never forwarded on a redirect.
|
|
319
|
+
|
|
320
|
+
## Good to know
|
|
321
|
+
|
|
322
|
+
Muriel is a research and analysis tool, not a compliance determination.
|
|
323
|
+
Results are as states published them; coverage varies by state, and the
|
|
324
|
+
absence of a result is never evidence of compliance. Only your state primacy
|
|
325
|
+
agency can say whether a system is in compliance.
|
|
326
|
+
|
|
327
|
+
## Documentation
|
|
328
|
+
|
|
329
|
+
- Guides and SDK reference: [docs.murielai.io](https://docs.murielai.io)
|
|
330
|
+
- MCP connector reference: [murielai.io/developers](https://murielai.io/developers#connector)
|
|
331
|
+
- REST API reference: [api.murielai.io/docs](https://api.murielai.io/docs)
|
|
332
|
+
|
|
333
|
+
## Support
|
|
334
|
+
|
|
335
|
+
- Email: [info@murielai.io](mailto:info@murielai.io)
|
|
336
|
+
- Issues: [github.com/Muriel-AI-Inc/muriel-sdk-python/issues](https://github.com/Muriel-AI-Inc/muriel-sdk-python/issues)
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
MIT. Copyright (c) 2024-2026 Muriel AI, Inc. Use of Muriel is governed by the
|
|
341
|
+
terms at [murielai.io/terms](https://murielai.io/terms).
|
muriel-0.2.0/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Muriel Python SDK
|
|
2
|
+
|
|
3
|
+
[Muriel](https://murielai.io) is the AI agent for U.S. drinking water data. It
|
|
4
|
+
answers plain-English questions about any public water system from the
|
|
5
|
+
monitoring results states publish, checks them against federal limits, and
|
|
6
|
+
shows the records behind every answer.
|
|
7
|
+
|
|
8
|
+
There are three ways to use it, and this README covers them in the order most
|
|
9
|
+
people need them:
|
|
10
|
+
|
|
11
|
+
1. **[In your AI assistant](#1-use-muriel-in-your-ai-assistant-mcp-connector)**:
|
|
12
|
+
add the Muriel connector to Claude, ChatGPT, Cursor, VS Code or any MCP
|
|
13
|
+
client and ask in the chat you already use. No code, no key.
|
|
14
|
+
2. **[The agent, from code](#2-ask-the-agent-from-code)**: `muriel.query("...")`
|
|
15
|
+
sends a question to the same agent and returns the answer, the records it
|
|
16
|
+
used, and any CSV it produced, shaped the way your program needs.
|
|
17
|
+
3. **[The REST API](#3-query-the-rest-api-directly)**: exact, parameter-driven
|
|
18
|
+
queries for one system's history, one contaminant nationwide, every system
|
|
19
|
+
in a state, or the system serving an address. Use this package's functions or
|
|
20
|
+
call it over HTTP from any language.
|
|
21
|
+
|
|
22
|
+
All three draw on the same data and the same Muriel account and credits.
|
|
23
|
+
|
|
24
|
+
## What Muriel covers
|
|
25
|
+
|
|
26
|
+
- **88 million+ results** from **32 states** and **63,000+ water systems**
|
|
27
|
+
- **Current:** many states update daily, often within days of sampling
|
|
28
|
+
- **Deep:** some states go back to the 1970s
|
|
29
|
+
- **Every contaminant states test for:** lead and copper, PFAS, nitrate,
|
|
30
|
+
disinfection byproducts, arsenic, coliform, VOCs and more
|
|
31
|
+
|
|
32
|
+
Every answer is built from the sample results themselves, not from summaries or
|
|
33
|
+
annual reports, and says how current each state's data is. A gap is never
|
|
34
|
+
passed off as a clean record. See exactly which states, contaminants and years
|
|
35
|
+
are covered, live, at [murielai.io/overview](https://murielai.io/overview).
|
|
36
|
+
|
|
37
|
+
Muriel is built for people who work with drinking water: utilities and the
|
|
38
|
+
regulators who oversee them, and the labs, engineering firms and vendors who
|
|
39
|
+
serve them.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Use Muriel in your AI assistant (MCP connector)
|
|
44
|
+
|
|
45
|
+
The quickest way to put Muriel to work. Add one URL to your assistant, sign in
|
|
46
|
+
to your Muriel account and click **Allow**. There is no API key to copy and
|
|
47
|
+
nothing to install.
|
|
48
|
+
|
|
49
|
+
**Connector URL:** `https://mcp.murielai.io/mcp`
|
|
50
|
+
|
|
51
|
+
| Assistant | How to add it |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **Claude** (claude.ai and desktop) | Settings → Connectors → *Add custom connector* → name it Muriel, paste the URL → *Connect* |
|
|
54
|
+
| **Claude Code** | `claude mcp add --transport http --scope user muriel https://mcp.murielai.io/mcp`, then `/mcp` → muriel → *Authenticate* |
|
|
55
|
+
| **Cursor** | In `~/.cursor/mcp.json`: `{"mcpServers": {"muriel": {"url": "https://mcp.murielai.io/mcp"}}}` |
|
|
56
|
+
| **VS Code** (Copilot agent mode) | In `.vscode/mcp.json`: `{"servers": {"muriel": {"type": "http", "url": "https://mcp.murielai.io/mcp"}}}` |
|
|
57
|
+
| **ChatGPT** | Where custom connectors are enabled: Settings → Connectors → create one with the URL and OAuth |
|
|
58
|
+
| **Anything else** | Any client that supports remote MCP servers with OAuth takes the URL. For local-only (stdio) clients: `npx -y mcp-remote https://mcp.murielai.io/mcp` |
|
|
59
|
+
|
|
60
|
+
The first time your assistant uses Muriel it opens a Muriel page: sign in if
|
|
61
|
+
you need to, see what it is asking for, and click **Allow**. To disconnect,
|
|
62
|
+
use *Connected apps* on your [account page](https://murielai.io/account).
|
|
63
|
+
|
|
64
|
+
Then just ask, in the chat you already use:
|
|
65
|
+
|
|
66
|
+
> Anything new with Chicago's water this week?
|
|
67
|
+
>
|
|
68
|
+
> Which systems in Georgia had a nitrate result above the MCL in the last year?
|
|
69
|
+
>
|
|
70
|
+
> Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania?
|
|
71
|
+
|
|
72
|
+
Your assistant calls Muriel whenever a question needs drinking water data and
|
|
73
|
+
answers from what Muriel returns. The connector gives it 16 read-only tools,
|
|
74
|
+
the same ones Muriel's own agent uses:
|
|
75
|
+
|
|
76
|
+
| Tools | What they answer |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `find_water_system` | A utility or town name → its water system ID (PWSID) |
|
|
79
|
+
| `get_sample_results`, `summarize_sample_results`, `deep_history_search` | Individual results and summaries, current and archived |
|
|
80
|
+
| `get_notable_events`, `get_record_changes` | What changed in the latest updates |
|
|
81
|
+
| `analyte_trend` | Whether a contaminant is going up or down |
|
|
82
|
+
| `check_compliance_from_data`, `check_monitoring_gaps` | Running annual averages, 90th percentiles and missing samples |
|
|
83
|
+
| `rank_systems_by_size` | State-wide questions with no system named |
|
|
84
|
+
| `data_coverage`, `historical_depth` | What data exists for a state or system, and how far back |
|
|
85
|
+
| `lookup_standard`, `evaluate_result`, `resolve_analyte`, `search_regulations` | The federal standards and rules |
|
|
86
|
+
|
|
87
|
+
The connector can't see your Muriel conversations or documents, change your
|
|
88
|
+
account or buy anything. Full reference:
|
|
89
|
+
[murielai.io/developers](https://murielai.io/developers#connector).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 2. Ask the agent from code
|
|
94
|
+
|
|
95
|
+
Install the package and set your key. Create a key on your
|
|
96
|
+
[account page](https://murielai.io/account).
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install muriel
|
|
100
|
+
export MURIEL_API_KEY="muriel_live_..."
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Every function reads `MURIEL_API_KEY`; you can also pass `api_key=` directly.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import muriel
|
|
107
|
+
|
|
108
|
+
answer = muriel.query("Is Chicago's water meeting federal limits for lead and copper?")
|
|
109
|
+
|
|
110
|
+
print(answer.summary) # the answer, in plain English
|
|
111
|
+
for r in answer.results[:5]: # the records it rests on
|
|
112
|
+
print(r["sample_collection_date"], r["system_name"],
|
|
113
|
+
r["result_analyte_name"], r["result_value"], r["result_unit"])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`muriel.query()` asks the same agent that answers at
|
|
117
|
+
[murielai.io](https://murielai.io). It works out which systems, contaminants and
|
|
118
|
+
dates the question is about, pulls the results, compares them with MCLs, action
|
|
119
|
+
levels and treatment rules, and tells you when a state's data is out of date.
|
|
120
|
+
Each answer carries:
|
|
121
|
+
|
|
122
|
+
| Field | What it holds |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `summary` | The answer in plain English |
|
|
125
|
+
| `results`, `count` | The records the answer rests on |
|
|
126
|
+
| `documents` | Any CSV or PDF the agent produced, with a download URL |
|
|
127
|
+
| `warning` | A caveat, when the data needs one |
|
|
128
|
+
| `execution_log` | The steps the agent took |
|
|
129
|
+
| `credits` | What the question used, and your balance after it |
|
|
130
|
+
|
|
131
|
+
### What you can ask
|
|
132
|
+
|
|
133
|
+
| | |
|
|
134
|
+
|---|---|
|
|
135
|
+
| **Compliance** | Which systems in Georgia had a nitrate result above the MCL in the last year? |
|
|
136
|
+
| **Trends** | Which systems are within 20% of an MCL and trending up? |
|
|
137
|
+
| **PFAS** | Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania? |
|
|
138
|
+
| **What changed** | What new results were published in California this week? |
|
|
139
|
+
| **Market research** | Which Illinois systems serving over 50,000 people have detected PFAS? |
|
|
140
|
+
|
|
141
|
+
### Scope it
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
answer = muriel.query("Any new detections?", pwsid="IL0316000", since="2026-01-01")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Get exactly the fields you need
|
|
148
|
+
|
|
149
|
+
Pass a list of field names, a JSON schema or a Pydantic model, and the answer
|
|
150
|
+
comes back in that shape, ready for a database, a dashboard or another program:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
latest = muriel.query(
|
|
154
|
+
"What was the most recent lead result for Chicago?",
|
|
155
|
+
pwsid="IL0316000",
|
|
156
|
+
schema=["sample_date", "result", "units", "above_action_level"],
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from typing import List
|
|
162
|
+
from pydantic import BaseModel
|
|
163
|
+
from muriel import Agent
|
|
164
|
+
|
|
165
|
+
class SystemSummary(BaseModel):
|
|
166
|
+
pwsid: str
|
|
167
|
+
system_name: str
|
|
168
|
+
total_samples: int
|
|
169
|
+
contaminants_found: List[str]
|
|
170
|
+
|
|
171
|
+
summary = Agent().query("Summarize water quality for IL0316000.", schema=SystemSummary)
|
|
172
|
+
print(summary.system_name, summary.total_samples)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### From question to CSV
|
|
176
|
+
|
|
177
|
+
Ask for an export and the file comes back with the answer:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
answer = muriel.query("Which systems in Oregon have a lead 90th percentile above 10 ppb? "
|
|
181
|
+
"Export it to a CSV, largest systems first.")
|
|
182
|
+
for doc in answer.documents:
|
|
183
|
+
print(doc["filename"], doc["url"])
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 3. Query the REST API directly
|
|
189
|
+
|
|
190
|
+
When you know exactly what you want, such as one system's full history or one
|
|
191
|
+
contaminant across the country, query the data directly. Same key as above.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from muriel import get_results, get_lead, get_systems
|
|
195
|
+
|
|
196
|
+
# One water system, history included
|
|
197
|
+
rows = get_results(pwsid="IL0316000", analyte="Lead", limit=100)
|
|
198
|
+
|
|
199
|
+
# A whole state, or one group of contaminants
|
|
200
|
+
rows = get_results(state="IL", analyte_group="pfas", limit=500)
|
|
201
|
+
|
|
202
|
+
# The water system that serves an address, then systems nearby
|
|
203
|
+
rows = get_results(address="121 N LaSalle St, Chicago, IL", radius_miles=10)
|
|
204
|
+
|
|
205
|
+
# The latest lead results nationwide
|
|
206
|
+
rows = get_lead(limit=50, detections_only=True)
|
|
207
|
+
|
|
208
|
+
# Every water system in a state, with size and latest sample
|
|
209
|
+
systems = get_systems(state="IL")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The same calls work from any language over HTTP:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
curl "https://api.murielai.io/api/v1/records/system/IL0316000?result_analyte_name=Lead&limit=12" \
|
|
216
|
+
-H "X-API-Key: $MURIEL_API_KEY"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Every record has the same fields whichever state it came from: the system, the
|
|
220
|
+
contaminant, the value as published and converted to mg/L, the sample date, the
|
|
221
|
+
place where known, and `era`: `"recent"` for the window states currently
|
|
222
|
+
publish, `"historical"` for the archive. Pass `era="all"` to include history in
|
|
223
|
+
the national feeds, or `era="recent"` to limit a system or state query to
|
|
224
|
+
current results.
|
|
225
|
+
|
|
226
|
+
| Function | What it returns |
|
|
227
|
+
|---|---|
|
|
228
|
+
| `get_results()` | Results by system, state, analyte group or address, with filters for dates, detections, concentration and analyte |
|
|
229
|
+
| `get_latest()`, `get_lead()`, `get_pfas()`, `get_coliform()`, `get_nitrate()`, `get_dbps()`, `get_arsenic()`, `get_manganese()`, `get_vocs()` | The latest results for one contaminant, nationwide |
|
|
230
|
+
| `get_systems()` | Water systems in a state |
|
|
231
|
+
| `get_state_sampling_points()`, `get_sampling_point_geocoding()` | Sampling points with a location |
|
|
232
|
+
| `get_state_facilities()`, `get_facility_geocoding()` | Facilities with a location, where states publish them |
|
|
233
|
+
| `SamplesClient`, `SystemsClient` | The same calls as objects |
|
|
234
|
+
|
|
235
|
+
Up to 1,000 records per call; page with `offset`. Monitoring requirements and
|
|
236
|
+
violation records (`get_requirements`, `get_violations`) are not available yet;
|
|
237
|
+
ask the agent about monitoring gaps and exceedances instead.
|
|
238
|
+
|
|
239
|
+
Interactive reference for every route and field:
|
|
240
|
+
[api.murielai.io/docs](https://api.murielai.io/docs).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Pricing
|
|
245
|
+
|
|
246
|
+
One balance of credits on your Muriel account covers all three:
|
|
247
|
+
|
|
248
|
+
- **An agent question**, in the app or from code, is metered on the work it
|
|
249
|
+
takes: typically 1–3 credits, never more than 8.
|
|
250
|
+
- **Data calls**, through the connector's tools or the REST API, cost 1 credit
|
|
251
|
+
per 1,000 records returned, added up across calls. A call that returns
|
|
252
|
+
nothing is free.
|
|
253
|
+
|
|
254
|
+
Every account gets 8 free credits a month, usable on any of them. For more,
|
|
255
|
+
email [info@murielai.io](mailto:info@murielai.io).
|
|
256
|
+
|
|
257
|
+
## Errors
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from muriel import MurielAPIError, MurielCreditsError, get_results
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
rows = get_results(pwsid="IL0316000")
|
|
264
|
+
except MurielCreditsError:
|
|
265
|
+
print("Out of credits.")
|
|
266
|
+
except MurielAPIError as e:
|
|
267
|
+
print(e.status_code, e.message)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
| Exception | When |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `MurielAuthenticationError` | Missing or invalid API key (401) |
|
|
273
|
+
| `MurielValidationError` | A bad parameter (400) |
|
|
274
|
+
| `MurielCreditsError` | Out of credits (402); nothing was charged |
|
|
275
|
+
| `MurielNotFoundError` | Unknown system or resource (404) |
|
|
276
|
+
| `MurielRateLimitError` | Too many requests (429); wait and retry |
|
|
277
|
+
| `MurielServerError` | Not available (501), or a server error worth retrying (5xx) |
|
|
278
|
+
|
|
279
|
+
All of them inherit from `MurielAPIError`. Keys are only ever sent over HTTPS,
|
|
280
|
+
and never forwarded on a redirect.
|
|
281
|
+
|
|
282
|
+
## Good to know
|
|
283
|
+
|
|
284
|
+
Muriel is a research and analysis tool, not a compliance determination.
|
|
285
|
+
Results are as states published them; coverage varies by state, and the
|
|
286
|
+
absence of a result is never evidence of compliance. Only your state primacy
|
|
287
|
+
agency can say whether a system is in compliance.
|
|
288
|
+
|
|
289
|
+
## Documentation
|
|
290
|
+
|
|
291
|
+
- Guides and SDK reference: [docs.murielai.io](https://docs.murielai.io)
|
|
292
|
+
- MCP connector reference: [murielai.io/developers](https://murielai.io/developers#connector)
|
|
293
|
+
- REST API reference: [api.murielai.io/docs](https://api.murielai.io/docs)
|
|
294
|
+
|
|
295
|
+
## Support
|
|
296
|
+
|
|
297
|
+
- Email: [info@murielai.io](mailto:info@murielai.io)
|
|
298
|
+
- Issues: [github.com/Muriel-AI-Inc/muriel-sdk-python/issues](https://github.com/Muriel-AI-Inc/muriel-sdk-python/issues)
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT. Copyright (c) 2024-2026 Muriel AI, Inc. Use of Muriel is governed by the
|
|
303
|
+
terms at [murielai.io/terms](https://murielai.io/terms).
|