#
tokens: 5519/50000 16/16 files
lines: off (toggle) GitHub
raw markdown copy
# 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.

![Server Mood Response](docs/images/server-mood.png)

## Quick Start

1. Click "Deploy to Heroku" button

    [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](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

[![smithery badge](https://smithery.ai/badge/@kirill-markin/example-mcp-server)](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

```