Uvx support (#1359)

* add uvx support

* updates

* update

* update

* update

* modifications to include the new artifact

* update

* update

* added pypi settings

* update release-pypi script

* address feedback

* ping build tool versions

---------

Co-authored-by: Scott Beddall <scbedd@microsoft.com>
This commit is contained in:
Xiang YanandScott Beddall authored and GitHub committed 2026-01-23 15:27:40 -08:00
1 parent a66acba42b
commit 023bfd6ccb
13 files changed
+813 -3

No files matched your search

@@ -0,0 +1,39 @@
parameters:
- name: DependsOn
type: object
jobs:
- job: PackPyPI
displayName: "Pack PyPI Package"
dependsOn: ${{ parameters.DependsOn }}
timeoutInMinutes: 120
steps:
- checkout: self
- task: UsePythonVersion@0
displayName: Use Python 3.12
inputs:
versionSpec: '3.12'
- download: current
displayName: Download build_info
artifact: build_info
- download: current
displayName: Download binaries_signed
artifact: binaries_signed
- task: Powershell@2
displayName: "Pack modules for PyPI"
inputs:
pwsh: true
filePath: $(Build.SourcesDirectory)/eng/scripts/Pack-Pypi.ps1
arguments: >
-BuildInfoPath '$(Pipeline.Workspace)/build_info/build_info.json'
-ArtifactsPath '$(Pipeline.Workspace)/binaries_signed'
-OutputPath '$(Build.ArtifactStagingDirectory)'
- template: /eng/common/pipelines/templates/steps/publish-1es-artifact.yml
parameters:
ArtifactPath: $(Build.ArtifactStagingDirectory)
ArtifactName: packages_pypi
@@ -0,0 +1,82 @@
parameters:
- name: ServerName
type: string
- name: IncludeNative
type: boolean
- name: DependsOn
type: object
jobs:
- deployment: PublishPyPI
displayName: "Publish to PyPI"
dependsOn: ${{ parameters.DependsOn }}
condition: and(succeeded(), ne(variables['Skip.PublishPackage'], 'true'))
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
artifactName: packages_pypi
targetPath: $(Pipeline.Workspace)/drop
environment: package-publish
timeoutInMinutes: 120
pool:
name: azsdk-pool
image: ubuntu-24.04
os: linux
strategy:
runOnce:
deploy:
steps:
- pwsh: |
$esrpDirectory = "$(Pipeline.Workspace)/esrp-release/${{ parameters.ServerName }}"
New-Item -ItemType Directory -Force -Path $esrpDirectory
Get-ChildItem -Path "$(Pipeline.Workspace)/drop/${{ parameters.ServerName }}" `
| Where-Object { ($_.Name -like "*.tar.gz" -or $_.Name -like "*.whl") } `
| Copy-Item -Destination $esrpDirectory
Get-ChildItem $esrpDirectory
displayName: Isolate files for ESRP Publish
- task: EsrpRelease@9
displayName: 'Publish to ESRP'
inputs:
ConnectedServiceName: 'Azure SDK PME Managed Identity'
ClientId: '5f81938c-2544-4f1f-9251-dd9de5b8a81b'
DomainTenantId: '975f013f-7f24-47e8-a7d3-abc4752bf346'
UseManagedIdentity: true
KeyVaultName: 'kv-azuresdk-codesign'
SignCertName: 'azure-sdk-esrp-release-certificate'
Intent: 'PackageDistribution'
ContentType: 'PyPI'
FolderLocation: $(Pipeline.Workspace)/esrp-release/${{ parameters.ServerName }}
Owners: ${{ coalesce(variables['Build.RequestedForEmail'], 'azuresdk@microsoft.com') }}
Approvers: ${{ coalesce(variables['Build.RequestedForEmail'], 'azuresdk@microsoft.com') }}
ServiceEndpointUrl: 'https://api.esrp.microsoft.com'
MainPublisher: 'ESRPRELPACMANTEST'
- ${{ if eq(parameters.IncludeNative, true) }}:
- pwsh: |
$esrpDirectory = "$(Pipeline.Workspace)/esrp-release/${{ parameters.ServerName }}-native"
New-Item -ItemType Directory -Force -Path $esrpDirectory
Get-ChildItem -Path "$(Pipeline.Workspace)/drop/${{ parameters.ServerName }}-native" `
| Where-Object { ($_.Name -like "*.tar.gz" -or $_.Name -like "*.whl") } `
| Copy-Item -Destination $esrpDirectory
Get-ChildItem $esrpDirectory
displayName: Isolate native files for ESRP Publish
- task: EsrpRelease@9
displayName: 'Publish native package to ESRP'
inputs:
ConnectedServiceName: 'Azure SDK PME Managed Identity'
ClientId: '5f81938c-2544-4f1f-9251-dd9de5b8a81b'
DomainTenantId: '975f013f-7f24-47e8-a7d3-abc4752bf346'
UseManagedIdentity: true
KeyVaultName: 'kv-azuresdk-codesign'
SignCertName: 'azure-sdk-esrp-release-certificate'
Intent: 'PackageDistribution'
ContentType: 'PyPI'
FolderLocation: $(Pipeline.Workspace)/esrp-release/${{ parameters.ServerName }}-native
Owners: ${{ coalesce(variables['Build.RequestedForEmail'], 'azuresdk@microsoft.com') }}
Approvers: ${{ coalesce(variables['Build.RequestedForEmail'], 'azuresdk@microsoft.com') }}
ServiceEndpointUrl: 'https://api.esrp.microsoft.com'
MainPublisher: 'ESRPRELPACMANTEST'
+6
View File
@@ -55,6 +55,12 @@ jobs:
IncludeNative: ${{ parameters.IncludeNative }}
DependsOn: TagRepository
- template: /eng/pipelines/templates/jobs/pypi/release-pypi.yml
parameters:
ServerName: ${{ parameters.ServerName }}
IncludeNative: ${{ parameters.IncludeNative }}
DependsOn: TagRepository
- template: /eng/pipelines/templates/jobs/nuget/release-nuget.yml
parameters:
ServerName: ${{ parameters.ServerName }}
@@ -110,6 +110,10 @@ jobs:
parameters:
DependsOn: Sign
- template: /eng/pipelines/templates/jobs/pypi/pack-pypi.yml
parameters:
DependsOn: Sign
- template: /eng/pipelines/templates/jobs/nuget/pack-and-sign-nuget.yml
parameters:
DependsOn: Sign
+177
View File
@@ -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
View File
@@ -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
View File
@@ -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 = "azuremcp@microsoft.com" }
]
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",
]
+4
View File
@@ -54,6 +54,10 @@ $propertyList = @(
'DnxDescription',
'DnxToolCommandName',
'PypiPackageName',
'PypiPackageKeywords',
'PypiDescription',
'IsAotCompatible',
'McpRepositoryName'
+4 -1
View File
@@ -510,9 +510,12 @@ function Get-ServerDetails {
dnxDescription = $props.DnxDescription
dnxToolCommandName = $props.DnxToolCommandName
dnxPackageTags = @($props.DnxPackageTags -split '[;,] *' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
pypiPackageName = $props.PypiPackageName
pypiDescription = $props.PypiDescription
pypiPackageKeywords = @($props.PypiPackageKeywords -split '[;,] *' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
platforms = $platforms
mcpRepositoryName = $props.McpRepositoryName
serverJsonPath = $props.ServerJsonPath | Get-RepoRelativePath -NormalizeSeparators
platforms = $platforms
}
}
+341
View File
@@ -0,0 +1,341 @@
#!/bin/env pwsh
#Requires -Version 7
<#
.SYNOPSIS
Packs Azure MCP Server binaries into PyPI wheels for distribution.
.DESCRIPTION
This script creates platform-specific PyPI wheels for the Azure MCP Server.
Each wheel contains the binary for a specific platform (OS + architecture).
The wheels are named according to PyPI conventions:
- msmcp_azure-1.0.0-py3-none-win_amd64.whl
- msmcp_azure-1.0.0-py3-none-macosx_11_0_arm64.whl
- msmcp_azure-1.0.0-py3-none-manylinux_2_17_x86_64.whl
etc.
Users can install with:
- pip install msmcp-azure
- uvx msmcp-azure
- pipx install msmcp-azure
.PARAMETER ArtifactsPath
Path to the build artifacts containing the server binaries.
.PARAMETER BuildInfoPath
Path to the build_info.json file containing server and platform details.
.PARAMETER OutputPath
Path where the PyPI packages will be created.
.EXAMPLE
./Pack-Pypi.ps1
Creates PyPI packages using default local paths.
.EXAMPLE
./Pack-Pypi.ps1 -ArtifactsPath ".work/build" -BuildInfoPath ".work/build_info.json"
Creates PyPI packages using specified artifact and build info paths.
#>
[CmdletBinding()]
param(
[string] $ArtifactsPath,
[string] $BuildInfoPath,
[string] $OutputPath
)
$ErrorActionPreference = "Stop"
. "$PSScriptRoot/../common/scripts/common.ps1"
$RepoRoot = $RepoRoot.Path.Replace('\', '/')
$pypiSourcePath = "$RepoRoot/eng/pypi"
# When running locally, ignore missing artifacts instead of failing
$ignoreMissingArtifacts = $env:TF_BUILD -ne 'true'
$exitCode = 0
if (!$ArtifactsPath) {
$ArtifactsPath = "$RepoRoot/.work/build"
}
if (!$BuildInfoPath) {
$BuildInfoPath = "$RepoRoot/.work/build_info.json"
}
if (!$OutputPath) {
$OutputPath = "$RepoRoot/.work/packages_pypi"
Remove-Item -Path $OutputPath -Recurse -Force -ErrorAction SilentlyContinue -ProgressAction SilentlyContinue
}
if (!(Test-Path $ArtifactsPath)) {
LogError "Artifacts path $ArtifactsPath does not exist."
$exitCode = 1
}
if (!(Test-Path $BuildInfoPath)) {
LogError "Build info file $BuildInfoPath does not exist. Run eng/scripts/New-BuildInfo.ps1 to create it."
$exitCode = 1
}
if ($exitCode -ne 0) {
exit $exitCode
}
$buildInfo = Get-Content $BuildInfoPath -Raw | ConvertFrom-Json -AsHashtable
$tempFolder = "$RepoRoot/.work/temp_pypi"
# Map node OS names to PyPI wheel platform tags
# See: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/
$wheelPlatformMap = @{
'win32-x64' = 'win_amd64'
'win32-arm64' = 'win_arm64'
'darwin-x64' = 'macosx_11_0_x86_64'
'darwin-arm64' = 'macosx_11_0_arm64'
'linux-x64' = 'manylinux_2_17_x86_64.manylinux2014_x86_64'
'linux-arm64' = 'manylinux_2_17_aarch64.manylinux2014_aarch64'
}
# Map OS names to Python classifier OS names
$osClassifierMap = @{
'win32' = 'Microsoft :: Windows'
'darwin' = 'MacOS'
'linux' = 'POSIX :: Linux'
}
function Get-ModuleName($packageName) {
return $packageName.Replace('-', '_')
}
function Get-KeywordsString($keywords) {
return ($keywords | ForEach-Object { "`"$_`"" }) -join ', '
}
function Get-PythonCommand {
# Try python3 first (common on Linux/macOS), but verify it works
# On Windows, "python3" may be a Store alias that doesn't work
try {
$null = & python3 --version 2>&1
if ($LASTEXITCODE -eq 0) {
return "python3"
}
} catch {}
# Fall back to python
try {
$null = & python --version 2>&1
if ($LASTEXITCODE -eq 0) {
return "python"
}
} catch {}
throw "Python is not installed or not in PATH. Please install Python 3.10+."
}
function BuildServerPackages([hashtable] $server, [bool] $native) {
$serverDirectory = "$ArtifactsPath/$($server.artifactPath)"
if (!(Test-Path $serverDirectory)) {
$message = "Server directory $serverDirectory does not exist."
if ($ignoreMissingArtifacts) {
Write-Warning $message
}
else {
Write-Error $message
}
return
}
$filteredPlatforms = $server.platforms | Where-Object { $_.native -eq $native -and -not $_.specialPurpose }
if ($filteredPlatforms.Count -eq 0) {
Write-Host "No platforms to build for server $($server.name) with native=$native"
return
}
$serverOutputPath = "$OutputPath/$($server.artifactPath)"
New-Item -ItemType Directory -Force -Path $serverOutputPath | Out-Null
# Use PyPI package name from csproj, skip servers without PyPI configuration
$basePackageName = if ([string]::IsNullOrWhiteSpace($server.pypiPackageName)) { $null } else { $server.pypiPackageName }
if (!$basePackageName) {
Write-Host "Skipping $($server.name) - no PyPI package name configured" -ForegroundColor Yellow
return
}
$description = if ([string]::IsNullOrWhiteSpace($server.pypiDescription)) { $server.description } else { $server.pypiDescription }
$cliName = $server.cliName
$keywords = @(if ($server.pypiPackageKeywords) { $server.pypiPackageKeywords } else { $server.npmPackageKeywords })
$moduleName = Get-ModuleName $basePackageName
if ($native) {
$basePackageName += "-native"
$description += " with native dependencies"
$keywords += "native"
$moduleName = Get-ModuleName $basePackageName
}
$builtPlatforms = @()
# Build a wheel for each platform
foreach ($platform in $filteredPlatforms) {
$platformDirectory = "$ArtifactsPath/$($platform.artifactPath)"
if (!(Test-Path $platformDirectory)) {
$errorMessage = "Platform directory $platformDirectory does not exist."
if ($ignoreMissingArtifacts) {
Write-Warning $errorMessage
continue
}
Write-Error $errorMessage
return
}
$pypiOs = $platform.nodeOs
$arch = $platform.architecture
$platformKey = "$pypiOs-$arch"
$wheelPlatformTag = $wheelPlatformMap[$platformKey]
if (!$wheelPlatformTag) {
Write-Warning "Unknown platform: $platformKey, skipping"
continue
}
$osClassifier = $osClassifierMap[$pypiOs]
$extension = $platform.extension
Write-Host "`nBuilding wheel for $basePackageName ($platformKey)" -ForegroundColor Cyan
# Clean temp folder
Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue -ProgressAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $tempFolder | Out-Null
New-Item -ItemType Directory -Force -Path "$tempFolder/src/$moduleName/bin" | Out-Null
# Copy binary files
Write-Host " Copying binaries from $platformDirectory"
Copy-Item -Path "$platformDirectory/*" -Destination "$tempFolder/src/$moduleName/bin" -Recurse -Force
# Copy package __init__.py
Copy-Item -Path "$pypiSourcePath/__init__.py" -Destination "$tempFolder/src/$moduleName/__init__.py" -Force
# Remove symbols files before packing
Write-Host " Removing symbol files"
Get-ChildItem -Path $tempFolder -Recurse -Include "*.pdb", "*.dSYM", "*.dbg" | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue -ProgressAction SilentlyContinue
# Read template and replace placeholders
$pyprojectTemplate = Get-Content "$pypiSourcePath/pyproject.toml.template" -Raw
$pyprojectContent = $pyprojectTemplate `
-replace '{{PACKAGE_NAME}}', $basePackageName `
-replace '{{VERSION}}', $server.version `
-replace '{{DESCRIPTION}}', $description `
-replace '{{KEYWORDS}}', (Get-KeywordsString $keywords) `
-replace '{{OS_CLASSIFIER}}', $osClassifier `
-replace '{{CLI_NAME}}', $cliName `
-replace '{{MODULE_NAME}}', $moduleName `
-replace '{{HOMEPAGE}}', $server.readmeUrl
$pyprojectPath = "$tempFolder/pyproject.toml"
Write-Host " Writing pyproject.toml"
$pyprojectContent | Out-File -FilePath $pyprojectPath -Encoding utf8 -Force
# Update version in __init__.py
$initPyPath = "$tempFolder/src/$moduleName/__init__.py"
$initPyContent = Get-Content $initPyPath -Raw
$initPyContent = $initPyContent -replace '__version__ = "0\.0\.0"', "__version__ = `"$($server.version)`""
$initPyContent | Out-File -FilePath $initPyPath -Encoding utf8 -Force
# Set executable permissions on non-Windows
$binPath = "bin/$cliName$extension"
if (!$IsWindows) {
Write-Host " Setting executable permissions" -ForegroundColor Yellow
$binFullPath = "$tempFolder/src/$moduleName/$binPath"
if (Test-Path $binFullPath) {
Invoke-LoggedCommand "chmod +x `"$binFullPath`""
}
}
else {
Write-Warning " Executable permissions are not set when packing on a Windows agent."
}
# Process and copy README
& "$RepoRoot/eng/scripts/Process-PackageReadMe.ps1" `
-Command "extract" `
-InputReadMePath "$RepoRoot/$($server.readmePath)" `
-PackageType "pypi" `
-InsertPayload @{ ToolTitle = 'PyPI Package' } `
-OutputDirectory $tempFolder
Write-Host " Copying LICENSE and NOTICE.txt"
Copy-Item -Path "$RepoRoot/LICENSE" -Destination $tempFolder -Force
Copy-Item -Path "$RepoRoot/NOTICE.txt" -Destination $tempFolder -Force
# Build the wheel with platform-specific tag
Write-Host " Building wheel" -ForegroundColor Green
Push-Location $tempFolder
try {
$pythonCmd = Get-PythonCommand
Invoke-LoggedCommand "$pythonCmd -m pip install --quiet build==1.2.2 wheel==0.45.1"
# Build wheel only (no sdist for platform packages)
# We use --wheel and then rename to set the correct platform tag
Invoke-LoggedCommand "$pythonCmd -m build --wheel"
# Rename the wheel to include the correct platform tag
$distPath = "$tempFolder/dist"
if (Test-Path $distPath) {
$wheels = Get-ChildItem -Path $distPath -Filter "*.whl"
foreach ($wheel in $wheels) {
# The default wheel name is like: msmcp_azure-1.0.0-py3-none-any.whl
# We need to change it to: msmcp_azure-1.0.0-py3-none-<platform>.whl
$newName = $wheel.Name -replace '-py3-none-any\.whl$', "-py3-none-$wheelPlatformTag.whl"
$newPath = Join-Path $distPath $newName
Write-Host " Renaming wheel to $newName"
Move-Item -Path $wheel.FullName -Destination $newPath -Force
# Copy to output
Copy-Item -Path $newPath -Destination $serverOutputPath -Force
Write-Host " ✅ Created: $newName" -ForegroundColor Green
}
}
}
finally {
Pop-Location
}
$builtPlatforms += $platformKey
}
Write-Host "`n✅ PyPI packages built successfully for $($server.name)" -ForegroundColor Green
Write-Host " Package: $basePackageName"
Write-Host " Platforms: $($builtPlatforms -join ', ')"
}
# Main execution
foreach ($server in $buildInfo.servers) {
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Building PyPI packages for $($server.name)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Build non-native packages
BuildServerPackages $server $false
# Build native packages if available
$hasNative = ($server.platforms | Where-Object { $_.native -eq $true }).Count -gt 0
if ($hasNative) {
BuildServerPackages $server $true
}
}
# Cleanup temp folder
Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue -ProgressAction SilentlyContinue
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "PyPI packaging complete!" -ForegroundColor Green
Write-Host "Output: $OutputPath" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
exit $exitCode
+2 -2
View File
@@ -44,7 +44,7 @@ param (
[string] $Command,
[string] $InputReadMePath,
[string] $OutputDirectory,
[ValidateSet('nuget','npm','vsix')]
[ValidateSet('nuget','npm','vsix','pypi')]
[string] $PackageType,
[hashtable] $InsertPayload = @{}
)
@@ -101,7 +101,7 @@ function Extract-PackageSpecificReadMe {
[Parameter(Mandatory=$true)]
[string] $OutputDirectory,
[Parameter(Mandatory=$true)]
[ValidateSet('nuget','npm','vsix')]
[ValidateSet('nuget','npm','vsix','pypi')]
[string] $PackageType,
[hashtable] $InsertPayload = @{}
)
@@ -23,6 +23,10 @@
<DnxPackageId>Azure.Mcp</DnxPackageId>
<DnxToolCommandName>$(CliName)</DnxToolCommandName>
<DnxPackageTags>azure,mcp</DnxPackageTags>
<!-- pypi settings -->
<PypiPackageName>msmcp-azure</PypiPackageName>
<PypiPackageKeywords>azure,mcp,model-context-protocol,ai,llm</PypiPackageKeywords>
</PropertyGroup>
<PropertyGroup>
@@ -23,6 +23,10 @@
<DnxPackageId>Microsoft.Template.Mcp</DnxPackageId>
<DnxToolCommandName>$(CliName)</DnxToolCommandName>
<DnxPackageTags>template,mcp</DnxPackageTags>
<!-- pypi settings -->
<PypiPackageName>msmcp-template</PypiPackageName>
<PypiPackageKeywords>template,mcp,model-context-protocol,ai,llm</PypiPackageKeywords>
</PropertyGroup>
<PropertyGroup>