Skip to content

Commit cf8e965

Browse files
authored
AI-331: bridge 3.0.4 - pretty slugs, dxt packing fix + latest refresh, dispatch-only npm STAGE (OIDC) (#29)
3.0.4 metadata (canonical docs slugs, DXT manifest version sync) plus release-flow hardening: fix the .dxt packing so the extension contains its entry point (with a CI integrity gate), create bridge releases with --latest=false and auto-refresh the .dxt on the latest release, carry the .dxt forward onto new plugin releases, and a dispatch-only OIDC npm STAGE job (CI can stage but never release). npm 3.0.4 was already published from a byte-verified tarball. AI-331.
1 parent 15cbb63 commit cf8e965

7 files changed

Lines changed: 138 additions & 16 deletions

File tree

.github/workflows/publish-shippo-mcp.yml

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
name: Release @shippo/shippo-mcp (.dxt)
22
on:
33
workflow_dispatch:
4+
inputs:
5+
npm_tag:
6+
description: "Existing shippo-mcp-v* tag to publish to npm (empty = verify only, no publish)"
7+
required: false
8+
default: ''
49
push:
510
tags: ['shippo-mcp-v*']
6-
# npm publishing is intentionally NOT automated: the maintainer runs
7-
# `npm publish` manually from a verified local checkout. This workflow only
8-
# verifies the tagged code and publishes the GitHub release with the .dxt,
9-
# which the README's "Add to Claude Desktop" button links to.
11+
# Tag push = release ONLY: verify the tagged code, attach the .dxt to the
12+
# GitHub release (the README's "Add to Claude Desktop" button), and refresh
13+
# the .dxt on the "latest" release for the docs download URL.
14+
# CI NEVER releases to npm. The npm-stage job runs only when a maintainer
15+
# manually dispatches this workflow with the npm_tag input set, then pauses
16+
# at the npm-stage-publish environment for required-reviewer approval, and the
17+
# npm Trusted Publisher (goshippo/ai + this filename + environment
18+
# npm-stage-publish) is restricted to the STAGE action: the upload lands as a
19+
# staged version that a maintainer must release on npmjs.com. Triple gate,
20+
# no long-lived npm token anywhere. Staging must be dispatched ON the tag ref
21+
# itself (guard-enforced), so the ref the release job verified, the packed
22+
# content, and the provenance attestation all describe the same commit.
1023
# contents: write so the tagged run can create that release.
1124
permissions:
1225
contents: write
@@ -27,14 +40,99 @@ jobs:
2740
- run: npm run build
2841
# Build the Desktop Extension and publish it as the tag's release asset, so
2942
# the README download button (releases/download/<tag>/shippo.dxt) resolves.
30-
# Only on a tag push (a release needs a tag); skipped on workflow_dispatch.
31-
- if: startsWith(github.ref, 'refs/tags/')
43+
# --latest=false: the repo's "latest" release must stay the shippo-plugin
44+
# release (docs.goshippo.com deep-links releases/latest/download/ for the
45+
# plugin zip, knowledge pack, and shippo.dxt); a bridge release taking
46+
# "latest" 404s those links (happened 2026-07-17, fixed 2026-07-22).
47+
# Only on the tag-push EVENT: a workflow_dispatch never touches release
48+
# assets, even when dispatched on a tag ref (npm staging requires that).
49+
- if: github.event_name == 'push'
3250
run: npm run build-dxt
33-
- if: startsWith(github.ref, 'refs/tags/')
51+
# Refuse to distribute a broken extension: the packer must have included
52+
# the bundled entry point (3.0.3's .dxtignore excluded dxt-dist/, so its
53+
# shippo.dxt could not launch in Claude Desktop).
54+
- if: github.event_name == 'push'
55+
run: unzip -l shippo.dxt | grep -q 'dxt-dist/index.js'
56+
- if: github.event_name == 'push'
3457
env:
3558
GH_TOKEN: ${{ github.token }}
3659
run: |
37-
gh release create "$GITHUB_REF_NAME" shippo.dxt \
60+
gh release create "$GITHUB_REF_NAME" shippo.dxt --latest=false \
3861
--title "@shippo/shippo-mcp $GITHUB_REF_NAME" \
3962
--notes "Local bridge to the hosted Shippo MCP server. To install in Claude Desktop, download shippo.dxt and open it." \
4063
|| gh release upload "$GITHUB_REF_NAME" shippo.dxt --clobber
64+
# Keep the docs' stable URL fresh: docs.goshippo.com's download button
65+
# links releases/latest/download/shippo.dxt, and "latest" is pinned to
66+
# the shippo-plugin release (see --latest=false above). Clobber this
67+
# build's .dxt onto whatever release is currently "latest" so the docs
68+
# always serve the newest bridge, with no manual refresh step.
69+
- if: github.event_name == 'push'
70+
env:
71+
GH_TOKEN: ${{ github.token }}
72+
run: |
73+
LATEST=$(gh release view --json tagName --jq .tagName 2>/dev/null || true)
74+
if [ -n "$LATEST" ] && [ "$LATEST" != "$GITHUB_REF_NAME" ]; then
75+
gh release upload "$LATEST" shippo.dxt --clobber
76+
fi
77+
npm-stage:
78+
# Dispatch-only, triple-gated: (1) a maintainer must manually run this
79+
# workflow with npm_tag set; (2) the npm-stage-publish environment's required
80+
# reviewer must approve the job; (3) the npm Trusted Publisher's allowed
81+
# action is STAGE only, so this job cannot release: it stages the version
82+
# and a maintainer releases it on npmjs.com. Guards: the input tag must
83+
# match the checked-out package.json version, and an already-released
84+
# version is skipped cleanly (e.g. one that was published manually).
85+
needs: release
86+
if: github.event_name == 'workflow_dispatch' && inputs.npm_tag != ''
87+
runs-on: ubuntu-latest
88+
environment: npm-stage-publish
89+
permissions:
90+
contents: read
91+
id-token: write
92+
defaults:
93+
run:
94+
working-directory: tools/modelcontextprotocol
95+
steps:
96+
- uses: actions/checkout@v4
97+
with:
98+
ref: ${{ inputs.npm_tag }}
99+
# Node 24 bundles npm 11.16+: OIDC trusted publishing needs npm >= 11.5.1
100+
# and the npm stage command needs >= 11.15.0. Node 22 bundles npm 10.x,
101+
# which supports neither (the job would fail auth with no token to fall
102+
# back on). The release job stays on 22 to match .node-version.
103+
- uses: actions/setup-node@v4
104+
with:
105+
node-version: '24'
106+
registry-url: 'https://registry.npmjs.org'
107+
- run: npm ci
108+
- id: guard
109+
env:
110+
NPM_TAG: ${{ inputs.npm_tag }}
111+
run: |
112+
# The dispatch must be run ON the tag itself ("Use workflow from" ->
113+
# the tag). Then the release job (needs) typechecked/tested/built this
114+
# exact content, and the provenance attestation records the same ref
115+
# that was packed. Release-asset steps stay untouched either way
116+
# (they are gated on the push event).
117+
if [ "$GITHUB_REF" != "refs/tags/$NPM_TAG" ]; then
118+
echo "Dispatch this workflow ON the tag: pick '$NPM_TAG' in the 'Use workflow from' picker (run ref is '$GITHUB_REF')."
119+
exit 1
120+
fi
121+
PKG_VER=$(node -p "require('./package.json').version")
122+
TAG_VER="${NPM_TAG#shippo-mcp-v}"
123+
if [ "$PKG_VER" != "$TAG_VER" ]; then
124+
echo "package.json version ($PKG_VER) does not match tag ($TAG_VER)"; exit 1
125+
fi
126+
if npm view "@shippo/shippo-mcp@$PKG_VER" version >/dev/null 2>&1; then
127+
echo "@shippo/shippo-mcp@$PKG_VER already on the registry; skipping publish"
128+
echo "skip=true" >> "$GITHUB_OUTPUT"
129+
fi
130+
# npm stage publish uploads a STAGED version (provenance is generated
131+
# automatically under trusted publishing). It cannot release: the Trusted
132+
# Publisher's only allowed action is stage; a maintainer releases via
133+
# npm stage approve (or the Staged Packages tab on npmjs.com) with 2FA.
134+
# NOTE: a pending staged version is invisible to npm view, so the guard
135+
# above cannot skip it; re-dispatching while a stage awaits approval
136+
# fails loudly here. Approve or reject the pending stage first.
137+
- if: steps.guard.outputs.skip != 'true'
138+
run: npm stage publish

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,26 @@ jobs:
105105
dist/app-plugin/shippo-plugin.zip
106106
dist/shippo-knowledge-pack.md
107107
108+
- name: Carry forward shippo.dxt from the current latest release
109+
# This new plugin release becomes the repo's "latest" when published,
110+
# and docs.goshippo.com deep-links releases/latest/download/shippo.dxt
111+
# (the Claude Desktop button). Fetch the bridge .dxt from the CURRENT
112+
# latest (still the previous plugin release at this point) so the new
113+
# latest carries all three assets; skip cleanly on bootstrap (no dxt
114+
# anywhere yet). gh release download fails on a missing asset, so a
115+
# 404 HTML page can never be uploaded as shippo.dxt.
116+
if: steps.gate.outputs.release == 'true'
117+
env:
118+
GH_TOKEN: ${{ github.token }}
119+
run: |
120+
mkdir -p dist
121+
if gh release download --pattern shippo.dxt --output dist/shippo.dxt 2>/dev/null; then
122+
unzip -l dist/shippo.dxt | grep -q 'manifest.json' || { echo "carried shippo.dxt is not a valid archive"; exit 1; }
123+
echo "will carry shippo.dxt onto the new release"
124+
else
125+
echo "no shippo.dxt on the current latest release; skipping carry"
126+
fi
127+
108128
- name: Publish GitHub Release
109129
if: steps.gate.outputs.release == 'true'
110130
env:
@@ -118,4 +138,8 @@ jobs:
118138
fi
119139
# --clobber so re-runs replace the same-named asset idempotently.
120140
gh release upload "$TAG" dist/app-plugin/shippo-plugin.zip dist/shippo-knowledge-pack.md --clobber
141+
if [ -f dist/shippo.dxt ]; then
142+
gh release upload "$TAG" dist/shippo.dxt --clobber
143+
echo "Carried shippo.dxt onto release $TAG."
144+
fi
121145
echo "Published shippo-plugin.zip and shippo-knowledge-pack.md to release $TAG."

tools/modelcontextprotocol/.dxtignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
node_modules/
22
src/
33
test/
4-
dist/
4+
dist/**
55
tsconfig.json
66
tsup.config.ts
77
build-dxt.js

tools/modelcontextprotocol/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ You must register for a [Shippo account](https://apps.goshippo.com/join) to use
99
You sign in through your browser (OAuth), so there is no API key to manage.
1010

1111
<!-- API-KEY-AUTH (hidden until the hosted key door ships; the code path stays live, this is docs-only):
12-
For headless or automation use (CI, cron, service accounts) you can supply a Shippo [API token](https://docs.goshippo.com/docs/guides_general/authentication/) instead. A `shippo_test_` key runs in test mode and produces test labels; a `shippo_live_` key runs on your live account, where buying a label is billable.
12+
For headless or automation use (CI, cron, service accounts) you can supply a Shippo [API token](https://docs.goshippo.com/guides/authentication) instead. A `shippo_test_` key runs in test mode and produces test labels; a `shippo_live_` key runs on your live account, where buying a label is billable.
1313
-->
1414

1515
## Summary

tools/modelcontextprotocol/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"dxt_version": "0.1",
33
"name": "@shippo/shippo-mcp",
44
"display_name": "Shippo",
5-
"version": "3.0.0",
5+
"version": "3.0.4",
66
"description": "Multi-carrier shipping for AI agents: compare rates, buy labels, track packages, and validate addresses. Local bridge to the hosted Shippo MCP server.",
77
"author": {
88
"name": "Shippo",
99
"email": "support@goshippo.com"
1010
},
11-
"documentation": "https://docs.goshippo.com/docs/Guides_general/MCPServer",
11+
"documentation": "https://docs.goshippo.com/guides/mcp-server",
1212
"server": {
1313
"type": "node",
1414
"entry_point": "dxt-dist/index.js",

tools/modelcontextprotocol/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/modelcontextprotocol/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"name": "@shippo/shippo-mcp",
33
"mcpName": "com.shippo/shippo-mcp",
4-
"version": "3.0.3",
4+
"version": "3.0.4",
55
"description": "Multi-carrier shipping for AI agents: compare rates, buy labels, track packages, validate addresses. Local bridge to the hosted Shippo MCP server.",
66
"license": "MIT",
77
"repository": {
88
"type": "git",
99
"url": "git+https://github.com/goshippo/ai.git",
1010
"directory": "tools/modelcontextprotocol"
1111
},
12-
"homepage": "https://docs.goshippo.com/docs/Guides_general/MCPServer",
12+
"homepage": "https://docs.goshippo.com/guides/mcp-server",
1313
"bugs": "https://github.com/goshippo/ai/issues",
1414
"type": "module",
1515
"bin": {

0 commit comments

Comments
 (0)