Artist MCP
The HQ Artist MCP is a remote Model Context Protocol server for managing an artist's show drafts. A compatible client connects over Streamable HTTP, signs in to an existing HQ account through OAuth 2.1, and can then read shows, create drafts, update drafts, and upload cover images.
The endpoint is:
https://api.b90hq.app/mcp
This is a separate authenticated surface from the public show catalog. The catalog is unauthenticated and read-only. Artist MCP uses browser consent and an OAuth access token for account-scoped dashboard show access.
What the server does
The server provides these operations for the authenticated artist:
- Read the shows that the artist can manage, including dashboard fields, lineup, and review state.
- Create an unpublished show draft.
- Update an unpublished show draft.
- Upload one source image and receive derived cover and thumbnail URLs.
- Mark every mutation as an MCP change that needs review in the HQ dashboard.
What the server does not do
- It does not publish a show.
- It does not delete a show.
- It does not edit an already-published show through MCP.
- It does not provide direct Supabase database or Storage access.
- It does not expose an API key or a long-lived personal access token.
- It does not provide access to private RSVP identities, credentials, or unrelated user data.
After an MCP draft is ready, open the show in the HQ dashboard, check the details, and publish it there. Publishing from the dashboard clears the MCP review marker.
Requirements
You need all of the following:
- An existing HQ account that is an artist account.
- The account must have the
artistrole and theartist.show.add_ownpermission. - An MCP client that supports remote Streamable HTTP servers, OAuth 2.1, Proof Key for Code Exchange (PKCE), and Dynamic Client Registration (DCR).
- A browser available for the client's HQ sign-in and consent step.
The MCP endpoint checks the authenticated OAuth client token as well as the HQ artist permission. Being signed in to HQ in another browser tab does not by itself authorize an MCP client.
Install a client connection
All clients below connect to the same remote endpoint. Use a client that supports the required remote Streamable HTTP and OAuth 2.1/DCR features. Do not paste a bearer token into a config file. The client opens the browser consent flow and stores its credentials in the client's own credential store.
Codex desktop or IDE
In Codex desktop, open Settings, select MCP servers, and select Add server. In the IDE
extension, open the gear menu, select MCP servers, and select Add server. Enter hq-artist
as the name, select Streamable HTTP, and enter https://api.b90hq.app/mcp as the URL. Save the
server, restart the app or extension when prompted, and select Authenticate to complete the HQ
browser consent flow.
Codex CLI
Run these commands in a terminal:
codex mcp add hq-artist --url https://api.b90hq.app/mcp
codex mcp login hq-artist
The first command adds the remote server. The second command opens the OAuth browser flow. Approve the requested HQ access after you sign in.
You can also add the server to Codex's TOML configuration:
[mcp_servers.hq-artist]
url = "https://api.b90hq.app/mcp"
After adding the TOML entry, start the client login flow for hq-artist. See the official
Codex MCP documentation for client-specific configuration
and login behavior.
Claude Code
Run these commands:
claude mcp add --transport http hq-artist https://api.b90hq.app/mcp
claude mcp login hq-artist
You can also start or manage the connection from a Claude Code session with /mcp. Complete the
browser consent flow when Claude Code opens it. See the official
Claude Code MCP documentation.
VS Code
Add the server to the workspace or user mcp.json file. VS Code uses the http server type for a
remote Streamable HTTP endpoint:
{
"servers": {
"hq-artist": {
"type": "http",
"url": "https://api.b90hq.app/mcp"
}
}
}
Start the server from VS Code and complete the browser OAuth consent flow. See the official VS Code MCP documentation.
Other compatible clients
Add a remote Streamable HTTP server with the URL https://api.b90hq.app/mcp. The exact config shape
depends on the client. Select HTTP or Streamable HTTP, not a local process command such as stdio.
Then start the client's OAuth 2.1 login flow. A compatible client must support:
- Protected-resource metadata discovery.
- OAuth 2.1 authorization with PKCE.
- Dynamic Client Registration.
- A browser-based consent redirect.
- Bearer tokens bound to the MCP resource URL.
If a client only accepts an API key, a manually entered bearer token, or a local stdio command,
it cannot connect to this server.
These instructions target Codex, Claude Code, VS Code, and compatible clients that expose remote MCP configuration. They do not describe arbitrary local MCP configuration in ChatGPT web.
Authorization and consent
The endpoint uses the OAuth protected-resource flow:
- The client discovers the protected-resource metadata from the endpoint's
401challenge. The metadata identifies the MCP resource and the HQ OAuth authorization server. The path-aware metadata URL ishttps://api.b90hq.app/.well-known/oauth-protected-resource/mcp; the roothttps://api.b90hq.app/.well-known/oauth-protected-resourceis also available. - The client registers itself dynamically when required by its OAuth implementation.
- The client opens a browser and sends you to HQ sign-in and consent. PKCE binds the authorization response to the client that started the flow.
- After consent, the client receives an OAuth access token for the resource
https://api.b90hq.app/mcp. - The client sends that token as a bearer token over HTTPS for subsequent MCP requests.
- HQ validates the token, its audience, the OAuth-client claim, and the artist permission before it exposes the tool catalog.
The OAuth scopes advertised by the protected-resource metadata are openid and profile. The
resource audience is also required. A normal HQ session token, a token issued for another resource,
or a token copied from another client is rejected.
Consent grants this client access to the show-management tools for the signed-in artist. Review the client name and requested access before approving. The server does not publish or delete shows as a side effect of consent.
Remove a local connection
Remove the server entry and the client-side credentials when you no longer want to use a client:
codex mcp logout hq-artist
codex mcp remove hq-artist
claude mcp logout hq-artist
claude mcp remove hq-artist
For VS Code, remove the hq-artist object from mcp.json, then remove the saved MCP account or
OAuth credential through the VS Code account or MCP controls. Other clients provide an equivalent
remove-server and sign-out or clear-credentials action.
Removing a local connection does not delete shows. It also does not claim to revoke every token on the server. If a client offers a provider account-revocation control, use that control as well. To stop using the integration immediately, remove the connection and clear the client's stored credentials.
Safe workflows
Treat create, update, and upload as write operations. Ask the client to show the proposed values and ask for an explicit confirmation before each write. Use a read-before-write sequence so the client does not overwrite changes made in the dashboard or by another tool.
Inspect existing shows
Example prompt:
Use list_shows to inspect my shows. Group the results into published and unpublished shows, and
show the show ID, date, venue, lineup, review marker, and current image URLs. Do not change anything.
Use get_show with the returned UUID before editing one show.
Create a draft
Example prompt:
Create an HQ show draft for 2026-10-10 at The Venue in Berlin for my artist UUID
00000000-0000-4000-8000-000000000001. Use Europe/Berlin, the exact event times I provide, and this
lineup. First show me the complete create payload and wait for my confirmation. After confirmation,
call create_show_draft only. Do not publish the show.
The response includes a new show ID and status: "review". The show remains unpublished.
Update a draft
Example prompt:
Get show 00000000-0000-4000-8000-000000000002 first. Change only the title and web URL in the
existing unpublished draft. Show me the complete resulting values and wait for confirmation before
calling update_show_draft. Do not change the lineup or publish the show.
An update accepts a partial patch. The server merges it with the current show, so omitted fields
stay unchanged. If you provide artist_playtimes, the supplied array replaces the full lineup.
Upload a cover
Example prompt:
Get show 00000000-0000-4000-8000-000000000002 first and confirm that it is unpublished. Upload the
attached JPEG as its cover only after I confirm. Use the image content object and report both
resulting image URLs. Do not publish the show.
The upload returns a new thumbnail_url and cover_url. Review the result in the dashboard with
the show details, then publish from the dashboard when the content is ready.
Tool reference
The server registers exactly five tools. Successful tool results contain one MCP text content item whose text is a JSON value. Clients often parse that text and display it as structured data.
| Tool | Read/write | Idempotency and effect |
|---|---|---|
list_shows | Read | Safe to repeat. Lists the authenticated artist's dashboard-visible shows. |
get_show | Read | Safe to repeat for one show UUID. Returns show_not_found when the show is not readable. |
create_show_draft | Write | Not idempotent at the tool boundary. Each invocation creates a new command ID; repeat only after checking for a prior success. Duplicate-match protection applies unless force_create is true. |
update_show_draft | Write | Semantically idempotent. It applies the requested values to the selected show and returns the same show ID; repeating the same patch is safe after a read. It always leaves the show in review. |
upload_show_cover | Write | Not idempotent. Each invocation creates fresh derived image objects and URLs; repeat only when a new upload is intended. |
The MCP schema does not expose an Idempotency-Key input. The service creates an internal UUID for
each invocation. A network timeout can therefore leave a write uncertain; call get_show or
list_shows before retrying a create or upload.
list_shows
Input: no fields.
Success: a JSON array of dashboard show objects. The list is scoped to shows that the authenticated artist can manage and is ordered by event date and start time.
get_show
Input:
| Field | Type | Required | Constraints |
|---|---|---|---|
show_id | UUID string | Yes | Must identify a show that the authenticated artist can read. |
Success: one dashboard show object. If the UUID is unknown, deleted, or outside the artist's scope,
the tool returns show_not_found.
create_show_draft
Input: all fields in the create and update field reference, except
show_id. Required fields are marked there. The force_create field defaults to false.
Success:
{
"id": "00000000-0000-4000-8000-000000000003",
"status": "review"
}
The returned ID is the new show UUID. Creates always set published to false, mark the record as
MCP and Needs review, and do not attach to an existing show.
update_show_draft
Input: show_id plus any non-empty subset of the update fields. Every field is optional except
show_id; omitted values remain unchanged. The force_create field is not accepted for updates.
Success:
{
"id": "00000000-0000-4000-8000-000000000002",
"status": "review"
}
The update replaces the supplied values, keeps the show unpublished, and marks it for dashboard review. A published show cannot be edited by this tool.
upload_show_cover
Input:
| Field | Type | Required | Constraints |
|---|---|---|---|
show_id | UUID string | Yes | Must identify an unpublished show that the artist can edit. |
image | object | Preferred | { "type": "image", "data": "<base64>", "mimeType": "image/jpeg" }. |
image_base64 | string | Legacy alternative | Base64 source data. Use with mime_type. |
mime_type | string | With legacy input | One of image/jpeg, image/png, image/webp, or image/gif. |
Send the image object when the client supports MCP image content. Do not send both forms with
conflicting data. The image object and the legacy fields are accepted as base64 input because the
remote endpoint receives JSON.
Success:
{
"id": "00000000-0000-4000-8000-000000000002",
"status": "review",
"thumbnail_url": "https://cdn.example.com/thumbnail.jpg",
"cover_url": "https://cdn.example.com/cover.jpg"
}
The URLs are fresh public show-covers objects. The show remains unpublished and marked for review.
Create and update fields
The create schema exposes every field accepted by the dashboard show editor. create_show_draft
requires the fields marked “Create”; update_show_draft accepts the fields marked “Update” as
optional patch fields. The names are snake_case exactly as shown.
| Field | Type | Create | Update | Constraints and meaning |
|---|---|---|---|---|
title | string | Required | Optional | At least 1 and at most 500 characters. |
event_date | string | Required | Optional | Calendar date in YYYY-MM-DD format. |
start_time | string | Required | Optional | Non-empty ISO 8601 timestamp with an explicit offset or Z. |
end_time | string | Required | Optional | Non-empty ISO 8601 timestamp with an explicit offset or Z. |
is_playtime_tba | boolean | Optional, default false | Optional | Marks the top-level playtime as to be announced. |
timezone | string | Required | Optional | Non-empty timezone identifier, such as Europe/Berlin; at most 100 characters. |
location_name | string | Required | Optional | Non-empty venue name; at most 500 characters. |
location_city | string | Required | Optional | Non-empty city; at most 200 characters. |
location_country | string | Required | Optional | Country code with 1 or 2 characters; use the two-letter country code, such as DE. |
latitude | number | Required | Optional | Finite decimal from -90 to 90. |
longitude | number | Required | Optional | Finite decimal from -180 to 180. |
venue_place_id | string or null | Optional | Optional | At most 500 characters. Omit or send null when no place ID exists. |
web_url | string or null | Optional | Optional | Valid URL, at most 2,000 characters. Omit or send null when absent. |
thumbnail_url | string or null | Optional | Optional | Valid URL, at most 2,000 characters. This is a custom image URL; omit or send null when absent. |
artist_id | UUID string | Required | Optional | Owner or primary artist UUID. For an artist account, this normally is the authenticated artist UUID. |
community_id | UUID string or null | Optional | Optional | Community UUID or null. The artist must be allowed to use the selected community. |
artist_playtimes | array | Required | Optional | At least one lineup row. On update, replaces the complete lineup. |
force_create | boolean | Optional, default false | Not accepted | If true, create a separate show even when duplicate candidates are found. |
Lineup and playtimes
Each artist_playtimes item has this schema:
| Field | Type | Required | Constraints and meaning |
|---|---|---|---|
artist_id | UUID string | Yes | Artist UUID for this lineup row. |
community_id | UUID string or null | No | Community UUID for this artist's row, or null. |
start_time | string | Yes | Non-empty ISO 8601 timestamp. |
end_time | string | Yes | Non-empty ISO 8601 timestamp. |
is_playtime_tba | boolean | No, default false | Marks this artist's playtime as to be announced. |
The array must contain at least one row. The service stores the lineup rows as the source for the show's aggregate start, end, and TBA values. On update, submit the complete lineup if you want to change it; omitting the array keeps the current lineup.
When is_playtime_tba is true, still send usable fallback start_time and end_time values. HQ
uses those values for ordering and scheduling, while clients should display “TBA” instead of
presenting them as confirmed artist times.
Dates, timestamps, coordinates, and IDs
event_dateis a date without a time zone. Use the local event calendar date.start_time,end_time, and every lineup timestamp are ISO 8601 instants. Include an offset, for example2026-10-10T22:00:00+02:00, or useZfor UTC.timezoneis the event's IANA time zone. It provides the local context for the date and display.latitudeandlongitudeare separate decimal numbers. Latitude is north/south and longitude is east/west. Do not send a PostgreSQL point string in the mutation input.- Every
artist_id,community_id, andshow_idmust be a UUID. A missing optional community is represented by omission ornull, not an empty string. - If an artist has exactly one eligible owned community and
community_idis omitted on create, HQ can resolve that community automatically. Send the UUID explicitly when the choice matters.
Nullable fields and partial updates
On create, venue_place_id, web_url, thumbnail_url, and community_id may be omitted and are
stored as null. A lineup row's community_id is also nullable. On update, an omitted field keeps
its current value; sending null clears a nullable field. Do not use an empty string as a substitute
for null.
artist_playtimes is different: if supplied, the array replaces the full lineup and must contain at
least one item. Keep the current array when you only want to change title, venue, URL, or another
shell field.
Show response fields
list_shows and get_show return dashboard-shaped objects with snake_case keys. The response can
include unpublished shows and review metadata, so do not expose it as a public catalog response.
{
"id": "00000000-0000-4000-8000-000000000002",
"title": "HQ at the venue",
"event_date": "2026-10-10",
"start_time": "2026-10-10T20:00:00.000Z",
"end_time": "2026-10-11T02:00:00.000Z",
"is_playtime_tba": false,
"timezone": "Europe/Berlin",
"location_name": "The Venue",
"location_city": "Berlin",
"location_country": "DE",
"location_coordinates": "(13.405,52.52)",
"venue_place_id": null,
"web_url": "https://joinhq.app/show/example",
"thumbnail_url": "https://cdn.example.com/thumbnail.jpg",
"cover_url": "https://cdn.example.com/cover.jpg",
"community_thumbnail_url": null,
"community_cover_url": null,
"total_rsvps": 0,
"artist_id": "00000000-0000-4000-8000-000000000001",
"artist_playtimes": [
{
"artist_id": "00000000-0000-4000-8000-000000000001",
"display_name": "Artist name",
"username": "artist",
"avatar_url": null,
"avatar_thumb_url": null,
"community_id": null,
"community_name": null,
"start_time": "2026-10-10T20:00:00.000Z",
"end_time": "2026-10-10T22:00:00.000Z",
"is_playtime_tba": false,
"sort_order": 0
}
],
"owner_display_name": "Artist name",
"owner_username": "artist",
"owner_avatar_url": null,
"owner_avatar_thumb_url": null,
"published": false,
"created_at": "2026-08-01T12:00:00.000Z",
"updated_at": "2026-08-01T12:00:00.000Z",
"booking_api_provider": null,
"booking_api_external_id": null,
"booking_api_imported_at": null,
"booking_api_needs_review": true,
"review_source": "mcp"
}
The response fields are:
| Field | Type | Nullable | Meaning |
|---|---|---|---|
id | UUID string | No | Stable show ID. |
title | string | No | Show title. |
event_date | YYYY-MM-DD string | No | Event calendar date. |
start_time, end_time | ISO timestamp strings | No | Aggregate show time window. |
is_playtime_tba | boolean | No | Aggregate TBA state. |
timezone, location_name, location_city, location_country | string | No | Event time zone and venue fields. |
location_coordinates | string | Yes | Coordinates in the database point representation, when available. |
venue_place_id, web_url, thumbnail_url, cover_url | string | Yes | Venue, URL, and image fields. |
community_thumbnail_url, community_cover_url | string | Yes | Related community image fields. |
total_rsvps | integer | No | Current RSVP count. |
artist_id | UUID string | No | Show owner or primary artist field. |
artist_playtimes | array | No | Full lineup with profile, community, and playtime values. |
owner_display_name, owner_username, owner_avatar_url, owner_avatar_thumb_url | string | Yes | Public owner profile fields, when available. |
published | boolean | No | Whether the dashboard has published the show. |
created_at, updated_at | ISO timestamp strings | Yes | Creation and last-update times, when present. |
booking_api_provider, booking_api_external_id, booking_api_imported_at | string or timestamp | Yes | Booking import metadata, when a show came from a booking provider. |
booking_api_needs_review | boolean | No | Whether dashboard review is required. |
review_source | string | Yes | Review source, normally mcp, aboss, or null. |
Nullable response values are returned as JSON null. The artist_playtimes array is returned as an
array and can be empty for a shell-only imported draft. Each lineup item uses the fields in the
lineup table above plus profile fields and sort_order.
Cover image requirements
upload_show_cover accepts JPEG, PNG, WebP, or GIF source data. The decoded source must be no larger
than 6 MiB. The schema also bounds the base64 input before decoding. Send a real base64 value, not a
data URL with a data:image/...;base64, prefix.
Preferred image input:
{
"show_id": "00000000-0000-4000-8000-000000000002",
"image": {
"type": "image",
"data": "<base64 image bytes>",
"mimeType": "image/jpeg"
}
}
Legacy-compatible input:
{
"show_id": "00000000-0000-4000-8000-000000000002",
"image_base64": "<base64 image bytes>",
"mime_type": "image/jpeg"
}
HQ validates the MIME type and image bytes, applies the source image's rotation metadata, and derives two centered JPEG crops:
- A dashboard cover at
1200 x 900pixels. - A dashboard thumbnail at
1200 x 630pixels.
The source format does not determine the output format. Both returned image URLs point to JPEG derivatives. A corrupt image, unsupported MIME type, malformed base64 value, or source larger than 6 MiB is rejected before storage.
Draft, review, and publication rules
Every create, update, and cover upload carries the mcp review source through the service command.
The database records review_source: "mcp" and booking_api_needs_review: true. The dashboard shows
the MCP and Needs review markers.
- A create always produces
published: false. - An update can change only a show that is still unpublished. It leaves the show unpublished.
- A cover upload can change only a show that is still unpublished. It leaves the show unpublished.
- Dashboard publishing is the only publication path for these MCP drafts.
- Dashboard publishing clears the MCP review marker.
- MCP has no publish or delete tool, and a client cannot bypass this restriction by calling the
ordinary
/v1/*API with its OAuth client token.
Errors and troubleshooting
MCP tool failures contain an error object in the text content. Input schema failures can be reported directly by the client before a tool runs. The most useful server error codes are:
| Code or response | Meaning | What to do |
|---|---|---|
401 invalid access token | The token is absent, expired, not an OAuth-client token, or is for another resource. | Use the client's MCP login flow again. Do not paste a manual bearer token. Check that the URL is exactly https://api.b90hq.app/mcp. |
403 forbidden | The signed-in HQ user is not an eligible artist or lacks artist.show.add_own. | Sign in with the artist account that manages the shows, or ask an HQ administrator to grant the required artist permission. |
503 MCP show routes are unavailable | The hosted MCP route is not activated. | Contact the HQ operator. This is a server configuration issue, not a client payload issue. |
show_not_found | The show UUID is unknown, deleted, or outside the authenticated artist's scope. | Call list_shows, copy the exact UUID, and read it again before updating or uploading. |
show_operation_failed for a published show | The service rejected an MCP update or cover change because the show is published (mcp_show_published_forbidden). | Make edits in the dashboard. MCP cannot reopen or mutate a published show. |
show_match_found | A create request matches an existing show by venue, date, time, and title. | Call list_shows and inspect likely matches. Create a separate show only when that is intended, then repeat with force_create: true. |
invalid_show_image | The image object, MIME type, base64 value, or decoded image is invalid. | Use a JPEG, PNG, WebP, or GIF with valid base64 and no data-URL prefix. |
show_image_too_large | The decoded source is larger than 6 MiB. | Resize or recompress the source and retry after confirming the show is still unpublished. |
show_operation_failed | A service command or permission check failed without a more specific mapped code. | Read the show again, check the payload and permissions, and inspect the dashboard for a partial result before retrying. |
OAuth or login does not open
Confirm that the client supports remote Streamable HTTP, OAuth 2.1, PKCE, and DCR. Remove the local
server entry and add it again with the exact endpoint. Then start codex mcp login hq-artist,
claude mcp login hq-artist, or the equivalent client login action. A client that asks for a static
API key is not compatible with this endpoint.
The client discovers no tools
Check that the client uses the remote HTTP transport and has completed OAuth consent. The endpoint does not expose tools until the token is valid and the user passes the artist permission check. A tool list that includes publish or delete is not the HQ Artist MCP catalog; reconnect to the exact HQ endpoint and report the client version to the HQ operator.
A write timed out
Do not immediately repeat create_show_draft or upload_show_cover. First call list_shows or
get_show to check whether the operation succeeded. The MCP tool input has no client idempotency key,
and uploads intentionally create fresh image URLs.
A duplicate warning is unexpected
Call list_shows, inspect likely matches, and compare the venue, date, times, and lineup. The public
tool error does not include the candidate rows. The default force_create: false protects against
creating the same event twice. Use force_create: true only after an explicit confirmation that a
separate event is intended.
A draft is not visible in the public catalog
This is expected. MCP creates and updates remain unpublished until a user reviews and publishes them
in the HQ dashboard. The public /v1/* show catalog returns published shows only.