# Freeticket MCP

> FreeTicket MCP server (`@freeticket/mcp`)

- Skill: `appfreeticket/freeticket-mcp` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add appfreeticket/freeticket-mcp`
- Raw SKILL.md: https://api.skillmd.com/api/skills/appfreeticket/freeticket-mcp/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: AppFreeticket (https://skillmd.com/u/appfreeticket)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/appfreeticket/freeticket-mcp

---


# FreeTicket MCP server (`@freeticket/mcp`)

The official MCP server for FreeTicket. It exposes the **same OpenAPI contract**
the `ft` CLI consumes — one tool per contract operation, generated from the spec,
never hand-written. If the API can do it, there is a tool; if there is no tool,
the API cannot do it yet.

**103 tools** across three contracts: B2B `/api/v1` (76), superadmin
`/api/admin` (21), public B2C `/api/public` (6). Contracts at `1.7.0` / `1.3.0` /
`0.4.0` — the same surface the website operates on: events and sales, the
**members area** (memberships, profile, own tickets), **content** (videos, feed,
live streams), and **settlement receipts**. Full inventory with signatures:
[`references/tools.md`](references/tools.md).

## MCP server or `ft` CLI?

Both hit the same backend. Pick by where the work happens:

| Use the **MCP server** | Use the **`ft` CLI** ([`freeticket-cli`](../freeticket-cli/SKILL.md)) |
|---|---|
| The user is in a chat client (claude.ai, Claude Desktop, Cursor) | The user is in a terminal, or you are writing a script |
| You want lists and reports rendered as tables/KPIs in the host | You need CSV, `--json` piped to `jq`, or cron |
| A buyer-side agent with no credentials at all (`public_*`) | Minting or revoking credentials (deliberately not in MCP) |

They share one session: `ft login` writes `~/.freeticket/config.json`, and the
local MCP server reads it. Log in once, both work.

## Setup

### Fastest: the plugin (Claude Code)

```
/plugin marketplace add AppFreeticket/agent-skills
/plugin install freeticket@freeticket
```

Installs the three skills **and** wires the server (remote transport, OAuth in
the browser on first use). Nothing to configure by hand.

### Local (Claude Code, Claude Desktop, Cursor)

> ⚠️ `@freeticket/mcp` **is not on npm yet** — `npx -y @freeticket/mcp` fails
> with "Connection closed". Until it ships, use the plugin or the remote
> connector below, or run it from a clone (`pnpm build` → `node dist/index.js`).

```jsonc
{
  "mcpServers": {
    "freeticket": {
      "command": "npx",
      "args": ["-y", "@freeticket/mcp"]
      // No env needed: it reuses the `ft login` session.
      // Headless/CI only: "env": { "FT_API_KEY": "ft_live_…", "FT_WORKSPACE_ID": "ws_…" }
    }
  }
}
```

Config precedence: **env > `~/.freeticket/config.json` > defaults**.

| Variable | Effect |
|---|---|
| `FT_API_URL` | API base **without** `/api/v1` (default `https://admin.appfreeticket.com`) |
| `FT_API_KEY` | B2B credential — unlocks the workspace tools |
| `FT_WORKSPACE_ID` | Active workspace (`X-Workspace-Id`) |
| `FT_ADMIN_SESSION` | SUPER_ADMIN session cookie — unlocks `admin_*` |

### Remote (claude.ai custom connector)

claude.ai cannot send API keys or custom headers, so the server ships its own
**OAuth 2.1 authorization server**. Settings → Connectors → Add custom connector
→ URL `https://<deploy>/mcp`, leave Client ID/Secret **empty** (dynamic client
registration, RFC 7591). The consent page offers **"Continuar con FreeTicket"**:
the user signs into free-admin with their normal account and approves — same
device flow as `ft login`, nothing to paste. Advanced options still accept a raw
API key (CI) or a superadmin cookie.

`POST /mcp/public` needs no auth at all and serves only `public_*` — that is the
endpoint for a buyer's agent.

## The three credential layers

Tools are registered per session by what the credential carries. **A missing tool
is not a bug — it is a missing credential.**

| Layer | Requires | Tools |
|---|---|---|
| Public B2C | nothing | `public_*` (6) — always registered |
| B2B workspace | API key / `ft login` session | events, sales, tickets, plans, venues, staff, reports, settlements, content (76) |
| Members area | enterprise key **+** buyer session (`X-Customer-Session`) | `customer_*` — speak for a buyer, not for the workspace |
| Superadmin | `FT_ADMIN_SESSION` | `admin_*` (21) — cross-tenant |

If the user asks for something and the tool isn't there, check the layer before
anything else: no `events_list` means no API key; no `admin_users` means no
superadmin session.

## Working rules

**Reads are free, writes are not.** Destructive tools (`*_delete`,
`sales_refund`, `sales_cancel`, `admin_workspaces_suspend`, `admin_impersonate`)
carry `destructiveHint` and say so in their description. Confirm with the human
before calling one, and quote what will be affected — id, name, amount.

**Never invent an endpoint.** These tools are generated from the contract. If a
capability is missing, it is missing upstream in `free-admin`, and the fix is to
request it there — not to compose a workaround out of other tools that mutates
data in a way the API didn't intend.

**Credentials are read-only.** `api_keys_list` and `admin_tokens` show what
exists so it can be audited; minting and revoking are CLI-only, on purpose. Do
not ask the user for an `ft_live_…` key — send them to `ft login` or the
connector consent page.

**Payments stay with the human.** `public_orders_create` returns a Mercado Pago
`checkoutUrl`. Hand the user the link. Never ask for card data, and never claim
an order is paid until `public_orders_get` says so.

**Multi-workspace reads.** Every B2B list tool takes an optional `workspace`
argument: `"all"` aggregates every workspace the session can reach, or pass an
array of ids. Each row comes back tagged with `workspaceId`/`workspaceName`.
Omit it for the active workspace alone. Writes have no global mode — a mutation
is always explicitly scoped to one workspace.

**Permissions are per workspace, and the backend enforces them.** `whoami`
returns, for every workspace, the **effective role** and the enabled `sections`
(`null` = unrestricted, `[]` = expired or revoked). The old top-level `role` is
deprecated — read the row, not the global field. A user capped in the panel is
capped here too: a 403 on one workspace of a global read is that cap working,
not a bug to route around.

**Files come back as links, not bytes.** `settlements_document` and
`settlements_proof` return a signed URL that expires in 5 minutes. Hand it to
the user; do not try to fetch or transcribe the PDF.

**Content playback needs a token.** The `content_*` listings never carry the
playback id. Mint one with `content_playback_token` (30 min live, 1 h video);
`memberOnly` items also need the buyer session of someone with an active
membership.

**Money is in COP** and lists are cursor-paginated (`limit` 1–100, default 20,
plus `cursor`). Dates are ISO 8601; events carry their own IANA timezone.

## The view (MCP Apps)

Lists and reports do not arrive as a wall of JSON. The server implements the
official **`io.modelcontextprotocol/ui`** extension (MCP Apps, spec
`2026-01-26`): 29 tools declare `_meta.ui.resourceUri` pointing at
`ui://freeticket/view.html`, and a supporting host renders them — **array →
table**, **object → KPI tiles** — with FreeTicket's mark and accent, adopting the
host's own palette and locale for everything else.

What this means for you:

- The result you receive is unchanged: JSON in `content`, plus
  `structuredContent` for the view. Reason over the JSON as always.
- **Do not re-render the table in your reply.** The user already sees it. Say
  what it means — the outlier, the trend, the number they asked for.
- Hosts without the extension (terminals, older clients) simply get the text.
  Nothing degrades, so never branch your behaviour on whether a view exists.

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Only `public_*` tools listed | No B2B credential in the session | `ft login`, or set `FT_API_KEY` |
| No `admin_*` tools | No superadmin session | Set `FT_ADMIN_SESSION` (cookie `better-auth.session_token`) |
| 401 on every B2B tool | Expired session or wrong `FT_API_URL` | Re-run `ft login`; check the base URL has no `/api/v1` |
| 403 on one resource | Role too low, or resource in another workspace | Check `whoami`; pass the right `workspace` |
| Connector won't authorize on claude.ai | Client ID/Secret filled in | Leave both empty — registration is dynamic |
| Tokens die after every deploy | `MCP_TOKEN_SECRET` unset on the host | Set it (`openssl rand -hex 32`); without it the secret is ephemeral |
| Tool exists in the docs, not in the client | Client cached an old tool list | Reconnect the server |

## Related

- CLI: [`freeticket-cli`](../freeticket-cli/SKILL.md) — same contract, terminal-side
- Copy & event advice: [`freeticket-eventos`](../freeticket-eventos/SKILL.md)
- Server source: [`AppFreeticket/freeticket-mcp`](https://github.com/AppFreeticket/freeticket-mcp)

