Add support to publish servers as MCP Bundles#1681
Conversation
…nd update .NET SDK usage
1082b6f to
c107d61
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces end-to-end infrastructure to package MCP servers as MCP Bundles (.mcpb), sign them via ESRP detached PKCS#7 signing, publish bundles to GitHub Releases, and surface MCPB entries in server.json (including SHA256 hash updates during publishing).
Changes:
- Added MCPB packaging/signing PowerShell scripts and new pipeline job templates to pack, sign, verify, and publish
.mcpbartifacts. - Added per-server MCPB manifests/icons and updated server registry metadata (
server.json) to include MCPB package entries. - Added/updated documentation describing MCPB packaging/signing and updated engineering docs/pipeline parameters to support
PackageMCPB.
Reviewed changes
Copilot reviewed 24 out of 28 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| servers/Template.Mcp.Server/server.json | Adds MCPB package entries (URLs + SHA placeholders) to registry metadata. |
| servers/Template.Mcp.Server/mcpb/servericon.png | Adds MCPB icon asset for Template server. |
| servers/Template.Mcp.Server/mcpb/manifest.json | Adds MCPB bundle manifest for Template server. |
| servers/Template.Mcp.Server/build.yml | Introduces PackageMCPB parameter wiring for Template pipeline. |
| servers/Fabric.Mcp.Server/mcpb/servericon.png | Adds MCPB icon asset for Fabric server. |
| servers/Fabric.Mcp.Server/mcpb/manifest.json | Adds MCPB bundle manifest for Fabric server. |
| servers/Fabric.Mcp.Server/build.yml | Introduces PackageMCPB parameter wiring for Fabric pipeline. |
| servers/Azure.Mcp.Server/server.json | Adds MCPB package entries (URLs + SHA placeholders) to registry metadata. |
| servers/Azure.Mcp.Server/mcpb/servericon.png | Adds MCPB icon asset for Azure server. |
| servers/Azure.Mcp.Server/mcpb/manifest.json | Adds MCPB bundle manifest for Azure server. |
| servers/Azure.Mcp.Server/changelog-entries/1770669516879.yaml | Adds changelog entry noting MCPB support. |
| servers/Azure.Mcp.Server/build.yml | Enables PackageMCPB by default for Azure pipeline. |
| eng/scripts/Verify-McpbSignatures.ps1 | Adds MCPB signature verification script (mcpb info/verify). |
| eng/scripts/Update-ServerJsonMcpbHashes.ps1 | Adds script to compute SHA256 for signed bundles and update server.json. |
| eng/scripts/Stage-McpbForSigning.ps1 | Adds staging script for ESRP detached signing workflow. |
| eng/scripts/Pack-Mcpb.ps1 | Adds packaging script to generate .mcpb bundles from trimmed binaries + manifests. |
| eng/scripts/New-ServerJson.ps1 | Updates server.json generation to populate MCPB download URLs. |
| eng/scripts/Apply-McpbSignatures.ps1 | Adds script to embed detached PKCS#7 signatures into MCPB format. |
| eng/pipelines/templates/jobs/update-mcp-repository.yml | Updates MCP repository publish job to apply MCPB SHA256 hashes. |
| eng/pipelines/templates/jobs/sign-and-pack.yml | Adds PackageMCPB parameter and MCPB pack/sign job inclusion. |
| eng/pipelines/templates/jobs/release.yml | Adds MCPB publishing job and wires MCPB into release flow. |
| eng/pipelines/templates/jobs/mcpb/release-mcpb.yml | Adds job template to upload signed .mcpb assets to GitHub Releases. |
| eng/pipelines/templates/jobs/mcpb/pack-and-sign-mcpb.yml | Adds job template to pack, ESRP-sign, apply signatures, and verify MCPB bundles. |
| eng/pipelines/templates/common.yml | Plumbs PackageMCPB parameter through common pipeline template. |
| eng/pipelines/templates/1es-redirect.yml | Enables 1ES golang internal module proxy feature flag. |
| eng/README.md | Documents MCPB artifact type and related scripts/pipelines. |
| docs/design/mcpb-packaging-and-signing-via-esrp.md | Adds detailed design document for MCPB packaging/signing via ESRP. |
| .gitignore | Ignores .vscode/settings.json. |
|
|
||
| # Note: --repo is required because checkout is not allowed in release jobs, | ||
| # so gh cannot determine the repository from the git context | ||
| & gh release upload $tag $mcpb.FullName --repo microsoft/mcp --clobber |
There was a problem hiding this comment.
This will break when we move to immutable releases. If we need to ensure the files are part of the GitHub release, we should come up with a scheme to include them in some binaries_github artifact, then have the generic release job upload everything in that artifact to GH when the release is created.
| @@ -83,9 +91,12 @@ jobs: | |||
| - template: /eng/pipelines/templates/jobs/update-mcp-repository.yml | |||
There was a problem hiding this comment.
How does update-mcp-repository know if it needs to update a docker entry without a PackageDocker parameter?
There was a problem hiding this comment.
By default, it assumes we ship them all at the same time; that there aren't partial updates to our public releases. We would have to modify the script if we are expecting partial updates.
| $signedContent = New-Object byte[] ($mcpbContent.Length + $sigV1MarkerBytes.Length + $lengthBytes.Length + $signatureBytes.Length + $sigEndMarkerBytes.Length) | ||
|
|
||
| $offset = 0 | ||
|
|
||
| # Copy MCPB content | ||
| [Array]::Copy($mcpbContent, 0, $signedContent, $offset, $mcpbContent.Length) | ||
| $offset += $mcpbContent.Length | ||
|
|
||
| # Copy MCPB_SIG_V1 marker | ||
| [Array]::Copy($sigV1MarkerBytes, 0, $signedContent, $offset, $sigV1MarkerBytes.Length) | ||
| $offset += $sigV1MarkerBytes.Length | ||
|
|
||
| # Copy length prefix | ||
| [Array]::Copy($lengthBytes, 0, $signedContent, $offset, $lengthBytes.Length) | ||
| $offset += $lengthBytes.Length | ||
|
|
||
| # Copy signature | ||
| [Array]::Copy($signatureBytes, 0, $signedContent, $offset, $signatureBytes.Length) | ||
| $offset += $signatureBytes.Length | ||
|
|
||
| # Copy MCPB_SIG_END marker | ||
| [Array]::Copy($sigEndMarkerBytes, 0, $signedContent, $offset, $sigEndMarkerBytes.Length) | ||
|
|
||
| # Write signed file | ||
| [System.IO.File]::WriteAllBytes($OutputFile, $signedContent) |
There was a problem hiding this comment.
Since we're already working in .net types, we could use List<byte> and avoid all the offset math by leaning on .AddRange(bytes)
i.e.
| $signedContent = New-Object byte[] ($mcpbContent.Length + $sigV1MarkerBytes.Length + $lengthBytes.Length + $signatureBytes.Length + $sigEndMarkerBytes.Length) | |
| $offset = 0 | |
| # Copy MCPB content | |
| [Array]::Copy($mcpbContent, 0, $signedContent, $offset, $mcpbContent.Length) | |
| $offset += $mcpbContent.Length | |
| # Copy MCPB_SIG_V1 marker | |
| [Array]::Copy($sigV1MarkerBytes, 0, $signedContent, $offset, $sigV1MarkerBytes.Length) | |
| $offset += $sigV1MarkerBytes.Length | |
| # Copy length prefix | |
| [Array]::Copy($lengthBytes, 0, $signedContent, $offset, $lengthBytes.Length) | |
| $offset += $lengthBytes.Length | |
| # Copy signature | |
| [Array]::Copy($signatureBytes, 0, $signedContent, $offset, $signatureBytes.Length) | |
| $offset += $signatureBytes.Length | |
| # Copy MCPB_SIG_END marker | |
| [Array]::Copy($sigEndMarkerBytes, 0, $signedContent, $offset, $sigEndMarkerBytes.Length) | |
| # Write signed file | |
| [System.IO.File]::WriteAllBytes($OutputFile, $signedContent) | |
| $signedContent = [System.Collections.Generic.List[byte]]::new() | |
| $signedContent.AddRange($mcpbContent) | |
| $signedContent.AddRange($sigV1MarkerBytes) | |
| $signedContent.AddRange($lengthBytes) | |
| $signedContent.AddRange($signatureBytes) | |
| $signedContent.AddRange($sigEndMarkerBytes) | |
| [System.IO.File]::WriteAllBytes($OutputFile, $signedContent) |
There was a problem hiding this comment.
We already have a server icon in each server directory and we refer to it in each server's csproj. New-BuildInfo is responsible for ensuring that the csproj relative path is converted to a repo-root relative path in buildInfo.json.
The packaging manifests refer to the image in a static location and the packaging scripts use the server's imagePath property to copy the server image into that location:
https://github.com/microsoft/mcp/blob/main/servers/Azure.Mcp.Server/images/azureicon.png
"icon": "resources/package-icon.png", Write-Host "Copying server icon from $($server.packageIcon) to $tempPath/resources/package-icon.png"
New-Item -Path "$tempPath/resources" -ItemType Directory -Force | Out-Null
Copy-Item -Path "$RepoRoot/$($server.packageIcon)" -Destination "$tempPath/resources/package-icon.png" -Force| } | ||
|
|
||
| $signedCount = 0 | ||
| $failedCount = 0 |
There was a problem hiding this comment.
could we keep track of the failed items as objects so we don't have to recurse through the file directory multiple times? (powershell does support classes.)
| $platformKey = $Matches[1] | ||
|
|
||
| # Map placeholder suffix to MCPB filename convention | ||
| $platformMap = @{ |
There was a problem hiding this comment.
This should go into New-BuildInfo.ps1. I Believe we wanted to keep all these different platform monikers in one location.
| continue | ||
| } | ||
|
|
||
| Write-Host "`n========================================" -ForegroundColor Cyan |
There was a problem hiding this comment.
You can use LogInfo since we imported the common.ps1
| } | ||
|
|
||
| # Map platform names to their placeholder suffixes | ||
| $platformToPlaceholder = @{ |
There was a problem hiding this comment.
See comment about using build_info for keeping these in one location.
|
|
||
| # Main script logic | ||
| if (!(Test-Path $ArtifactsPath)) { | ||
| LogError "Staging directory not found: $ArtifactsPath" |
There was a problem hiding this comment.
Artifacts directory rather than staging directory?
| $identifier = $package.identifier | ||
|
|
||
| # Extract platform from placeholder (e.g., "WinX64" from "<<McpbUrlWinX64>>") | ||
| if ($identifier -match '<<McpbUrl(\w+)>>') { |
There was a problem hiding this comment.
Looking through server.json for the source of truth for package information seems a little fragile. I feel like we could continue the trend of keeping these as properties in the csproj and derive the platforms supported from it... like
<McbcPlatforms>win-x64,mac-x64</McpbPlatforms>
What does this PR do?
This PR implements end-to-end MCPB (MCP Bundle) packaging and signing infrastructure using Microsoft's ESRP (Enterprise Signing and Release Pipeline) service. MCPB is a standardized bundle format for distributing MCP servers, similar to Chrome extensions (.crx) or VS Code extensions (.vsix).
Documentation
PowerShell Scripts
Pack-Mcpb.ps1- Packages trimmed and self-contained server binaries into MCPB formatStage-McpbForSigning.ps1- Prepares MCPB files for ESRP signingApply-McpbSignatures.ps1- Applies ESRP detached signatures to MCPB filesVerify-McpbSignatures.ps1- Validates MCPB signaturesUpdate-ServerJsonMcpbHashes.ps1- Updatesserver.jsonwith MCPB hashesNew-ServerJson.ps1- Added entries for MCPB filesPipeline Templates
pack-and-sign-mcpb.yml- Main packaging and signing jobrelease-mcpb.yml- Release MCPB files to GitHub releasescommon.ymlrelease.ymlsign-and-pack.ymlupdate-mcp-repository.ymlbuild.yml1es-reditect- Fixed issue with Go package downloads. Module downloads now go through Microsoft's internal proxy server instead of going directly to the public internet (proxy.golang.org).Server Manifests
manifest.jsonfor all servers. These files serve as a base that gets updated during release to include the most recent server metadata (version, tools, etc.).server.jsonfor Azure and Template servers to list MCP Bundles in the official MCP registry.Testing
mcpb verifyvalidates signed bundlesGitHub issue number?
Fixes: #128
Pre-merge Checklist
servers/Azure.Mcp.Server/CHANGELOG.mdand/orservers/Fabric.Mcp.Server/CHANGELOG.mdfor product changes (features, bug fixes, UI/UX, updated dependencies)servers/Azure.Mcp.Server/README.mdand/orservers/Fabric.Mcp.Server/README.mddocumentationeng/scripts/Process-PackageReadMe.ps1. See Package README/servers/Azure.Mcp.Server/docs/azmcp-commands.mdand/or/docs/fabric-commands.md.\eng\scripts\Update-AzCommandsMetadata.ps1to update tool metadata in azmcp-commands.md (required for CI)ToolDescriptionEvaluatorand obtained a score of0.4or more and a top 3 ranking for all related test promptsconsolidated-tools.json/servers/Azure.Mcp.Server/docs/e2eTestPrompts.mdcrypto mining, spam, data exfiltration, etc.)/azp run mcp - pullrequest - liveto run Live Test Pipeline