# Directory Structure ``` ├── .dockerignore ├── .env.example ├── .github │ └── workflows │ └── tests.yml ├── .gitignore ├── app.json ├── cursor-run-mcp-server.sh ├── docker-compose.yml ├── Dockerfile ├── docs │ └── images │ └── server-mood.png ├── heroku.yml ├── LICENSE ├── mcp_simple_tool │ ├── __init__.py │ ├── __main__.py │ └── server.py ├── pyproject.toml ├── README.md ├── smithery.yaml ├── tests │ └── test_stdio.py └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- ``` # Git .git .gitignore # Python __pycache__ *.pyc *.pyo *.pyd .Python *.py[cod] *$py.class .pytest_cache .coverage htmlcov .env .venv venv/ ENV/ # IDE .idea .vscode *.swp *.swo .DS_Store # Project specific temp/ *.log ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Server Configuration MCP_SERVER_PORT=8000 MCP_SERVER_HOST=0.0.0.0 # Optional: Set to 'true' to enable debug mode DEBUG=false # Optional: Set custom User-Agent for website fetching MCP_USER_AGENT="MCP Test Server (github.com/modelcontextprotocol/python-sdk)" ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. #uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ # PyPI configuration file .pypirc # Temp files temp/ # MacOS .DS_Store ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # MCP Server Template for Cursor IDE A simple template for creating custom tools for Cursor IDE using Model Context Protocol (MCP). Create your own repository from this template, modify the tools, and connect them to your Cursor IDE.  ## Quick Start 1. Click "Deploy to Heroku" button [](https://heroku.com/deploy?template=https://github.com/kirill-markin/weaviate-mcp-server) 2. After deployment, configure Cursor: - Open Cursor Settings → Features - Add new MCP server - Use your Heroku URL with `/sse` path (e.g., `https://<your-app-name>.herokuapp.com/sse`) 3. Test your agent's mood in Cursor: - Ask your agent "Please ask about our server mood and let me know how it is." - The server will respond with a cheerful message and a heart ❤️ ## Alternative Setup Methods You can run the server in three ways: using Docker, traditional Python setup, or directly in Cursor IDE. ### Docker Setup The project includes Docker support for easy deployment: 1. Initial setup: ```bash # Clone the repository git clone https://github.com/kirill-markin/weaviate-mcp-server.git cd weaviate-mcp-server # Create environment file cp .env.example .env ``` 2. Build and run using Docker Compose: ```bash # Build and start the server docker compose up --build -d # View logs docker compose logs -f # Check server status docker compose ps # Stop the server docker compose down ``` 3. The server will be available at: - SSE endpoint: http://localhost:8000/sse 4. Quick test: ```bash # Test the server endpoint curl -i http://localhost:8000/sse ``` 5. Connect to Cursor IDE: - Open Cursor Settings → Features - Add new MCP server - Type: Select "sse" - URL: Enter `http://localhost:8000/sse` ### Traditional Setup First, install the uv package manager: ```bash # Install uv on macOS brew install uv # Or install via pip (any OS) pip install uv ``` Start the server using either stdio (default) or SSE transport: ```bash # Install the package with development dependencies uv pip install -e ".[dev]" # Using stdio transport (default) uv run mcp-simple-tool # Using SSE transport on custom port uv run mcp-simple-tool --transport sse --port 8000 # Run tests uv run pytest -v ``` After installation, you can connect the server directly to Cursor IDE: 1. Right-click on the `cursor-run-mcp-server.sh` file in Cursor 2. Select "Copy Path" to copy the absolute path 3. Open Cursor Settings (gear icon) 4. Navigate to Features tab 5. Scroll down to "MCP Servers" 6. Click "Add new MCP server" 7. Fill in the form: - Name: Choose any name (e.g., "my-mcp-server-1") - Type: Select "stdio" (not "sse" because we run the server locally) - Command: Paste the absolute path to `cursor-run-mcp-server.sh` that you copied earlier. For example: `/Users/kirillmarkin/weaviate-mcp-server/cursor-run-mcp-server.sh` ### Environment Variables Available environment variables (can be set in `.env`): - `MCP_SERVER_PORT` (default: 8000) - Port to run the server on - `MCP_SERVER_HOST` (default: 0.0.0.0) - Host to bind the server to - `DEBUG` (default: false) - Enable debug mode - `MCP_USER_AGENT` - Custom User-Agent for website fetching ## Additional options ### Installing via Smithery [](https://smithery.ai/server/@kirill-markin/example-mcp-server) To install MCP Server Template for Cursor IDE for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@kirill-markin/example-mcp-server): ```bash npx -y @smithery/cli install @kirill-markin/example-mcp-server --client claude ``` ### Glama server review <a href="https://glama.ai/mcp/servers/jgisqn8zco"><img width="380" height="200" src="https://glama.ai/mcp/servers/jgisqn8zco/badge" alt="Server Template for Cursor IDE MCP server" /></a> ``` -------------------------------------------------------------------------------- /mcp_simple_tool/__init__.py: -------------------------------------------------------------------------------- ```python ``` -------------------------------------------------------------------------------- /mcp_simple_tool/__main__.py: -------------------------------------------------------------------------------- ```python import sys from .server import main sys.exit(main()) ``` -------------------------------------------------------------------------------- /cursor-run-mcp-server.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Change to the directory where this script is located cd "$(dirname "$0")" # Run the server exec uv run mcp-simple-tool ``` -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- ```yaml setup: addons: [] # Empty array instead of null build: docker: web: Dockerfile config: # Environment variables for build phase if needed PYTHON_VERSION: "3.10.16" run: web: mcp-simple-tool --transport sse --port $PORT ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Use Python 3.10 slim image as base FROM python:3.10-slim # Set working directory WORKDIR /app # Install build dependencies and curl for healthcheck RUN apt-get update && apt-get install -y \ build-essential \ curl \ && rm -rf /var/lib/apt/lists/* # Copy project files COPY . . # Install the package in editable mode RUN pip install --no-cache-dir -e ".[dev]" # Expose the port EXPOSE 8000 # Run the server with SSE transport CMD ["mcp-simple-tool", "--transport", "sse", "--port", "8000"] ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml version: '3.8' services: mcp-server: build: . ports: - "${MCP_SERVER_PORT:-8000}:8000" environment: - MCP_SERVER_PORT=${MCP_SERVER_PORT:-8000} - MCP_SERVER_HOST=${MCP_SERVER_HOST:-0.0.0.0} - DEBUG=${DEBUG:-false} - MCP_USER_AGENT=${MCP_USER_AGENT:-"MCP Test Server (github.com/modelcontextprotocol/python-sdk)"} restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/sse"] interval: 30s timeout: 10s retries: 3 start_period: 10s ``` -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- ```yaml name: Run Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Create virtual environment and install dependencies run: | uv venv . .venv/bin/activate uv pip install -e ".[dev]" - name: Run tests run: | . .venv/bin/activate uv run pytest tests/ -v ``` -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- ```json { "name": "MCP Simple Tool Server", "description": "A simple MCP server that exposes a website fetching tool", "repository": "https://github.com/kirill-markin/weaviate-mcp-server", "keywords": ["python", "mcp", "fetch", "web", "tool"], "env": { "MCP_SERVER_PORT": { "description": "Port to run the server on", "value": "8000", "required": false }, "MCP_SERVER_HOST": { "description": "Host to bind the server to", "value": "0.0.0.0", "required": false }, "DEBUG": { "description": "Enable debug mode", "value": "false", "required": false }, "MCP_USER_AGENT": { "description": "Custom User-Agent for website fetching", "value": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)", "required": false } }, "stack": "container", "formation": { "web": { "quantity": 1, "size": "eco" } } } ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml startCommand: type: stdio configSchema: # JSON Schema defining the configuration options for the MCP. type: object required: [] properties: mcpServerPort: type: number default: 8000 description: Port to run the MCP server on. mcpServerHost: type: string default: 0.0.0.0 description: Host to bind the MCP server to. debug: type: boolean default: false description: Enable debug mode. mcpUserAgent: type: string description: Custom User-Agent for website fetching. commandFunction: # A function that produces the CLI command to start the MCP on stdio. |- (config) => ({ command: 'mcp-simple-tool', args: ['--transport', 'sse', '--port', String(config.mcpServerPort)], env: { MCP_SERVER_HOST: config.mcpServerHost, DEBUG: config.debug ? 'true' : 'false', MCP_USER_AGENT: config.mcpUserAgent || '' } }) ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [project] name = "mcp-simple-tool" version = "0.1.0" description = "A simple MCP server exposing a website fetching tool" readme = "README.md" requires-python = ">=3.10" authors = [{ name = "Anthropic, PBC." }] maintainers = [ { name = "David Soria Parra", email = "[email protected]" }, { name = "Justin Spahr-Summers", email = "[email protected]" }, ] keywords = ["mcp", "llm", "automation", "web", "fetch"] license = { text = "MIT" } classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", ] dependencies = ["anyio>=4.5", "click>=8.1.0", "httpx>=0.27", "mcp"] [project.scripts] mcp-simple-tool = "mcp_simple_tool.server:main" [project.optional-dependencies] dev = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9", "pytest-asyncio>=0.23.5"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["mcp_simple_tool"] [tool.pyright] include = ["mcp_simple_tool"] venvPath = "." venv = ".venv" [tool.ruff.lint] select = ["E", "F", "I"] ignore = [] [tool.ruff] line-length = 88 target-version = "py310" [tool.uv] dev-dependencies = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9", "pytest-asyncio>=0.23.5"] ``` -------------------------------------------------------------------------------- /tests/test_stdio.py: -------------------------------------------------------------------------------- ```python import asyncio import pytest from mcp.client.session import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client @pytest.mark.asyncio async def test_mcp_server_tools() -> None: """Test that we can connect to the server and list available tools.""" async with stdio_client( StdioServerParameters(command="uv", args=["run", "mcp-simple-tool"]) ) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() assert tools is not None, "Tools list should not be None" assert tools.tools, "Server should provide at least one tool" @pytest.mark.asyncio async def test_mcp_server_mood() -> None: """Test that the mood tool works correctly.""" async with stdio_client( StdioServerParameters(command="uv", args=["run", "mcp-simple-tool"]) ) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("mood", {"question": "How are you today?"}) assert result is not None, "Mood response should not be None" assert "❤️" in str(result), "Mood response should contain a heart emoji" if __name__ == "__main__": asyncio.run(test_mcp_server_tools()) asyncio.run(test_mcp_server_mood()) ``` -------------------------------------------------------------------------------- /mcp_simple_tool/server.py: -------------------------------------------------------------------------------- ```python import anyio import click import httpx import mcp.types as types from mcp.server.lowlevel import Server async def fetch_website( url: str, ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: headers = { "User-Agent": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)" } try: timeout = httpx.Timeout(10.0, connect=5.0) async with httpx.AsyncClient( follow_redirects=True, headers=headers, timeout=timeout ) as client: response = await client.get(url) response.raise_for_status() return [types.TextContent(type="text", text=response.text)] except httpx.TimeoutException: return [types.TextContent( type="text", text="Error: Request timed out while trying to fetch the website." )] except httpx.HTTPStatusError as e: return [types.TextContent( type="text", text=(f"Error: HTTP {e.response.status_code} " "error while fetching the website.") )] except Exception as e: return [types.TextContent( type="text", text=f"Error: Failed to fetch website: {str(e)}" )] async def check_mood( question: str, ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: """Check server's mood - always responds cheerfully with a heart.""" msg: str = "I'm feeling great and happy to help you! ❤️" return [types.TextContent(type="text", text=msg)] @click.command() @click.option("--port", default=8000, help="Port to listen on for SSE") @click.option( "--transport", type=click.Choice(["stdio", "sse"]), default="stdio", help="Transport type", ) def main(port: int, transport: str) -> int: app = Server("mcp-website-fetcher") mood_description: str = ( "Ask this MCP server about its mood! You can phrase your question " "in any way you like - 'How are you?', 'What's your mood?', or even " "'Are you having a good day?'. The server will always respond with " "a cheerful message and a heart ❤️" ) @app.call_tool() async def fetch_tool( # type: ignore[unused-function] name: str, arguments: dict ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: if name == "mcp_fetch": if "url" not in arguments: return [types.TextContent( type="text", text="Error: Missing required argument 'url'" )] return await fetch_website(arguments["url"]) elif name == "mood": if "question" not in arguments: return [types.TextContent( type="text", text="Error: Missing required argument 'question'" )] return await check_mood(arguments["question"]) else: return [types.TextContent( type="text", text=f"Error: Unknown tool: {name}" )] @app.list_tools() async def list_tools() -> list[types.Tool]: # type: ignore[unused-function] return [ types.Tool( name="mcp_fetch", description="Fetches a website and returns its content", inputSchema={ "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "description": "URL to fetch", } }, }, ), types.Tool( name="mood", description="Ask the server about its mood - it's always happy!", inputSchema={ "type": "object", "required": ["question"], "properties": { "question": { "type": "string", "description": mood_description, } }, }, ) ] if transport == "sse": from mcp.server.sse import SseServerTransport from starlette.applications import Starlette from starlette.routing import Mount, Route sse = SseServerTransport("/messages/") async def handle_sse(request): async with sse.connect_sse( request.scope, request.receive, request._send ) as streams: await app.run( streams[0], streams[1], app.create_initialization_options() ) starlette_app = Starlette( debug=True, routes=[ Route("/sse", endpoint=handle_sse), Mount("/messages/", app=sse.handle_post_message), ], ) import uvicorn uvicorn.run(starlette_app, host="0.0.0.0", port=port) else: from mcp.server.stdio import stdio_server async def arun(): async with stdio_server() as streams: await app.run( streams[0], streams[1], app.create_initialization_options() ) anyio.run(arun) return 0 ```