nxd-tools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v5
17
+ with:
18
+ version: "latest"
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.11"
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ uv pip install --system -e ".[dev,mcp-bridge]"
28
+
29
+ - name: Run ruff check
30
+ run: ruff check .
31
+
32
+ - name: Run ruff format check
33
+ run: ruff format --check .
34
+
35
+ test:
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ matrix:
39
+ python-version: ["3.11", "3.12", "3.13"]
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+
43
+ - name: Install uv
44
+ uses: astral-sh/setup-uv@v5
45
+ with:
46
+ version: "latest"
47
+
48
+ - name: Set up Python ${{ matrix.python-version }}
49
+ uses: actions/setup-python@v5
50
+ with:
51
+ python-version: ${{ matrix.python-version }}
52
+
53
+ - name: Install dependencies
54
+ run: |
55
+ uv pip install --system -e ".[mcp-bridge]"
56
+
57
+ - name: Verify installation
58
+ run: |
59
+ python -c "import nxd_tools; print(nxd_tools.__version__)"
60
+ python -m nxd_tools.mcp_bridge --help || true
61
+ nxd-bridge --help || true
@@ -0,0 +1,58 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write # Required for trusted publishing
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+ with:
21
+ version: "latest"
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.11"
27
+
28
+ - name: Install build dependencies
29
+ run: |
30
+ uv pip install --system build
31
+
32
+ - name: Build package
33
+ run: python -m build
34
+
35
+ - name: Upload artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment: pypi
45
+ permissions:
46
+ id-token: write
47
+ steps:
48
+ - name: Download artifacts
49
+ uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ packages-dir: dist/
58
+ # repository-url: https://test.pypi.org/legacy/ # For testing
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1,31 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.8.4
4
+ hooks:
5
+ # Run the linter
6
+ - id: ruff
7
+ args: [--fix]
8
+ # Run the formatter
9
+ - id: ruff-format
10
+
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v5.0.0
13
+ hooks:
14
+ - id: trailing-whitespace
15
+ - id: end-of-file-fixer
16
+ - id: check-yaml
17
+ - id: check-added-large-files
18
+ - id: check-merge-conflict
19
+ - id: check-toml
20
+
21
+ ci:
22
+ autofix_commit_msg: |
23
+ [pre-commit.ci] auto fixes from pre-commit.com hooks
24
+
25
+ for more information, see https://pre-commit.ci
26
+ autofix_prs: true
27
+ autoupdate_branch: ''
28
+ autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate'
29
+ autoupdate_schedule: weekly
30
+ skip: []
31
+ submodules: false
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nextdata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: nxd-tools
3
+ Version: 0.1.0
4
+ Summary: Collection of tools for working with MCP and other protocols
5
+ Author-email: Nextdata <dev@nextdata.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.11
16
+ Provides-Extra: dev
17
+ Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
18
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
19
+ Provides-Extra: mcp-bridge
20
+ Requires-Dist: anyio>=4.8.0; extra == 'mcp-bridge'
21
+ Requires-Dist: httpx>=0.28.1; extra == 'mcp-bridge'
22
+ Requires-Dist: mcp>=1.1.2; extra == 'mcp-bridge'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # nxd-tools
26
+
27
+ Collection of tools for working with MCP (Model Context Protocol) and other protocols.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ # Install the MCP bridge
33
+ pip install nxd-tools[mcp-bridge]
34
+
35
+ # Or with uv
36
+ uv pip install nxd-tools[mcp-bridge]
37
+ ```
38
+
39
+ ## Components
40
+
41
+ ### MCP Bridge (`nxd-tools[mcp-bridge]`)
42
+
43
+ A stdio-to-HTTP proxy that enables stdio-based MCP clients (like Claude Desktop) to communicate with remote streamable HTTP MCP servers.
44
+
45
+ **Usage:**
46
+
47
+ ```bash
48
+ # Using the console script
49
+ nxd-bridge --base-url https://server.example.com --token YOUR_TOKEN
50
+
51
+ # Using module invocation
52
+ python -m nxd_tools.mcp_bridge --base-url https://server.example.com --token YOUR_TOKEN
53
+
54
+ # Disable SSL verification (for development)
55
+ nxd-bridge --base-url https://server.example.com --token YOUR_TOKEN --no-ssl-verify
56
+ ```
57
+
58
+ **Claude Desktop Configuration:**
59
+
60
+ Add to your Claude Desktop MCP settings:
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "my-remote-server": {
66
+ "command": "nxd-bridge",
67
+ "args": [
68
+ "--base-url",
69
+ "https://server.example.com",
70
+ "--token",
71
+ "YOUR_TOKEN"
72
+ ]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Development
79
+
80
+ ```bash
81
+ # Install with dev dependencies
82
+ uv pip install -e ".[dev,mcp-bridge]"
83
+
84
+ # Install pre-commit hooks
85
+ pre-commit install
86
+
87
+ # Run quality checks
88
+ ruff check . --fix
89
+ ruff format .
90
+
91
+ # Build package
92
+ python -m build
93
+ ```
@@ -0,0 +1,69 @@
1
+ # nxd-tools
2
+
3
+ Collection of tools for working with MCP (Model Context Protocol) and other protocols.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Install the MCP bridge
9
+ pip install nxd-tools[mcp-bridge]
10
+
11
+ # Or with uv
12
+ uv pip install nxd-tools[mcp-bridge]
13
+ ```
14
+
15
+ ## Components
16
+
17
+ ### MCP Bridge (`nxd-tools[mcp-bridge]`)
18
+
19
+ A stdio-to-HTTP proxy that enables stdio-based MCP clients (like Claude Desktop) to communicate with remote streamable HTTP MCP servers.
20
+
21
+ **Usage:**
22
+
23
+ ```bash
24
+ # Using the console script
25
+ nxd-bridge --base-url https://server.example.com --token YOUR_TOKEN
26
+
27
+ # Using module invocation
28
+ python -m nxd_tools.mcp_bridge --base-url https://server.example.com --token YOUR_TOKEN
29
+
30
+ # Disable SSL verification (for development)
31
+ nxd-bridge --base-url https://server.example.com --token YOUR_TOKEN --no-ssl-verify
32
+ ```
33
+
34
+ **Claude Desktop Configuration:**
35
+
36
+ Add to your Claude Desktop MCP settings:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "my-remote-server": {
42
+ "command": "nxd-bridge",
43
+ "args": [
44
+ "--base-url",
45
+ "https://server.example.com",
46
+ "--token",
47
+ "YOUR_TOKEN"
48
+ ]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ # Install with dev dependencies
58
+ uv pip install -e ".[dev,mcp-bridge]"
59
+
60
+ # Install pre-commit hooks
61
+ pre-commit install
62
+
63
+ # Run quality checks
64
+ ruff check . --fix
65
+ ruff format .
66
+
67
+ # Build package
68
+ python -m build
69
+ ```
@@ -0,0 +1,3 @@
1
+ """nxd-tools: A collection of tools for working with MCP and other protocols."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,16 @@
1
+ """Main entry point for python -m nxd_tools."""
2
+
3
+ import sys
4
+
5
+
6
+ def main():
7
+ """Main entry point showing available subcommands."""
8
+ print("nxd-tools: Available commands:")
9
+ print(" python -m nxd_tools.mcp_bridge - Run the MCP stdio bridge")
10
+ print("\nOr use the console scripts:")
11
+ print(" nxd-bridge - Run the MCP stdio bridge")
12
+ sys.exit(0)
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,5 @@
1
+ """MCP stdio bridge for proxying to remote HTTP MCP servers."""
2
+
3
+ from nxd_tools.mcp_bridge.bridge import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,8 @@
1
+ """Main entry point for python -m nxd_tools.mcp_bridge."""
2
+
3
+ import sys
4
+
5
+ from nxd_tools.mcp_bridge.bridge import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1,142 @@
1
+ """
2
+ MCP stdio bridge for Claude Desktop.
3
+ Creates a stdio MCP server that proxies all requests to a remote streamable HTTP MCP server.
4
+ """
5
+
6
+ import argparse
7
+ import asyncio
8
+ import logging
9
+ from contextlib import AsyncExitStack
10
+
11
+ import httpx
12
+ from mcp import ClientSession
13
+ from mcp.client.streamable_http import streamablehttp_client
14
+ from mcp.server import Server
15
+ from mcp.server.stdio import stdio_server
16
+ from mcp.types import Tool
17
+
18
+ # Set up logging
19
+ logging.basicConfig(
20
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class MCPProxyServer:
26
+ """MCP server that proxies to a remote streamable HTTP MCP server."""
27
+
28
+ def __init__(self, base_url: str, token: str, no_ssl_verify: bool = False):
29
+ self.base_url = base_url
30
+ self.token = token
31
+ self.no_ssl_verify = no_ssl_verify
32
+ self.mcp_url = f"{base_url}/mcp"
33
+ self.server = Server("mcp-stdio-bridge")
34
+ self.remote_session: ClientSession | None = None
35
+ self.tools_cache: list[Tool] = []
36
+ self.exit_stack = AsyncExitStack()
37
+
38
+ # Register handlers
39
+ self.server.list_tools()(self.handle_list_tools)
40
+ self.server.call_tool()(self.handle_call_tool)
41
+
42
+ def create_httpx_client(self, headers=None, timeout=None, auth=None):
43
+ """Create httpx client with SSL verification settings."""
44
+ return httpx.AsyncClient(
45
+ verify=not self.no_ssl_verify,
46
+ headers=headers,
47
+ timeout=timeout or 30.0,
48
+ auth=auth,
49
+ )
50
+
51
+ async def connect_to_remote(self):
52
+ """Connect to the remote MCP server."""
53
+ logger.info(f"Connecting to remote MCP server at {self.mcp_url}")
54
+ headers = {"X-Nextdata-Token": self.token}
55
+
56
+ # Use AsyncExitStack to properly manage context managers
57
+ read_stream, write_stream, _ = await self.exit_stack.enter_async_context(
58
+ streamablehttp_client(
59
+ self.mcp_url,
60
+ headers=headers,
61
+ httpx_client_factory=self.create_httpx_client,
62
+ )
63
+ )
64
+
65
+ # Create and enter the client session context
66
+ self.remote_session = await self.exit_stack.enter_async_context(
67
+ ClientSession(read_stream, write_stream)
68
+ )
69
+
70
+ # Initialize the remote session
71
+ await self.remote_session.initialize()
72
+ logger.info("Remote MCP session initialized successfully")
73
+
74
+ # Cache available tools
75
+ tools_result = await self.remote_session.list_tools()
76
+ self.tools_cache = tools_result.tools
77
+ logger.info(f"Cached {len(self.tools_cache)} tools from remote server")
78
+
79
+ async def handle_list_tools(self) -> list[Tool]:
80
+ """Handle list_tools request by returning cached tools from remote server."""
81
+ logger.info(f"Handling list_tools request, returning {len(self.tools_cache)} tools")
82
+ return self.tools_cache
83
+
84
+ async def handle_call_tool(self, name: str, arguments: dict):
85
+ """Handle call_tool request by proxying to remote server."""
86
+ logger.info(f"Handling call_tool request for {name} with args: {arguments}")
87
+
88
+ if not self.remote_session:
89
+ raise RuntimeError("Not connected to remote server")
90
+
91
+ # Call the tool on the remote server
92
+ result = await self.remote_session.call_tool(name, arguments)
93
+
94
+ if result.isError:
95
+ logger.error(f"Tool call failed: {result.content}")
96
+ raise RuntimeError(f"Tool call failed: {result.content}")
97
+
98
+ logger.info(f"Tool call successful, returning {len(result.content)} content items")
99
+ return result.content
100
+
101
+ async def cleanup(self):
102
+ """Clean up all connections."""
103
+ await self.exit_stack.aclose()
104
+
105
+
106
+ async def async_main(args):
107
+ """Async main function."""
108
+ # Create proxy server
109
+ proxy = MCPProxyServer(args.base_url, args.token, args.no_ssl_verify)
110
+
111
+ try:
112
+ # Connect to remote server first
113
+ await proxy.connect_to_remote()
114
+
115
+ # Run stdio server
116
+ logger.info("Starting stdio MCP server...")
117
+ async with stdio_server() as (read_stream, write_stream):
118
+ await proxy.server.run(
119
+ read_stream, write_stream, proxy.server.create_initialization_options()
120
+ )
121
+ except KeyboardInterrupt:
122
+ logger.info("Shutting down...")
123
+ except Exception as e:
124
+ logger.error(f"Error running MCP bridge: {e}", exc_info=True)
125
+ raise
126
+ finally:
127
+ await proxy.cleanup()
128
+
129
+
130
+ def main():
131
+ """Main entry point for the MCP bridge."""
132
+ parser = argparse.ArgumentParser(description="MCP stdio bridge for Claude Desktop")
133
+ parser.add_argument("--base-url", required=True, help="Base URL for MCP server")
134
+ parser.add_argument("--token", required=True, help="PAT token for authentication")
135
+ parser.add_argument("--no-ssl-verify", action="store_true", help="Disable SSL verification")
136
+ args = parser.parse_args()
137
+
138
+ asyncio.run(async_main(args))
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
File without changes
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "nxd-tools"
3
+ version = "0.1.0"
4
+ description = "Collection of tools for working with MCP and other protocols"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ authors = [
8
+ { name = "Nextdata", email = "dev@nextdata.com" }
9
+ ]
10
+ license = { text = "MIT" }
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.optional-dependencies]
23
+ mcp-bridge = [
24
+ "mcp>=1.1.2",
25
+ "httpx>=0.28.1",
26
+ "anyio>=4.8.0",
27
+ ]
28
+ dev = [
29
+ "ruff>=0.8.0",
30
+ "pre-commit>=4.0.0",
31
+ ]
32
+
33
+ [project.scripts]
34
+ nxd-bridge = "nxd_tools.mcp_bridge:main"
35
+
36
+ [build-system]
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["nxd_tools"]
42
+
43
+ [tool.ruff]
44
+ line-length = 100
45
+ target-version = "py311"
46
+
47
+ [tool.ruff.lint]
48
+ select = [
49
+ "E", # pycodestyle errors
50
+ "W", # pycodestyle warnings
51
+ "F", # pyflakes
52
+ "I", # isort
53
+ "N", # pep8-naming
54
+ "UP", # pyupgrade
55
+ "B", # flake8-bugbear
56
+ "C4", # flake8-comprehensions
57
+ "SIM", # flake8-simplify
58
+ "RUF", # ruff-specific rules
59
+ ]
60
+ ignore = [
61
+ "E501", # line too long (handled by formatter)
62
+ ]
63
+
64
+ [tool.ruff.lint.isort]
65
+ known-first-party = ["nxd_tools"]
66
+
67
+ [tool.ruff.format]
68
+ quote-style = "double"
69
+ indent-style = "space"
70
+ skip-magic-trailing-comma = false
71
+ line-ending = "auto"