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
6 changes: 3 additions & 3 deletions codemcp/code_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import os
import subprocess
from typing import List, Optional, Union
from typing import List, Optional

import tomli

Expand Down Expand Up @@ -43,9 +43,9 @@ def get_command_from_config(project_dir: str, command_name: str) -> Optional[Lis
cmd_config = config["commands"][command_name]
# Handle both direct command lists and dictionaries with 'command' field
if isinstance(cmd_config, list):
return cmd_config
return cmd_config # type: ignore
elif isinstance(cmd_config, dict) and "command" in cmd_config:
return cmd_config["command"]
return cmd_config["command"] # type: ignore

return None
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion codemcp/git_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
log = logging.getLogger(__name__)


def append_metadata_to_message(message: str, metadata: dict) -> str:
def append_metadata_to_message(message: str, metadata: dict[str, str]) -> str:
"""Append trailers to Git commit message

Args:
Expand Down
29 changes: 19 additions & 10 deletions codemcp/hot_reload_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import logging
import os
import sys
from asyncio import Queue, Task
from typing import Optional
from asyncio import Future, Queue, Task
from typing import Any, Optional, Tuple

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
Expand All @@ -33,8 +33,10 @@ class HotReloadManager:
"""

def __init__(self):
self._task: Optional[Task] = None
self._request_queue: Optional[Queue] = None
self._task: Optional[Task[None]] = None
self._request_queue: Optional[Queue[Tuple[str, Any, asyncio.Future[Any]]]] = (
None
)
self._hot_reload_file = os.path.join(
os.path.dirname(os.path.dirname(__file__)), ".hot_reload"
)
Expand Down Expand Up @@ -87,7 +89,7 @@ async def stop(self) -> None:
"""Stop the background task and clean up resources."""
if self._task and not self._task.done() and self._request_queue:
# Create a future for the stop command
stop_future = asyncio.Future()
stop_future: Future[bool] = asyncio.Future()

# Get a local reference to the queue and task before clearing
request_queue = self._request_queue
Expand All @@ -102,7 +104,7 @@ async def stop(self) -> None:
await stop_future
await task

async def call_tool(self, **kwargs) -> str:
async def call_tool(self, **kwargs: Any) -> str:
"""Call the codemcp tool in the subprocess."""
# Check if we need to reload based on .hot_reload file
if (
Expand All @@ -118,15 +120,18 @@ async def call_tool(self, **kwargs) -> str:
await self.start()

# Create a future for this specific request
response_future = asyncio.Future()
response_future: Future[str] = asyncio.Future()

# Send the request and its associated future to the manager task
await self._request_queue.put(("call", kwargs, response_future))
if self._request_queue is not None:
await self._request_queue.put(("call", kwargs, response_future))

# Wait for the response
return await response_future

async def _run_manager_task(self, request_queue: Queue) -> None:
async def _run_manager_task(
self, request_queue: Queue[Tuple[str, Any, asyncio.Future[Any]]]
) -> None:
"""
Background task that owns and manages the async context managers lifecycle.

Expand Down Expand Up @@ -165,6 +170,10 @@ async def _run_manager_task(self, request_queue: Queue) -> None:
match result.content:
case [TextContent(text=err)]:
future.set_exception(RuntimeError(err))
case _:
future.set_exception(
RuntimeError("Unknown error")
)
future.set_result(result.content)

except Exception as e:
Expand All @@ -184,7 +193,7 @@ async def aexit():

@mcp.tool()
@functools.wraps(original_codemcp) # This copies the signature and docstring
async def codemcp(**kwargs) -> str:
async def codemcp(**kwargs: Any) -> str:
"""This is a wrapper that forwards all tool calls to the codemcp/main.py process.
This allows for hot-reloading as main.py will be reloaded on each call.

Expand Down
Loading