mirror of
https://github.com/haris-musa/excel-mcp-server.git
synced 2025-12-08 17:12:41 +08:00
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:
66
README.md
66
README.md
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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()
|
||||
@ -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")
|
||||
Reference in New Issue
Block a user