agami-core 0.3.2__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.
- agami_core-0.3.2/PKG-INFO +185 -0
- agami_core-0.3.2/README.md +164 -0
- agami_core-0.3.2/pyproject.toml +51 -0
- agami_core-0.3.2/setup.cfg +4 -0
- agami_core-0.3.2/src/admin.py +1343 -0
- agami_core-0.3.2/src/agami_core.egg-info/PKG-INFO +185 -0
- agami_core-0.3.2/src/agami_core.egg-info/SOURCES.txt +42 -0
- agami_core-0.3.2/src/agami_core.egg-info/dependency_links.txt +1 -0
- agami_core-0.3.2/src/agami_core.egg-info/requires.txt +14 -0
- agami_core-0.3.2/src/agami_core.egg-info/top_level.txt +20 -0
- agami_core-0.3.2/src/agami_paths.py +180 -0
- agami_core-0.3.2/src/contracts.py +239 -0
- agami_core-0.3.2/src/deploy_preflight.py +116 -0
- agami_core-0.3.2/src/execute_sql.py +785 -0
- agami_core-0.3.2/src/mcp_harness.py +150 -0
- agami_core-0.3.2/src/mcp_http.py +381 -0
- agami_core-0.3.2/src/model_deploy.py +136 -0
- agami_core-0.3.2/src/model_store.py +449 -0
- agami_core-0.3.2/src/oauth_server.py +660 -0
- agami_core-0.3.2/src/oidc.py +190 -0
- agami_core-0.3.2/src/onboarding.py +174 -0
- agami_core-0.3.2/src/oss_adapters.py +84 -0
- agami_core-0.3.2/src/passwords.py +42 -0
- agami_core-0.3.2/src/ports.py +106 -0
- agami_core-0.3.2/src/semantic_model/__init__.py +35 -0
- agami_core-0.3.2/src/semantic_model/build.py +655 -0
- agami_core-0.3.2/src/semantic_model/cli.py +1242 -0
- agami_core-0.3.2/src/semantic_model/curate.py +1006 -0
- agami_core-0.3.2/src/semantic_model/derived.py +201 -0
- agami_core-0.3.2/src/semantic_model/dialects.py +617 -0
- agami_core-0.3.2/src/semantic_model/introspect.py +1113 -0
- agami_core-0.3.2/src/semantic_model/loader.py +543 -0
- agami_core-0.3.2/src/semantic_model/metadata_sources.py +232 -0
- agami_core-0.3.2/src/semantic_model/models.py +708 -0
- agami_core-0.3.2/src/semantic_model/org_draft.py +210 -0
- agami_core-0.3.2/src/semantic_model/requirements.txt +11 -0
- agami_core-0.3.2/src/semantic_model/runtime.py +1311 -0
- agami_core-0.3.2/src/semantic_model/snapshot.py +121 -0
- agami_core-0.3.2/src/semantic_model/units.py +192 -0
- agami_core-0.3.2/src/semantic_model/validator.py +768 -0
- agami_core-0.3.2/src/store.py +166 -0
- agami_core-0.3.2/src/tools.py +1226 -0
- agami_core-0.3.2/src/ui.py +503 -0
- agami_core-0.3.2/src/user_store.py +233 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agami-core
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: agami-core — governed semantic model, the shared MCP TOOLS harness, and the unified local query executor.
|
|
5
|
+
Author-email: Agami AI <skills@agami.ai>
|
|
6
|
+
License-Expression: LicenseRef-Agami-FUL
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Provides-Extra: model
|
|
10
|
+
Requires-Dist: pydantic<3,>=2; extra == "model"
|
|
11
|
+
Requires-Dist: PyYAML>=6; extra == "model"
|
|
12
|
+
Requires-Dist: sqlglot>=20; extra == "model"
|
|
13
|
+
Provides-Extra: server
|
|
14
|
+
Requires-Dist: mcp>=1.2; extra == "server"
|
|
15
|
+
Requires-Dist: uvicorn>=0.30; extra == "server"
|
|
16
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "server"
|
|
17
|
+
Requires-Dist: argon2-cffi>=23; extra == "server"
|
|
18
|
+
Requires-Dist: PyJWT>=2.8; extra == "server"
|
|
19
|
+
Requires-Dist: httpx>=0.27; extra == "server"
|
|
20
|
+
Requires-Dist: cryptography>=43; extra == "server"
|
|
21
|
+
|
|
22
|
+
# agami-core (library)
|
|
23
|
+
|
|
24
|
+
The importable core behind agami: the governed **semantic model**, the shared **MCP `TOOLS`
|
|
25
|
+
harness** (stdio entrypoint), and the **unified local query executor** (`execute_sql` + the
|
|
26
|
+
read-only safety pass + unit formatting).
|
|
27
|
+
|
|
28
|
+
One package serves every consumer — the local Claude Code skill, the MCP server, and any
|
|
29
|
+
downstream that imports the same flat module names.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e packages/agami-core # executor + stdio harness (pure-stdlib)
|
|
35
|
+
pip install -e 'packages/agami-core[model]' # + the semantic model (pydantic / sqlglot / pyyaml)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Flat module names (an invariant)
|
|
39
|
+
|
|
40
|
+
`semantic_model`, `mcp_harness`, `execute_sql`, `agami_paths` are top-level importable names —
|
|
41
|
+
no `sys.path` manipulation, no parent package — so a consumer's imports resolve unchanged:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from mcp_harness import TOOLS
|
|
45
|
+
import semantic_model
|
|
46
|
+
import execute_sql
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Entry points
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m mcp_harness # the stdio MCP server (Claude Desktop)
|
|
53
|
+
python -m execute_sql --sql … # the local query executor
|
|
54
|
+
python -m semantic_model.cli # the semantic-model CLI (driven by the `sm` launcher)
|
|
55
|
+
python -m mcp_http # the networked HTTP MCP server (see below)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## HTTP server — networked, with auth (`python -m mcp_http`)
|
|
59
|
+
|
|
60
|
+
The `[server]` extra (`pip install -e 'packages/agami-core[server]'`) adds a networked MCP
|
|
61
|
+
transport: the same `TOOLS` surface as the stdio server, but over HTTP with OAuth + a small admin
|
|
62
|
+
console. It's the self-host shape of the hosted product.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
PUBLIC_BASE_URL=https://your-host \
|
|
66
|
+
AGAMI_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
67
|
+
AGAMI_DB_URL=postgresql://… \
|
|
68
|
+
AGAMI_ADMIN_USERNAME=you@example.com \
|
|
69
|
+
AGAMI_ADMIN_FIRST_NAME=Alex AGAMI_ADMIN_LAST_NAME=Kim \
|
|
70
|
+
AGAMI_ADMIN_PASSWORD=… \
|
|
71
|
+
AGAMI_ADMIN_PROVIDER=google \
|
|
72
|
+
AGAMI_OIDC_GOOGLE_CLIENT_ID=… AGAMI_OIDC_GOOGLE_CLIENT_SECRET=… \
|
|
73
|
+
python -m mcp_http
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The admin is identified by **email** (`AGAMI_ADMIN_USERNAME`). Their sign-in method is whatever you
|
|
77
|
+
configure — **a password and/or a pinned social provider, at least one**: set `AGAMI_ADMIN_PASSWORD`,
|
|
78
|
+
and/or `AGAMI_ADMIN_PROVIDER` (`google` | `microsoft`, which must also have its
|
|
79
|
+
`AGAMI_OIDC_<PROVIDER>_CLIENT_ID/SECRET` set). The admin login then offers the same Google/Microsoft
|
|
80
|
+
option as the MCP login. Register **one** OAuth redirect URI with the provider —
|
|
81
|
+
`{base}/oauth/oidc/callback` — it serves both the connector and the admin flows.
|
|
82
|
+
|
|
83
|
+
### One host, two entry points
|
|
84
|
+
|
|
85
|
+
A deployment is one host (`PUBLIC_BASE_URL`). Everything lives under it:
|
|
86
|
+
|
|
87
|
+
| URL | Who | What |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `{base}/mcp` | a teammate, in Claude | the **only** URL to add as a custom connector — Claude auto-discovers the OAuth endpoints from it |
|
|
90
|
+
| `{base}/admin` | the admin, in a browser | the console to add/enable/disable users |
|
|
91
|
+
|
|
92
|
+
### Access model — two separate credentials
|
|
93
|
+
|
|
94
|
+
- **Query surface (`/mcp`)** — gated by a **Bearer JWT** from the OAuth flow. **Any** onboarded user
|
|
95
|
+
who signs in can query (that's the product). No token → `401` + `WWW-Authenticate`, which starts
|
|
96
|
+
Claude's OAuth. Admin-ness does **not** gate `/mcp`.
|
|
97
|
+
- **Admin surface (`/admin`)** — gated by a **session cookie** *and* the admin-gate
|
|
98
|
+
(`AGAMI_ADMIN_USERNAME`). The admin signs in with their **pinned** social provider or a password; a
|
|
99
|
+
valid non-admin is refused (and a social identity for the admin email via a *different* provider is
|
|
100
|
+
refused — the pin closes IdP-confusion). An `/mcp` bearer token is useless here (different
|
|
101
|
+
credential). Unset `AGAMI_ADMIN_USERNAME` ⇒ the admin console is disabled entirely.
|
|
102
|
+
|
|
103
|
+
### Onboarding a teammate
|
|
104
|
+
|
|
105
|
+
The admin adds a teammate by **email + name** (a *pending* user). How they finish setting up follows
|
|
106
|
+
the deployment's **single auth method** — uniform for everyone, set by what you configured:
|
|
107
|
+
|
|
108
|
+
- **OIDC deployment** (a Google/Microsoft client is configured): the teammate just adds `{base}/mcp` to
|
|
109
|
+
Claude and signs in with that provider — the IdP verifies their email and binds the account on first
|
|
110
|
+
login. No link to share.
|
|
111
|
+
- **Password deployment** (no OIDC configured): the admin **copies the teammate's setup link** from the
|
|
112
|
+
Users tab and shares it out-of-band; the teammate opens it and sets their own password. The link is a
|
|
113
|
+
signed, time-boxed token and is single-use (it stops working once the account is set up).
|
|
114
|
+
|
|
115
|
+
The login surfaces show only the configured method (the admin keeps a password **break-glass** fallback
|
|
116
|
+
on `/admin/login`).
|
|
117
|
+
|
|
118
|
+
> **Trust note.** OIDC onboarding binds the account to whoever first proves the teammate's email at the
|
|
119
|
+
> configured IdP — so add a teammate by an email the *right* person controls there. The setup link and
|
|
120
|
+
> the other pre-auth endpoints aren't rate-limited in-process; put them behind your proxy/LB if exposed.
|
|
121
|
+
|
|
122
|
+
### Activity view
|
|
123
|
+
|
|
124
|
+
The admin console has one read-only **Activity** tab: every MCP tool call, folded into the conversation
|
|
125
|
+
it belongs to — **thread (conversation) ▸ turn (one user question) ▸ call**. Open a conversation and you
|
|
126
|
+
see its whole arc, *not just the queries*: the `list_datasources` / `get_datasource_schema` calls that
|
|
127
|
+
scoped the work sit alongside the `execute_sql`s that answered it (*"User asked what datasources →
|
|
128
|
+
`list_datasources`; user asked revenue by region → `get_datasource_schema`, then `execute_sql` ×2"*). A
|
|
129
|
+
query call shows its SQL, row count, latency, and status; a non-query call shows its tool name. Every
|
|
130
|
+
call carries its **own** datasource, because a conversation — or even a single turn — can span several:
|
|
131
|
+
the user switches datasource mid-session, or asks something that runs one query per datasource. The
|
|
132
|
+
conversation row lists the full set it touched.
|
|
133
|
+
|
|
134
|
+
The split is deliberate: it is **audit-grade for *what* ran** — the server observes every call directly,
|
|
135
|
+
so nothing is dropped — and **best-effort for *how* it's grouped**. The MCP protocol carries neither the
|
|
136
|
+
user's question, a conversation id, nor a turn boundary, so Claude self-reports them on every call: a
|
|
137
|
+
`user_question` (kept verbatim), a `thread_id` (per conversation), and a `correlation_id` (per turn). The
|
|
138
|
+
turn's question is taken from the **first** call in the turn (the model sometimes drifts it on later
|
|
139
|
+
refinements). When Claude doesn't supply the ids, a call simply shows as its own singleton conversation —
|
|
140
|
+
the view degrades, never drops a call. Treat the self-reported grouping as a hint, not a record.
|
|
141
|
+
|
|
142
|
+
> **Free-tier limit.** A turn that produces **no** tool call — Claude answering "let's continue" from
|
|
143
|
+
> context — never reaches the server and so can't appear here; this is an audit of what ran against your
|
|
144
|
+
> data, not a chat transcript.
|
|
145
|
+
|
|
146
|
+
The `tool_calls` log grows one row per call and has **no automatic retention** — it's your local store,
|
|
147
|
+
so prune it on your own schedule if it gets large.
|
|
148
|
+
|
|
149
|
+
### Model view
|
|
150
|
+
|
|
151
|
+
The **Model** tab (`{base}/admin/model`) is a **read-only** explorer of the semantic model you've
|
|
152
|
+
deployed — the same tree the MCP tools serve, so it can't drift from what Claude actually reads. It's a
|
|
153
|
+
catalog: a browse rail (datasource → subject area → table) and one page at a time —
|
|
154
|
+
|
|
155
|
+
- a **datasource overview** (description, glossary, storage-connection names/types, the subject areas),
|
|
156
|
+
- a **subject-area landing** (its tables, metrics, entities),
|
|
157
|
+
- a **table page** — the schema, with each column's type and description, and flags only where they
|
|
158
|
+
carry signal (**PK / FK / sensitive / unit / enum / caveat**). Trust is shown as a single table-level
|
|
159
|
+
**confidence** badge; **caveats** (the domain gotchas) are elevated to a callout; wide tables collapse
|
|
160
|
+
behind "show all N", and tables that author `column_groups` render those as collapsible sections,
|
|
161
|
+
- a **Relationships** page (when the model has cross-area joins) — the org-level relationships that
|
|
162
|
+
span subject areas, **grouped by area-pair**, so the cross-area topology is readable in one place,
|
|
163
|
+
- a **domain-context** page (your `ORGANIZATION.md`, rendered as safe markdown).
|
|
164
|
+
|
|
165
|
+
It is **read-only by construction** — a single GET endpoint, no write path. Editing the model stays
|
|
166
|
+
conversational in Claude (the in-app editor is a Hosted feature); connection **credentials are never
|
|
167
|
+
rendered** (names and types only). Deploy a change in Claude and it shows up here on the next deploy.
|
|
168
|
+
|
|
169
|
+
### Local end-to-end test (HTTPS via a tunnel)
|
|
170
|
+
|
|
171
|
+
OAuth and the `Secure` admin cookie need HTTPS, so expose the local server through a tunnel:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# terminal 1 — the server
|
|
175
|
+
PUBLIC_BASE_URL=https://<your-subdomain>.trycloudflare.com \
|
|
176
|
+
AGAMI_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
177
|
+
AGAMI_DB_URL=sqlite:///$PWD/agami.db \
|
|
178
|
+
AGAMI_ADMIN_USERNAME=you@example.com AGAMI_ADMIN_PASSWORD=choose-a-strong-one \
|
|
179
|
+
python -m mcp_http
|
|
180
|
+
|
|
181
|
+
# terminal 2 — the HTTPS tunnel (prints the https URL to use as PUBLIC_BASE_URL)
|
|
182
|
+
cloudflared tunnel --url http://127.0.0.1:8000
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Open `{base}/admin`, sign in, add a user — then add `{base}/mcp` as a connector in Claude.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# agami-core (library)
|
|
2
|
+
|
|
3
|
+
The importable core behind agami: the governed **semantic model**, the shared **MCP `TOOLS`
|
|
4
|
+
harness** (stdio entrypoint), and the **unified local query executor** (`execute_sql` + the
|
|
5
|
+
read-only safety pass + unit formatting).
|
|
6
|
+
|
|
7
|
+
One package serves every consumer — the local Claude Code skill, the MCP server, and any
|
|
8
|
+
downstream that imports the same flat module names.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e packages/agami-core # executor + stdio harness (pure-stdlib)
|
|
14
|
+
pip install -e 'packages/agami-core[model]' # + the semantic model (pydantic / sqlglot / pyyaml)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Flat module names (an invariant)
|
|
18
|
+
|
|
19
|
+
`semantic_model`, `mcp_harness`, `execute_sql`, `agami_paths` are top-level importable names —
|
|
20
|
+
no `sys.path` manipulation, no parent package — so a consumer's imports resolve unchanged:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from mcp_harness import TOOLS
|
|
24
|
+
import semantic_model
|
|
25
|
+
import execute_sql
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Entry points
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m mcp_harness # the stdio MCP server (Claude Desktop)
|
|
32
|
+
python -m execute_sql --sql … # the local query executor
|
|
33
|
+
python -m semantic_model.cli # the semantic-model CLI (driven by the `sm` launcher)
|
|
34
|
+
python -m mcp_http # the networked HTTP MCP server (see below)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## HTTP server — networked, with auth (`python -m mcp_http`)
|
|
38
|
+
|
|
39
|
+
The `[server]` extra (`pip install -e 'packages/agami-core[server]'`) adds a networked MCP
|
|
40
|
+
transport: the same `TOOLS` surface as the stdio server, but over HTTP with OAuth + a small admin
|
|
41
|
+
console. It's the self-host shape of the hosted product.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
PUBLIC_BASE_URL=https://your-host \
|
|
45
|
+
AGAMI_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
46
|
+
AGAMI_DB_URL=postgresql://… \
|
|
47
|
+
AGAMI_ADMIN_USERNAME=you@example.com \
|
|
48
|
+
AGAMI_ADMIN_FIRST_NAME=Alex AGAMI_ADMIN_LAST_NAME=Kim \
|
|
49
|
+
AGAMI_ADMIN_PASSWORD=… \
|
|
50
|
+
AGAMI_ADMIN_PROVIDER=google \
|
|
51
|
+
AGAMI_OIDC_GOOGLE_CLIENT_ID=… AGAMI_OIDC_GOOGLE_CLIENT_SECRET=… \
|
|
52
|
+
python -m mcp_http
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The admin is identified by **email** (`AGAMI_ADMIN_USERNAME`). Their sign-in method is whatever you
|
|
56
|
+
configure — **a password and/or a pinned social provider, at least one**: set `AGAMI_ADMIN_PASSWORD`,
|
|
57
|
+
and/or `AGAMI_ADMIN_PROVIDER` (`google` | `microsoft`, which must also have its
|
|
58
|
+
`AGAMI_OIDC_<PROVIDER>_CLIENT_ID/SECRET` set). The admin login then offers the same Google/Microsoft
|
|
59
|
+
option as the MCP login. Register **one** OAuth redirect URI with the provider —
|
|
60
|
+
`{base}/oauth/oidc/callback` — it serves both the connector and the admin flows.
|
|
61
|
+
|
|
62
|
+
### One host, two entry points
|
|
63
|
+
|
|
64
|
+
A deployment is one host (`PUBLIC_BASE_URL`). Everything lives under it:
|
|
65
|
+
|
|
66
|
+
| URL | Who | What |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `{base}/mcp` | a teammate, in Claude | the **only** URL to add as a custom connector — Claude auto-discovers the OAuth endpoints from it |
|
|
69
|
+
| `{base}/admin` | the admin, in a browser | the console to add/enable/disable users |
|
|
70
|
+
|
|
71
|
+
### Access model — two separate credentials
|
|
72
|
+
|
|
73
|
+
- **Query surface (`/mcp`)** — gated by a **Bearer JWT** from the OAuth flow. **Any** onboarded user
|
|
74
|
+
who signs in can query (that's the product). No token → `401` + `WWW-Authenticate`, which starts
|
|
75
|
+
Claude's OAuth. Admin-ness does **not** gate `/mcp`.
|
|
76
|
+
- **Admin surface (`/admin`)** — gated by a **session cookie** *and* the admin-gate
|
|
77
|
+
(`AGAMI_ADMIN_USERNAME`). The admin signs in with their **pinned** social provider or a password; a
|
|
78
|
+
valid non-admin is refused (and a social identity for the admin email via a *different* provider is
|
|
79
|
+
refused — the pin closes IdP-confusion). An `/mcp` bearer token is useless here (different
|
|
80
|
+
credential). Unset `AGAMI_ADMIN_USERNAME` ⇒ the admin console is disabled entirely.
|
|
81
|
+
|
|
82
|
+
### Onboarding a teammate
|
|
83
|
+
|
|
84
|
+
The admin adds a teammate by **email + name** (a *pending* user). How they finish setting up follows
|
|
85
|
+
the deployment's **single auth method** — uniform for everyone, set by what you configured:
|
|
86
|
+
|
|
87
|
+
- **OIDC deployment** (a Google/Microsoft client is configured): the teammate just adds `{base}/mcp` to
|
|
88
|
+
Claude and signs in with that provider — the IdP verifies their email and binds the account on first
|
|
89
|
+
login. No link to share.
|
|
90
|
+
- **Password deployment** (no OIDC configured): the admin **copies the teammate's setup link** from the
|
|
91
|
+
Users tab and shares it out-of-band; the teammate opens it and sets their own password. The link is a
|
|
92
|
+
signed, time-boxed token and is single-use (it stops working once the account is set up).
|
|
93
|
+
|
|
94
|
+
The login surfaces show only the configured method (the admin keeps a password **break-glass** fallback
|
|
95
|
+
on `/admin/login`).
|
|
96
|
+
|
|
97
|
+
> **Trust note.** OIDC onboarding binds the account to whoever first proves the teammate's email at the
|
|
98
|
+
> configured IdP — so add a teammate by an email the *right* person controls there. The setup link and
|
|
99
|
+
> the other pre-auth endpoints aren't rate-limited in-process; put them behind your proxy/LB if exposed.
|
|
100
|
+
|
|
101
|
+
### Activity view
|
|
102
|
+
|
|
103
|
+
The admin console has one read-only **Activity** tab: every MCP tool call, folded into the conversation
|
|
104
|
+
it belongs to — **thread (conversation) ▸ turn (one user question) ▸ call**. Open a conversation and you
|
|
105
|
+
see its whole arc, *not just the queries*: the `list_datasources` / `get_datasource_schema` calls that
|
|
106
|
+
scoped the work sit alongside the `execute_sql`s that answered it (*"User asked what datasources →
|
|
107
|
+
`list_datasources`; user asked revenue by region → `get_datasource_schema`, then `execute_sql` ×2"*). A
|
|
108
|
+
query call shows its SQL, row count, latency, and status; a non-query call shows its tool name. Every
|
|
109
|
+
call carries its **own** datasource, because a conversation — or even a single turn — can span several:
|
|
110
|
+
the user switches datasource mid-session, or asks something that runs one query per datasource. The
|
|
111
|
+
conversation row lists the full set it touched.
|
|
112
|
+
|
|
113
|
+
The split is deliberate: it is **audit-grade for *what* ran** — the server observes every call directly,
|
|
114
|
+
so nothing is dropped — and **best-effort for *how* it's grouped**. The MCP protocol carries neither the
|
|
115
|
+
user's question, a conversation id, nor a turn boundary, so Claude self-reports them on every call: a
|
|
116
|
+
`user_question` (kept verbatim), a `thread_id` (per conversation), and a `correlation_id` (per turn). The
|
|
117
|
+
turn's question is taken from the **first** call in the turn (the model sometimes drifts it on later
|
|
118
|
+
refinements). When Claude doesn't supply the ids, a call simply shows as its own singleton conversation —
|
|
119
|
+
the view degrades, never drops a call. Treat the self-reported grouping as a hint, not a record.
|
|
120
|
+
|
|
121
|
+
> **Free-tier limit.** A turn that produces **no** tool call — Claude answering "let's continue" from
|
|
122
|
+
> context — never reaches the server and so can't appear here; this is an audit of what ran against your
|
|
123
|
+
> data, not a chat transcript.
|
|
124
|
+
|
|
125
|
+
The `tool_calls` log grows one row per call and has **no automatic retention** — it's your local store,
|
|
126
|
+
so prune it on your own schedule if it gets large.
|
|
127
|
+
|
|
128
|
+
### Model view
|
|
129
|
+
|
|
130
|
+
The **Model** tab (`{base}/admin/model`) is a **read-only** explorer of the semantic model you've
|
|
131
|
+
deployed — the same tree the MCP tools serve, so it can't drift from what Claude actually reads. It's a
|
|
132
|
+
catalog: a browse rail (datasource → subject area → table) and one page at a time —
|
|
133
|
+
|
|
134
|
+
- a **datasource overview** (description, glossary, storage-connection names/types, the subject areas),
|
|
135
|
+
- a **subject-area landing** (its tables, metrics, entities),
|
|
136
|
+
- a **table page** — the schema, with each column's type and description, and flags only where they
|
|
137
|
+
carry signal (**PK / FK / sensitive / unit / enum / caveat**). Trust is shown as a single table-level
|
|
138
|
+
**confidence** badge; **caveats** (the domain gotchas) are elevated to a callout; wide tables collapse
|
|
139
|
+
behind "show all N", and tables that author `column_groups` render those as collapsible sections,
|
|
140
|
+
- a **Relationships** page (when the model has cross-area joins) — the org-level relationships that
|
|
141
|
+
span subject areas, **grouped by area-pair**, so the cross-area topology is readable in one place,
|
|
142
|
+
- a **domain-context** page (your `ORGANIZATION.md`, rendered as safe markdown).
|
|
143
|
+
|
|
144
|
+
It is **read-only by construction** — a single GET endpoint, no write path. Editing the model stays
|
|
145
|
+
conversational in Claude (the in-app editor is a Hosted feature); connection **credentials are never
|
|
146
|
+
rendered** (names and types only). Deploy a change in Claude and it shows up here on the next deploy.
|
|
147
|
+
|
|
148
|
+
### Local end-to-end test (HTTPS via a tunnel)
|
|
149
|
+
|
|
150
|
+
OAuth and the `Secure` admin cookie need HTTPS, so expose the local server through a tunnel:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# terminal 1 — the server
|
|
154
|
+
PUBLIC_BASE_URL=https://<your-subdomain>.trycloudflare.com \
|
|
155
|
+
AGAMI_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
156
|
+
AGAMI_DB_URL=sqlite:///$PWD/agami.db \
|
|
157
|
+
AGAMI_ADMIN_USERNAME=you@example.com AGAMI_ADMIN_PASSWORD=choose-a-strong-one \
|
|
158
|
+
python -m mcp_http
|
|
159
|
+
|
|
160
|
+
# terminal 2 — the HTTPS tunnel (prints the https URL to use as PUBLIC_BASE_URL)
|
|
161
|
+
cloudflared tunnel --url http://127.0.0.1:8000
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Open `{base}/admin`, sign in, add a user — then add `{base}/mcp` as a connector in Claude.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# agami-core — the importable library behind the agami skill and the MCP server: the semantic
|
|
2
|
+
# model, the shared MCP TOOLS harness, and the unified local query executor.
|
|
3
|
+
#
|
|
4
|
+
# Flat top-level module names (semantic_model, mcp_harness, execute_sql, agami_paths) are an
|
|
5
|
+
# invariant: a consumer's imports must resolve unchanged. The src/ layout keeps the importable
|
|
6
|
+
# names flat while keeping them out of an accidental-import top level.
|
|
7
|
+
|
|
8
|
+
[build-system]
|
|
9
|
+
requires = ["setuptools>=68"]
|
|
10
|
+
build-backend = "setuptools.build_meta"
|
|
11
|
+
|
|
12
|
+
[project]
|
|
13
|
+
name = "agami-core"
|
|
14
|
+
version = "0.3.2"
|
|
15
|
+
description = "agami-core — governed semantic model, the shared MCP TOOLS harness, and the unified local query executor."
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.10"
|
|
18
|
+
license = "LicenseRef-Agami-FUL"
|
|
19
|
+
authors = [{ name = "Agami AI", email = "skills@agami.ai" }]
|
|
20
|
+
# The executor + stdio harness are deliberately pure-stdlib (the local no-egress executor
|
|
21
|
+
# runs SQL with only a DB driver). The model deps live in the [model] extra so the
|
|
22
|
+
# Python-driver tier stays lean; `sm` / the server install [model].
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
model = [
|
|
27
|
+
"pydantic>=2,<3",
|
|
28
|
+
"PyYAML>=6",
|
|
29
|
+
"sqlglot>=20",
|
|
30
|
+
]
|
|
31
|
+
# The HTTP MCP transport (mcp_http): the official MCP SDK + an ASGI server. Kept out of the base
|
|
32
|
+
# install so the local executor/skill stay stdlib-lean — only a hosted deployment installs it.
|
|
33
|
+
server = [
|
|
34
|
+
"mcp>=1.2",
|
|
35
|
+
"uvicorn>=0.30",
|
|
36
|
+
"psycopg2-binary>=2.9",
|
|
37
|
+
"argon2-cffi>=23",
|
|
38
|
+
"PyJWT>=2.8",
|
|
39
|
+
"httpx>=0.27", # OIDC discovery + token exchange (the server's one outbound egress)
|
|
40
|
+
"cryptography>=43", # RS256 backend for PyJWT id-token verification
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
package-dir = { "" = "src" }
|
|
45
|
+
# Flat top-level modules (not under a parent package) — listed explicitly so the import
|
|
46
|
+
# names stay flat regardless of disk layout.
|
|
47
|
+
py-modules = ["agami_paths", "execute_sql", "mcp_harness", "mcp_http", "tools", "ports", "contracts", "oss_adapters", "store", "model_store", "model_deploy", "deploy_preflight", "oauth_server", "oidc", "passwords", "user_store", "admin", "ui", "onboarding"]
|
|
48
|
+
packages = ["semantic_model"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
semantic_model = ["requirements.txt"]
|