Feat/add stdio transport (#25)

* feat: Add stdio transport method.
Update relevant documentation and code to support both SSE and stdio connection methods, initialize the EXCEL_FILES_PATH variable and perform the corresponding path handling.

* Remove PyPI mirror configuration.
This commit is contained in:
allenZhang
2025-05-17 22:39:06 +08:00
committed by GitHub
parent 46adf0b173
commit bbab5bf184
4 changed files with 131 additions and 54 deletions

View File

@ -12,6 +12,7 @@ A Model Context Protocol (MCP) server that lets you manipulate Excel files witho
- 📈 Create charts and visualizations
- 📊 Generate pivot tables
- 🔄 Manage worksheets and ranges
- 🔌 Dual transport support: stdio and SSE
## Quick Start
@ -34,36 +35,59 @@ uv pip install -e .
### Running the Server
Start the server (default port 8000):
The server supports two transport modes: stdio and SSE.
#### Using stdio transport (default)
Stdio transport is ideal for direct integration with tools like Cursor Desktop or local development, which can manipulate local files:
```bash
uv run excel-mcp-server
excel-mcp-server stdio
```
Custom port (e.g., 8080):
#### Using SSE transport
SSE transport is perfect for web-based applications and remote connections, which manipulate remote files:
```bash
# Bash/Linux/macOS
export FASTMCP_PORT=8080 && uv run excel-mcp-server
excel-mcp sse
```
# Windows PowerShell
$env:FASTMCP_PORT = "8080"; uv run excel-mcp-server
You can specify host and port for the SSE server:
```bash
excel-mcp sse --host 127.0.0.1 --port 8080
```
## Using with AI Tools
### Cursor IDE
1. Add this configuration to Cursor:
1. Add this configuration to Cursor, choosing the appropriate transport method for your needs:
**Stdio transport connection** (for local integration):
```json
{
"mcpServers": {
"excel": {
"url": "http://localhost:8000/sse",
"env": {
"EXCEL_FILES_PATH": "/path/to/excel/files"
"mcpServers": {
"excel-stdio": {
"command": "uv",
"args": ["run", "excel-mcp-server", "stdio"]
}
}
}
}
}
```
**SSE transport connection** (for web-based applications):
```json
{
"mcpServers": {
"excel": {
"url": "http://localhost:8000/sse",
"env": {
"EXCEL_FILES_PATH": "/path/to/excel/files"
}
}
}
}
```
@ -71,17 +95,17 @@ $env:FASTMCP_PORT = "8080"; uv run excel-mcp-server
### Remote Hosting & Transport Protocols
This server uses Server-Sent Events (SSE) transport protocol. For different use cases:
This server supports both stdio and SSE transport protocols for maximum flexibility:
1. **Using with Claude Desktop (requires stdio):**
- Use [Supergateway](https://github.com/supercorp-ai/supergateway) to convert SSE to stdio:
1. **Using with Claude Desktop:**
- Use Stdio transport
2. **Hosting Your MCP Server:**
2. **Hosting Your MCP Server (SSE):**
- [Remote MCP Server Guide](https://developers.cloudflare.com/agents/guides/remote-mcp-server/)
## Environment Variables
## Environment Variables for SSE transport
- `FASTMCP_PORT`: Server port (default: 8000)
- `FASTMCP_PORT`: Server port for SSE transport (default: 8000)
- `EXCEL_FILES_PATH`: Directory for Excel files (default: `./excel_files`)
## Available Tools

View File

@ -1,26 +1,27 @@
[project]
name = "excel-mcp-server"
version = "0.1.1"
description = "MCP server for Excel file manipulation"
description = "Excel MCP Server for manipulating Excel files"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"mcp[cli]>=1.2.0",
"openpyxl>=3.1.2"
"mcp[cli]>=1.6.0",
"openpyxl>=3.1.2",
"typer>=0.15.1"
]
[[project.authors]]
name = "haris"
email = "haris.musa@outlook.com"
[project.scripts]
excel-mcp-server = "excel_mcp.__main__:app"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project.scripts]
excel-mcp-server = "excel_mcp.__main__:main"
[tool.hatch.build.targets.wheel]
packages = ["src/excel_mcp"]
[tool.hatch.build]
packages = ["src/excel_mcp"]
packages = ["src/excel_mcp"]

View File

@ -1,13 +1,19 @@
import asyncio
from .server import run_server
import typer
from typing import Optional
def main():
"""Start the Excel MCP server."""
from .server import run_sse, run_stdio
app = typer.Typer(help="Excel MCP Server")
@app.command()
def sse():
"""Start Excel MCP Server in SSE mode"""
print("Excel MCP Server - SSE mode")
print("----------------------")
print("Press Ctrl+C to exit")
try:
print("Excel MCP Server")
print("---------------")
print("Starting server... Press Ctrl+C to exit")
asyncio.run(run_server())
asyncio.run(run_sse())
except KeyboardInterrupt:
print("\nShutting down server...")
except Exception as e:
@ -15,7 +21,21 @@ def main():
import traceback
traceback.print_exc()
finally:
print("Server stopped.")
print("Service stopped.")
@app.command()
def stdio():
"""Start Excel MCP Server in stdio mode"""
try:
run_stdio()
except KeyboardInterrupt:
print("\nShutting down server...")
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
finally:
print("Service stopped.")
if __name__ == "__main__":
main()
app()

View File

@ -34,25 +34,31 @@ from excel_mcp.sheet import (
unmerge_range,
)
# Get project root directory path for log file path.
# When using the stdio transmission method,
# relative paths may cause log files to fail to create
# due to the client's running location and permission issues,
# resulting in the program not being able to run.
# Thus using os.path.join(ROOT_DIR, "excel-mcp.log") instead.
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
LOG_FILE = os.path.join(ROOT_DIR, "excel-mcp.log")
# Initialize EXCEL_FILES_PATH variable without assigning a value
EXCEL_FILES_PATH = None
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("excel-mcp.log")
# Referring to https://github.com/modelcontextprotocol/python-sdk/issues/409#issuecomment-2816831318
# The stdio mode server MUST NOT write anything to its stdout that is not a valid MCP message.
logging.FileHandler(LOG_FILE)
],
force=True
)
logger = logging.getLogger("excel-mcp")
# Get Excel files path from environment or use default
EXCEL_FILES_PATH = os.environ.get("EXCEL_FILES_PATH", "./excel_files")
# Create the directory if it doesn't exist
os.makedirs(EXCEL_FILES_PATH, exist_ok=True)
# Initialize FastMCP server
mcp = FastMCP(
"excel-mcp",
@ -80,8 +86,13 @@ def get_excel_path(filename: str) -> str:
# If filename is already an absolute path, return it
if os.path.isabs(filename):
return filename
# Use the configured Excel files path
# Check if in SSE mode (EXCEL_FILES_PATH is not None)
if EXCEL_FILES_PATH is None:
# Must use absolute path
raise ValueError(f"Invalid filename: {filename}, must be an absolute path when not in SSE mode")
# In SSE mode, if it's a relative path, resolve it based on EXCEL_FILES_PATH
return os.path.join(EXCEL_FILES_PATH, filename)
@mcp.tool()
@ -491,10 +502,16 @@ def validate_excel_range(
logger.error(f"Error validating range: {e}")
raise
async def run_server():
"""Run the Excel MCP server."""
async def run_sse():
"""Run Excel MCP server in SSE mode."""
# Assign value to EXCEL_FILES_PATH in SSE mode
global EXCEL_FILES_PATH
EXCEL_FILES_PATH = os.environ.get("EXCEL_FILES_PATH", "./excel_files")
# Create directory if it doesn't exist
os.makedirs(EXCEL_FILES_PATH, exist_ok=True)
try:
logger.info(f"Starting Excel MCP server (files directory: {EXCEL_FILES_PATH})")
logger.info(f"Starting Excel MCP server with SSE transport (files directory: {EXCEL_FILES_PATH})")
await mcp.run_sse_async()
except KeyboardInterrupt:
logger.info("Server stopped by user")
@ -503,4 +520,19 @@ async def run_server():
logger.error(f"Server failed: {e}")
raise
finally:
logger.info("Server shutdown complete")
logger.info("Server shutdown complete")
def run_stdio():
"""Run Excel MCP server in stdio mode."""
# No need to assign EXCEL_FILES_PATH in stdio mode
try:
logger.info("Starting Excel MCP server with stdio transport")
mcp.run(transport="stdio")
except KeyboardInterrupt:
logger.info("Server stopped by user")
except Exception as e:
logger.error(f"Server failed: {e}")
raise
finally:
logger.info("Server shutdown complete")