Skip to content

Commit 554c7b1

Browse files
Fixing issues on quickstart and adding tests
1 parent 80e1fe8 commit 554c7b1

11 files changed

Lines changed: 412 additions & 50 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Run Pytest with uv
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v3
16+
17+
- name: Install uv
18+
run: |
19+
curl -LsSf https://astral.sh/uv/install.sh | sh
20+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v4
24+
with:
25+
python-version: '3.13'
26+
27+
- name: Run tests
28+
run: |
29+
uv run pytest tests# if not in requirements.txt

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true
7+
}

README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ architecture-beta
4949

5050
The MCP server runs locally on the machine that runs the LLM frontend (eg Claude). The installation steps are simple
5151

52-
1. Clone or download this repository.
52+
1. Clone or download this repository.
5353
2. Install the [uv](https://docs.astral.sh/uv/guides/install-python/) package manager. The MCP server requires python (3.11 or later)
5454
3. Do a sanity check by doing
5555

@@ -83,9 +83,9 @@ There are two configurations necessary before the MCP server can be invoked.
8383

8484
## Quick start
8585

86-
The quickest way to do this setup is -
86+
The quickest way to do this setup is -
8787

88-
1. Create the dremio config file using
88+
1. Create the dremio config file using
8989

9090
```shell
9191
$ uv run dremio-mcp-server config create dremio \
@@ -95,13 +95,13 @@ $ uv run dremio-mcp-server config create dremio \
9595
# --project-id <dremio project id>
9696
```
9797

98-
2. Download and install Claude desktop. And then create the Claude config file using
98+
2. Download and install Claude desktop. And then create the Claude config file using
9999

100100
```shell
101101
$ uv run dremio-mcp-server config create claude
102102
```
103103

104-
3. Validate the config files using
104+
3. Validate the config files using
105105

106106
```shell
107107
$ uv run dremio-mcp-server config list --type claude`
@@ -229,6 +229,18 @@ This will pickup the default location of MCP server config file. It can also be
229229

230230
This repository is intended to be open source software that encourages contributions of any kind, like adding features, reporting issues and contributing fixes. This is not a part of Dremio product support.
231231

232+
## Testing
233+
234+
The project uses pytest for testing. To run the tests:
235+
236+
```shell
237+
# Run all tests
238+
$ uv run pytest tests
239+
240+
```
241+
242+
GitHub Actions automatically runs tests on pull requests and pushes to the main branch.
243+
232244
## Contributing
233245

234246
Please see our [Contributing Guide](CONTRIBUTING.md) for details on:
@@ -237,3 +249,4 @@ Please see our [Contributing Guide](CONTRIBUTING.md) for details on:
237249
- Making contributions
238250
- Code style guidelines
239251
- Documentation requirements
252+
- Running tests

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ dependencies = [
2424
"prompt-toolkit>=3.0.50",
2525
"pydantic>=2.10.6",
2626
"pydantic-settings>=2.8.1",
27+
"pytest>=8.3.5",
28+
"pytest-asyncio>=0.26.0",
2729
"pyyaml>=6.0.2",
2830
"requests>=2.32.3",
2931
"rich>=13.9.4",

pytest.ini

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[pytest]
2+
testpaths = tests
3+
python_files = test_*.py
4+
python_classes = Test*
5+
python_functions = test_*
6+
asyncio_mode = strict
7+
8+
# Display summary info for skipped, xfailed, xpassed tests
9+
# along with the percentage of passing tests at the end
10+
addopts = -v --showlocals
11+
12+
# Set the default timeout for async tests (in seconds)
13+
asyncio_timeout = 30

src/dremioai/config/settings.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from dremioai.config.tools import ToolType
2121
from enum import auto, StrEnum
2222
from pathlib import Path
23-
from yaml import safe_load
23+
from yaml import safe_load, add_representer, dump
2424
from functools import reduce
2525
from operator import ior
2626
from shutil import which
@@ -150,8 +150,8 @@ class Anthropic(BaseModel):
150150

151151

152152
class BeeAI(BaseModel):
153-
mcp_server: Optional[MCPServer] = Field(default=None, alias="mcpServer")
154-
sliding_memory_size: Optional[int] = Field(default=10, alias="slidingMemorySize")
153+
mcp_server: Optional[MCPServer] = Field(default=None)
154+
sliding_memory_size: Optional[int] = Field(default=10)
155155
anthropic: Optional[Anthropic] = Field(default=None)
156156
openai: Optional[OpenAi] = Field(default=None)
157157
ollama: Optional[Ollama] = Field(default=None)
@@ -223,9 +223,14 @@ def configure(cfg: Union[str, Path] = None, force=False) -> ContextVar[Settings]
223223
if cfg is None:
224224
cfg = default_config()
225225

226+
if not cfg.exists():
227+
print(f"Creating default config file: {cfg!s}")
228+
cfg.parent.mkdir(parents=True, exist_ok=True)
229+
cfg.touch()
230+
226231
with cfg.open() as f:
227232
s = safe_load(f)
228-
_settings.set(Settings.model_validate(s))
233+
_settings.set(Settings.model_validate(s if s else {}))
229234

230235
return _settings
231236

@@ -261,3 +266,31 @@ async def _call():
261266

262267
ctx = copy_context()
263268
return await _call()
269+
270+
271+
def write_settings(
272+
cfg: Path = None, inst: Settings = None, dry_run: bool = False
273+
) -> str | None:
274+
if cfg is None:
275+
cfg = default_config()
276+
277+
if not isinstance(inst, Settings):
278+
inst = instance()
279+
280+
d = inst.model_dump(
281+
exclude_none=True, mode="json", exclude_unset=True, by_alias=True
282+
)
283+
add_representer(
284+
str,
285+
lambda dumper, data: dumper.represent_scalar(
286+
"tag:yaml.org,2002:str", data, style=('"' if "@" in data else None)
287+
),
288+
)
289+
if dry_run:
290+
return dump(d)
291+
292+
if not cfg.exists() or not cfg.parent.exists():
293+
cfg.parent.mkdir(parents=True, exist_ok=True)
294+
295+
with cfg.open("w") as f:
296+
dump(d, f)

src/dremioai/servers/mcp.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
from mcp.server.fastmcp import FastMCP
1818
from mcp.server.fastmcp.prompts import Prompt
1919
from mcp.server.fastmcp.resources import FunctionResource
20+
from mcp.cli.claude import get_claude_config_path
2021
from pydantic.networks import AnyUrl
2122
from dremioai.tools import tools
2223
import os
23-
from typing import List, Union, Annotated, Optional, Tuple
24+
from typing import List, Union, Annotated, Optional, Tuple, Dict, Any
2425
from functools import reduce
2526
from operator import ior
2627
from pathlib import Path
@@ -34,6 +35,7 @@
3435
from shutil import which
3536
import asyncio
3637
from yaml import dump, add_representer
38+
import sys
3739

3840

3941
def init(
@@ -71,10 +73,10 @@ def init(
7173

7274

7375
app = None
74-
if __name__ != "__main__":
75-
if mode := os.environ.get("MODE"):
76-
mode = [tools.ToolType[m.upper()] for m in ",".split(mode)]
77-
app = init(mode=mode)
76+
# if __name__ != "__main__":
77+
# if mode := os.environ.get("MODE"):
78+
# mode = [tools.ToolType[m.upper()] for m in ",".split(mode)]
79+
# app = init(mode=mode)
7880

7981

8082
def _mode() -> List[str]:
@@ -154,6 +156,18 @@ class ConfigTypes(StrEnum):
154156
claude = auto()
155157

156158

159+
def get_claude_config_path() -> Path:
160+
# copy of the function from mcp sdk, but returns the path whether or not
161+
# it exists
162+
dir = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude")
163+
match sys.platform:
164+
case "win32":
165+
dir = Path(Path.home(), "AppData", "Roaming", "Claude")
166+
case "darwin":
167+
dir = Path(Path.home(), "Library", "Application Support", "Claude")
168+
return dir / "claude_desktop_config.json"
169+
170+
157171
@tc.command("list", help="Show default configuration, if it exists")
158172
def show_default_config(
159173
show_filename: Annotated[
@@ -179,13 +193,7 @@ def show_default_config(
179193
)
180194
)
181195
case ConfigTypes.claude:
182-
cc = (
183-
Path.home()
184-
/ "Library"
185-
/ "Application Support"
186-
/ "Claude"
187-
/ "claude_desktop_config.json"
188-
)
196+
cc = get_claude_config_path()
189197
pp(f"Default config file: '{cc!s}' (exists = {cc.exists()!s})")
190198
if not show_filename:
191199
pp(load(cc.open()))
@@ -199,42 +207,40 @@ def show_default_config(
199207
tc.add_typer(cc)
200208

201209

202-
@cc.command("claude", help="Create a default configuration file for Claude")
203-
def create_default_config(
204-
dry_run: Annotated[
205-
bool, Option(help="Dry run, do not overwrite the config file. Just print it")
206-
] = False,
207-
):
210+
def create_default_mcpserver_config() -> Dict[str, Any]:
208211
if (uv := which("uv")) is not None:
209212
uv = Path(uv).resolve()
210213
dir = str(Path(os.getcwd()).resolve())
211-
dmcp = {
212-
"Dremio": {
213-
"command": str(uv),
214-
"args": ["run", "--directory", dir, "dremio-mcp-server", "run"],
215-
}
214+
return {
215+
"command": str(uv),
216+
"args": ["run", "--directory", dir, "dremio-mcp-server", "run"],
216217
}
217-
cc = (
218-
Path.home()
219-
/ "Library"
220-
/ "Application Support"
221-
/ "Claude"
222-
/ "claude_desktop_config.json"
223-
)
224-
c = load(cc.open()) if cc.exists() else {"mcpServers": {}}
225-
c["mcpServers"].update(dmcp)
226-
if dry_run:
227-
pp(c)
228-
else:
229-
if not cc.exists():
230-
cc.parent.mkdir(parents=True, exist_ok=True)
231-
with cc.open("w") as f:
232-
jdump(c, f)
233-
pp(f"Created default config file: {cc!s}")
234218
else:
235219
raise FileNotFoundError("uv command not found. Please install uv")
236220

237221

222+
@cc.command("claude", help="Create a default configuration file for Claude")
223+
def create_default_config(
224+
dry_run: Annotated[
225+
bool, Option(help="Dry run, do not overwrite the config file. Just print it")
226+
] = False,
227+
):
228+
cc = get_claude_config_path()
229+
dcmp = {"Dremio": cc}
230+
c = load(cc.open()) if cc.exists() else {"mcpServers": {}}
231+
c["mcpServers"].update(dcmp)
232+
if dry_run:
233+
pp(c)
234+
return
235+
236+
if not cc.exists():
237+
cc.parent.mkdir(parents=True, exist_ok=True)
238+
239+
with cc.open("w") as f:
240+
jdump(c, f)
241+
pp(f"Created default config file: {cc!s}")
242+
243+
238244
@cc.command("dremioai", help="Create a default configuration file")
239245
def create_default_config(
240246
uri: Annotated[
@@ -273,7 +279,7 @@ def create_default_config(
273279
"enable_experimental": enable_experimental,
274280
}
275281
)
276-
ts = settings.Tools.model_validate({"server_mode": tools.ToolType.FOR_SELF})
282+
ts = settings.Tools.model_validate({"server_mode": mode})
277283
settings.configure(settings.default_config(), force=True)
278284
settings.instance().dremio = dremio
279285
settings.instance().tools = ts

0 commit comments

Comments
 (0)