sqlmesh-mcp 0.1.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.
- sqlmesh_mcp/__init__.py +1 -0
- sqlmesh_mcp/context.py +23 -0
- sqlmesh_mcp/py.typed +0 -0
- sqlmesh_mcp/server.py +232 -0
- sqlmesh_mcp-0.1.0.dist-info/METADATA +125 -0
- sqlmesh_mcp-0.1.0.dist-info/RECORD +9 -0
- sqlmesh_mcp-0.1.0.dist-info/WHEEL +4 -0
- sqlmesh_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- sqlmesh_mcp-0.1.0.dist-info/licenses/LICENSE +21 -0
sqlmesh_mcp/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
sqlmesh_mcp/context.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Lazily-created, cached SQLMesh Context for the configured project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
|
|
8
|
+
from sqlmesh.core.context import Context
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectNotConfiguredError(RuntimeError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache(maxsize=1)
|
|
16
|
+
def get_context() -> Context:
|
|
17
|
+
path = os.environ.get("SQLMESH_PROJECT_PATH")
|
|
18
|
+
if not path:
|
|
19
|
+
raise ProjectNotConfiguredError(
|
|
20
|
+
"SQLMESH_PROJECT_PATH is not set. Point it at a directory containing "
|
|
21
|
+
"a SQLMesh config.py/config.yaml."
|
|
22
|
+
)
|
|
23
|
+
return Context(paths=path)
|
sqlmesh_mcp/py.typed
ADDED
|
File without changes
|
sqlmesh_mcp/server.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""sqlmesh-mcp: exposes a SQLMesh project to LLM agents via MCP.
|
|
2
|
+
|
|
3
|
+
Every tool here is read-only except apply_plan, which is the one tool that
|
|
4
|
+
changes real data in whatever warehouse the project points at.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import sqlglot
|
|
13
|
+
from mcp.server.mcpserver import MCPServer
|
|
14
|
+
from mcp.server.mcpserver.exceptions import ToolError
|
|
15
|
+
from mcp_types import ToolAnnotations
|
|
16
|
+
from sqlmesh.core.lineage import column_dependencies
|
|
17
|
+
from sqlmesh.utils.errors import SQLMeshError
|
|
18
|
+
|
|
19
|
+
from .context import get_context
|
|
20
|
+
|
|
21
|
+
server = MCPServer("sqlmesh-mcp")
|
|
22
|
+
|
|
23
|
+
# Plans previewed via `plan` are cached here so `apply_plan` can apply exactly
|
|
24
|
+
# what was shown, keyed by Plan.plan_id. Plan objects aren't JSON-serializable
|
|
25
|
+
# and re-running plan() at apply time could compute something different if the
|
|
26
|
+
# project changed in between preview and apply.
|
|
27
|
+
_PLAN_CACHE: dict[str, Any] = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _translate_errors(fn):
|
|
31
|
+
"""Without this, the MCP SDK treats any exception that isn't a ToolError as
|
|
32
|
+
a crash: the agent sees only the generic "Error executing tool <name>" and
|
|
33
|
+
the real message (e.g. SQLMesh's "Apply a plan first") is dropped, visible
|
|
34
|
+
only in server-side logs. SQLMeshError covers every error the underlying
|
|
35
|
+
library itself raises intentionally, so translating it is always safe to
|
|
36
|
+
show the agent -- it's exactly the informative half of the message.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@functools.wraps(fn)
|
|
40
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
41
|
+
try:
|
|
42
|
+
return fn(*args, **kwargs)
|
|
43
|
+
except SQLMeshError as e:
|
|
44
|
+
raise ToolError(str(e)) from e
|
|
45
|
+
|
|
46
|
+
return wrapper
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _snapshot_model_name(snapshot_id: Any) -> str:
|
|
50
|
+
"""SnapshotId.name is a quoted, catalog-qualified identifier (e.g.
|
|
51
|
+
'"db"."sqlmesh_example"."seed_model"') -- normalize to the plain
|
|
52
|
+
'schema.model' form every other tool here uses.
|
|
53
|
+
"""
|
|
54
|
+
t = sqlglot.exp.to_table(snapshot_id.name)
|
|
55
|
+
return f"{t.db}.{t.name}" if t.db else t.name
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _model_summary(model: Any) -> dict:
|
|
59
|
+
return {
|
|
60
|
+
"name": model.name,
|
|
61
|
+
"kind": str(model.kind.name),
|
|
62
|
+
"description": model.description,
|
|
63
|
+
"owner": model.owner,
|
|
64
|
+
"tags": list(model.tags or []),
|
|
65
|
+
"columns": {col: str(dtype) for col, dtype in (model.columns_to_types or {}).items()},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
70
|
+
@_translate_errors
|
|
71
|
+
def list_models() -> list[dict]:
|
|
72
|
+
"""List every model in the SQLMesh project with its kind, columns, owner, and description."""
|
|
73
|
+
ctx = get_context()
|
|
74
|
+
return [_model_summary(m) for m in ctx.models.values()]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
78
|
+
@_translate_errors
|
|
79
|
+
def get_model(model_name: str) -> dict:
|
|
80
|
+
"""Full detail for one model: rendered query, columns, kind, owner, tags, description."""
|
|
81
|
+
ctx = get_context()
|
|
82
|
+
model = ctx.get_model(model_name, raise_if_missing=True)
|
|
83
|
+
summary = _model_summary(model)
|
|
84
|
+
summary["query"] = model.render_query_or_raise().sql(dialect=model.dialect, pretty=True)
|
|
85
|
+
return summary
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
89
|
+
@_translate_errors
|
|
90
|
+
def plan(environment: str | None = None, select_models: list[str] | None = None) -> dict:
|
|
91
|
+
"""Preview what a plan against an environment would change. Does not apply anything.
|
|
92
|
+
|
|
93
|
+
Returns a plan_id -- pass it to apply_plan to actually apply this exact plan.
|
|
94
|
+
"""
|
|
95
|
+
ctx = get_context()
|
|
96
|
+
p = ctx.plan(
|
|
97
|
+
environment=environment,
|
|
98
|
+
select_models=select_models,
|
|
99
|
+
no_prompts=True,
|
|
100
|
+
auto_apply=False,
|
|
101
|
+
)
|
|
102
|
+
_PLAN_CACHE[p.plan_id] = p
|
|
103
|
+
diff = p.context_diff
|
|
104
|
+
return {
|
|
105
|
+
"plan_id": p.plan_id,
|
|
106
|
+
"environment": p.environment_naming_info.name,
|
|
107
|
+
"has_changes": diff.has_changes,
|
|
108
|
+
"requires_backfill": p.requires_backfill,
|
|
109
|
+
"added_models": sorted(_snapshot_model_name(s) for s in diff.added),
|
|
110
|
+
"removed_models": sorted(_snapshot_model_name(s) for s in diff.removed_snapshots),
|
|
111
|
+
"modified_models": sorted(diff.modified_snapshots),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=True))
|
|
116
|
+
@_translate_errors
|
|
117
|
+
def apply_plan(plan_id: str, confirm: bool = False) -> dict:
|
|
118
|
+
"""Apply a previously-previewed plan. THIS CHANGES REAL DATA in the target warehouse.
|
|
119
|
+
|
|
120
|
+
Requires confirm=true. plan_id must come from a plan() call in this same session --
|
|
121
|
+
plans aren't kept across server restarts.
|
|
122
|
+
"""
|
|
123
|
+
if not confirm:
|
|
124
|
+
raise ToolError(
|
|
125
|
+
"Refusing to apply without confirm=true -- this changes real data "
|
|
126
|
+
"in the target warehouse."
|
|
127
|
+
)
|
|
128
|
+
p = _PLAN_CACHE.get(plan_id)
|
|
129
|
+
if p is None:
|
|
130
|
+
raise ToolError(
|
|
131
|
+
f"No cached plan with id {plan_id!r}. Call plan() again in this session first."
|
|
132
|
+
)
|
|
133
|
+
ctx = get_context()
|
|
134
|
+
ctx.apply(p)
|
|
135
|
+
del _PLAN_CACHE[plan_id]
|
|
136
|
+
return {"applied": True, "plan_id": plan_id}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
140
|
+
@_translate_errors
|
|
141
|
+
def lineage(model_name: str, column: str) -> dict:
|
|
142
|
+
"""Column-level lineage: which upstream models/columns does this column depend on."""
|
|
143
|
+
ctx = get_context()
|
|
144
|
+
deps = column_dependencies(ctx, model_name, column)
|
|
145
|
+
return {k: sorted(v) for k, v in deps.items()}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
149
|
+
@_translate_errors
|
|
150
|
+
def run_audit(
|
|
151
|
+
model_name: str | None = None,
|
|
152
|
+
start: str | None = None,
|
|
153
|
+
end: str | None = None,
|
|
154
|
+
) -> dict:
|
|
155
|
+
"""Run audits for a model (or all models if omitted). start/end bound the data checked."""
|
|
156
|
+
ctx = get_context()
|
|
157
|
+
passed = ctx.audit(start=start, end=end, models=[model_name] if model_name else None)
|
|
158
|
+
return {"passed": passed, "model": model_name}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
162
|
+
@_translate_errors
|
|
163
|
+
def run_test(model_name: str | None = None) -> dict:
|
|
164
|
+
"""Run unit tests for a model (or all tests if omitted)."""
|
|
165
|
+
ctx = get_context()
|
|
166
|
+
result = ctx.test(model_names=[model_name] if model_name else None)
|
|
167
|
+
return {
|
|
168
|
+
"success": result.wasSuccessful(),
|
|
169
|
+
"tests_run": result.testsRun,
|
|
170
|
+
"failures": [str(f[0]) for f in result.failures],
|
|
171
|
+
"errors": [str(e[0]) for e in result.errors],
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
176
|
+
@_translate_errors
|
|
177
|
+
def diff_environment(environment: str) -> dict:
|
|
178
|
+
"""Diff the current context against a target environment."""
|
|
179
|
+
ctx = get_context()
|
|
180
|
+
has_diff = ctx.diff(environment=environment)
|
|
181
|
+
return {"environment": environment, "has_diff": has_diff}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=True))
|
|
185
|
+
@_translate_errors
|
|
186
|
+
def list_environments() -> list[dict]:
|
|
187
|
+
"""List every environment that exists in this project's state (e.g. prod, dev, ...)."""
|
|
188
|
+
ctx = get_context()
|
|
189
|
+
envs = ctx.state_reader.get_environments()
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
"name": e.name,
|
|
193
|
+
"plan_id": e.plan_id,
|
|
194
|
+
"start_at": str(e.start_at) if e.start_at else None,
|
|
195
|
+
"end_at": str(e.end_at) if e.end_at else None,
|
|
196
|
+
"finalized_ts": e.finalized_ts,
|
|
197
|
+
"expiration_ts": e.expiration_ts,
|
|
198
|
+
}
|
|
199
|
+
for e in envs
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@server.tool(annotations=ToolAnnotations(read_only_hint=False, destructive_hint=True))
|
|
204
|
+
@_translate_errors
|
|
205
|
+
def run(
|
|
206
|
+
environment: str | None = None,
|
|
207
|
+
confirm: bool = False,
|
|
208
|
+
start: str | None = None,
|
|
209
|
+
end: str | None = None,
|
|
210
|
+
) -> dict:
|
|
211
|
+
"""Execute scheduled/due model runs for an environment. THIS CHANGES REAL DATA.
|
|
212
|
+
|
|
213
|
+
Distinct from plan/apply_plan: this runs already-promoted models for their
|
|
214
|
+
due intervals (what a cron trigger would do), rather than previewing or
|
|
215
|
+
promoting structural changes. Requires confirm=true.
|
|
216
|
+
"""
|
|
217
|
+
if not confirm:
|
|
218
|
+
raise ToolError(
|
|
219
|
+
"Refusing to run without confirm=true -- this changes real data "
|
|
220
|
+
"in the target warehouse."
|
|
221
|
+
)
|
|
222
|
+
ctx = get_context()
|
|
223
|
+
status = ctx.run(environment=environment, start=start, end=end)
|
|
224
|
+
return {"status": status.name, "environment": environment}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def main() -> None:
|
|
228
|
+
server.run(transport="stdio")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
main()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sqlmesh-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server exposing a SQLMesh project (models, plans, column-level lineage, audits) to LLM agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/Zain-ul-Abdin45/sqlmesh-mcp
|
|
6
|
+
Project-URL: Issues, https://github.com/Zain-ul-Abdin45/sqlmesh-mcp/issues
|
|
7
|
+
Author: Zain Ul Abdin
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent,data-engineering,llm,mcp,sqlmesh
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: mcp>=2.0.0
|
|
18
|
+
Requires-Dist: sqlmesh>=0.236.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# sqlmesh-mcp
|
|
26
|
+
|
|
27
|
+
[](https://github.com/Zain-ul-Abdin45/sqlmesh-mcp/actions/workflows/ci.yml)
|
|
28
|
+
[](https://pypi.org/project/sqlmesh-mcp/)
|
|
29
|
+
[](https://pypi.org/project/sqlmesh-mcp/)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
MCP server exposing a [SQLMesh](https://github.com/SQLMesh/sqlmesh) project to LLM agents: model metadata, plan previews, column-level lineage, audits/tests, and environment diffs.
|
|
33
|
+
|
|
34
|
+
Not officially affiliated with SQLMesh or Tobiko Data.
|
|
35
|
+
|
|
36
|
+
## Why
|
|
37
|
+
|
|
38
|
+
SQLMesh's standout feature is column-level lineage, which is exactly the kind of question an agent is good at answering interactively ("where does `revenue` in `finance.daily_summary` come from?") that a CLI isn't. As of writing, the only prior MCP server for SQLMesh ([`sherman94062/sqlmesh-mcp`](https://github.com/sherman94062/sqlmesh-mcp)) is a small unmaintained side project — this one aims to be documented, tested, and kept current with SQLMesh's API.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install sqlmesh-mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
Point it at a SQLMesh project directory:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"sqlmesh": {
|
|
54
|
+
"command": "sqlmesh-mcp",
|
|
55
|
+
"env": { "SQLMESH_PROJECT_PATH": "/path/to/your/sqlmesh/project" }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Example
|
|
62
|
+
|
|
63
|
+
Calling `list_models` against [`examples/demo_project`](examples/demo_project) (a stock `sqlmesh init duckdb` project) returns:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
[
|
|
67
|
+
{
|
|
68
|
+
"name": "sqlmesh_example.full_model",
|
|
69
|
+
"kind": "FULL",
|
|
70
|
+
"description": null,
|
|
71
|
+
"owner": null,
|
|
72
|
+
"tags": [],
|
|
73
|
+
"columns": { "item_id": "INT", "num_orders": "BIGINT" }
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "sqlmesh_example.incremental_model",
|
|
77
|
+
"kind": "INCREMENTAL_BY_TIME_RANGE",
|
|
78
|
+
"description": null,
|
|
79
|
+
"owner": null,
|
|
80
|
+
"tags": [],
|
|
81
|
+
"columns": { "id": "INT", "item_id": "INT", "event_date": "DATE" }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "sqlmesh_example.seed_model",
|
|
85
|
+
"kind": "SEED",
|
|
86
|
+
"description": null,
|
|
87
|
+
"owner": null,
|
|
88
|
+
"tags": [],
|
|
89
|
+
"columns": { "id": "INT", "item_id": "INT", "event_date": "DATE" }
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
From there, `lineage("sqlmesh_example.full_model", "num_orders")` traces that column back to `incremental_model.id` — the kind of question this server exists for.
|
|
95
|
+
|
|
96
|
+
## Tools
|
|
97
|
+
|
|
98
|
+
| Tool | Read-only? | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `list_models` | Yes | List all models in the project with kind, columns, description |
|
|
101
|
+
| `get_model` | Yes | Full detail for one model |
|
|
102
|
+
| `plan` | Yes | Preview what a plan against an environment would change |
|
|
103
|
+
| `apply_plan` | **No** | Apply a previously-previewed plan. Requires `confirm=true`. |
|
|
104
|
+
| `lineage` | Yes | Column-level lineage for a model's column |
|
|
105
|
+
| `run_audit` | Yes | Run a model's audits |
|
|
106
|
+
| `run_test` | Yes | Run a model's unit tests |
|
|
107
|
+
| `diff_environment` | Yes | Diff two environments |
|
|
108
|
+
| `list_environments` | Yes | List every environment that exists in the project's state |
|
|
109
|
+
| `run` | **No** | Execute scheduled/due model runs for an environment (what a cron trigger would do). Requires `confirm=true`. |
|
|
110
|
+
|
|
111
|
+
`apply_plan` and `run` are the two tools that change real data in whatever warehouse the project points at. Every other tool is read-only. Both are marked `destructiveHint`/non-`readOnlyHint` in their MCP tool annotations so clients can warn a user before calling them.
|
|
112
|
+
|
|
113
|
+
One server process is scoped to a single SQLMesh project, set once via `SQLMESH_PROJECT_PATH` (the context is cached for the life of the process). Point a client at multiple projects by running multiple server instances, one per `SQLMESH_PROJECT_PATH`.
|
|
114
|
+
|
|
115
|
+
### Not yet covered
|
|
116
|
+
|
|
117
|
+
SQLMesh's `table_diff` and `format` commands aren't exposed as tools yet — planned, not forgotten. Contributions welcome.
|
|
118
|
+
|
|
119
|
+
## Testing
|
|
120
|
+
|
|
121
|
+
See [`TEST_CASES.md`](TEST_CASES.md) for a plain-English index of every test case and what it covers, including a real bug the protocol-level tests caught that direct function-call tests couldn't (tool errors getting silently replaced with a generic message unless raised as the SDK's own `ToolError`).
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
sqlmesh_mcp/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
+
sqlmesh_mcp/context.py,sha256=qyx2kSE_XNOp3CfclvgkQ4kq9YHsPFqzKE2K0sQlHEw,585
|
|
3
|
+
sqlmesh_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
sqlmesh_mcp/server.py,sha256=TWeQMqhm8nba2IihZZ9gC64Hs0ksbkFQzxwo0C5arjI,8220
|
|
5
|
+
sqlmesh_mcp-0.1.0.dist-info/METADATA,sha256=GUfVZLo8W5PCbLlreREW2GxQc7v5U5ysIGcIZuXr29c,5100
|
|
6
|
+
sqlmesh_mcp-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
7
|
+
sqlmesh_mcp-0.1.0.dist-info/entry_points.txt,sha256=AwWqV3M1RwsYbprEzgo5scAktXnZHIOWaA3JSdINuHs,56
|
|
8
|
+
sqlmesh_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=cBM1LM6j5rzRZN9debz9be_EGSdSQ6xEBD13QBe0EHU,1070
|
|
9
|
+
sqlmesh_mcp-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zain Ul Abdin
|
|
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.
|