MCP server & Claude Code skill
The Voqalize MCP server exposes the platform’s management surface as tools your
editor’s agent (Claude Code, etc.) can call. Paired with the voqalize skill,
it takes a developer from an empty project to a running voice agent without leaving
the editor.
It is a hosted, remote MCP endpoint — you don’t install or run anything. Point your MCP client at the URL, authenticate once with Google in the browser, and the tools are available.
Connect
Section titled “Connect”Add the server to your MCP client. In Claude Code:
claude mcp add --transport http voqalize https://app.voqalize.com/mcpOr, project-scoped, drop an .mcp.json at your repo root (this is the file the
voqalize skill ships):
{ "mcpServers": { "voqalize": { "type": "http", "url": "https://app.voqalize.com/mcp" } }}On first use your client runs a browser Google sign-in and the tools light up. There is no API key, client ID, or secret to configure — the client registers itself dynamically and carries the resulting token.
Auth & tenancy
Section titled “Auth & tenancy”- Google OAuth (the same login as the console). Your MCP client authenticates with Google via Dynamic Client Registration — it self-registers, you complete the browser sign-in, and identity comes from your Google account. First sign-in provisions your Voqalize user automatically.
- The token is the credential;
tenantis a selector. Every scoped tool takes a requiredtenantslug and is checked against your membership before it runs — passing a slug you don’t belong to fails. You can’t act on a tenant just by naming it. whoamifirst, thenlist_tenants.whoamireturns your identity;list_tenantsreturns every workspace you can act on. Call them at the start of a session to learn whichtenantslug to pass to everything else.
Sixteen tools. Every tool returns the control plane’s raw JSON. Errors surface with
a platform code — not_authorized (you’re not a member of that tenant, or your role
is too low) or validation_error (bad input, e.g. a non-wss:// brain_url on a
non-loopback host).
Identity & workspace
Section titled “Identity & workspace”| Tool | Signature | Does |
|---|---|---|
whoami | () -> dict | Identify the authenticated developer. Call first. |
list_tenants | () -> dict | Every tenant (workspace) you can act on. |
create_tenant | (about="", display_name="") -> dict | Create your workspace + seed demo agents. Idempotent — returns your existing tenant if you have one. |
Agents
Section titled “Agents”| Tool | Signature | Does |
|---|---|---|
create_agent | (tenant, name, description="", brain_url="") -> dict | Create an agent. Returns {agent, session_key (sk_…, once)}. |
create_agent_credentials | (tenant, agent_id, label="") -> dict | Mint Cortex outbound credentials for a brain that can’t accept inbound (localhost, serverless, egress-only). Returns {agent_secret (ak_…, once), cortex_url, brain_url, key_id, usage}. |
get_agent | (tenant, agent_id) -> dict | One agent: id, name, description, status, brain_url, Playground test_url, timestamps. It does not return STT/TTS config. |
list_agents | (tenant, status="", limit=20) -> dict | List agents; optional draft|active|archived filter. |
update_agent | (tenant, agent_id, name="", description="", brain_url="") -> dict | Rename, re-describe, and/or point the brain at a WS URL. |
archive_agent | (tenant, agent_id) -> dict | Soft delete (stops serving new sessions). |
There is no separate set_brain_url tool — pass brain_url to create_agent up
front, or set it later with update_agent. It must be wss:// (ws:// only for
localhost/127.0.0.1); an empty brain_url falls back to the hosted welcome
demo brain so a bare agent still greets.
create_agent_credentials returns two different URLs and they are not
interchangeable: cortex_url goes to the SDK’s cortex_url= argument (it already
carries the /agent path — pass it verbatim), while brain_url is what the agent’s
own brain_url must become so the runtime dials Cortex instead of your server.
Setting it is not automatic — finish with
update_agent(tenant, agent_id, brain_url=…). The ak_ secret is shown once, never
expires, and minting revokes nothing, so rotation is: mint → redeploy → revoke the
old key. This is what makes local development tunnel-free; see
Cortex relay.
| Tool | Signature | Does |
|---|---|---|
create_api_key | (tenant, label, kind="secret", allowed_origins=None) -> dict | Mint a runtime key. kind="publishable" (pk_, browser — pass origins) or "secret" (sk_, backend). Raw key shown once. |
list_api_keys | (tenant, include_revoked=False) -> dict | List keys (prefixes only). |
revoke_api_key | (tenant, key_id) -> dict | Revoke by id (irreversible). |
Calls & logs (observability)
Section titled “Calls & logs (observability)”| Tool | Signature | Does |
|---|---|---|
list_meetings | (tenant, agent_id="", state="", limit=20) -> dict | List calls, most recent first; filter by agent/state. |
get_meeting | (tenant, meeting_id) -> dict | One call’s detail. |
list_meeting_events | (tenant, meeting_id) -> dict | The event timeline for a call. |
query_logs | (tenant, meeting_id, severity_min="INFO", component="", limit=100) -> dict | Platform runtime log lines for one call. |
These four are the inspect-a-call loop: find the call, read its transcript, check how
far it got, then read the logs. query_logs returns the platform’s logs — your
brain runs in your own environment and logs there. The meeting’s
active_session_id is the brain’s session.id, so that string joins the two sides.
The voqalize skill
Section titled “The voqalize skill”The skill (skill/SKILL.md) drives the end-to-end build on top of those tools. It is
a short entry file plus references loaded on demand (skill/references/), and it
walks the flow:
- Prereqs — confirm the MCP server is connected (
whoami→list_tenants); Python 3.12+; a React app for the embed. - Draft the brain — scaffold from
templates/brain.py; implementon_session_start/on_interaction/on_client_message/on_user_idle. - Create the agent —
create_agent(tenant, name)→{agent, session_key}. - Run it and point
brain_urlat it — locally,create_agent_credentialsand dial out over Cortex (no tunnel); in production, an inbound route. Either way finish withupdate_agent. - Test it unattended — the conformance harness drives
the brain in text mode, with no audio and no human. Then talk to it live at the
agent’s
test_url. - Embed in the browser —
create_api_key(tenant, label, kind="publishable", …)→pk_…, then@voqalize/client-react. - Instrument and observe —
on_inference_finalized/on_errorbrain-side,list_meetings/get_meeting/query_logsplatform-side.
Templates ship alongside it: brain.py, run_cortex.py, inbound_app.py,
test_brain.py, and react_embed.tsx.
- Quickstart — the same flow, by hand.
- Where the brain runs — inbound vs. Cortex.
- Testing a brain — the unattended test loop.