diff --git a/.gitignore b/.gitignore
index 0a18b2e..caea720 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ venv
# ai
.cunzhi-memory/
+.kilocode/
diff --git a/README.md b/README.md
index 9767b2e..11ccf3d 100644
--- a/README.md
+++ b/README.md
@@ -331,7 +331,23 @@ Web 管理界面提供:
## 最近更新
-### 版本 0.1.3(最新)
+### 版本 0.1.5(最新)
+
+**新特性:**
+- ✨ **日志系统优化**:将 FastAPI/Uvicorn 日志重定向到 loguru,防止污染 MCP stdio 协议
+- ✨ **工具调试接口**:Web 管理界面新增工具列表和调试功能
+
+**改进:**
+- 🔧 **日志输出控制**:移除控制台日志输出,仅输出到文件,避免干扰 stdio 协议
+- 🔧 **标准库日志拦截**:使用 `InterceptHandler` 拦截所有标准库日志
+- 🔧 **Web API 增强**:新增 `/api/tools` 端点列出可用工具
+
+**技术细节:**
+- 实现了 `InterceptHandler` 类来拦截标准库 logging
+- 配置 uvicorn 使用 `log_config=None` 禁用默认日志
+- 所有日志统一输出到 `~/.acemcp/log/acemcp.log`
+
+### 版本 0.1.4
**新特性:**
- ✨ **多编码支持**:自动检测和处理多种文件编码(UTF-8、GBK、GB2312、Latin-1)
diff --git a/README_EN.md b/README_EN.md
index 0b26f61..5b7d2d4 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -331,7 +331,23 @@ To enable the web interface, use the `--web-port` argument when starting the ser
## Recent Updates
-### Version 0.1.3 (Latest)
+### Version 0.1.5 (Latest)
+
+**New Features:**
+- ✨ **Logging System Optimization**: Redirect FastAPI/Uvicorn logs to loguru to prevent pollution of MCP stdio protocol
+- ✨ **Tool Debugging Interface**: Web management interface now includes tool listing and debugging functionality
+
+**Improvements:**
+- 🔧 **Log Output Control**: Removed console log output, only output to file to avoid interfering with stdio protocol
+- 🔧 **Standard Library Log Interception**: Use `InterceptHandler` to intercept all standard library logs
+- 🔧 **Web API Enhancement**: New `/api/tools` endpoint to list available tools
+
+**Technical Details:**
+- Implemented `InterceptHandler` class to intercept standard library logging
+- Configured uvicorn with `log_config=None` to disable default logging
+- All logs unified to output to `~/.acemcp/log/acemcp.log`
+
+### Version 0.1.4
**New Features:**
- ✨ **Multi-Encoding Support**: Automatic detection and handling of multiple file encodings (UTF-8, GBK, GB2312, Latin-1)
diff --git a/pyproject.toml b/pyproject.toml
index 086440c..a568790 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "acemcp"
-version = "0.1.4"
+version = "0.1.5"
description = "MCP server for codebase indexing and semantic search with multi-encoding support and .gitignore integration"
readme = "README.md"
authors = [
diff --git a/src/acemcp/logging_config.py b/src/acemcp/logging_config.py
index 054ce95..136e027 100644
--- a/src/acemcp/logging_config.py
+++ b/src/acemcp/logging_config.py
@@ -1,5 +1,6 @@
"""Global logging configuration for acemcp."""
+import logging
from pathlib import Path
from loguru import logger
@@ -11,7 +12,31 @@ _console_handler_id: int | None = None
_file_handler_id: int | None = None
-def setup_logging() -> None:
+class InterceptHandler(logging.Handler):
+ """Intercept standard logging messages and redirect them to loguru."""
+
+ def emit(self, record: logging.LogRecord) -> None:
+ """Emit a log record to loguru.
+
+ Args:
+ record: The log record to emit
+ """
+ # Get corresponding Loguru level if it exists
+ try:
+ level = logger.level(record.levelname).name
+ except ValueError:
+ level = record.levelno
+
+ # Find caller from where originated the logged message
+ frame, depth = logging.currentframe(), 2
+ while frame.f_code.co_filename == logging.__file__:
+ frame = frame.f_back
+ depth += 1
+
+ logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
+
+
+def setup_logging(intercept_stdlib: bool = False) -> None:
"""Setup global logging configuration with file rotation.
Configures loguru to write logs to ~/.acemcp/log/acemcp.log with:
@@ -21,6 +46,9 @@ def setup_logging() -> None:
This function can be called multiple times safely - it will only configure once.
Note: This function preserves any existing handlers (e.g., WebSocket log broadcaster).
+
+ Args:
+ intercept_stdlib: If True, intercept standard library logging (uvicorn, fastapi, etc.)
"""
global _logging_configured, _console_handler_id, _file_handler_id # noqa: PLW0603
@@ -43,12 +71,13 @@ def setup_logging() -> None:
pass
# Add console handler with INFO level
- _console_handler_id = logger.add(
- sink=lambda msg: print(msg, end=""),
- format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}",
- level="INFO",
- colorize=True,
- )
+ # Only output to file to avoid polluting stdio
+ # _console_handler_id = logger.add(
+ # sink=lambda msg: print(msg, end=""),
+ # format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}",
+ # level="INFO",
+ # colorize=True,
+ # )
# Add file handler with rotation
_file_handler_id = logger.add(
@@ -62,6 +91,15 @@ def setup_logging() -> None:
enqueue=True, # Thread-safe logging
)
+ # Intercept standard library logging if requested
+ if intercept_stdlib:
+ logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
+ # Intercept specific loggers
+ for logger_name in ["uvicorn", "uvicorn.access", "uvicorn.error", "fastapi"]:
+ logging_logger = logging.getLogger(logger_name)
+ logging_logger.handlers = [InterceptHandler()]
+ logging_logger.propagate = False
+
_logging_configured = True
logger.info(f"Logging configured: log file at {log_file}")
diff --git a/src/acemcp/server.py b/src/acemcp/server.py
index e252669..98f728e 100644
--- a/src/acemcp/server.py
+++ b/src/acemcp/server.py
@@ -72,13 +72,15 @@ async def run_web_server(port: int) -> None:
port: Port to run the web server on
"""
web_app = create_app()
- # Set log_level to "warning" to reduce WebSocket connection noise
+ # Configure uvicorn to use loguru through InterceptHandler
+ # This prevents uvicorn from polluting stdout (which breaks MCP stdio protocol)
config_uvicorn = uvicorn.Config(
web_app,
host="0.0.0.0",
port=port,
log_level="warning",
- access_log=False # Disable access log to reduce noise
+ access_log=False, # Disable access log to reduce noise
+ log_config=None, # Disable default logging config to use our interceptor
)
server = uvicorn.Server(config_uvicorn)
await server.serve()
@@ -130,7 +132,8 @@ def run() -> None:
get_log_broadcaster() # Initialize the broadcaster
# Setup logging after log broadcaster is initialized
- setup_logging()
+ # Intercept stdlib logging (uvicorn, fastapi) to prevent stdout pollution
+ setup_logging(intercept_stdlib=True)
asyncio.run(main(base_url=args.base_url, token=args.token, web_port=args.web_port))
diff --git a/src/acemcp/web/app.py b/src/acemcp/web/app.py
index 5dca2ae..8104d13 100644
--- a/src/acemcp/web/app.py
+++ b/src/acemcp/web/app.py
@@ -136,6 +136,27 @@ def create_app() -> FastAPI:
return {"status": "running", "project_count": project_count, "storage_path": str(config.index_storage_path)}
+ @app.get("/api/tools")
+ async def list_tools() -> dict:
+ """List available tools for debugging.
+
+ Returns:
+ Dictionary containing available tools and their descriptions
+ """
+ return {
+ "tools": [
+ {
+ "name": "search_context",
+ "description": "Search for code context in indexed projects",
+ "status": "stable",
+ "parameters": {
+ "project_root_path": "string (required) - Absolute path to project root",
+ "query": "string (required) - Search query",
+ },
+ },
+ ]
+ }
+
@app.post("/api/tools/execute")
async def execute_tool(tool_request: ToolRequest) -> dict:
"""Execute a tool for debugging.
diff --git a/uv.lock b/uv.lock
index 45e130e..928f23a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
[[package]]
name = "acemcp"
-version = "0.1.4"
+version = "0.1.5"
source = { editable = "." }
dependencies = [
{ name = "dynaconf" },