dataspring-cli 0.3.0__py3-none-any.whl
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.
- cli/__init__.py +15 -0
- cli/_skills/dataspring-author/SKILL.md +401 -0
- cli/_skills/dataspring-consume/SKILL.md +712 -0
- cli/_skills/dataspring-correct/SKILL.md +124 -0
- cli/auth.py +375 -0
- cli/bundled_manifest.py +36 -0
- cli/contract.py +1138 -0
- cli/generated.py +1297 -0
- cli/main.py +3232 -0
- cli/output.py +266 -0
- cli/runtime.py +201 -0
- cli/skills_commands.py +247 -0
- cli/skilltree.py +350 -0
- cli/upgrade.py +66 -0
- cli/version.py +123 -0
- dataspring_cli-0.3.0.dist-info/METADATA +202 -0
- dataspring_cli-0.3.0.dist-info/RECORD +20 -0
- dataspring_cli-0.3.0.dist-info/WHEEL +4 -0
- dataspring_cli-0.3.0.dist-info/entry_points.txt +2 -0
- settings.py +78 -0
cli/contract.py
ADDED
|
@@ -0,0 +1,1138 @@
|
|
|
1
|
+
"""The dispatch contract: every ``params_model`` and action union, and nothing else.
|
|
2
|
+
|
|
3
|
+
This is the ``dataspring_contract`` of decision D19
|
|
4
|
+
(``docs/2026-09-17-one-registry-three-surfaces.md``): the pydantic models
|
|
5
|
+
that describe what each registry operation takes. ``services/dispatch.py``
|
|
6
|
+
imports them and pairs each with its handler; ``GET /api/dispatch``
|
|
7
|
+
publishes their JSON schemas; the MCP tools are generated from them; and
|
|
8
|
+
``cli/generated.py`` is generated from them, so the CLI validates a body
|
|
9
|
+
against the same class the server will.
|
|
10
|
+
|
|
11
|
+
It lives in ``cli/`` for the reason ``cli/skilltree.py`` does: the
|
|
12
|
+
``dataspring-cli`` wheel ships exactly this package, and the server imports
|
|
13
|
+
from it, so both ends read one definition. It imports pydantic and the
|
|
14
|
+
standard library only. ``tests/test_cli_thin_client.py`` and
|
|
15
|
+
``tests/test_cli_wheel_imports.py`` keep it that way.
|
|
16
|
+
|
|
17
|
+
``extra="forbid"`` on the base is the contract every surface relies on: a
|
|
18
|
+
field the server does not know is refused at the boundary instead of
|
|
19
|
+
silently dropped. Field descriptions are the parameter docs every surface
|
|
20
|
+
shows, verbatim; a model's docstring is its description.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Annotated, Any, Literal
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DispatchParams(BaseModel):
|
|
31
|
+
"""Base of every model on the dispatch boundary: each registry entry's
|
|
32
|
+
``params_model`` and every member of the ``<domain>_edit`` action unions
|
|
33
|
+
(decision D19).
|
|
34
|
+
|
|
35
|
+
``extra="forbid"`` is the contract every surface relies on: a field the
|
|
36
|
+
server does not know is refused at the boundary (HTTP 400, an MCP
|
|
37
|
+
validation error) instead of silently dropped, so a client can never
|
|
38
|
+
believe it sent something the handler never saw.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
model_config = ConfigDict(extra="forbid")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Dashboard action types
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CreateDashboardAction(DispatchParams):
|
|
50
|
+
"""Create a new dashboard."""
|
|
51
|
+
|
|
52
|
+
action: Literal["create"]
|
|
53
|
+
title: str = Field(description="Dashboard title")
|
|
54
|
+
description: str | None = Field(default=None, description="Optional description")
|
|
55
|
+
visibility: Literal["private", "org"] = Field(
|
|
56
|
+
default="private",
|
|
57
|
+
description='"private" (only you) or "org" (shared with organization)',
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class UpdateDashboardAction(DispatchParams):
|
|
62
|
+
"""Update an existing dashboard's metadata (title/description/visibility)."""
|
|
63
|
+
|
|
64
|
+
action: Literal["update"]
|
|
65
|
+
id: str = Field(description="Dashboard ID")
|
|
66
|
+
title: str | None = None
|
|
67
|
+
description: str | None = None
|
|
68
|
+
visibility: Literal["private", "org"] | None = None
|
|
69
|
+
expected_version: int | None = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Optimistic lock — if provided, update fails on mismatch. "
|
|
72
|
+
"Use the version from get_dashboard to prevent overwriting concurrent changes.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DeleteDashboardAction(DispatchParams):
|
|
77
|
+
"""Delete a dashboard. Cannot be undone."""
|
|
78
|
+
|
|
79
|
+
action: Literal["delete"]
|
|
80
|
+
id: str = Field(description="Dashboard ID")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DuplicateDashboardAction(DispatchParams):
|
|
84
|
+
"""Duplicate a dashboard with all pages, sections, and widgets (new IDs)."""
|
|
85
|
+
|
|
86
|
+
action: Literal["duplicate"]
|
|
87
|
+
id: str = Field(description="Source dashboard ID")
|
|
88
|
+
new_title: str | None = Field(
|
|
89
|
+
default=None, description='Title for the copy (default: "Copy of {original}")'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ShareDashboardAction(DispatchParams):
|
|
94
|
+
"""Toggle dashboard visibility between private and shared (org)."""
|
|
95
|
+
|
|
96
|
+
action: Literal["share"]
|
|
97
|
+
id: str = Field(description="Dashboard ID")
|
|
98
|
+
visibility: Literal["private", "org"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SetDashboardControlsAction(DispatchParams):
|
|
102
|
+
"""Update a dashboard's date-range, grain, and comparison controls."""
|
|
103
|
+
|
|
104
|
+
action: Literal["set_controls"]
|
|
105
|
+
id: str = Field(description="Dashboard ID")
|
|
106
|
+
date_range: dict | None = Field(
|
|
107
|
+
default=None,
|
|
108
|
+
description=(
|
|
109
|
+
'Date range config. Relative: {"mode": "relative", "preset": "last_30_days"}. '
|
|
110
|
+
'Absolute: {"mode": "absolute", "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD"}.'
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
grain: Literal["day", "week", "month", "quarter", "year"] | None = None
|
|
114
|
+
comparison: Literal["none", "previous_period", "same_period_last_year"] | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
DashboardAction = Annotated[
|
|
118
|
+
CreateDashboardAction
|
|
119
|
+
| UpdateDashboardAction
|
|
120
|
+
| DeleteDashboardAction
|
|
121
|
+
| DuplicateDashboardAction
|
|
122
|
+
| ShareDashboardAction
|
|
123
|
+
| SetDashboardControlsAction,
|
|
124
|
+
Field(discriminator="action"),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class DashboardEditParams(DispatchParams):
|
|
129
|
+
"""The ``dashboard_edit`` call: one ``action`` from the family's union."""
|
|
130
|
+
|
|
131
|
+
action: DashboardAction
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Widget action types
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AddWidgetAction(DispatchParams):
|
|
140
|
+
"""Add a widget to a dashboard section."""
|
|
141
|
+
|
|
142
|
+
action: Literal["add"]
|
|
143
|
+
dashboard_id: str
|
|
144
|
+
widget: dict = Field(
|
|
145
|
+
description=(
|
|
146
|
+
"Widget definition. Required: type (kpi|area_chart|bar_chart|line_chart|"
|
|
147
|
+
"table|donut|heatmap), title, query. Optional: format, width (1-10), "
|
|
148
|
+
"time_scope (range|latest|latest_complete), pivot (for table widgets)."
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
page_index: int = 0
|
|
152
|
+
section_index: int = 0
|
|
153
|
+
expected_version: int | None = None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class UpdateWidgetAction(DispatchParams):
|
|
157
|
+
"""Update a widget's fields (title, type, query, format, width, time_scope, pivot)."""
|
|
158
|
+
|
|
159
|
+
action: Literal["update"]
|
|
160
|
+
dashboard_id: str
|
|
161
|
+
page_index: int
|
|
162
|
+
section_index: int
|
|
163
|
+
widget_index: int
|
|
164
|
+
updates: dict = Field(description="Fields to patch — only provided keys are changed")
|
|
165
|
+
expected_version: int | None = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class RemoveWidgetAction(DispatchParams):
|
|
169
|
+
"""Remove a widget from a dashboard section (irreversible)."""
|
|
170
|
+
|
|
171
|
+
action: Literal["remove"]
|
|
172
|
+
dashboard_id: str
|
|
173
|
+
page_index: int
|
|
174
|
+
section_index: int
|
|
175
|
+
widget_index: int
|
|
176
|
+
expected_version: int | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class ReorderWidgetsAction(DispatchParams):
|
|
180
|
+
"""Reorder widgets within a section. All widget IDs in the section must be listed."""
|
|
181
|
+
|
|
182
|
+
action: Literal["reorder"]
|
|
183
|
+
dashboard_id: str
|
|
184
|
+
page_index: int
|
|
185
|
+
section_index: int
|
|
186
|
+
widget_ids: list[str] = Field(description="Full list of widget IDs in desired order")
|
|
187
|
+
expected_version: int | None = None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class MoveWidgetAction(DispatchParams):
|
|
191
|
+
"""Move a widget to a different page/section (keeps the same ID)."""
|
|
192
|
+
|
|
193
|
+
action: Literal["move"]
|
|
194
|
+
dashboard_id: str
|
|
195
|
+
widget_id: str
|
|
196
|
+
target_page_id: str
|
|
197
|
+
target_section_index: int
|
|
198
|
+
position: int | None = Field(
|
|
199
|
+
default=None, description="Position within target section (None = append)"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class CopyWidgetAction(DispatchParams):
|
|
204
|
+
"""Copy a widget with a new ID."""
|
|
205
|
+
|
|
206
|
+
action: Literal["copy"]
|
|
207
|
+
dashboard_id: str
|
|
208
|
+
widget_id: str
|
|
209
|
+
target_page_id: str
|
|
210
|
+
target_section_index: int
|
|
211
|
+
new_title: str | None = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class SwapWidgetsAction(DispatchParams):
|
|
215
|
+
"""Swap the positions of two widgets (may be in different sections)."""
|
|
216
|
+
|
|
217
|
+
action: Literal["swap"]
|
|
218
|
+
dashboard_id: str
|
|
219
|
+
widget_id_1: str
|
|
220
|
+
widget_id_2: str
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
WidgetAction = Annotated[
|
|
224
|
+
AddWidgetAction
|
|
225
|
+
| UpdateWidgetAction
|
|
226
|
+
| RemoveWidgetAction
|
|
227
|
+
| ReorderWidgetsAction
|
|
228
|
+
| MoveWidgetAction
|
|
229
|
+
| CopyWidgetAction
|
|
230
|
+
| SwapWidgetsAction,
|
|
231
|
+
Field(discriminator="action"),
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class WidgetEditParams(DispatchParams):
|
|
236
|
+
"""The ``widget_edit`` call: one ``action`` from the family's union."""
|
|
237
|
+
|
|
238
|
+
action: WidgetAction
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# Page action types
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class CreatePageAction(DispatchParams):
|
|
247
|
+
action: Literal["create"]
|
|
248
|
+
dashboard_id: str
|
|
249
|
+
title: str
|
|
250
|
+
position: int | None = Field(default=None, description="Insert position (None = append)")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class DeletePageAction(DispatchParams):
|
|
254
|
+
action: Literal["delete"]
|
|
255
|
+
dashboard_id: str
|
|
256
|
+
page_id: str
|
|
257
|
+
cascade: bool = Field(default=False, description="Delete even if page has widgets")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class RenamePageAction(DispatchParams):
|
|
261
|
+
action: Literal["rename"]
|
|
262
|
+
dashboard_id: str
|
|
263
|
+
page_id: str
|
|
264
|
+
title: str
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ReorderPagesAction(DispatchParams):
|
|
268
|
+
action: Literal["reorder"]
|
|
269
|
+
dashboard_id: str
|
|
270
|
+
page_ids: list[str] = Field(description="Full list of page IDs in desired order")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
PageAction = Annotated[
|
|
274
|
+
CreatePageAction | DeletePageAction | RenamePageAction | ReorderPagesAction,
|
|
275
|
+
Field(discriminator="action"),
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class PageEditParams(DispatchParams):
|
|
280
|
+
"""The ``page_edit`` call: one ``action`` from the family's union."""
|
|
281
|
+
|
|
282
|
+
action: PageAction
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# Section action types
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class CreateSectionAction(DispatchParams):
|
|
291
|
+
action: Literal["create"]
|
|
292
|
+
dashboard_id: str
|
|
293
|
+
page_id: str
|
|
294
|
+
title: str | None = None
|
|
295
|
+
position: int | None = None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class DeleteSectionAction(DispatchParams):
|
|
299
|
+
action: Literal["delete"]
|
|
300
|
+
dashboard_id: str
|
|
301
|
+
page_id: str
|
|
302
|
+
section_index: int
|
|
303
|
+
cascade: bool = False
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RenameSectionAction(DispatchParams):
|
|
307
|
+
action: Literal["rename"]
|
|
308
|
+
dashboard_id: str
|
|
309
|
+
page_id: str
|
|
310
|
+
section_index: int
|
|
311
|
+
title: str | None = Field(description="New title (None to clear)")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class MoveSectionAction(DispatchParams):
|
|
315
|
+
action: Literal["move"]
|
|
316
|
+
dashboard_id: str
|
|
317
|
+
source_page_id: str
|
|
318
|
+
section_index: int
|
|
319
|
+
target_page_id: str
|
|
320
|
+
target_position: int | None = None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
SectionAction = Annotated[
|
|
324
|
+
CreateSectionAction | DeleteSectionAction | RenameSectionAction | MoveSectionAction,
|
|
325
|
+
Field(discriminator="action"),
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class SectionEditParams(DispatchParams):
|
|
330
|
+
"""The ``section_edit`` call: one ``action`` from the family's union."""
|
|
331
|
+
|
|
332
|
+
action: SectionAction
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
# Semantic-model action types (admin/owner only)
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
_EXPECT_VERSION_DOC = (
|
|
341
|
+
"Managed tenants only: the datacore version (draft snapshot id) this edit "
|
|
342
|
+
"was written against, from the previous edit's `version` or "
|
|
343
|
+
"dataspring://datacore/files; refused with the current one when stale"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class CreateSemanticModelAction(DispatchParams):
|
|
348
|
+
action: Literal["create"]
|
|
349
|
+
model_data: dict = Field(
|
|
350
|
+
description="Full semantic-model definition (name, measures, dimensions, entities)"
|
|
351
|
+
)
|
|
352
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class UpdateSemanticModelAction(DispatchParams):
|
|
356
|
+
action: Literal["update"]
|
|
357
|
+
name: str
|
|
358
|
+
updates: dict
|
|
359
|
+
confirmed: bool = Field(
|
|
360
|
+
default=False,
|
|
361
|
+
description=(
|
|
362
|
+
"set true only after the user confirmed a redefinition of a "
|
|
363
|
+
"NATIVE entity whose impact you showed them; has no effect on "
|
|
364
|
+
"imported entities"
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class DeleteSemanticModelAction(DispatchParams):
|
|
371
|
+
action: Literal["delete"]
|
|
372
|
+
name: str
|
|
373
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class PreviewSemanticModelAction(DispatchParams):
|
|
377
|
+
"""Validate a semantic model without writing it or touching the warehouse."""
|
|
378
|
+
|
|
379
|
+
action: Literal["preview"]
|
|
380
|
+
model_data: dict = Field(
|
|
381
|
+
description="Full semantic-model definition to validate (name, "
|
|
382
|
+
"measures, dimensions, entities)"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
SemanticModelAction = Annotated[
|
|
387
|
+
CreateSemanticModelAction
|
|
388
|
+
| UpdateSemanticModelAction
|
|
389
|
+
| DeleteSemanticModelAction
|
|
390
|
+
| PreviewSemanticModelAction,
|
|
391
|
+
Field(discriminator="action"),
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class SemanticModelEditParams(DispatchParams):
|
|
396
|
+
"""The ``semantic_model_edit`` call: one ``action`` from the family's union."""
|
|
397
|
+
|
|
398
|
+
action: SemanticModelAction
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ---------------------------------------------------------------------------
|
|
402
|
+
# Metric action types (admin/owner only for writes, preview is open)
|
|
403
|
+
# ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class CreateMetricAction(DispatchParams):
|
|
407
|
+
action: Literal["create"]
|
|
408
|
+
metric_data: dict = Field(description="Metric definition (name, type, type_params)")
|
|
409
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class UpdateMetricAction(DispatchParams):
|
|
413
|
+
action: Literal["update"]
|
|
414
|
+
name: str
|
|
415
|
+
updates: dict
|
|
416
|
+
confirmed: bool = Field(
|
|
417
|
+
default=False,
|
|
418
|
+
description=(
|
|
419
|
+
"set true only after the user confirmed a redefinition of a "
|
|
420
|
+
"NATIVE entity whose impact you showed them; has no effect on "
|
|
421
|
+
"imported entities"
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class DeleteMetricAction(DispatchParams):
|
|
428
|
+
action: Literal["delete"]
|
|
429
|
+
name: str
|
|
430
|
+
expect_version: str | None = Field(default=None, description=_EXPECT_VERSION_DOC)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class PreviewMetricAction(DispatchParams):
|
|
434
|
+
action: Literal["preview"]
|
|
435
|
+
metric_data: dict = Field(description="Metric definition to preview")
|
|
436
|
+
sample_query: dict | None = Field(
|
|
437
|
+
default=None,
|
|
438
|
+
description="Optional query params (start_date, end_date, grain, limit)",
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class ImpactMetricAction(DispatchParams):
|
|
443
|
+
"""What applying ``updates`` to this metric would touch, without writing."""
|
|
444
|
+
|
|
445
|
+
action: Literal["impact"]
|
|
446
|
+
name: str
|
|
447
|
+
updates: dict = Field(description="The updates whose impact to assess")
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
MetricAction = Annotated[
|
|
451
|
+
CreateMetricAction
|
|
452
|
+
| UpdateMetricAction
|
|
453
|
+
| DeleteMetricAction
|
|
454
|
+
| PreviewMetricAction
|
|
455
|
+
| ImpactMetricAction,
|
|
456
|
+
Field(discriminator="action"),
|
|
457
|
+
]
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class MetricEditParams(DispatchParams):
|
|
461
|
+
"""The ``metric_edit`` call: one ``action`` from the family's union."""
|
|
462
|
+
|
|
463
|
+
action: MetricAction
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# ---------------------------------------------------------------------------
|
|
467
|
+
# Quick-metric action types
|
|
468
|
+
# ---------------------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class CreateQuickMetricAction(DispatchParams):
|
|
472
|
+
action: Literal["create"]
|
|
473
|
+
name: str = Field(description='Metric name (e.g., "revenue_per_order")')
|
|
474
|
+
expression: str = Field(
|
|
475
|
+
description='Arithmetic expression over existing metrics (e.g., "total_revenue / order_count")'
|
|
476
|
+
)
|
|
477
|
+
description: str | None = None
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class UpdateQuickMetricAction(DispatchParams):
|
|
481
|
+
action: Literal["update"]
|
|
482
|
+
metric_id: str
|
|
483
|
+
name: str | None = None
|
|
484
|
+
expression: str | None = None
|
|
485
|
+
description: str | None = None
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class DeleteQuickMetricAction(DispatchParams):
|
|
489
|
+
action: Literal["delete"]
|
|
490
|
+
metric_id: str
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
QuickMetricAction = Annotated[
|
|
494
|
+
CreateQuickMetricAction | UpdateQuickMetricAction | DeleteQuickMetricAction,
|
|
495
|
+
Field(discriminator="action"),
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class QuickMetricEditParams(DispatchParams):
|
|
500
|
+
"""The ``quick_metric_edit`` call: one ``action`` from the family's union."""
|
|
501
|
+
|
|
502
|
+
action: QuickMetricAction
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# ---------------------------------------------------------------------------
|
|
506
|
+
# Scheduled-report action types
|
|
507
|
+
# ---------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
_Frequency = Literal["daily", "weekly", "monthly"]
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class CreateReportAction(DispatchParams):
|
|
514
|
+
action: Literal["create"]
|
|
515
|
+
name: str
|
|
516
|
+
frequency_type: _Frequency
|
|
517
|
+
time: str = Field(description="HH:MM in UTC, 24-hour")
|
|
518
|
+
recipients: list[str]
|
|
519
|
+
dashboard_id: str | None = Field(
|
|
520
|
+
default=None, description="For dashboard reports (PDF/PNG)"
|
|
521
|
+
)
|
|
522
|
+
metrics: list[str] | None = Field(
|
|
523
|
+
default=None, description="For query reports (CSV/JSON)"
|
|
524
|
+
)
|
|
525
|
+
dimensions: list[str] | None = None
|
|
526
|
+
format: str = Field(
|
|
527
|
+
default="pdf",
|
|
528
|
+
description='"pdf"|"png" for dashboards, "csv"|"json" for queries',
|
|
529
|
+
)
|
|
530
|
+
day_of_week: int | None = Field(default=None, description="0=Mon … 6=Sun (weekly)")
|
|
531
|
+
day_of_month: int | None = Field(default=None, description="1-28 (monthly)")
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class UpdateReportAction(DispatchParams):
|
|
535
|
+
action: Literal["update"]
|
|
536
|
+
schedule_id: str
|
|
537
|
+
name: str | None = None
|
|
538
|
+
enabled: bool | None = Field(
|
|
539
|
+
default=None, description="Set False to pause without deleting"
|
|
540
|
+
)
|
|
541
|
+
frequency_type: _Frequency | None = None
|
|
542
|
+
time: str | None = None
|
|
543
|
+
day_of_week: int | None = None
|
|
544
|
+
day_of_month: int | None = None
|
|
545
|
+
recipients: list[str] | None = None
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class DeleteReportAction(DispatchParams):
|
|
549
|
+
action: Literal["delete"]
|
|
550
|
+
schedule_id: str
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
ReportAction = Annotated[
|
|
554
|
+
CreateReportAction | UpdateReportAction | DeleteReportAction,
|
|
555
|
+
Field(discriminator="action"),
|
|
556
|
+
]
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class ReportEditParams(DispatchParams):
|
|
560
|
+
"""The ``report_edit`` call: one ``action`` from the family's union."""
|
|
561
|
+
|
|
562
|
+
action: ReportAction
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# ---------------------------------------------------------------------------
|
|
566
|
+
# business_context action types
|
|
567
|
+
# ---------------------------------------------------------------------------
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class GetBusinessContextAction(DispatchParams):
|
|
571
|
+
"""Read the current org business_context document."""
|
|
572
|
+
|
|
573
|
+
action: Literal["get"]
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class ShowSizeBusinessContextAction(DispatchParams):
|
|
577
|
+
"""Report current size and remaining room within the 2000-byte UTF-8 cap."""
|
|
578
|
+
|
|
579
|
+
action: Literal["show_size"]
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class SetBusinessContextAction(DispatchParams):
|
|
583
|
+
"""Replace the org's business_context with ``content`` (admin/owner)."""
|
|
584
|
+
|
|
585
|
+
action: Literal["set"]
|
|
586
|
+
content: str = Field(description="Full document content to save")
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
class AppendBusinessContextAction(DispatchParams):
|
|
590
|
+
"""Append ``content`` to the existing business_context (admin/owner).
|
|
591
|
+
|
|
592
|
+
Reads the current value, concatenates, and writes the combined text.
|
|
593
|
+
Useful for incremental edits where the agent doesn't already have
|
|
594
|
+
the prior content in scope.
|
|
595
|
+
"""
|
|
596
|
+
|
|
597
|
+
action: Literal["append"]
|
|
598
|
+
content: str = Field(description="Text to append to the current document")
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
BusinessContextAction = Annotated[
|
|
602
|
+
GetBusinessContextAction
|
|
603
|
+
| ShowSizeBusinessContextAction
|
|
604
|
+
| SetBusinessContextAction
|
|
605
|
+
| AppendBusinessContextAction,
|
|
606
|
+
Field(discriminator="action"),
|
|
607
|
+
]
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class BusinessContextEditParams(DispatchParams):
|
|
611
|
+
"""The ``business_context_edit`` call: one ``action`` from the family's union."""
|
|
612
|
+
|
|
613
|
+
action: BusinessContextAction
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
# ---------------------------------------------------------------------------
|
|
617
|
+
# Learned-trail action types
|
|
618
|
+
# ---------------------------------------------------------------------------
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class ListLearnedAction(DispatchParams):
|
|
622
|
+
"""List what DataSpring learned for this org (plus your personal entries)."""
|
|
623
|
+
|
|
624
|
+
action: Literal["list"]
|
|
625
|
+
limit: int = 20
|
|
626
|
+
include_reverted: bool = False
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
class UndoLearnedAction(DispatchParams):
|
|
630
|
+
"""Revert one learning (or, with no id, your most recent reversible one)."""
|
|
631
|
+
|
|
632
|
+
action: Literal["undo"]
|
|
633
|
+
learning_id: str | None = Field(
|
|
634
|
+
default=None,
|
|
635
|
+
description="Learning to revert; omit for your most recent reversible entry",
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
LearnedAction = Annotated[
|
|
640
|
+
ListLearnedAction | UndoLearnedAction,
|
|
641
|
+
Field(discriminator="action"),
|
|
642
|
+
]
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
class LearnedEditParams(DispatchParams):
|
|
646
|
+
"""The ``learned_edit`` call: one ``action`` from the family's union."""
|
|
647
|
+
|
|
648
|
+
action: LearnedAction
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# ---------------------------------------------------------------------------
|
|
652
|
+
# Verified-query action types
|
|
653
|
+
# ---------------------------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class ListVerifiedQueriesAction(DispatchParams):
|
|
657
|
+
"""The org's verified queries plus your own personal ones."""
|
|
658
|
+
|
|
659
|
+
action: Literal["list"]
|
|
660
|
+
limit: int = 20
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class RecordVerifiedQueryAction(DispatchParams):
|
|
664
|
+
"""Record one confirmed (question, query) pair."""
|
|
665
|
+
|
|
666
|
+
action: Literal["record"]
|
|
667
|
+
question: str = Field(description="The user's question, verbatim")
|
|
668
|
+
params: dict = Field(
|
|
669
|
+
description=(
|
|
670
|
+
"The query that answers it, in the query_metrics wire shape: "
|
|
671
|
+
"metrics, dimensions, grain, start_date, end_date, where, "
|
|
672
|
+
"order_by, limit"
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
note: str | None = Field(
|
|
676
|
+
default=None,
|
|
677
|
+
description="One line on WHY this is the right query for that question",
|
|
678
|
+
)
|
|
679
|
+
scope: Literal["org", "user"] = Field(
|
|
680
|
+
default="user",
|
|
681
|
+
description=(
|
|
682
|
+
"'user' (the default) records it in this user's personal set; "
|
|
683
|
+
"'org' curates it for everyone and requires admin/owner"
|
|
684
|
+
),
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class DeleteVerifiedQueryAction(DispatchParams):
|
|
689
|
+
"""Remove one verified query by id."""
|
|
690
|
+
|
|
691
|
+
action: Literal["delete"]
|
|
692
|
+
id: str
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
VerifiedQueryAction = Annotated[
|
|
696
|
+
ListVerifiedQueriesAction | RecordVerifiedQueryAction | DeleteVerifiedQueryAction,
|
|
697
|
+
Field(discriminator="action"),
|
|
698
|
+
]
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
class VerifiedQueryEditParams(DispatchParams):
|
|
702
|
+
"""The ``verified_query_edit`` call: one ``action`` from the family's union."""
|
|
703
|
+
|
|
704
|
+
action: VerifiedQueryAction
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
# ---------------------------------------------------------------------------
|
|
708
|
+
# Warehouse action types (docs/2026-09-17-datacore-chunk2-plan.md)
|
|
709
|
+
# ---------------------------------------------------------------------------
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class ActivateWarehouseAction(DispatchParams):
|
|
713
|
+
"""Make one warehouse the org's default: every query that names no
|
|
714
|
+
``warehouse`` runs against it."""
|
|
715
|
+
|
|
716
|
+
action: Literal["activate"]
|
|
717
|
+
id: str = Field(description="Warehouse id, e.g. 'managed' or 'external'")
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
class AddExternalWarehouseAction(DispatchParams):
|
|
721
|
+
"""Register a customer-owned BigQuery project as an external warehouse.
|
|
722
|
+
|
|
723
|
+
The customer's key is NOT an argument: it lives in Secret Manager in the
|
|
724
|
+
data project and is only named here (``key_secret``)."""
|
|
725
|
+
|
|
726
|
+
action: Literal["add_external"]
|
|
727
|
+
label: str = Field(description="Display name, e.g. \"Noon's BigQuery\"")
|
|
728
|
+
project: str = Field(description="The customer's GCP project id")
|
|
729
|
+
dataset: str = Field(description="The dataset the semantic manifest points at")
|
|
730
|
+
location: str = Field(description="BigQuery location of that dataset, e.g. 'europe-north2'")
|
|
731
|
+
key_secret: str = Field(
|
|
732
|
+
description=(
|
|
733
|
+
"NAME of the Secret Manager secret holding the customer's "
|
|
734
|
+
"service-account key (t-<org>-external-bigquery-key). Never the key itself."
|
|
735
|
+
)
|
|
736
|
+
)
|
|
737
|
+
id: str = Field(default="external", description="Warehouse id to create (default 'external')")
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
class RemoveWarehouseAction(DispatchParams):
|
|
741
|
+
"""Delete a warehouse and its manifest. Refused for the active warehouse
|
|
742
|
+
and for the managed one."""
|
|
743
|
+
|
|
744
|
+
action: Literal["remove"]
|
|
745
|
+
id: str = Field(description="Warehouse id to remove")
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
WarehouseAction = Annotated[
|
|
749
|
+
ActivateWarehouseAction | AddExternalWarehouseAction | RemoveWarehouseAction,
|
|
750
|
+
Field(discriminator="action"),
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
class WarehouseEditParams(DispatchParams):
|
|
755
|
+
"""The ``warehouse_edit`` call: one ``action`` from the family's union."""
|
|
756
|
+
|
|
757
|
+
action: WarehouseAction
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
# ---------------------------------------------------------------------------
|
|
761
|
+
# Datacore action types (chunk 3): the workspace files, and the loop
|
|
762
|
+
# ---------------------------------------------------------------------------
|
|
763
|
+
|
|
764
|
+
_DATACORE_VERSION_DOC = (
|
|
765
|
+
"The datacore version this edit was written against: the `version` the "
|
|
766
|
+
"previous edit returned, or dataspring://datacore/files. An edit against "
|
|
767
|
+
"a stale version is refused with the current one. Omit to skip the check."
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
_DATACORE_NAME_DOC = (
|
|
772
|
+
"For kind connection or pipeline: the name, i.e. the file name without .yaml "
|
|
773
|
+
"(connections/<name>.yaml, pipelines/<name>.yaml). For kind model: the path "
|
|
774
|
+
"under models/, the dbt project (models/models/marts/orders.sql, "
|
|
775
|
+
"models/models/staging/schema.yml, models/seeds/x.csv, models/dbt_project.yml); "
|
|
776
|
+
"no traversal, .sql .yml .yaml .csv .md .txt .json, never target/ or dbt_packages/"
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
DatacoreKind = Literal["connection", "pipeline", "model"]
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
class ApplyDatacoreFileAction(DispatchParams):
|
|
784
|
+
"""Write one workspace file whole (create or replace): a connection or
|
|
785
|
+
pipeline as a ``document`` (validated against its schema and the
|
|
786
|
+
catalog), a dbt project file as ``content``."""
|
|
787
|
+
|
|
788
|
+
action: Literal["apply"]
|
|
789
|
+
kind: DatacoreKind = Field(description="connection (admin), pipeline (admin) or model (member)")
|
|
790
|
+
name: str = Field(description=_DATACORE_NAME_DOC)
|
|
791
|
+
document: dict | None = Field(
|
|
792
|
+
default=None,
|
|
793
|
+
description=(
|
|
794
|
+
"kind connection: the whole document (connection.schema.json: kind, base_url, "
|
|
795
|
+
"auth with secret NAMES). kind pipeline: the whole document (pipeline.schema.json: "
|
|
796
|
+
"a catalog `connector` with config, secrets, overrides; or a `connection` reference, "
|
|
797
|
+
"an inline `source` or named `sources`, with `resources`; schedule, checks, then). "
|
|
798
|
+
"Secrets are named, never valued; every name must exist"
|
|
799
|
+
),
|
|
800
|
+
)
|
|
801
|
+
content: str | None = Field(default=None, description="kind model: the whole file content")
|
|
802
|
+
expect_version: str | None = Field(default=None, description=_DATACORE_VERSION_DOC)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
class PatchDatacoreFileAction(DispatchParams):
|
|
806
|
+
"""Apply a JSON merge patch (RFC 7386: a key set to null is removed) to an
|
|
807
|
+
existing YAML file: a connection, a pipeline (a schedule, a window, a
|
|
808
|
+
check), or a YAML file under models/ (not SQL)."""
|
|
809
|
+
|
|
810
|
+
action: Literal["patch"]
|
|
811
|
+
kind: DatacoreKind = Field(description="connection (admin), pipeline (admin) or model (member)")
|
|
812
|
+
name: str = Field(description=_DATACORE_NAME_DOC)
|
|
813
|
+
patch: dict = Field(description="JSON merge patch over the current document")
|
|
814
|
+
expect_version: str | None = Field(default=None, description=_DATACORE_VERSION_DOC)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
class DeleteDatacoreFileAction(DispatchParams):
|
|
818
|
+
"""Remove one workspace file."""
|
|
819
|
+
|
|
820
|
+
action: Literal["delete"]
|
|
821
|
+
kind: DatacoreKind = Field(description="connection (admin), pipeline (admin) or model (member)")
|
|
822
|
+
name: str = Field(description=_DATACORE_NAME_DOC)
|
|
823
|
+
expect_version: str | None = Field(default=None, description=_DATACORE_VERSION_DOC)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
DatacoreAction = Annotated[
|
|
827
|
+
ApplyDatacoreFileAction | PatchDatacoreFileAction | DeleteDatacoreFileAction,
|
|
828
|
+
Field(discriminator="action"),
|
|
829
|
+
]
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
class DatacoreEditParams(DispatchParams):
|
|
833
|
+
"""The ``datacore_edit`` call: one ``action`` from the family's union."""
|
|
834
|
+
|
|
835
|
+
action: DatacoreAction
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class CheckDatacoreAction(DispatchParams):
|
|
839
|
+
"""Check the current draft; no arguments beyond an optional note."""
|
|
840
|
+
|
|
841
|
+
action: Literal["check"]
|
|
842
|
+
note: str | None = Field(default=None, description="Optional note, recorded on the check run")
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
class DeployDatacoreAction(DispatchParams):
|
|
846
|
+
action: Literal["deploy"]
|
|
847
|
+
snapshot: str | None = Field(
|
|
848
|
+
default=None,
|
|
849
|
+
description=(
|
|
850
|
+
"Omit to promote the current draft (needs a green check bound to it). "
|
|
851
|
+
"A previously deployed snapshot id rolls back to it"
|
|
852
|
+
),
|
|
853
|
+
)
|
|
854
|
+
note: str | None = Field(default=None, description="Why, recorded on the learned trail")
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
class RunWindowParams(DispatchParams):
|
|
858
|
+
start: str = Field(description="First day, YYYY-MM-DD, inclusive")
|
|
859
|
+
end: str = Field(description="Last day, YYYY-MM-DD, inclusive")
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
class RunCursorParams(DispatchParams):
|
|
863
|
+
start: Any = Field(
|
|
864
|
+
alias="from",
|
|
865
|
+
description="For a cursor-driven pipeline (Uniconta): re-pull from this cursor value",
|
|
866
|
+
)
|
|
867
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
class RunPipelineAction(DispatchParams):
|
|
871
|
+
action: Literal["run"]
|
|
872
|
+
pipeline: str = Field(description="Pipeline name (a deployed pipelines/<name>.yaml)")
|
|
873
|
+
window: RunWindowParams | None = Field(
|
|
874
|
+
default=None,
|
|
875
|
+
description="A date window to load instead of the pipeline's own; wider than backfill_chunk becomes a chunked backfill",
|
|
876
|
+
)
|
|
877
|
+
cursor: RunCursorParams | None = Field(default=None, description="{from: <value>}: a cursor to re-pull from")
|
|
878
|
+
backfill_chunk: str | None = Field(
|
|
879
|
+
default=None,
|
|
880
|
+
description="Chunk size for a windowed run: 7d, 2w or 1M (calendar months, the default)",
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
class ResetPipelineAction(DispatchParams):
|
|
885
|
+
action: Literal["reset"]
|
|
886
|
+
pipeline: str = Field(description="The pipeline whose dlt state and raw tables to remove")
|
|
887
|
+
approve: bool = Field(
|
|
888
|
+
default=False,
|
|
889
|
+
description="Must be true: the reset is irreversible. Ask the user first",
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
DatacoreRunAction = Annotated[
|
|
894
|
+
CheckDatacoreAction | DeployDatacoreAction | RunPipelineAction | ResetPipelineAction,
|
|
895
|
+
Field(discriminator="action"),
|
|
896
|
+
]
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
class DatacoreRunParams(DispatchParams):
|
|
900
|
+
"""The ``datacore_run`` call: one ``action`` from the family's union."""
|
|
901
|
+
|
|
902
|
+
action: DatacoreRunAction
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class MCPQueryParams(DispatchParams):
|
|
906
|
+
"""One metric query as every surface sends it: string dates, MetricFlow
|
|
907
|
+
``where`` constraints, an optional warehouse. ``query_metrics`` and
|
|
908
|
+
``explain_query`` both take it; ``services.queries.QueryParams`` is the
|
|
909
|
+
parsed form the query service runs.
|
|
910
|
+
"""
|
|
911
|
+
|
|
912
|
+
metrics: list[str] = Field(description="List of metric names to query (e.g., ['total_revenue', 'order_count'])")
|
|
913
|
+
dimensions: list[str] | None = Field(default=None, description="Dimensions to group by. Use qualified names from dataspring://dimensions (e.g., ['customer__segment', 'order__region'])")
|
|
914
|
+
grain: str | None = Field(default=None, description="Time granularity: 'day', 'week', 'month', 'quarter', or 'year'")
|
|
915
|
+
start_date: str | None = Field(default=None, description="Start date in YYYY-MM-DD format")
|
|
916
|
+
end_date: str | None = Field(default=None, description="End date in YYYY-MM-DD format")
|
|
917
|
+
limit: int | None = Field(default=None, description="Maximum number of rows to return")
|
|
918
|
+
order_by: str | None = Field(default=None, description="Column to sort by, append ' desc' for descending (e.g., 'total_revenue desc')")
|
|
919
|
+
where: list[str] | None = Field(
|
|
920
|
+
default=None,
|
|
921
|
+
description=(
|
|
922
|
+
"Row filters in MetricFlow's constraint syntax, one string each, "
|
|
923
|
+
"e.g. \"{{ Dimension('customer__segment') }} NOT IN ('Direct')\". "
|
|
924
|
+
"Use qualified names from dataspring://dimensions - an unresolvable "
|
|
925
|
+
"name comes back as an error whose suggestion names the qualified "
|
|
926
|
+
"form. `where` decides WHICH ROWS the numbers cover; `dimensions` "
|
|
927
|
+
"breaks the numbers OUT by a column; send both for a breakdown of "
|
|
928
|
+
"a filtered slice. A `where` on a dimension also overrides the "
|
|
929
|
+
"user's standing filter on that same dimension for this call."
|
|
930
|
+
),
|
|
931
|
+
)
|
|
932
|
+
warehouse: str | None = Field(
|
|
933
|
+
default=None,
|
|
934
|
+
description=(
|
|
935
|
+
"Which of the org's warehouses to run against - an id from "
|
|
936
|
+
"dataspring://warehouses ('managed', 'external', ...). Omit for the "
|
|
937
|
+
"active one. During a migration ask both and compare."
|
|
938
|
+
),
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
class QueryMetricsParams(MCPQueryParams):
|
|
943
|
+
"""``query_metrics``: the query, plus whether to suggest a chart for it
|
|
944
|
+
and the shape of the answer (rows, or a CSV document)."""
|
|
945
|
+
|
|
946
|
+
suggest_visualization: bool = Field(
|
|
947
|
+
default=False,
|
|
948
|
+
description="Also return a suggested visualization type for the result.",
|
|
949
|
+
)
|
|
950
|
+
format: Literal["json", "csv"] = Field(
|
|
951
|
+
default="json",
|
|
952
|
+
description=(
|
|
953
|
+
"'json' (default) answers rows in `data`; 'csv' answers the same "
|
|
954
|
+
"result as one CSV document in `content` (header row first) for a "
|
|
955
|
+
"file or a spreadsheet, with `columns` and `row_count` alongside."
|
|
956
|
+
),
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
class ExplainQueryParams(MCPQueryParams):
|
|
961
|
+
"""``explain_query``: the same query ``query_metrics`` takes, compiled and
|
|
962
|
+
described instead of run."""
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class ExportDataParams(DispatchParams):
|
|
966
|
+
"""``export_data``: a query to export, or a dashboard whose data to export."""
|
|
967
|
+
|
|
968
|
+
format: Literal["csv", "json"] = Field(default="csv", description="Output format: 'csv' or 'json'.")
|
|
969
|
+
metrics: list[str] | None = Field(default=None, description="Metric names to query (query export).")
|
|
970
|
+
dimensions: list[str] | None = Field(default=None, description="Dimensions to group by (query export).")
|
|
971
|
+
grain: str | None = Field(default=None, description="Time granularity: 'day', 'week', 'month', 'quarter' or 'year' (query export).")
|
|
972
|
+
start_date: str | None = Field(default=None, description="Start date, YYYY-MM-DD (query export).")
|
|
973
|
+
end_date: str | None = Field(default=None, description="End date, YYYY-MM-DD (query export).")
|
|
974
|
+
dashboard_id: str | None = Field(default=None, description="Dashboard id for a dashboard export; takes precedence over the query fields.")
|
|
975
|
+
output_path: str | None = Field(default=None, description="If given, save to this file path instead of returning the content.")
|
|
976
|
+
warehouse: str | None = Field(default=None, description="One of the org's warehouses (see dataspring://warehouses); omit for the active one (query export only).")
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
class UpdateContextParams(DispatchParams):
|
|
980
|
+
"""``update_context``: the preference fields to merge in."""
|
|
981
|
+
|
|
982
|
+
updates: dict[str, Any] = Field(
|
|
983
|
+
description=(
|
|
984
|
+
"Fields to update; only these change. Presentation: default_currency, "
|
|
985
|
+
"default_grain, decimal_places, preferred_chart_type. Lists and maps "
|
|
986
|
+
"REPLACE what is stored: favorite_metrics, standing_filters "
|
|
987
|
+
"([{dimension, operator: in|not_in|eq|neq, values}]), "
|
|
988
|
+
"metric_substitutions ({asked: preferred}), default_segment "
|
|
989
|
+
"({dimension, value} or null)."
|
|
990
|
+
),
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
class ImportManifestParams(DispatchParams):
|
|
995
|
+
"""``import_manifest``: a semantic manifest and the warehouse it belongs to."""
|
|
996
|
+
|
|
997
|
+
manifest_data: dict[str, Any] = Field(
|
|
998
|
+
description="The semantic manifest to import (semantic models, metrics), as a JSON object."
|
|
999
|
+
)
|
|
1000
|
+
warehouse: str | None = Field(
|
|
1001
|
+
default=None,
|
|
1002
|
+
description="Which of the org's warehouses the manifest belongs to (see dataspring://warehouses); omit for the active one.",
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
class RenderDashboardParams(DispatchParams):
|
|
1007
|
+
"""``render_dashboard``: which dashboard, in which mode."""
|
|
1008
|
+
|
|
1009
|
+
dashboard_id: str = Field(description="ID of the dashboard to render.")
|
|
1010
|
+
format: Literal["pdf", "png", "app"] = Field(
|
|
1011
|
+
default="app",
|
|
1012
|
+
description="'app' for an interactive iframe payload (default), 'pdf' or 'png' for a static export.",
|
|
1013
|
+
)
|
|
1014
|
+
page_id: str | None = Field(default=None, description="(pdf/png only) One page to render; default: all pages.")
|
|
1015
|
+
width: int = Field(default=1200, description="(pdf/png only) Viewport width in pixels.")
|
|
1016
|
+
height: int = Field(default=800, description="(pdf/png only) Viewport height in pixels.")
|
|
1017
|
+
output_path: str | None = Field(default=None, description="(pdf/png only) Save to this file path instead of returning base64.")
|
|
1018
|
+
warehouse: str | None = Field(
|
|
1019
|
+
default=None,
|
|
1020
|
+
description="One of the org's warehouses (see dataspring://warehouses) the widgets query; omit for the active one.",
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
class RenderWidgetParams(DispatchParams):
|
|
1025
|
+
"""``render_widget``: which widget of which dashboard, in which mode."""
|
|
1026
|
+
|
|
1027
|
+
dashboard_id: str = Field(description="ID of the dashboard containing the widget.")
|
|
1028
|
+
widget_id: str = Field(description="ID of the widget to render.")
|
|
1029
|
+
format: Literal["png", "app"] = Field(
|
|
1030
|
+
default="app",
|
|
1031
|
+
description="'app' for an interactive iframe payload (default), 'png' for a static snapshot.",
|
|
1032
|
+
)
|
|
1033
|
+
width: int = Field(default=600, description="(png only) Viewport width in pixels.")
|
|
1034
|
+
height: int = Field(default=400, description="(png only) Viewport height in pixels.")
|
|
1035
|
+
output_path: str | None = Field(default=None, description="(png only) Save to this file path instead of returning base64.")
|
|
1036
|
+
warehouse: str | None = Field(
|
|
1037
|
+
default=None,
|
|
1038
|
+
description="One of the org's warehouses (see dataspring://warehouses) the widget queries; omit for the active one.",
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
class SwitchOrganizationParams(DispatchParams):
|
|
1043
|
+
"""``switch_organization``: the org to switch to."""
|
|
1044
|
+
|
|
1045
|
+
org_id: str = Field(description="The organization ID to switch to (see read(kind=\"organizations\")).")
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
class SubmitErrorReportParams(DispatchParams):
|
|
1049
|
+
"""``submit_error_report``: what failed, and what was expected."""
|
|
1050
|
+
|
|
1051
|
+
command: str = Field(description="The tool, resource, or action that failed (e.g. 'query_metrics', 'dataspring://dashboards', 'dashboard_edit').")
|
|
1052
|
+
error_message: str = Field(description="The exact error message received.")
|
|
1053
|
+
expected: str | None = Field(default=None, description="What you expected to happen.")
|
|
1054
|
+
context_info: str | None = Field(default=None, description="What you were trying to achieve.")
|
|
1055
|
+
source: str = Field(default="mcp", description="Which surface sent the report: 'mcp', 'cli' or 'api'.")
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
# ---------------------------------------------------------------------------
|
|
1059
|
+
# secret_edit: secrets in both directions (D17 and its 2026-09-18 amendment)
|
|
1060
|
+
# ---------------------------------------------------------------------------
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
class SetSecretAction(DispatchParams):
|
|
1064
|
+
"""Get a one-time link where a PERSON pastes a credential. The value
|
|
1065
|
+
never comes through here; the same name rotates a credential."""
|
|
1066
|
+
|
|
1067
|
+
action: Literal["set"]
|
|
1068
|
+
name: str = Field(
|
|
1069
|
+
description=(
|
|
1070
|
+
"The secret to set or rotate: t-<org>-<connection>-<field> for your "
|
|
1071
|
+
"organization, lower-case (t-acme-kanpla-api_key, "
|
|
1072
|
+
"t-acme-external-bigquery-key). A connection file names it."
|
|
1073
|
+
)
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
class RevealSecretAction(DispatchParams):
|
|
1078
|
+
"""Get a one-time link that shows a credential DataSpring ISSUED to this
|
|
1079
|
+
organization (the Airbyte destination key today), once."""
|
|
1080
|
+
|
|
1081
|
+
action: Literal["reveal"]
|
|
1082
|
+
name: str | None = Field(
|
|
1083
|
+
default=None,
|
|
1084
|
+
description=(
|
|
1085
|
+
"The issued credential, by secret name. Omit for the Airbyte "
|
|
1086
|
+
"destination key (t-<org>-airbyte-key). Only issued credentials can "
|
|
1087
|
+
"be revealed, never ones you gave DataSpring."
|
|
1088
|
+
),
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
SecretAction = Annotated[
|
|
1093
|
+
SetSecretAction | RevealSecretAction,
|
|
1094
|
+
Field(discriminator="action"),
|
|
1095
|
+
]
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
class SecretEditParams(DispatchParams):
|
|
1099
|
+
"""The ``secret_edit`` call: one ``action`` from the family's union."""
|
|
1100
|
+
|
|
1101
|
+
action: SecretAction
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
#: Rows one ``run_sql`` call may return, and the ceiling ``max_rows`` may ask for.
|
|
1105
|
+
RUN_SQL_DEFAULT_ROWS = 1000
|
|
1106
|
+
RUN_SQL_MAX_ROWS = 10_000
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
class RunSqlParams(DispatchParams):
|
|
1110
|
+
"""``run_sql``: one statement and how much of its result to return."""
|
|
1111
|
+
|
|
1112
|
+
sql: str = Field(
|
|
1113
|
+
description=(
|
|
1114
|
+
"One SQL statement (BigQuery Standard SQL on a managed warehouse). "
|
|
1115
|
+
"Name tables as <org>_marts.<table>, <org>_core.<table> or "
|
|
1116
|
+
"<org>_staging.<table>; the identity it runs as can read those and "
|
|
1117
|
+
"nothing else, so a write or a reach outside them is refused by "
|
|
1118
|
+
"the warehouse."
|
|
1119
|
+
)
|
|
1120
|
+
)
|
|
1121
|
+
warehouse: str | None = Field(
|
|
1122
|
+
default=None,
|
|
1123
|
+
description="One of the org's warehouses (see dataspring://warehouses); omit for the active one.",
|
|
1124
|
+
)
|
|
1125
|
+
max_rows: int = Field(
|
|
1126
|
+
default=RUN_SQL_DEFAULT_ROWS,
|
|
1127
|
+
ge=1,
|
|
1128
|
+
le=RUN_SQL_MAX_ROWS,
|
|
1129
|
+
description=f"Rows to return at most (default {RUN_SQL_DEFAULT_ROWS}, cap {RUN_SQL_MAX_ROWS}). Aggregate in SQL rather than paging.",
|
|
1130
|
+
)
|
|
1131
|
+
format: Literal["json", "csv", "markdown"] = Field(
|
|
1132
|
+
default="json",
|
|
1133
|
+
description="'json' answers rows in `data`; 'csv' or 'markdown' answer one document in `content`.",
|
|
1134
|
+
)
|
|
1135
|
+
dry_run: bool = Field(
|
|
1136
|
+
default=False,
|
|
1137
|
+
description="Only estimate the bytes the statement would process (and check it parses); run nothing.",
|
|
1138
|
+
)
|