Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions eng/pypi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# PyPI Packaging for Azure MCP Server

This document describes how the Azure MCP Server is packaged for distribution on [PyPI](https://pypi.org), enabling installation via `pip`, `pipx`, or execution via `uvx`.

## Package Architecture

The Azure MCP Server is published as a single package (`msmcp-azure`) with platform-specific wheels. Each wheel contains the pre-compiled binary for a specific OS and architecture combination.

### Wheel Naming Convention

Wheels follow PyPI's platform tag conventions:

- `msmcp_azure-1.0.0-py3-none-win_amd64.whl` - Windows x64
- `msmcp_azure-1.0.0-py3-none-win_arm64.whl` - Windows ARM64
- `msmcp_azure-1.0.0-py3-none-macosx_11_0_x86_64.whl` - macOS x64 (Intel)
- `msmcp_azure-1.0.0-py3-none-macosx_11_0_arm64.whl` - macOS ARM64 (Apple Silicon)
- `msmcp_azure-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl` - Linux x64
- `msmcp_azure-1.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl` - Linux ARM64

When you install with `pip install msmcp-azure`, pip automatically selects the correct wheel for your platform.

## Installation Methods

### Using pip

```bash
# Install - pip automatically selects the correct wheel for your platform
pip install msmcp-azure

# Run
azmcp server start
```

### Using uvx (recommended for MCP servers)

```bash
# Run directly without installation
uvx msmcp-azure server start
```

### Using pipx

```bash
# Install as a global tool
pipx install msmcp-azure

# Run
azmcp server start
```

## Configuration with MCP Clients

### VS Code / GitHub Copilot

```json
{
"mcpServers": {
"azure": {
"command": "uvx",
"args": ["msmcp-azure", "server", "start"]
}
}
}
```

### Claude Desktop

```json
{
"mcpServers": {
"azure": {
"command": "uvx",
"args": ["msmcp-azure", "server", "start"]
}
}
}
```

## Building PyPI Packages

### Prerequisites

1. Python 3.10+ with `pip` and `build` package
2. .NET SDK for building server binaries
3. PowerShell 7+

### Build Steps

```powershell
# 1. Create build info
./eng/scripts/New-BuildInfo.ps1 -PublishTarget internal

# 2. Build the server binaries for all platforms
./eng/scripts/Build-Code.ps1 -OperatingSystems windows,linux,macos -Architectures x64,arm64

# 3. Create PyPI packages
./eng/scripts/Pack-Pypi.ps1

# Output will be in .work/packages_pypi/
```

### Local Development

For quick local testing:

```powershell
# Build for current platform only
./eng/scripts/Build-Code.ps1

# Create PyPI packages
./eng/scripts/Pack-Pypi.ps1 -UsePaths

# Install locally for testing
pip install .work/packages_pypi/Azure.Mcp.Server/*.whl
```

## Publishing to PyPI

### Test PyPI (recommended for testing)

```bash
# Upload to Test PyPI
twine upload --repository testpypi .work/packages_pypi/Azure.Mcp.Server/*.whl .work/packages_pypi/Azure.Mcp.Server/*.tar.gz

# Test installation
pip install --index-url https://test.pypi.org/simple/ msmcp-azure
```

### Production PyPI

```bash
# Upload to production PyPI
twine upload .work/packages_pypi/Azure.Mcp.Server/*.whl .work/packages_pypi/Azure.Mcp.Server/*.tar.gz
```

## Project Structure

```
eng/
├── pypi/
│ ├── __init__.py # Package entry point with binary execution
│ ├── pyproject.toml.template # Template for pyproject.toml
│ └── README.md # This file
└── scripts/
└── Pack-Pypi.ps1 # Packaging script
```

## Debugging

Set the `DEBUG` environment variable to enable verbose logging:

```bash
# Enable debug output
DEBUG=true azmcp server start

# Or
DEBUG=mcp azmcp server start
```

## Troubleshooting

### Unsupported platform

If you see an error about an unsupported platform, check that your OS and architecture combination is in the supported list above.

### Permission denied on Unix

The package sets executable permissions during installation, but if you encounter issues:

```bash
chmod +x $(python -c "import msmcp_azure; print(msmcp_azure.get_executable_path())")
```

### Reporting Issues

If you encounter issues not covered here, please report them at:
https://github.com/microsoft/mcp/issues
95 changes: 95 additions & 0 deletions eng/pypi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
Azure MCP Server - PyPI package.

This module provides the entry point for the Azure MCP Server CLI.
The binary is bundled directly in the wheel for the target platform.
"""

import os
import platform
import subprocess
import sys
from pathlib import Path

__version__ = "0.0.0" # Will be replaced during packaging

# Debug mode check
DEBUG = os.environ.get("DEBUG", "").lower() in ("true", "1", "*") or "mcp" in os.environ.get("DEBUG", "")


def debug_log(*args, **kwargs):
"""Print debug messages to stderr if DEBUG is enabled."""
if DEBUG:
print(*args, file=sys.stderr, **kwargs)


def get_executable_path():
"""Get the path to the platform-specific executable."""
# The binary is located in the bin subdirectory of this package
package_dir = Path(__file__).parent
bin_dir = package_dir / "bin"

# Determine the executable name based on platform
system = platform.system().lower()
if system == "windows":
executable_name = "azmcp.exe"
else:
executable_name = "azmcp"

executable_path = bin_dir / executable_name

debug_log(f"Package directory: {package_dir}")
debug_log(f"Binary directory: {bin_dir}")
debug_log(f"Executable path: {executable_path}")

return executable_path


def run_executable(args=None):
"""
Run the platform-specific executable with the given arguments.

Args:
args: List of command-line arguments to pass to the executable.
Defaults to sys.argv[1:] if not provided.

Returns:
The exit code from the executable.
"""
if args is None:
args = sys.argv[1:]

executable_path = get_executable_path()

if not executable_path.exists():
print(f"Error: Executable not found at {executable_path}", file=sys.stderr)
print(f"This may indicate a packaging issue or unsupported platform.", file=sys.stderr)
return 1

debug_log(f"Running: {executable_path} {' '.join(args)}")

try:
result = subprocess.run(
[str(executable_path)] + list(args),
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
)
return result.returncode
except PermissionError:
print(f"Error: Permission denied executing {executable_path}", file=sys.stderr)
print("Try: chmod +x " + str(executable_path), file=sys.stderr)
return 126
except OSError as e:
print(f"Error executing {executable_path}: {e}", file=sys.stderr)
return 1


def main():
"""Main entry point for the CLI."""
sys.exit(run_executable())


if __name__ == "__main__":
main()
51 changes: 51 additions & 0 deletions eng/pypi/pyproject.toml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "{{PACKAGE_NAME}}"
version = "{{VERSION}}"
description = "{{DESCRIPTION}}"
readme = "README.md"
license = "MIT"
authors = [
{ name = "Microsoft", email = "[email protected]" }
]
keywords = [{{KEYWORDS}}]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: {{OS_CLASSIFIER}}",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
]
requires-python = ">=3.10"
dependencies = []

[project.scripts]
{{CLI_NAME}} = "{{MODULE_NAME}}:main"

[project.urls]
Homepage = "{{HOMEPAGE}}"
Documentation = "{{HOMEPAGE}}"
Repository = "https://github.com/microsoft/mcp"
Issues = "https://github.com/microsoft/mcp/issues"

[tool.hatch.build.targets.wheel]
packages = ["src/{{MODULE_NAME}}"]
artifacts = ["src/{{MODULE_NAME}}/bin/*"]

[tool.hatch.build.targets.sdist]
include = [
"src/{{MODULE_NAME}}/**/*.py",
"src/{{MODULE_NAME}}/bin/**/*",
"README.md",
"LICENSE",
"NOTICE.txt",
]
Loading