Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "dacli"
version = "0.4.37"
version = "0.4.38"
description = "Documentation Access CLI - Navigate and query large documentation projects"
readme = "README.md"
license = { text = "MIT" }
Expand All @@ -21,9 +21,9 @@ dependencies = [
"click>=8.3.1",
"cryptography>=46.0.5", # Security fix: CVE-2026-26007
"fastapi>=0.115.0",
"fastmcp>=2.14.0,<3",
"fastmcp>=3.2.0,<4",
"pathspec>=1.0.3",
"pydocket<0.17", # Pin to avoid breaking change in 0.17 (fastmcp compatibility)
"pydocket>=0.17.2,<0.19", # Required by fastmcp 2.14.7
"pyyaml>=6.0",
"uvicorn>=0.30.0",
]
Expand Down
2 changes: 1 addition & 1 deletion src/dacli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
through hierarchical, content-aware access via the Model Context Protocol (MCP).
"""

__version__ = "0.4.37"
__version__ = "0.4.38"
18 changes: 9 additions & 9 deletions tests/test_circular_include_validation_251.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
warnings.
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -64,9 +65,8 @@ def test_circular_include_reported_as_error(self, temp_circular_include: Path):
"""Issue #251: Circular includes should be reported as circular_include errors."""
mcp = create_mcp_server(temp_circular_include)

# Access validate_structure tool
tools = {t.name: t for t in mcp._tool_manager._tools.values()}
result = tools["validate_structure"].fn()
validate_tool = asyncio.run(mcp.get_tool("validate_structure"))
result = validate_tool.fn()
Comment on lines +68 to +69

# Should NOT be valid due to circular include
assert (
Expand All @@ -83,8 +83,8 @@ def test_circular_include_not_reported_as_orphaned(self, temp_circular_include:
"""Issue #251: Files involved in circular includes should NOT be reported as orphaned."""
mcp = create_mcp_server(temp_circular_include)

tools = {t.name: t for t in mcp._tool_manager._tools.values()}
result = tools["validate_structure"].fn()
validate_tool = asyncio.run(mcp.get_tool("validate_structure"))
result = validate_tool.fn()
Comment on lines +86 to +87

# Check that no orphaned_file warnings exist for the circular files
orphaned_warnings = [w for w in result["warnings"] if w["type"] == "orphaned_file"]
Expand All @@ -101,8 +101,8 @@ def test_circular_include_error_contains_chain(self, temp_circular_include: Path
"""Circular include error should include the include chain."""
mcp = create_mcp_server(temp_circular_include)

tools = {t.name: t for t in mcp._tool_manager._tools.values()}
result = tools["validate_structure"].fn()
validate_tool = asyncio.run(mcp.get_tool("validate_structure"))
result = validate_tool.fn()
Comment on lines +104 to +105

circular_errors = [e for e in result["errors"] if e["type"] == "circular_include"]
assert len(circular_errors) >= 1
Expand All @@ -116,8 +116,8 @@ def test_self_circular_include_reported_as_error(self, temp_self_circular_includ
"""A file that includes itself should be reported as circular_include error."""
mcp = create_mcp_server(temp_self_circular_include)

tools = {t.name: t for t in mcp._tool_manager._tools.values()}
result = tools["validate_structure"].fn()
validate_tool = asyncio.run(mcp.get_tool("validate_structure"))
result = validate_tool.fn()
Comment on lines +119 to +120

# Should NOT be valid
assert result["valid"] is False
Expand Down
7 changes: 2 additions & 5 deletions tests/test_elements_help_types_259.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
admonition, code, image, list, plantuml, table (not 'diagram').
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -39,11 +40,7 @@ def test_cli_help_lists_correct_types(self, docs_dir: Path):
def test_mcp_tool_docstring_lists_correct_types(self, docs_dir: Path):
"""MCP get_elements tool docstring should list correct types in Args section."""
mcp = create_mcp_server(docs_dir)
elements_tool = None
for tool in mcp._tool_manager._tools.values():
if tool.name == "get_elements":
elements_tool = tool
break
elements_tool = asyncio.run(mcp.get_tool("get_elements"))
assert elements_tool is not None
docstring = elements_tool.fn.__doc__
# The Args section listing valid types should include the correct ones
Expand Down
5 changes: 3 additions & 2 deletions tests/test_get_dependencies_67.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for get_dependencies endpoint (Issue #67, Phase 1: include_tree)."""

import asyncio
from pathlib import Path

from dacli.asciidoc_parser import AsciidocDocument, AsciidocStructureParser, IncludeInfo
Expand Down Expand Up @@ -164,8 +165,8 @@ def test_get_dependencies_tool_exists(self, tmp_path):
from dacli.mcp_app import create_mcp_server

mcp = create_mcp_server(tmp_path)
tool_names = [t.name for t in mcp._tool_manager._tools.values()]
assert "get_dependencies" in tool_names
tool = asyncio.run(mcp.get_tool("get_dependencies"))
assert tool is not None

def test_get_dependencies_tool_returns_structure(self, tmp_path):
"""get_dependencies MCP tool returns include_tree and cross_references."""
Expand Down
9 changes: 5 additions & 4 deletions tests/test_manipulation_bugs_244_245.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
the next heading when inserted content itself is a heading.
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -236,10 +237,10 @@ class TestBug244InsertAfterBlankLine:

def _get_insert_tool(self, mcp):
"""Helper to get the insert_content tool from MCP server."""
for tool in mcp._tool_manager._tools.values():
if tool.name == "insert_content":
return tool
raise AssertionError("insert_content tool not found")
tool = asyncio.run(mcp.get_tool("insert_content"))
if tool is None:
raise AssertionError("insert_content tool not found")
return tool

def test_insert_heading_after_section_blank_line_before_next(self, md_for_insert: Path):
"""Bug #244: When inserting heading content after a section, there should
Expand Down
14 changes: 3 additions & 11 deletions tests/test_mcp_insert_after_229.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
not just the section's own content.
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -42,12 +43,7 @@ def test_insert_after_parent_goes_after_children(self, temp_doc_with_children: P
"""Issue #229: Insert after parent should go after all children."""
mcp = create_mcp_server(temp_doc_with_children)

# Get the insert_content tool
insert_tool = None
for tool in mcp._tool_manager._tools.values():
if tool.name == "insert_content":
insert_tool = tool
break
insert_tool = asyncio.run(mcp.get_tool("insert_content"))

assert insert_tool is not None, "insert_content tool not found"

Expand Down Expand Up @@ -79,11 +75,7 @@ def test_insert_after_leaf_section(self, temp_doc_with_children: Path):
"""Insert after a section without children should work correctly."""
mcp = create_mcp_server(temp_doc_with_children)

insert_tool = None
for tool in mcp._tool_manager._tools.values():
if tool.name == "insert_content":
insert_tool = tool
break
insert_tool = asyncio.run(mcp.get_tool("insert_content"))

# Insert after "Child Section" (no children)
result = insert_tool.fn(
Expand Down
13 changes: 3 additions & 10 deletions tests/test_mcp_insert_blank_lines_232.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Add blank line after content when next line is a heading
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -39,11 +40,7 @@ def test_insert_before_adds_blank_line_after_content(self, temp_doc_for_blank_li
"""Issue #232: Insert before should add blank line when next is heading."""
mcp = create_mcp_server(temp_doc_for_blank_lines)

insert_tool = None
for tool in mcp._tool_manager._tools.values():
if tool.name == "insert_content":
insert_tool = tool
break
insert_tool = asyncio.run(mcp.get_tool("insert_content"))

# Insert content before Section 2
result = insert_tool.fn(
Comment on lines +43 to 46
Expand Down Expand Up @@ -72,11 +69,7 @@ def test_insert_after_adds_blank_line_before_next_heading(self, temp_doc_for_bla
"""Issue #232: Insert after should add blank line before next heading."""
mcp = create_mcp_server(temp_doc_for_blank_lines)

insert_tool = None
for tool in mcp._tool_manager._tools.values():
if tool.name == "insert_content":
insert_tool = tool
break
insert_tool = asyncio.run(mcp.get_tool("insert_content"))

# Insert content after Section 1
result = insert_tool.fn(
Comment on lines +72 to 75
Expand Down
35 changes: 6 additions & 29 deletions tests/test_mcp_negative_params_220.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
content_limit should be rejected with a clear error message.
"""

import asyncio
from pathlib import Path

import pytest
Expand Down Expand Up @@ -50,12 +51,7 @@ class TestSearchNegativeMaxResults:

def test_search_negative_max_results_should_error(self, mcp_server):
"""Issue #220: search with negative max_results should raise error."""
# Get the search tool
search_tool = None
for tool in mcp_server._tool_manager._tools.values():
if tool.name == "search":
search_tool = tool
break
search_tool = asyncio.run(mcp_server.get_tool("search"))

assert search_tool is not None, "search tool not found"

Expand All @@ -69,12 +65,7 @@ class TestGetStructureNegativeMaxDepth:

def test_get_structure_negative_max_depth_should_error(self, mcp_server):
"""Issue #220: get_structure with negative max_depth should raise error."""
# Get the get_structure tool
get_structure_tool = None
for tool in mcp_server._tool_manager._tools.values():
if tool.name == "get_structure":
get_structure_tool = tool
break
get_structure_tool = asyncio.run(mcp_server.get_tool("get_structure"))

assert get_structure_tool is not None, "get_structure tool not found"

Expand All @@ -88,12 +79,7 @@ class TestGetSectionsAtLevelNegativeLevel:

def test_get_sections_at_level_negative_should_error(self, mcp_server):
"""Issue #220: get_sections_at_level with negative level should raise error."""
# Get the get_sections_at_level tool
get_sections_tool = None
for tool in mcp_server._tool_manager._tools.values():
if tool.name == "get_sections_at_level":
get_sections_tool = tool
break
get_sections_tool = asyncio.run(mcp_server.get_tool("get_sections_at_level"))

assert get_sections_tool is not None, "get_sections_at_level tool not found"

Expand All @@ -103,11 +89,7 @@ def test_get_sections_at_level_negative_should_error(self, mcp_server):

def test_get_sections_at_level_zero_should_error(self, mcp_server):
"""Issue #220: get_sections_at_level with level=0 should raise error."""
get_sections_tool = None
for tool in mcp_server._tool_manager._tools.values():
if tool.name == "get_sections_at_level":
get_sections_tool = tool
break
get_sections_tool = asyncio.run(mcp_server.get_tool("get_sections_at_level"))

assert get_sections_tool is not None

Expand All @@ -121,12 +103,7 @@ class TestGetElementsNegativeContentLimit:

def test_get_elements_negative_content_limit_should_error(self, mcp_server):
"""Issue #220: get_elements with negative content_limit should raise error."""
# Get the get_elements tool
get_elements_tool = None
for tool in mcp_server._tool_manager._tools.values():
if tool.name == "get_elements":
get_elements_tool = tool
break
get_elements_tool = asyncio.run(mcp_server.get_tool("get_elements"))

assert get_elements_tool is not None, "get_elements tool not found"

Expand Down
12 changes: 12 additions & 0 deletions tests/test_project_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,15 @@ def test_dacli_mcp_can_be_run():
# Should exit cleanly (0) or with help message
# We accept both 0 and 2 (argparse help exits with 2 sometimes)
assert result.returncode in (0, 2), f"Failed with: {result.stderr}"


def test_fastmcp_jwt_import_has_no_authlib_jose_deprecation_warning():
"""Ensure FastMCP JWT auth import does not emit deprecated authlib.jose warning."""
result = subprocess.run(
[sys.executable, "-W", "default", "-c", "import fastmcp.server.auth.providers.jwt"],
capture_output=True,
text=True,
timeout=10,
)
assert result.returncode == 0, f"Import failed with: {result.stderr}"
assert "authlib.jose module is deprecated" not in result.stderr
Loading
Loading