diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..46b5c69 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Use a Python image with uv pre-installed +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS uv + +# Install the project into `/app` +WORKDIR /app + +# Enable bytecode compilation +ENV UV_COMPILE_BYTECODE=1 + +# Copy from the cache instead of linking since it's a mounted volume +ENV UV_LINK_MODE=copy + +# Install the project's dependencies using the lockfile and settings +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-dev --no-editable + +# Then, add the rest of the project source code and install it +# Installing separately from its dependencies allows optimal layer caching +ADD . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev --no-editable + +FROM python:3.13-slim-bookworm + +WORKDIR /app + +# COPY --from=uv /root/.local /root/.local +COPY --from=uv --chown=app:app /app/.venv /app/.venv + +# Copy the source code +COPY --from=uv --chown=app:app /app/src /app/src + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +# Run mcp server +ENTRYPOINT ["python", "src/mcp_text_editor/server.py"] \ No newline at end of file diff --git a/README.md b/README.md index 5b07bc2..ae86d9c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,25 @@ code ~/Library/Application\ Support/Claude/claude_desktop_config.json } ``` +or with docker: +```json +{ + "mcpServers": { + "text-editor": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--mount", + "type=bind,src=/some/path/src,dst=/some/path/dst", + "mcp/text-editor" + ] + } + } +} +``` + ## Overview MCP Text Editor Server is designed to facilitate safe and efficient line-based text file operations in a client-server architecture. It implements the Model Context Protocol, ensuring reliable file editing with robust conflict detection and resolution. The line-oriented approach makes it ideal for applications requiring synchronized file access, such as collaborative editing tools, automated text processing systems, or any scenario where multiple processes need to modify text files safely. The partial file access capability is particularly valuable for LLM-based tools, as it helps reduce token consumption by loading only the necessary portions of files. @@ -98,6 +117,12 @@ npx -y @smithery/cli install mcp-text-editor --client claude pip install -e . ``` +### Docker Installation +``` +docker build --network=host -t mcp/text-editor . +``` + + For development: ```bash @@ -112,6 +137,18 @@ Start the server: python -m mcp_text_editor ``` +Start the server with docker: + +```bash +docker run -i --rm --mount "type=bind,src=/some/path/src,dst=/some/path/dst" mcp/text-editor +``` + +with inspector: + +```bash +npx @modelcontextprotocol/inspector docker run -i --rm --mount "type=bind,src=/some/path/src,dst=/some/path/dst" mcp/text-editor +``` + ### MCP Tools The server provides two main tools: diff --git a/src/mcp_text_editor/handlers/append_text_file_contents.py b/src/mcp_text_editor/handlers/append_text_file_contents.py index e84af4e..fa84740 100644 --- a/src/mcp_text_editor/handlers/append_text_file_contents.py +++ b/src/mcp_text_editor/handlers/append_text_file_contents.py @@ -8,7 +8,7 @@ from mcp.types import TextContent, Tool -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler logger = logging.getLogger("mcp-text-editor") diff --git a/src/mcp_text_editor/handlers/base.py b/src/mcp_text_editor/handlers/base.py index f7c3b9a..47a06eb 100644 --- a/src/mcp_text_editor/handlers/base.py +++ b/src/mcp_text_editor/handlers/base.py @@ -4,7 +4,7 @@ from mcp.types import TextContent, Tool -from ..text_editor import TextEditor +from mcp_text_editor.text_editor import TextEditor class BaseHandler: diff --git a/src/mcp_text_editor/handlers/create_text_file.py b/src/mcp_text_editor/handlers/create_text_file.py index 5f7a7ec..f433577 100644 --- a/src/mcp_text_editor/handlers/create_text_file.py +++ b/src/mcp_text_editor/handlers/create_text_file.py @@ -8,7 +8,7 @@ from mcp.types import TextContent, Tool -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler logger = logging.getLogger("mcp-text-editor") diff --git a/src/mcp_text_editor/handlers/delete_text_file_contents.py b/src/mcp_text_editor/handlers/delete_text_file_contents.py index 033e758..fdce538 100644 --- a/src/mcp_text_editor/handlers/delete_text_file_contents.py +++ b/src/mcp_text_editor/handlers/delete_text_file_contents.py @@ -8,8 +8,8 @@ from mcp.types import TextContent, Tool -from ..models import DeleteTextFileContentsRequest, FileRange -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler +from mcp_text_editor.models import DeleteTextFileContentsRequest, FileRange logger = logging.getLogger("mcp-text-editor") diff --git a/src/mcp_text_editor/handlers/get_text_file_contents.py b/src/mcp_text_editor/handlers/get_text_file_contents.py index 21005cd..5197d42 100644 --- a/src/mcp_text_editor/handlers/get_text_file_contents.py +++ b/src/mcp_text_editor/handlers/get_text_file_contents.py @@ -6,7 +6,7 @@ from mcp.types import TextContent, Tool -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler class GetTextFileContentsHandler(BaseHandler): diff --git a/src/mcp_text_editor/handlers/insert_text_file_contents.py b/src/mcp_text_editor/handlers/insert_text_file_contents.py index 5539f4e..96cc1ba 100644 --- a/src/mcp_text_editor/handlers/insert_text_file_contents.py +++ b/src/mcp_text_editor/handlers/insert_text_file_contents.py @@ -8,7 +8,7 @@ from mcp.types import TextContent, Tool -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler logger = logging.getLogger("mcp-text-editor") diff --git a/src/mcp_text_editor/handlers/patch_text_file_contents.py b/src/mcp_text_editor/handlers/patch_text_file_contents.py index b3d4879..7e71ddb 100644 --- a/src/mcp_text_editor/handlers/patch_text_file_contents.py +++ b/src/mcp_text_editor/handlers/patch_text_file_contents.py @@ -8,7 +8,7 @@ from mcp.types import TextContent, Tool -from .base import BaseHandler +from mcp_text_editor.handlers.base import BaseHandler logger = logging.getLogger("mcp-text-editor") diff --git a/src/mcp_text_editor/server.py b/src/mcp_text_editor/server.py index a1265ed..0179149 100644 --- a/src/mcp_text_editor/server.py +++ b/src/mcp_text_editor/server.py @@ -1,5 +1,6 @@ """MCP Text Editor Server implementation.""" +import asyncio import logging import traceback from collections.abc import Sequence @@ -8,7 +9,7 @@ from mcp.server import Server from mcp.types import TextContent, Tool -from .handlers import ( +from mcp_text_editor.handlers import ( AppendTextFileContentsHandler, CreateTextFileHandler, DeleteTextFileContentsHandler, @@ -16,7 +17,7 @@ InsertTextFileContentsHandler, PatchTextFileContentsHandler, ) -from .version import __version__ +from mcp_text_editor.version import __version__ # Configure logging logging.basicConfig(level=logging.INFO) @@ -88,3 +89,7 @@ async def main() -> None: except Exception as e: logger.error(f"Server error: {str(e)}") raise + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/mcp_text_editor/service.py b/src/mcp_text_editor/service.py index 56a21ae..42785f3 100644 --- a/src/mcp_text_editor/service.py +++ b/src/mcp_text_editor/service.py @@ -3,7 +3,7 @@ import hashlib from typing import Dict, List, Optional, Tuple -from .models import ( +from mcp_text_editor.models import ( DeleteTextFileContentsRequest, EditFileOperation, EditPatch, diff --git a/src/mcp_text_editor/text_editor.py b/src/mcp_text_editor/text_editor.py index 360bd2c..5789e55 100644 --- a/src/mcp_text_editor/text_editor.py +++ b/src/mcp_text_editor/text_editor.py @@ -5,8 +5,8 @@ import os from typing import Any, Dict, List, Optional, Tuple -from .models import DeleteTextFileContentsRequest, EditPatch, FileRanges -from .service import TextEditorService +from mcp_text_editor.models import DeleteTextFileContentsRequest, EditPatch, FileRanges +from mcp_text_editor.service import TextEditorService logger = logging.getLogger(__name__)