Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/mcpm/clients/client_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from mcpm.clients.managers.fiveire import FiveireManager
from mcpm.clients.managers.gemini_cli import GeminiCliManager
from mcpm.clients.managers.goose import GooseClientManager
from mcpm.clients.managers.kiro import KiroManager
from mcpm.clients.managers.qwen_cli import QwenCliManager
from mcpm.clients.managers.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
Expand Down Expand Up @@ -51,6 +52,7 @@ class ClientRegistry:
"vscode": VSCodeManager,
"gemini-cli": GeminiCliManager,
"codex-cli": CodexCliManager,
"kiro": KiroManager,
"qwen-cli": QwenCliManager,
}

Expand Down
2 changes: 2 additions & 0 deletions src/mcpm/clients/managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mcpm.clients.managers.fiveire import FiveireManager
from mcpm.clients.managers.gemini_cli import GeminiCliManager
from mcpm.clients.managers.goose import GooseClientManager
from mcpm.clients.managers.kiro import KiroManager
from mcpm.clients.managers.qwen_cli import QwenCliManager
from mcpm.clients.managers.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
Expand All @@ -27,6 +28,7 @@
"ContinueManager",
"FiveireManager",
"GooseClientManager",
"KiroManager",
"QwenCliManager",
"TraeManager",
"VSCodeManager",
Expand Down
62 changes: 62 additions & 0 deletions src/mcpm/clients/managers/kiro.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Kiro IDE integration utilities for MCP
"""

import logging
import os
import shutil
from typing import Any, Dict

from mcpm.clients.base import JSONClientManager

logger = logging.getLogger(__name__)


class KiroManager(JSONClientManager):
"""Manages Kiro IDE MCP server configurations"""

# Client information
client_key = "kiro"
display_name = "Kiro"
download_url = "https://kiro.dev"

def __init__(self, config_path_override: str | None = None):
"""Initialize the Kiro client manager

Args:
config_path_override: Optional path to override the default config file location
"""
super().__init__(config_path_override=config_path_override)

if config_path_override:
self.config_path = config_path_override
else:
# Kiro stores its MCP settings in ~/.kiro/settings/mcp.json
# across macOS, Linux, and Windows (per Kiro's docs at
# https://kiro.dev/docs/mcp).
self.config_path = os.path.expanduser("~/.kiro/settings/mcp.json")

def _get_empty_config(self) -> Dict[str, Any]:
"""Get empty config structure for Kiro"""
return {self.configure_key_name: {}}

def is_client_installed(self) -> bool:
"""Check if Kiro is installed
Returns:
bool: True if kiro command is available, False otherwise
"""
# shutil.which() handles Windows PATHEXT automatically (.cmd, .bat, .exe, etc.)
return shutil.which("kiro") is not None

def get_client_info(self) -> Dict[str, str]:
"""Get information about this client

Returns:
Dict: Information about the client including display name, download URL, and config path
"""
return {
"name": self.display_name,
"download_url": self.download_url,
"config_file": self.config_path,
"description": "Kiro coding agent IDE",
}
109 changes: 109 additions & 0 deletions tests/test_clients/test_kiro.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Tests for the Kiro client manager."""

import json
import os
import tempfile
from unittest.mock import patch

import pytest

from mcpm.clients.managers.kiro import KiroManager
from mcpm.core.schema import STDIOServerConfig


@pytest.fixture
def temp_json_config():
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".json") as f:
json.dump({"mcpServers": {}}, f)
temp_path = f.name

yield temp_path
os.unlink(temp_path)


@pytest.fixture
def kiro_manager(temp_json_config):
return KiroManager(config_path_override=temp_json_config)


def test_default_config_path():
"""Test that the default config path is ~/.kiro/settings/mcp.json"""
with patch.dict(os.environ, {"HOME": "/home/user"}, clear=False):
manager = KiroManager()
assert manager.config_path.endswith(".kiro/settings/mcp.json")

Comment on lines +29 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize the default-path assertion for cross-platform test stability.

Line 33 hardcodes / separators, which can fail on Windows even when the path is correct. Normalize both sides before asserting.

✅ Proposed test fix
 def test_default_config_path():
     """Test that the default config path is ~/.kiro/settings/mcp.json"""
     with patch.dict(os.environ, {"HOME": "/home/user"}, clear=False):
         manager = KiroManager()
-        assert manager.config_path.endswith(".kiro/settings/mcp.json")
+        assert os.path.normpath(manager.config_path).endswith(
+            os.path.normpath(".kiro/settings/mcp.json")
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_clients/test_kiro.py` around lines 29 - 34, The test
test_default_config_path hardcodes forward slashes and may fail on Windows;
update it to normalize both the manager.config_path and the expected string
before asserting (e.g., use os.path.normpath or pathlib.Path to build/normalize
the expected ".kiro/settings/mcp.json" path) so the assertion compares
platform-normalized paths when calling KiroManager() and checking
manager.config_path.


def test_config_path_override(temp_json_config):
"""Test that config_path_override takes precedence over the default path"""
manager = KiroManager(config_path_override=temp_json_config)
assert manager.config_path == temp_json_config


def test_get_empty_config(kiro_manager):
"""Test that empty config returns the standard mcpServers shape"""
empty = kiro_manager._get_empty_config()
assert empty == {"mcpServers": {}}


def test_uses_standard_mcpServers_key():
"""Test that Kiro uses the standard 'mcpServers' key (no override)"""
assert KiroManager.configure_key_name == "mcpServers"


def test_get_client_info(kiro_manager):
info = kiro_manager.get_client_info()
assert info["name"] == "Kiro"
assert info["download_url"] == "https://kiro.dev"
assert "kiro" in info["description"].lower()
assert "config_file" in info


def test_is_client_installed_when_kiro_on_path(kiro_manager):
"""Test that is_client_installed returns True when kiro binary is on PATH"""
with patch("mcpm.clients.managers.kiro.shutil.which", return_value="/usr/local/bin/kiro"):
assert kiro_manager.is_client_installed() is True


def test_is_client_installed_when_kiro_missing(kiro_manager):
"""Test that is_client_installed returns False when kiro binary is missing"""
with patch("mcpm.clients.managers.kiro.shutil.which", return_value=None):
assert kiro_manager.is_client_installed() is False


def test_add_and_list_server(kiro_manager):
"""Test the add_server / list_servers / get_server lifecycle"""
server_config = STDIOServerConfig(
name="test-server",
command="npx",
args=["-y", "@modelcontextprotocol/server-test"],
)
success = kiro_manager.add_server(server_config)
assert success

servers = kiro_manager.list_servers()
assert "test-server" in servers

server = kiro_manager.get_server("test-server")
assert server is not None
assert server.name == "test-server"


def test_remove_server(kiro_manager):
"""Test the remove_server lifecycle"""
server_config = STDIOServerConfig(
name="test-server",
command="npx",
args=[],
)
kiro_manager.add_server(server_config)
assert kiro_manager.remove_server("test-server") is True
assert kiro_manager.get_server("test-server") is None


def test_load_config_returns_empty_when_file_missing():
"""Test that _load_config returns empty config shape when file doesn't exist"""
with tempfile.TemporaryDirectory() as tmpdir:
missing_path = os.path.join(tmpdir, "nonexistent.json")
manager = KiroManager(config_path_override=missing_path)
config = manager._load_config()
assert config == {"mcpServers": {}}
Loading