Summary
Every hookdeck gateway mcp tool response carries a meta block whose active_project_name is the empty string whenever the CLI is authenticated with a project-scoped credential (a key from hookdeck ci, or a dashboard/single-project API key). active_project_org is omitted entirely in the same case.
Observed against a project whose name is definitely set:
{"data":[...],"meta":{"active_project_id":"tm_2IFNDdmOuD4R","active_project_name":""}}
hookdeck whoami on the same config resolves the name fine — ... on project Shopify Demo in organization Demos — so the name is reachable; the MCP meta block just isn't getting it.
Why it matters
meta exists so an MCP client can tell the user which project it is operating on. An empty name means the client shows a bare tm_... id or nothing, so a user driving MCP gets no readable confirmation of which project they are about to act on — including before the write actions hookdeck_connections pause / unpause.
Root cause
fillProjectDisplayNameIfNeeded (pkg/gateway/mcp/project_display.go) is the only thing that populates client.ProjectName / client.ProjectOrg for a session started from a config file, and it has exactly one source: client.ListProjects() (GET /2026-09-01/projects).
- The profile on disk stores only
api_key, project_id, project_type / project_mode — never the project name — so every MCP process starts with ProjectName and ProjectOrg empty and must recover them from the API.
GET /projects returns 403 for project-scoped credentials. The CLI surfaces this elsewhere as "this credential is scoped to a single project and cannot list all projects".
fillProjectDisplayNameIfNeeded swallows the error and returns, leaving both fields "". JSONResultEnvelope then emits "active_project_name":"" and drops active_project_org.
GET /2026-09-01/cli-auth/validate does return the names for exactly these keys (team_name_no_org, organization_name) — that is where hookdeck whoami gets them — but the MCP path never consults it.
Reproduced locally with a hookdeck ci key (empty name) versus an account-wide CLI key (name present), confirming the split is credential scope, not project data.
Fix
Add a fallback in fillProjectDisplayNameIfNeeded: when ListProjects fails, or succeeds but does not contain the active project, call ValidateAPIKey() and take ProjectName / OrganizationName from it — but only when the response's team_id matches the active project_id, since /cli-auth/validate reports the project the key is bound to, which can differ from the profile's active project. That guard mirrors resolveActiveProject in pkg/cmd/whoami.go and prevents the meta block from naming the wrong project.
Covered by unit tests in pkg/gateway/mcp/project_display_test.go: fallback on a 403 project list, no mislabelling when the key's project differs, and no extra /cli-auth/validate call when the project list already resolves the name.
Related, separate: whoami prints an empty email parenthetical
hookdeck whoami renders Logged in as macdeck () on project .... This is not the same root cause. GET /2026-09-01/cli-auth/validate simply does not return a user_email field for CLI-client credentials (verified: the response carries user_name, organization_name, team_*, client_id and no email field at all), while it does return one for a user-bound CLI key. ValidateAPIKeyResponse.UserEmail is correctly mapped to user_email; there is nothing to read. The CLI-side fix would be cosmetic — omit the parenthetical when UserEmail is empty, in pkg/cmd/whoami.go and pkg/login.SuccessMessage. Not addressed here.
Filed by Claude on Phil's behalf.
Summary
Every
hookdeck gateway mcptool response carries ametablock whoseactive_project_nameis the empty string whenever the CLI is authenticated with a project-scoped credential (a key fromhookdeck ci, or a dashboard/single-project API key).active_project_orgis omitted entirely in the same case.Observed against a project whose name is definitely set:
{"data":[...],"meta":{"active_project_id":"tm_2IFNDdmOuD4R","active_project_name":""}}hookdeck whoamion the same config resolves the name fine —... on project Shopify Demo in organization Demos— so the name is reachable; the MCP meta block just isn't getting it.Why it matters
metaexists so an MCP client can tell the user which project it is operating on. An empty name means the client shows a baretm_...id or nothing, so a user driving MCP gets no readable confirmation of which project they are about to act on — including before the write actionshookdeck_connections pause/unpause.Root cause
fillProjectDisplayNameIfNeeded(pkg/gateway/mcp/project_display.go) is the only thing that populatesclient.ProjectName/client.ProjectOrgfor a session started from a config file, and it has exactly one source:client.ListProjects()(GET /2026-09-01/projects).api_key,project_id,project_type/project_mode— never the project name — so every MCP process starts withProjectNameandProjectOrgempty and must recover them from the API.GET /projectsreturns 403 for project-scoped credentials. The CLI surfaces this elsewhere as "this credential is scoped to a single project and cannot list all projects".fillProjectDisplayNameIfNeededswallows the error and returns, leaving both fields"".JSONResultEnvelopethen emits"active_project_name":""and dropsactive_project_org.GET /2026-09-01/cli-auth/validatedoes return the names for exactly these keys (team_name_no_org,organization_name) — that is wherehookdeck whoamigets them — but the MCP path never consults it.Reproduced locally with a
hookdeck cikey (empty name) versus an account-wide CLI key (name present), confirming the split is credential scope, not project data.Fix
Add a fallback in
fillProjectDisplayNameIfNeeded: whenListProjectsfails, or succeeds but does not contain the active project, callValidateAPIKey()and takeProjectName/OrganizationNamefrom it — but only when the response'steam_idmatches the activeproject_id, since/cli-auth/validatereports the project the key is bound to, which can differ from the profile's active project. That guard mirrorsresolveActiveProjectinpkg/cmd/whoami.goand prevents the meta block from naming the wrong project.Covered by unit tests in
pkg/gateway/mcp/project_display_test.go: fallback on a 403 project list, no mislabelling when the key's project differs, and no extra/cli-auth/validatecall when the project list already resolves the name.Related, separate:
whoamiprints an empty email parentheticalhookdeck whoamirendersLogged in as macdeck () on project .... This is not the same root cause.GET /2026-09-01/cli-auth/validatesimply does not return auser_emailfield for CLI-client credentials (verified: the response carriesuser_name,organization_name,team_*,client_idand no email field at all), while it does return one for a user-bound CLI key.ValidateAPIKeyResponse.UserEmailis correctly mapped touser_email; there is nothing to read. The CLI-side fix would be cosmetic — omit the parenthetical whenUserEmailis empty, inpkg/cmd/whoami.goandpkg/login.SuccessMessage. Not addressed here.Filed by Claude on Phil's behalf.