Skip to main content
Zuko exposes a Model Context Protocol (MCP) server so AI clients can read and write CRM data on behalf of a logged-in user. Authentication uses OAuth 2.1 with RFC 9728 protected-resource metadata — clients discover auth endpoints automatically.

Endpoint

POST {BACKEND_URL}/api/mcp
Only POST is accepted. GET and DELETE return 405 — the server is stateless with no sessions to manage.

OAuth flow

  1. Client hits /api/mcp without a token → receives 401 with WWW-Authenticate pointing to /.well-known/oauth-protected-resource/api/mcp
  2. Client fetches protected-resource metadata → discovers the authorization server at {BACKEND_URL}/auth
  3. Client fetches /.well-known/oauth-authorization-server/auth → gets authorize URL, token URL, JWKS URI, and supported scopes
  4. User completes the authorization code flow and approves scopes on the consent screen
  5. Client exchanges the code for a JWT access token (audience = {BACKEND_URL}/api/mcp)
  6. Client sends Authorization: Bearer <token> on every MCP request
Tokens are JWTs verified locally against {BACKEND_URL}/auth/jwks — no round-trip to the auth server per request.

Scopes

ScopeGrants
organizations:readList organizations the user belongs to
tasks:readList and retrieve tasks
tasks:writeCreate and update tasks

Tools

list_organizations

Scope: organizations:read Returns the organizations the authorized user belongs to. Call this first when the user belongs to multiple organizations to get the correct organizationId for task tools. Input: (none) Output:
[{ "id": 1, "name": "Acme", "slug": "acme", "role": "owner" }]

list_tasks

Scope: tasks:read Returns up to 50 tasks (most recently updated) across all organizations the user belongs to.
InputTypeDescription
status"TODO" | "IN_PROGRESS" | "DONE" | "CANCELLED"Filter by status (optional)
organizationIdintegerRestrict to one organization (optional)

get_task

Scope: tasks:read Returns full task detail including owners, subtasks, and parent task.
InputTypeDescription
taskIdintegerID of the task to retrieve

create_task

Scope: tasks:write Creates a task. If the user belongs to exactly one organization, organizationId can be omitted — call list_organizations first if they belong to multiple.
InputTypeDescription
titlestringTask title (required)
organizationIdintegerTarget organization (optional if user has exactly one)
descriptionanyDescription in JSON format (optional)
statusenumInitial status — defaults to "TODO" (optional)
assigneestringAssignee identifier (optional)

update_task

Scope: tasks:write Updates one or more fields on an existing task. Only supplied fields are changed.
InputTypeDescription
taskIdintegerID of the task to update (required)
titlestringNew title (optional)
descriptionanyNew description in JSON format (optional)
statusenumNew status (optional)
assigneestringNew assignee (optional)
completedAtstringCompletion timestamp in ISO 8601 format (optional)

Connecting a client

Claude Desktop

Add to claude_desktop_config.json:
{
  "mcpServers": {
    "zuko": {
      "type": "http",
      "url": "https://your-backend.example.com/api/mcp"
    }
  }
}

Claude Code

claude mcp add --transport http zuko https://your-backend.example.com/api/mcp

Cursor / VS Code

Add a remote MCP server pointing to {BACKEND_URL}/api/mcp. The client handles OAuth discovery and the login flow on first use.

Environment variables

VariableDefaultDescription
BACKEND_URLhttp://localhost:3001Public backend URL — used in OAuth metadata responses
BETTER_AUTH_URLhttp://localhost:8000Auth server base URL — used by the bearer guard to verify JWTs