#
tokens: 2692/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── .python-version
├── .vscode
│   └── settings.json
├── LICENSE
├── mcp-server-diff-python.code-workspace
├── pyproject.toml
├── README.md
├── src
│   └── mcp_server_diff_python
│       ├── __init__.py
│       └── server.py
├── tests
│   └── mcp_server_diff_python
│       └── test_server.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.11

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
.coverage

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# mcp-server-diff-python

An MCP server for obtaining text differences between two strings.
This server leverages Python's standard library `difflib` to efficiently generate and provide differences between two texts in Unified diff format, making it ideal for text comparison and version control purposes.

<a href="https://glama.ai/mcp/servers/qbwsx2g4vd"><img width="380" height="200" src="https://glama.ai/mcp/servers/qbwsx2g4vd/badge" alt="Server Diff Python MCP server" /></a>

## Features

### Tools

The server provides a single tool:

- **get-unified-diff**: Get differences between two texts in Unified diff format
  - Arguments:
    - `string_a`: Source text for comparison (required)
    - `string_b`: Target text to compare against (required)
  - Return value: A string containing the differences in Unified diff format

## Usage

### Claude Desktop

Using with Claude Desktop
To use with Claude Desktop, add the server config:

On MacOS:  `~/Library/Application\ Support/Claude/claude_desktop_config.json`  
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

```json
"mcpServers": {
  "mcp-server-diff-python": {
    "command": "uvx",
    "args": [
      "mcp-server-diff-python"
    ]
  }
}
```

or Add the following configuration:

```bash
git clone https://github.com/tatn/mcp-server-diff-python.git
cd mcp-server-diff-python
uv sync
uv build
```

```json
"mcpServers": {
  "mcp-server-diff-python": {
    "command": "uv",
    "args": [
      "--directory",
      "path\\to\\mcp-server-diff-python",
      "run",
      "mcp-server-diff-python"
    ]
  }
}
```

## Development
### Debugging

You can start the MCP Inspector using [npx](https://docs.npmjs.com/cli/v11/commands/npx)with the following commands:

```bash
npx @modelcontextprotocol/inspector uvx mcp-server-diff-python
```

```bash
npx @modelcontextprotocol/inspector uv --directory path\to\mcp-server-diff-python run mcp-server-diff-python
```


```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
{
    "python.testing.pytestArgs": [
        "tests"
    ],
    "python.testing.unittestEnabled": false,
    "python.testing.pytestEnabled": true
}
```

--------------------------------------------------------------------------------
/src/mcp_server_diff_python/__init__.py:
--------------------------------------------------------------------------------

```python
import asyncio

from . import server


def main():
    """Main entry point for the package."""
    asyncio.run(server.main())


# Optionally expose other important items at package level
__all__ = ["main", "server"]

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "mcp-server-diff-python"
version = "0.1.2"
description = "This is an MCP server that gets the difference between two texts."
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.11",
]
dependencies = [ "mcp>=1.2.0",]
[[project.authors]]
name = "tatn"

[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
    "black>=24.10.0",
    "build>=1.2.2.post1",
    "hatch>=1.14.0",
    "mypy>=1.14.1",
    "pytest>=8.3.4",
    "pytest-asyncio>=0.25.2",
    "pytest-cov>=6.0.0",
    "ruff>=0.9.2",
    "twine>=6.0.1",
]

[project.urls]
Homepage = "https://github.com/tatn/mcp-server-diff-python"
Repository = "https://github.com/tatn/mcp-server-diff-python"


[project.scripts]
mcp-server-diff-python = "mcp_server_diff_python:main"

[tool.pytest.ini_options]
addopts = "-ra -q"
testpaths = ["tests"]

[tool.hatch.build.targets.wheel]
packages = ["src/mcp_server_diff_python"]

[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'

[tool.ruff]
lint.select = ["E", "F", "I", "N"]
line-length = 88
target-version = "py311"

[tool.mypy]
python_version = "3.11"
strict = true

```

--------------------------------------------------------------------------------
/src/mcp_server_diff_python/server.py:
--------------------------------------------------------------------------------

```python
import difflib

import mcp.server.stdio
import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions

server = Server("mcp-server-diff-python")


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """
    List available tools.
    Each tool specifies its arguments using JSON Schema validation.
    """
    return [
        types.Tool(
            name="get-unified-diff",
            description="Get the difference between two text articles in Unified diff format. Use this when you want to extract the difference between texts.",  # noqa: E501
            inputSchema={
                "type": "object",
                "properties": {
                    "string_a": {"type": "string"},
                    "string_b": {"type": "string"},
                },
                "required": ["string_a", "string_b"],
            },
        )
    ]


@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
    """
    Handle tool execution requests.
    Tools can modify server state and notify clients of changes.
    """
    if name != "get-unified-diff":
        raise ValueError(f"Unknown tool: {name}")

    if not arguments:
        raise ValueError("Missing arguments")

    string_a: str = arguments.get("string_a")
    string_b: str = arguments.get("string_b")

    if string_a is None or string_b is None:
        raise ValueError("Missing 'string_a' or 'string_b' in arguments")

    diff_iterator = difflib.unified_diff(string_a.splitlines(), string_b.splitlines())

    return [types.TextContent(type="text", text="\n".join(diff_iterator))]


async def main():
    # Run the server using stdin/stdout streams
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="mcp-server-diff-python",
                server_version="0.1.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

```

--------------------------------------------------------------------------------
/tests/mcp_server_diff_python/test_server.py:
--------------------------------------------------------------------------------

```python
import mcp.types as types
import pytest

from mcp_server_diff_python.server import handle_call_tool, handle_list_tools


@pytest.mark.asyncio
async def test_handle_list_tools():
    tools = await handle_list_tools()
    assert len(tools) == 1
    tool = tools[0]
    assert tool.name == "get-unified-diff"
    assert (
        tool.description
        == "Get the difference between two text articles in Unified diff format. Use this when you want to extract the difference between texts."  # noqa: E501
    )
    assert "string_a" in tool.inputSchema["properties"]
    assert "string_b" in tool.inputSchema["properties"]
    assert "string_a" in tool.inputSchema["required"]
    assert "string_b" in tool.inputSchema["required"]


@pytest.mark.asyncio
async def test_handle_call_tool():
    arguments = {
        "string_a": "line1\nline2\nline3",
        "string_b": "line1\nline2 modified\nline3",
    }
    result = await handle_call_tool("get-unified-diff", arguments)
    assert len(result) == 1
    assert isinstance(result[0], types.TextContent)
    assert result[0].type == "text"
    excepted = (
        "--- \n\n+++ \n\n@@ -1,3 +1,3 @@\n\n line1\n-line2\n+line2 modified\n line3"
    )
    assert excepted in result[0].text


@pytest.mark.asyncio
async def test_handle_call_tool_missing_arguments():
    with pytest.raises(ValueError, match="Missing arguments"):
        await handle_call_tool("get-unified-diff", None)


@pytest.mark.asyncio
async def test_handle_call_tool_unknown_tool():
    with pytest.raises(ValueError, match="Unknown tool: unknown-tool"):
        await handle_call_tool("unknown-tool", {"string_a": "a", "string_b": "b"})


@pytest.mark.asyncio
async def test_handle_call_tool_missing_string_a():
    with pytest.raises(
        ValueError, match="Missing 'string_a' or 'string_b' in arguments"
    ):
        await handle_call_tool("get-unified-diff", {"string_b": "b"})


@pytest.mark.asyncio
async def test_handle_call_tool_missing_string_b():
    with pytest.raises(
        ValueError, match="Missing 'string_a' or 'string_b' in arguments"
    ):
        await handle_call_tool("get-unified-diff", {"string_a": "a"})

        @pytest.mark.asyncio
        async def test_handle_call_tool_empty_strings():
            arguments = {"string_a": "", "string_b": ""}
            result = await handle_call_tool("get-unified-diff", arguments)
            assert len(result) == 1
            assert isinstance(result[0], types.TextContent)
            assert result[0].type == "text"
            assert result[0].text == ""

        @pytest.mark.asyncio
        async def test_handle_call_tool_identical_strings():
            arguments = {
                "string_a": "line1\nline2\nline3",
                "string_b": "line1\nline2\nline3",
            }
            result = await handle_call_tool("get-unified-diff", arguments)
            assert len(result) == 1
            assert isinstance(result[0], types.TextContent)
            assert result[0].type == "text"
            assert result[0].text == ""

        @pytest.mark.asyncio
        async def test_handle_call_tool_different_strings():
            arguments = {
                "string_a": "line1\nline2\nline3",
                "string_b": "line1\nline2\nline4",
            }
            result = await handle_call_tool("get-unified-diff", arguments)
            assert len(result) == 1
            assert isinstance(result[0], types.TextContent)
            assert result[0].type == "text"
            assert (
                "--- \n+++ \n@@ -1,3 +1,3 @@\n line1\n line2\n-line3\n+line4"
                in result[0].text
            )

```