lkr-dev-cli 0.0.34__tar.gz → 0.0.35__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.
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/Makefile +3 -3
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/PKG-INFO +1 -1
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/codemode.md +4 -4
- lkr_dev_cli-0.0.35/lkr/codemode/main.py +200 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/logger.py +1 -1
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/mcp/main.py +2 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/pyproject.toml +1 -1
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/test_codemode2.py +2 -0
- lkr_dev_cli-0.0.34/lkr/codemode/main.py +0 -104
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.github/workflows/release.yml +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.github/workflows/test-dependencies.yml +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.gitignore +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.python-version +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.vscode/launch.json +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/.vscode/settings.json +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/Dockerfile +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/LICENSE +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/README.md +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/cloudbuild.yaml +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/__init__.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/auth/__init__.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/auth/main.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/auth/oauth.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/auth_service.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/classes.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/codemode/__init__.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/constants.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/custom_types.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/exceptions.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/main.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/mcp/classes.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/mcp/utils.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/observability/classes.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/observability/embed_container.html +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/observability/main.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/observability/utils.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/tools/classes.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/tools/main.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr/tools/permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/lkr.md +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/TESTING.md +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/test_codemode.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/test_dependency_resolution.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/test_deps.sh +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/tests/test_permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.34 → lkr_dev_cli-0.0.35}/uv.lock +0 -0
|
@@ -17,10 +17,10 @@ codemode-start:
|
|
|
17
17
|
@echo "Add this to your mcpServers config:"
|
|
18
18
|
@echo "{"
|
|
19
19
|
@echo " \"mcpServers\": {"
|
|
20
|
-
@echo " \"
|
|
20
|
+
@echo " \"lkr_codemode\": {"
|
|
21
21
|
@echo " \"command\": \"uvx\","
|
|
22
|
-
@echo " \"args\": [\"--from\", \"lkr-dev-cli[codemode]\", \"lkr\", \"code-mode\", \"run\"]"
|
|
22
|
+
@echo " \"args\": [\"-q\", \"--from\", \"lkr-dev-cli[codemode]\", \"lkr\", \"code-mode\", \"run\"]"
|
|
23
23
|
@echo " }"
|
|
24
24
|
@echo " }"
|
|
25
25
|
@echo "}"
|
|
26
|
-
uv run lkr code-mode run
|
|
26
|
+
uv run -q lkr code-mode run
|
|
@@ -19,7 +19,7 @@ The tool instantiates Looker SDK natively, searches all bound methods, and passe
|
|
|
19
19
|
### 1. Starting the Server
|
|
20
20
|
To immediately trigger the stdio listener, use:
|
|
21
21
|
```bash
|
|
22
|
-
uvx --from lkr-dev-cli[codemode] lkr code-mode run
|
|
22
|
+
uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
### 2. Client Configuration
|
|
@@ -28,9 +28,9 @@ To hook this server into Cursor or Claude Desktop natively over stdio, append th
|
|
|
28
28
|
```json
|
|
29
29
|
{
|
|
30
30
|
"mcpServers": {
|
|
31
|
-
"
|
|
31
|
+
"lkr_codemode": {
|
|
32
32
|
"command": "uvx",
|
|
33
|
-
"args": ["--from", "lkr-dev-cli[codemode]", "lkr", "code-mode", "run"],
|
|
33
|
+
"args": ["-q", "--from", "lkr-dev-cli[codemode]", "lkr", "code-mode", "run"],
|
|
34
34
|
"env": {
|
|
35
35
|
"LOOKERSDK_BASE_URL": "https://your.looker.instance",
|
|
36
36
|
"LOOKERSDK_CLIENT_ID": "your-client-id",
|
|
@@ -44,7 +44,7 @@ To hook this server into Cursor or Claude Desktop natively over stdio, append th
|
|
|
44
44
|
### 3. Visual Inspector
|
|
45
45
|
To check things out on a web panel:
|
|
46
46
|
```bash
|
|
47
|
-
npx @modelcontextprotocol/inspector uvx --from lkr-dev-cli[codemode] lkr code-mode run
|
|
47
|
+
npx @modelcontextprotocol/inspector uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
```
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
from contextlib import redirect_stdout
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
import pydantic_monty
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
|
|
11
|
+
from lkr.auth_service import get_auth, is_auth_expired
|
|
12
|
+
from lkr.classes import LkrCtxObj
|
|
13
|
+
from lkr.logger import logger
|
|
14
|
+
|
|
15
|
+
__all__ = ["group"]
|
|
16
|
+
|
|
17
|
+
mcp = FastMCP("lkr:codemode")
|
|
18
|
+
group = typer.Typer()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_mcp_sdk(ctx: LkrCtxObj):
|
|
23
|
+
sdk = get_auth(ctx).get_current_sdk(prompt_refresh_invalid_token=True)
|
|
24
|
+
sdk.auth.settings.agent_tag += "-codemode"
|
|
25
|
+
return sdk
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def to_primitive(obj):
|
|
30
|
+
seen = set()
|
|
31
|
+
|
|
32
|
+
def _to_primitive(o):
|
|
33
|
+
if isinstance(o, (str, int, float, bool, type(None))):
|
|
34
|
+
return o
|
|
35
|
+
|
|
36
|
+
obj_id = id(o)
|
|
37
|
+
if obj_id in seen:
|
|
38
|
+
return f"<Circular reference to {type(o).__name__}>"
|
|
39
|
+
seen.add(obj_id)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
if isinstance(o, list):
|
|
43
|
+
return [_to_primitive(item) for item in o]
|
|
44
|
+
elif isinstance(o, dict):
|
|
45
|
+
return {k: _to_primitive(v) for k, v in o.items()}
|
|
46
|
+
else:
|
|
47
|
+
try:
|
|
48
|
+
return _to_primitive(vars(o))
|
|
49
|
+
except TypeError:
|
|
50
|
+
return str(o)
|
|
51
|
+
except Exception:
|
|
52
|
+
return str(o)
|
|
53
|
+
finally:
|
|
54
|
+
seen.remove(obj_id)
|
|
55
|
+
|
|
56
|
+
return _to_primitive(obj)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@mcp.tool()
|
|
61
|
+
def run_python_code(code: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Execute Python code safely with access to all Looker SDK methods as global functions.
|
|
64
|
+
Capture the result.
|
|
65
|
+
|
|
66
|
+
AGENT HINTS:
|
|
67
|
+
- Use `dir()` and `help('method_name')` to discover available SDK methods.
|
|
68
|
+
- Do not instantiate an SDK; use global functions directly (e.g. `me()`).
|
|
69
|
+
- Returned Looker models are primitive dictionaries (use `obj["id"]`, not `obj.id`).
|
|
70
|
+
- Return your output (avoid using print() as it may pollute the stdio stream).
|
|
71
|
+
- Recursion: Use `folder_children(id)` to traverse nested folders.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
ctx = LkrCtxObj(force_oauth=False)
|
|
75
|
+
sdk = get_mcp_sdk(ctx)
|
|
76
|
+
|
|
77
|
+
external_funcs = {}
|
|
78
|
+
for name, method in inspect.getmembers(sdk, predicate=inspect.ismethod):
|
|
79
|
+
if not name.startswith('_'):
|
|
80
|
+
# Wrap in a lambda to recursively convert output to primitives
|
|
81
|
+
def make_wrapper(m):
|
|
82
|
+
def wrapper(*args, **kwargs):
|
|
83
|
+
res = m(*args, **kwargs)
|
|
84
|
+
return to_primitive(res)
|
|
85
|
+
return wrapper
|
|
86
|
+
external_funcs[name] = make_wrapper(method)
|
|
87
|
+
|
|
88
|
+
# Provide helper functions for the LLM to explore the SDK
|
|
89
|
+
external_funcs['dir'] = lambda: list(external_funcs.keys())
|
|
90
|
+
|
|
91
|
+
def _help(name: str) -> str:
|
|
92
|
+
if name in external_funcs:
|
|
93
|
+
if hasattr(sdk, name):
|
|
94
|
+
return getattr(sdk, name).__doc__ or "No docstring available."
|
|
95
|
+
return f"{name} is a built-in helper function."
|
|
96
|
+
return f"Function '{name}' not found."
|
|
97
|
+
external_funcs['help'] = _help
|
|
98
|
+
|
|
99
|
+
m = pydantic_monty.Monty(code)
|
|
100
|
+
|
|
101
|
+
# Redirect stdout to capture any print() statements
|
|
102
|
+
# and prevent them from corrupting the JSON-RPC stream
|
|
103
|
+
f = io.StringIO()
|
|
104
|
+
with redirect_stdout(f):
|
|
105
|
+
result = m.run(external_functions=external_funcs)
|
|
106
|
+
|
|
107
|
+
printed_output = f.getvalue()
|
|
108
|
+
|
|
109
|
+
# m.run() returns the evaluated result of the last expression (which is already a primitive)
|
|
110
|
+
try:
|
|
111
|
+
# Use JSON for nice formatting if it's a dict/list
|
|
112
|
+
if result is not None:
|
|
113
|
+
if isinstance(result, str):
|
|
114
|
+
output = result
|
|
115
|
+
else:
|
|
116
|
+
output = json.dumps(result, indent=2, default=str)
|
|
117
|
+
else:
|
|
118
|
+
output = ""
|
|
119
|
+
except Exception:
|
|
120
|
+
output = repr(result)
|
|
121
|
+
|
|
122
|
+
if printed_output:
|
|
123
|
+
return f"PRINTED OUTPUT:\n{printed_output}\nRESULT:\n{output}"
|
|
124
|
+
return output
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Error executing Monty: {e}")
|
|
127
|
+
if is_auth_expired(e):
|
|
128
|
+
return "Error: Your Looker OAuth session has expired. Please run 'lkr auth login' to re-authenticate."
|
|
129
|
+
return f"Error: {str(e)}"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@mcp.resource("looker://agent-hints")
|
|
133
|
+
def get_agent_hints() -> str:
|
|
134
|
+
"""Crucial hints and rules for AI agents writing Python for the Looker SDK."""
|
|
135
|
+
return """
|
|
136
|
+
1. **Global Functions**: All Looker SDK methods are global. Use `me()`, not `sdk.me()`.
|
|
137
|
+
2. **Dict Access**: Return values are dictionaries, not objects. Use `user["id"]`, not `user.id`.
|
|
138
|
+
3. **Discovery**: Use `dir()` and `help('method')` to explore the SDK.
|
|
139
|
+
4. **No Imports**: Do not `import looker_sdk`.
|
|
140
|
+
5. **Output**: Return your results instead of using `print()`.
|
|
141
|
+
6. **Efficiency**: Always use the `fields` parameter (e.g., `all_dashboards(fields="id,title")`) when listing many objects to prevent timeouts.
|
|
142
|
+
7. **Nested Folders**: Use `folder_children(id)` to get sub-folders.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@mcp.prompt("explore_looker_sdk")
|
|
147
|
+
def explore_looker_sdk() -> str:
|
|
148
|
+
"""Provide examples for how to explore the Looker SDK in code mode."""
|
|
149
|
+
return '''
|
|
150
|
+
To explore the Looker SDK, you can use the injected `dir()` and `help()` helpers.
|
|
151
|
+
Do not use print() as it may corrupt the MCP output stream; always return the result.
|
|
152
|
+
|
|
153
|
+
Example 1: Find all dashboard-related methods
|
|
154
|
+
```python
|
|
155
|
+
return [m for m in dir() if 'dashboard' in m.lower()]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Example 2: Get the description of a specific method
|
|
159
|
+
```python
|
|
160
|
+
return help('search_dashboards')
|
|
161
|
+
```
|
|
162
|
+
'''
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@mcp.prompt("list_personal_dashboards")
|
|
166
|
+
def list_personal_dashboards() -> str:
|
|
167
|
+
"""Provide an example of how to recursively list dashboards in a user's personal folder."""
|
|
168
|
+
return '''
|
|
169
|
+
Here is a robust example of how to traverse the folder hierarchy using the Looker SDK in code mode:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
def get_all_items(folder_id):
|
|
173
|
+
f = folder(folder_id)
|
|
174
|
+
items = {
|
|
175
|
+
"dashboards": f.get("dashboards", []),
|
|
176
|
+
"looks": f.get("looks", [])
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for child in folder_children(folder_id):
|
|
180
|
+
child_items = get_all_items(child["id"])
|
|
181
|
+
items["dashboards"].extend(child_items["dashboards"])
|
|
182
|
+
items["looks"].extend(child_items["looks"])
|
|
183
|
+
|
|
184
|
+
return items
|
|
185
|
+
|
|
186
|
+
me_data = me()
|
|
187
|
+
return get_all_items(me_data["personal_folder_id"])
|
|
188
|
+
```
|
|
189
|
+
'''
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@group.command(name="run")
|
|
193
|
+
def run(
|
|
194
|
+
ctx: typer.Context,
|
|
195
|
+
debug: bool = typer.Option(False, help="Debug mode"),
|
|
196
|
+
):
|
|
197
|
+
mcp.run()
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
mcp.run("sse")
|
|
@@ -36,7 +36,7 @@ theme = Theme(
|
|
|
36
36
|
) if RICH_AVAILABLE else None
|
|
37
37
|
|
|
38
38
|
# Create a console for logging
|
|
39
|
-
console = Console(theme=theme) if RICH_AVAILABLE else None
|
|
39
|
+
console = Console(theme=theme, stderr=True) if RICH_AVAILABLE else None
|
|
40
40
|
|
|
41
41
|
# Configure the logging handler
|
|
42
42
|
handler = RichHandler(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# server.py
|
|
2
|
+
import sys
|
|
2
3
|
import threading
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
@@ -549,6 +550,7 @@ def run(
|
|
|
549
550
|
set_log_level(LogLevel.DEBUG)
|
|
550
551
|
else:
|
|
551
552
|
set_log_level(LogLevel.ERROR)
|
|
553
|
+
sys.stdout = sys.stderr
|
|
552
554
|
mcp.run()
|
|
553
555
|
|
|
554
556
|
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
import pydantic_monty
|
|
6
|
-
from mcp.server.fastmcp import FastMCP
|
|
7
|
-
|
|
8
|
-
from lkr.auth_service import get_auth, is_auth_expired
|
|
9
|
-
from lkr.classes import LkrCtxObj
|
|
10
|
-
from lkr.logger import logger
|
|
11
|
-
|
|
12
|
-
__all__ = ["group"]
|
|
13
|
-
|
|
14
|
-
mcp = FastMCP("lkr:codemode")
|
|
15
|
-
group = typer.Typer()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def get_mcp_sdk(ctx: LkrCtxObj):
|
|
20
|
-
sdk = get_auth(ctx).get_current_sdk(prompt_refresh_invalid_token=True)
|
|
21
|
-
sdk.auth.settings.agent_tag += "-codemode"
|
|
22
|
-
return sdk
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def to_primitive(obj):
|
|
27
|
-
seen = set()
|
|
28
|
-
|
|
29
|
-
def _to_primitive(o):
|
|
30
|
-
if isinstance(o, (str, int, float, bool, type(None))):
|
|
31
|
-
return o
|
|
32
|
-
|
|
33
|
-
obj_id = id(o)
|
|
34
|
-
if obj_id in seen:
|
|
35
|
-
return f"<Circular reference to {type(o).__name__}>"
|
|
36
|
-
seen.add(obj_id)
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
if isinstance(o, list):
|
|
40
|
-
return [_to_primitive(item) for item in o]
|
|
41
|
-
elif isinstance(o, dict):
|
|
42
|
-
return {k: _to_primitive(v) for k, v in o.items()}
|
|
43
|
-
else:
|
|
44
|
-
try:
|
|
45
|
-
return _to_primitive(vars(o))
|
|
46
|
-
except TypeError:
|
|
47
|
-
return str(o)
|
|
48
|
-
except Exception:
|
|
49
|
-
return str(o)
|
|
50
|
-
finally:
|
|
51
|
-
seen.remove(obj_id)
|
|
52
|
-
|
|
53
|
-
return _to_primitive(obj)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@mcp.tool()
|
|
58
|
-
def run_python_code(code: str) -> str:
|
|
59
|
-
"""
|
|
60
|
-
Execute Python code safely with access to all Looker SDK methods as global functions.
|
|
61
|
-
Capture the result and any print outputs.
|
|
62
|
-
"""
|
|
63
|
-
try:
|
|
64
|
-
ctx = LkrCtxObj(force_oauth=False)
|
|
65
|
-
sdk = get_mcp_sdk(ctx)
|
|
66
|
-
|
|
67
|
-
external_funcs = {}
|
|
68
|
-
for name, method in inspect.getmembers(sdk, predicate=inspect.ismethod):
|
|
69
|
-
if not name.startswith('_'):
|
|
70
|
-
# Wrap in a lambda to recursively convert output to primitives
|
|
71
|
-
def make_wrapper(m):
|
|
72
|
-
def wrapper(*args, **kwargs):
|
|
73
|
-
res = m(*args, **kwargs)
|
|
74
|
-
return to_primitive(res)
|
|
75
|
-
return wrapper
|
|
76
|
-
external_funcs[name] = make_wrapper(method)
|
|
77
|
-
|
|
78
|
-
m = pydantic_monty.Monty(code)
|
|
79
|
-
result = m.run(external_functions=external_funcs)
|
|
80
|
-
|
|
81
|
-
# Monty run returns a MontyComplete or None
|
|
82
|
-
output = str(getattr(result, "output", "")) if result is not None else ""
|
|
83
|
-
|
|
84
|
-
# Try to append captured stdout if available on the object
|
|
85
|
-
stdout = getattr(result, "stdout", None) if result is not None else None
|
|
86
|
-
if stdout:
|
|
87
|
-
return f"Output:\n{output}\nStdout:\n{stdout}"
|
|
88
|
-
return output
|
|
89
|
-
except Exception as e:
|
|
90
|
-
logger.error(f"Error executing Monty: {e}")
|
|
91
|
-
if is_auth_expired(e):
|
|
92
|
-
return "Error: Your Looker OAuth session has expired. Please run 'lkr auth login' to re-authenticate."
|
|
93
|
-
return f"Error: {str(e)}"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@group.command(name="run")
|
|
97
|
-
def run(
|
|
98
|
-
ctx: typer.Context,
|
|
99
|
-
debug: bool = typer.Option(False, help="Debug mode"),
|
|
100
|
-
):
|
|
101
|
-
mcp.run()
|
|
102
|
-
|
|
103
|
-
if __name__ == "__main__":
|
|
104
|
-
mcp.run("sse")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|