Releases: drmaxbdc/productboard-mcp
Release list
v2.0.3 — OAuth client_secret support for confidential clients
Hotfix: makes 2.0.2's OAuth path actually finish. The 2.0.2 design assumed all manually-registered Productboard OAuth apps were Public Clients (PKCE-only, no secret). They are not: PB's admin UI issues a client_secret for every manually-registered app, with no Public Client / PKCE-only option. Authorize succeeds but token exchange fails with HTTP 400.
Added
PRODUCTBOARD_OAUTH_CLIENT_SECRETenv var. When set, included inPOST /oauth2/tokenfor both the initial authorization-code exchange (inoauth-setup.ts) and every subsequent refresh (inoauth-refresh.ts). Stripped of CR/LF and trimmed before use. Not persisted totokens.json— re-read from env at each refresh, so the secret stays in whatever store the consumer chose (tarsroles.json, Claude Code MCP config env block, shell init, etc.).resolveClientSecret()helper insrc/auth/types.ts. Lives intypes.ts(notresolver.ts) sooauth-refresh.tscan import it without creating aresolver↔refreshcircular import.
Changed
- 404 error message in
registerClient()now instructs the user to copy bothclient_idandclient_secretfrom PB admin UI and export both env vars. Previously instructed only onclient_id. SetupOptions.clientSecretfield added; threaded throughHandlerContextto the token-exchange body.
Known issues (still upstream)
- Productboard's
POST /oauth2/registerstill returns HTTP 404 (Kong gateway, no backend wired). When upstream is fixed, the dynamic-registration path will activate automatically and produce a true Public Client — at which pointPRODUCTBOARD_OAUTH_CLIENT_SECRETbecomes optional. The code already handles both cases (omitsclient_secretfrom the request when undefined).
Migration notes for callers
- Nothing breaks for PAT users. PAT mode is unaffected.
- Dr.Max users on tars: after
roles.jsonis bumped to 2.0.3,PRODUCTBOARD_OAUTH_CLIENT_SECRETmust be added to the productboard MCP's env block alongside the existing config. Without it, token exchange returns HTTP 400. - Non-Dr.Max consumers: must register their own OAuth app in PB admin UI and set both
PRODUCTBOARD_OAUTH_CLIENT_IDandPRODUCTBOARD_OAUTH_CLIENT_SECRET. The 404 error message now walks through both. - Existing OAuth installations from 2.0.2: delete
tokens.json(the 2.0.2 attempt never succeeded) and restart with the secret env var set.
v2.0.2 — OAuth manual-registration fallback
Hotfix: makes OAuth usable today by embedding a Dr.Max-registered client_id as the default, working around an upstream Productboard bug in POST /oauth2/register that returns HTTP 404 in production. The dynamic-registration code path is unchanged and will start working automatically the day Productboard fixes the endpoint.
Added
- Embedded
DEFAULT_OAUTH_CLIENT_IDinsrc/auth/types.tspointing at Dr.Max's OAuth application registered manually at https://app.productboard.com/oauth2/applications. Used as the default when noPRODUCTBOARD_OAUTH_CLIENT_IDenv override and no storedregistration.jsonexist. - Resolver priority chain extended to check
DEFAULT_OAUTH_CLIENT_IDafter disk-recovery and before falling back to dynamic registration. The chosenclient_idis persisted toregistration.jsonso subsequent starts skip the chain.
Changed
registerClient()404 handling now surfaces a long, actionable message: how to register an OAuth app manually in PB admin UI, what redirect URI to use, what scopes to pick, and which env var to export. Replaces the previous generic config_invalid error that exposed only the upstream response body.
Known issues (upstream)
- Productboard's Dynamic Client Registration endpoint (
POST /oauth2/register) returns HTTP 404 in production (Kong gateway, no backend wired). The endpoint is documented at oauth-public-client.md. A support ticket has been filed. When upstream resolves, this MCP will use dynamic registration automatically — no code change needed.
Migration notes for callers
- Nothing breaks for PAT users. PAT mode is unaffected.
- Dr.Max users on tars: after
roles.jsonis bumped to 2.0.2, OAuth setup works out of the box (embedded Dr.Max client_id, then chooser, then PB consent, then tokens persisted). - Non-Dr.Max consumers: must register their own OAuth app in PB admin UI and set
PRODUCTBOARD_OAUTH_CLIENT_IDenv var. The error message that fires when no override is set walks them through the steps.
v2.0.1 — OAuth 2.0 via Public Client Self-registration
Adds OAuth 2.0 Authorization Code flow (with PKCE) as a second authentication option alongside the existing Personal Access Token (PAT) path. Both paths are first-class and fully supported; OAuth is preferred for fresh installs because it offers rotation, per-user audit trail, and browser-based onboarding instead of admin-issued tokens.
Added
- OAuth 2.0 authentication via Public Client Self-registration (RFC 7591). First-run flow: the MCP dynamically registers itself as a public OAuth client at
POST https://app.productboard.com/oauth2/register(no manual app registration needed), then opens a browser-based scope chooser (Read only / Read+Write / Full), then runs the standard Productboard authorize-and-consent flow with PKCE. Tokens are persisted to the platform-native cache directory with file perms0600and refreshed proactively (5-minute buffer before expiry) and reactively (one retry after a 401). The 60-minute refresh-token grace window in Productboard's OAuth implementation is leveraged to handle multi-process token contention without explicit file locking. The dynamically registeredclient_idis persisted separately toregistration.jsonso deletingtokens.jsonto re-authorize does not burn a registration quota slot (PB rate-limits registration to 5/min, 50/day per IP). PRODUCTBOARD_AUTH_MODEenv var. Optional.oauthforces OAuth even ifPRODUCTBOARD_ACCESS_TOKENis set;patrequires the env var. Unset → auto (priority tree: PAT env > OAuth tokens.json > setup flow).PRODUCTBOARD_OAUTH_CLIENT_IDenv var. Optional advanced override. Pre-register your own custom-branded OAuth app in your PB workspace and set this env var to bypass the self-registration step. Most users don't need it.PRODUCTBOARD_OAUTH_CALLBACK_PORTenv var. Optional override of the default7779callback port (also re-register the new URL in your PB OAuth app if using your own pre-registered client).PRODUCTBOARD_OAUTH_TOKEN_PATHenv var. Optional override of the tokens.json location (Docker volumes, multi-tenant test setups).PRODUCTBOARD_OAUTH_REGISTRATION_PATHenv var. Optional override of the registration.json location.PRODUCTBOARD_OAUTH_SCOPESenv var. Optional space- or comma-separated list of scopes; bypasses the chooser page.
Changed
apiRequest/v1ApiRequestare now Bearer-source-agnostic. They consult an injectedAuthResolutioninstead of readingPRODUCTBOARD_ACCESS_TOKENdirectly. PAT mode preserves the previous behavior byte-for-byte.- HTTP 401 now triggers a refresh+retry once in OAuth mode, or a structured "switch to OAuth" hint in PAT mode (instead of a raw
Bad tokenerror). - Bearer values are now validated client-side before assembling the Authorization header: leading/trailing whitespace and embedded CR/LF are rejected with a clean error. This prevents the kind of "Productboard PAT label + newline pasted into env" mishap from echoing the token back in an HTTP-header-validation exception.
Internal
- New
src/auth/directory:types.ts,token-store.ts,oauth-setup.ts,oauth-refresh.ts,oauth-register.ts,resolver.ts. - No new npm dependencies. PKCE uses Node's
crypto; the callback listener uses Node'shttp; browser launch useschild_process.spawn; Dynamic Client Registration uses Node'sfetch.
Migration notes for callers
- Nothing breaks. Existing deployments with
PRODUCTBOARD_ACCESS_TOKENset continue to use PAT auth unchanged. - Fresh installs without an env var will self-register as an OAuth public client and open a browser at first start. Users complete the scope chooser + authorize once; both
registration.jsonandtokens.jsonpersist across restarts and refresh automatically. - Dr.Max tars users: the new package version will be picked up by
roles.jsononce bumped; the OAuth migration happens in tars in a separate phased rollout (see the design spec).
Full changelog: CHANGELOG.md
npm: https://www.npmjs.com/package/@drmaxbdc/productboard-mcp/v/2.0.1
v2.0.0 — Productboard API V2 migration
Migration release for Productboard's REST API v2. Productboard sunsets v1 on 2026-07-08; this release moves the MCP off v1 wherever v2 can serve the query, and clearly flags the few surfaces with no v2 equivalent.
Breaking
- Note response shape changed across all note tools. v1 returned rich top-level fields (
displayUrl,followers,features[].importance, embeddedcomments[],totalResults); v2 returns a leaner shape with fields nested underfields{...}, relationships underrelationships{...}, and the web UI URL atlinks.html(replacing top-leveldisplayUrl). Tools affected:list_notes,list_all_notes,get_note,get_note_v1,resolve_note, and the V2 path ofsearch_notes. list_notesandlist_all_notesnow hide archived notes by default. Passarchived: trueto include them.list_notes:sourceRecordIdfilter is now sent asmetadata[source][recordId](wassource[recordId]in v1). A newsourceSystemfilter exposes v2'smetadata[source][system]. Source metadata may be empty during the v1→v2 data transition.list_all_notes: response no longer carriestotalResults(v2 dropped it). Pagination still works vianextPageCursor.
Added
- Hybrid
search_notes. Routes to v2POST /notes/searchby default; falls back to v1 only whenterm(fulltext) is set,allTagshas 2+ values, or bothallTagsandanyTagare present (v2 tag filter is OR-only). Response carriesapiVersion("v1"or"v2") and an optional_warningsarray so callers know which path served the query and why. - Auto-translated
lastwindow.search_notes'slastrelative-window strings ("6m","10d","24h","1h") are now translated to v2updatedAt.fromautomatically, solastno longer forces v1. list_notes/list_all_notes:archivedfilter. New optional boolean exposing v2's archived-note filter. Defaultfalse.
Deprecated
get_note_v1is now a thin alias forget_note. Both hit v2GET /notes/{id}. Kept only for backwards compatibility during the transition. Will be removed in the next major (v2.0.0-cleanup).add_note_commenthas no v2 equivalent. The tool will stop working on 2026-07-08 and will be removed in the next major.
Removed (effectively — no v2 equivalent)
- Top-level
note.displayUrl— usenote.links.html. - Top-level
note.followers[]— no v2 alternative. - Top-level
note.features[]inline on note responses — callget_note_relationshipsper note if you need them.features[].importanceis permanently removed. - Embedded
note.comments[]— seeadd_note_commentdeprecation. totalResultsin list responses.
Migration notes for callers
- If you read
note.displayUrl, switch tonote.links.html. - If you read
note.followers/note.features[].importance, accept the loss or call relationship endpoints separately. - If you relied on
totalResultsfor "how many notes match?", iterate the pages and count, or change the UX to paginate-as-you-go. - If you were calling
search_notes(term="..."), that still works via v1 fallback until 2026-07-08; plan for an alternative (e.g. embeddings index on top oflist_all_notes) before then. - If you were calling
add_note_comment, plan to remove that integration point before 2026-07-08.
Full changelog: CHANGELOG.md
npm: https://www.npmjs.com/package/@drmaxbdc/productboard-mcp/v/2.0.0