altimate-datapilot-cli 0.0.17__py3-none-any.whl → 0.0.19__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: altimate-datapilot-cli
3
- Version: 0.0.17
3
+ Version: 0.0.19
4
4
  Summary: Assistant for Data Teams
5
5
  Home-page: https://github.com/AltimateAI/datapilot-cli
6
6
  Author: Altimate Inc
@@ -34,8 +34,10 @@ Requires-Dist: click~=8.1.7
34
34
  Requires-Dist: dbt-artifacts-parser~=0.8.1
35
35
  Requires-Dist: ruamel.yaml~=0.18.6
36
36
  Requires-Dist: tabulate~=0.9.0
37
- Requires-Dist: requests~=2.31.0
37
+ Requires-Dist: requests>=2.31
38
38
  Requires-Dist: sqlglot~=25.30.0
39
+ Requires-Dist: mcp~=1.9.0
40
+ Requires-Dist: pyperclip~=1.8.2
39
41
 
40
42
  ========
41
43
  Overview
@@ -1,7 +1,7 @@
1
- datapilot/__init__.py,sha256=WlMBNrzKywm5RAbqEVgEgcG7Opc4cf3-wpAnO8PdoEI,23
1
+ datapilot/__init__.py,sha256=AixLlU6Em9Z_zs4l1lTxAHg1b8pa8z3BTNKIHDkjBmo,23
2
2
  datapilot/__main__.py,sha256=I9USmeNnK-cAHb6LZfydJC0LeNSE8enieeY55wpR6uw,380
3
3
  datapilot/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- datapilot/cli/main.py,sha256=VSdqlkCiu8GSG9qQh8q0BzyocsQc4lKWxZAPEsjXF18,181
4
+ datapilot/cli/main.py,sha256=oXgzD68_coBkfWtOfo94apaMUEX-IxogIFbK2L77u2g,253
5
5
  datapilot/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  datapilot/clients/altimate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  datapilot/clients/altimate/client.py,sha256=00TRe_ck8UgbhFMAnrLBmug3fAWxAggNl2do5Um_4oU,4083
@@ -21,6 +21,8 @@ datapilot/core/insights/sql/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
21
21
  datapilot/core/insights/sql/base/insight.py,sha256=k8UUn0qrN-QG6NCunPl7Hd6L6kd1X1eUAeGEsyl8v0o,250
22
22
  datapilot/core/insights/sql/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  datapilot/core/insights/sql/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ datapilot/core/mcp_utils/__init__.py,sha256=39zN2cGQCsEjRFeExv2bX4MoqVv4H14o_SYp_QG2jHU,18
25
+ datapilot/core/mcp_utils/mcp.py,sha256=e-FbHmpEr673hxVBFHv996qnHCuqKqiCrMMy_g5tMjg,6027
24
26
  datapilot/core/platforms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
27
  datapilot/core/platforms/dbt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
28
  datapilot/core/platforms/dbt/constants.py,sha256=kW4PEsYWosfDjBEZ9JUhWFtMxKMSNoIn0DNPlJEJuYc,515
@@ -139,10 +141,10 @@ datapilot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
139
141
  datapilot/utils/utils.py,sha256=MY8q6ZBJ0hkrTuH7gWMxAlEAQGrajXFMabEhtGtT7sc,11524
140
142
  datapilot/utils/formatting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
143
  datapilot/utils/formatting/utils.py,sha256=rAVmIYuldvw9VvCSwG2kMTEgiT7cEconp_F1sAWVyCo,1377
142
- altimate_datapilot_cli-0.0.17.dist-info/AUTHORS.rst,sha256=S4H4zw_v3GVyz5_55jF5Gf_YNG3s5Y0VgbQaEov9PFk,50
143
- altimate_datapilot_cli-0.0.17.dist-info/LICENSE,sha256=Mf7VqpsmU2QR5_s2Cb_ZeeMB2Q9KW7YXJENZPFZRK1k,1100
144
- altimate_datapilot_cli-0.0.17.dist-info/METADATA,sha256=TTbwpdqhNYT67BniF6Fkdu6KbJ_7EURNhasQPsVE5tg,2382
145
- altimate_datapilot_cli-0.0.17.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
146
- altimate_datapilot_cli-0.0.17.dist-info/entry_points.txt,sha256=0zwgKxN40RLVB5jSmlJz7IH_FBqRtpFdbrdZn-xuQIY,141
147
- altimate_datapilot_cli-0.0.17.dist-info/top_level.txt,sha256=gAOFOdwB00vcxv74y4M1J-nQtPvEatU8-mYViEBcToo,10
148
- altimate_datapilot_cli-0.0.17.dist-info/RECORD,,
144
+ altimate_datapilot_cli-0.0.19.dist-info/AUTHORS.rst,sha256=S4H4zw_v3GVyz5_55jF5Gf_YNG3s5Y0VgbQaEov9PFk,50
145
+ altimate_datapilot_cli-0.0.19.dist-info/LICENSE,sha256=Mf7VqpsmU2QR5_s2Cb_ZeeMB2Q9KW7YXJENZPFZRK1k,1100
146
+ altimate_datapilot_cli-0.0.19.dist-info/METADATA,sha256=WEnAzPrpbzuvZPSfAsuNBcqXK2alr_hp7l3S-mh_pgk,2438
147
+ altimate_datapilot_cli-0.0.19.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
148
+ altimate_datapilot_cli-0.0.19.dist-info/entry_points.txt,sha256=0zwgKxN40RLVB5jSmlJz7IH_FBqRtpFdbrdZn-xuQIY,141
149
+ altimate_datapilot_cli-0.0.19.dist-info/top_level.txt,sha256=gAOFOdwB00vcxv74y4M1J-nQtPvEatU8-mYViEBcToo,10
150
+ altimate_datapilot_cli-0.0.19.dist-info/RECORD,,
datapilot/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.17"
1
+ __version__ = "0.0.19"
datapilot/cli/main.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import click
2
2
 
3
+ from datapilot.core.mcp_utils.mcp import mcp
3
4
  from datapilot.core.platforms.dbt.cli.cli import dbt
4
5
 
5
6
 
@@ -9,3 +10,4 @@ def datapilot():
9
10
 
10
11
 
11
12
  datapilot.add_command(dbt)
13
+ datapilot.add_command(mcp)
@@ -0,0 +1,2 @@
1
+ DBT = []
2
+ SQL = []
@@ -0,0 +1,176 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import shutil
5
+ from dataclasses import dataclass
6
+
7
+ import click
8
+ import pyperclip
9
+ from mcp import ClientSession
10
+ from mcp import StdioServerParameters
11
+ from mcp.client.stdio import stdio_client
12
+
13
+ logging.basicConfig(level=logging.INFO)
14
+
15
+
16
+ @dataclass
17
+ class InputParameter:
18
+ name: str
19
+ type: str
20
+ required: bool
21
+ key: str
22
+ description: str
23
+
24
+
25
+ def find_input_tokens(data):
26
+ tokens = set()
27
+ if isinstance(data, list):
28
+ for item in data:
29
+ tokens.update(find_input_tokens(item))
30
+ elif isinstance(data, dict):
31
+ for value in data.values():
32
+ tokens.update(find_input_tokens(value))
33
+ elif isinstance(data, str) and data.startswith("${input:"):
34
+ tokens.add(data[8:-1].strip())
35
+ return tokens
36
+
37
+
38
+ # New mcp group
39
+ @click.group()
40
+ def mcp():
41
+ """mcp specific commands."""
42
+
43
+
44
+ @mcp.command("inspect-mcp-server")
45
+ def create_mcp_proxy():
46
+ content = click.edit()
47
+ if content is None:
48
+ click.echo("No input provided.")
49
+ return
50
+
51
+ try:
52
+ config = json.loads(content)
53
+ except json.JSONDecodeError:
54
+ click.echo("Invalid JSON content.")
55
+ return
56
+
57
+ inputs = {}
58
+ mcp_config = config.get("mcp", {})
59
+
60
+ # Select server
61
+ # Support both "servers" and "mcpServers" naming conventions
62
+ servers = mcp_config.get("mcpServers", mcp_config.get("servers", {}))
63
+ server_names = list(servers.keys())
64
+
65
+ if not server_names:
66
+ ctx = click.get_current_context()
67
+ click.secho("Error: No servers configured in mcp config (tried keys: 'mcpServers' and 'servers')", fg="red")
68
+ ctx.exit(1)
69
+
70
+ if len(server_names) > 1:
71
+ server_name = click.prompt("Choose a server", type=click.Choice(server_names), show_choices=True)
72
+ else:
73
+ server_name = server_names[0]
74
+
75
+ if server_name in servers:
76
+ server_config = servers[server_name]
77
+
78
+ # Collect input tokens ONLY from this server's config
79
+ input_ids = find_input_tokens(server_config.get("args", []))
80
+ input_ids.update(find_input_tokens(server_config.get("env", {})))
81
+
82
+ # Create prompt definitions using BOTH discovered tokens AND configured inputs
83
+ existing_input_ids = {i["id"] for i in mcp_config.get("inputs", [])}
84
+ inputs_to_prompt = input_ids.intersection(existing_input_ids)
85
+ inputs_to_prompt.update(input_ids) # Add any undiscovered-by-config inputs
86
+
87
+ input_configs = []
88
+ for input_id in inputs_to_prompt:
89
+ input_def = next((d for d in mcp_config.get("inputs", []) if d["id"] == input_id), {})
90
+ inputs[input_id] = click.prompt(
91
+ input_def.get("description", input_id),
92
+ hide_input=True,
93
+ )
94
+ # Create InputParameters config entry
95
+ input_configs.append(
96
+ InputParameter(
97
+ name=input_def.get("name", input_id),
98
+ type="password",
99
+ required=True,
100
+ key=input_id,
101
+ description=input_def.get("description", ""),
102
+ ).__dict__
103
+ )
104
+
105
+ # Replace input tokens in args
106
+ processed_args = [
107
+ inputs.get(arg[8:-1], arg) if isinstance(arg, str) and arg.startswith("${input:") else arg
108
+ for arg in server_config.get("args", [])
109
+ ]
110
+
111
+ # Replace input tokens in environment variables
112
+ processed_env = {
113
+ k: inputs.get(v[8:-1], v) if isinstance(v, str) and v.startswith("${input:") else v
114
+ for k, v in server_config.get("env", {}).items()
115
+ }
116
+
117
+ # Execute with processed parameters
118
+ output = asyncio.run(
119
+ list_tools(server_config=server_config, command=server_config["command"], args=processed_args, env=processed_env)
120
+ )
121
+ # Add processed parameters to output
122
+ output_with_name = {
123
+ "name": server_name,
124
+ "config": input_configs,
125
+ "command": server_config["command"],
126
+ "args": [arg.replace("${input:", "${") if isinstance(arg, str) else arg for arg in server_config.get("args", [])],
127
+ "env": [
128
+ {"key": k, "value": v.replace("${input:", "${") if isinstance(v, str) else v}
129
+ for k, v in server_config.get("env", {}).items()
130
+ ],
131
+ **output,
132
+ }
133
+ output_json = json.dumps(output_with_name, indent=2)
134
+ click.echo(output_json)
135
+ try:
136
+ pyperclip.copy(output_json)
137
+ click.secho("\nOutput copied to clipboard!", fg="green")
138
+ except pyperclip.PyperclipException as e:
139
+ click.secho(f"\nFailed to copy to clipboard: {e!s}", fg="yellow")
140
+
141
+
142
+ async def list_tools(server_config: dict, command: str, args: list[str], env: dict[str, str]):
143
+ command_path = shutil.which(command)
144
+ if not command_path:
145
+ raise click.UsageError(f"Command not found: {command}")
146
+
147
+ try:
148
+ # Only support stdio server type
149
+ server_type = server_config.get("type", "stdio")
150
+ if server_type != "stdio":
151
+ raise click.UsageError(f"Only stdio MCP servers are supported. Found type: {server_type}")
152
+
153
+ server_params = StdioServerParameters(
154
+ command=command_path,
155
+ args=args,
156
+ env=env,
157
+ )
158
+
159
+ async with stdio_client(server_params) as (read, write):
160
+ async with ClientSession(read, write) as session:
161
+ await session.initialize()
162
+ tools = await session.list_tools()
163
+ mcp_tools = [
164
+ {
165
+ "name": tool.name,
166
+ "description": tool.description,
167
+ "inputSchema": tool.inputSchema,
168
+ }
169
+ for tool in tools.tools
170
+ ]
171
+
172
+ return {
173
+ "tools": mcp_tools,
174
+ }
175
+ except Exception as e:
176
+ raise click.UsageError("Could not connect to MCP server: " + str(e)) from e