Skip to content
Open
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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ robocop-mcp = "robocop.mcp.server:main"

[project.optional-dependencies]
mcp = [
"fastmcp>=2.13.0,<3"
"fastmcp>=3.0.0"
]

[project.urls]
Expand Down Expand Up @@ -98,7 +98,7 @@ doc = [
"pygments<2.20",
]
mcp = [
"fastmcp>=2.13.0",
"fastmcp>=3.0.0",
]

[tool.ruff]
Expand Down
4 changes: 2 additions & 2 deletions src/robocop/mcp/tools/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def _search_rules_impl(
return results


def _list_prompts_impl(mcp: FastMCP) -> list[PromptSummary]:
async def _list_prompts_impl(mcp: FastMCP) -> list[PromptSummary]:
"""
List all available MCP prompt templates.

Expand All @@ -263,7 +263,7 @@ def _list_prompts_impl(mcp: FastMCP) -> list[PromptSummary]:
if prompt.arguments
else [],
)
for prompt in mcp._prompt_manager._prompts.values() # type: ignore[attr-defined] # noqa: SLF001
for prompt in await mcp.list_prompts()
]
return sorted(prompts, key=lambda p: p.name)

Expand Down
2 changes: 1 addition & 1 deletion src/robocop/mcp/tools/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ async def list_prompts(ctx: Context | None = None) -> list[PromptSummary]:
if ctx:
await ctx.debug("Listing available prompts")

return _list_prompts_impl(mcp)
return await _list_prompts_impl(mcp)

@mcp.tool(
tags={"linting", "fixing"},
Expand Down
3 changes: 2 additions & 1 deletion tests/mcp/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def mcp_tools():
"""Get the registered MCP tools."""

async def get_tools():
return await mcp.get_tools()
tool_list = await mcp.list_tools()
return {t.name: await mcp.get_tool(t.name) for t in tool_list}

return asyncio.run(get_tools())

Expand Down
3 changes: 2 additions & 1 deletion tests/mcp/test_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def mcp_prompts():
"""Get the registered MCP prompts."""

async def get_prompts():
return await mcp.get_prompts()
prompt_list = await mcp.list_prompts()
return {p.name: await mcp.get_prompt(p.name) for p in prompt_list}

return asyncio.run(get_prompts())

Expand Down
14 changes: 7 additions & 7 deletions tests/mcp/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,20 @@ class TestResourcesRegistration:

def test_resources_accessible_via_mcp(self):
"""Test that resources are accessible via MCP server."""
resources = asyncio.run(mcp.get_resources())
resources = asyncio.run(mcp.list_resources())
# Should have at least the catalog resources
assert len(resources) >= 2

def test_rules_resource_registered(self):
"""Test that rules catalog resource is registered."""
resources = asyncio.run(mcp.get_resources())
# get_resources() returns a dict with URIs as keys
resource_uris = list(resources.keys())
resources = asyncio.run(mcp.list_resources())
# list_resources() returns a list of resource objects with .uri attribute
resource_uris = [str(r.uri) for r in resources]
assert any("rules" in uri for uri in resource_uris)

def test_formatters_resource_registered(self):
"""Test that formatters catalog resource is registered."""
resources = asyncio.run(mcp.get_resources())
# get_resources() returns a dict with URIs as keys
resource_uris = list(resources.keys())
resources = asyncio.run(mcp.list_resources())
# list_resources() returns a list of resource objects with .uri attribute
resource_uris = [str(r.uri) for r in resources]
assert any("formatters" in uri for uri in resource_uris)
11 changes: 5 additions & 6 deletions tests/mcp/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ class TestToolsRegistration:

def test_all_tools_registered(self):
"""Test that all tools are registered."""
tools = asyncio.run(mcp.get_tools())

tools = {t.name: t for t in asyncio.run(mcp.list_tools())}
expected_tools = [
"lint_content",
"lint_file",
Expand All @@ -68,7 +67,7 @@ def test_all_tools_registered(self):

def test_tools_count(self):
"""Test that correct number of tools are registered."""
tools = asyncio.run(mcp.get_tools())
tools = asyncio.run(mcp.list_tools())
# At least 15 tools should be registered
assert len(tools) >= 15

Expand All @@ -78,7 +77,7 @@ class TestResourcesRegistration:

def test_resources_registered(self):
"""Test that resources are registered."""
resources = asyncio.run(mcp.get_resources())
resources = asyncio.run(mcp.list_resources())
# Should have at least the 3 main resources
assert len(resources) >= 2

Expand All @@ -88,7 +87,7 @@ class TestPromptsRegistration:

def test_all_prompts_registered(self):
"""Test that all 6 prompts are registered."""
prompts = asyncio.run(mcp.get_prompts())
prompts = {p.name: p for p in asyncio.run(mcp.list_prompts())}

expected_prompts = [
"analyze_robot_file",
Expand All @@ -104,5 +103,5 @@ def test_all_prompts_registered(self):

def test_prompts_count(self):
"""Test that exactly 6 prompts are registered."""
prompts = asyncio.run(mcp.get_prompts())
prompts = asyncio.run(mcp.list_prompts())
assert len(prompts) == 6
9 changes: 5 additions & 4 deletions tests/mcp/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import asyncio
from pathlib import Path
from textwrap import dedent

Expand Down Expand Up @@ -2207,13 +2208,13 @@ class TestListPrompts:

def test_list_prompts_returns_list(self):
"""Test list_prompts returns a list."""
result = _list_prompts_impl(mcp)
result = asyncio.run(_list_prompts_impl(mcp))
assert isinstance(result, list)
assert len(result) > 0

def test_list_prompts_includes_required_fields(self):
"""Test each prompt has name, description, arguments."""
result = _list_prompts_impl(mcp)
result = asyncio.run(_list_prompts_impl(mcp))
# PromptSummary model validates all required fields exist
for prompt in result:
assert isinstance(prompt.arguments, list)
Expand All @@ -2225,7 +2226,7 @@ def test_list_prompts_discovers_all_prompts(self):
This test ensures that when new prompts are added to prompts.py,
the AST introspection picks them up correctly.
"""
result = _list_prompts_impl(mcp)
result = asyncio.run(_list_prompts_impl(mcp))
prompt_names = {p.name for p in result}

# All prompts that should be discovered from prompts.py
Expand All @@ -2245,7 +2246,7 @@ def test_list_prompts_discovers_all_prompts(self):

def test_list_prompts_argument_required_field(self):
"""Test that arguments correctly identify required vs optional."""
result = _list_prompts_impl(mcp)
result = asyncio.run(_list_prompts_impl(mcp))
prompts_by_name = {p.name: p for p in result}

# analyze_robot_file: content (required), focus (optional)
Expand Down
Loading