# MCP tools

> The Avtrz MCP server exposes three read-only tools: `get_profile_avatar` returns a real profile photo inline (plus a ready-to-paste `imageUrl`), `get_profile` returns structured person data plus Markdown, and `get_publishable_key` returns the workspace's avatar key and a URL template for building durable image URLs.

The two lookup tools (`get_profile_avatar`, `get_profile`) accept **exactly one** of `linkedin_url` or `username`. All three are annotated `readOnlyHint: true`, `destructiveHint: false`, `openWorldHint: false`, so clients can call them freely without a write-confirmation prompt.

See [Working image URLs](https://avtrz.dev/docs/mcp#working-image-urls) on the overview for why agents can emit durable Avtrz `<img>` URLs that render anywhere.

## `get_profile_avatar`

Fetch the avatar image for a business profile by LinkedIn URL or username. Returns the photo as an inline image. While the avatar is still being enriched, it returns deterministic initials art and the URL where the final image will appear.

### Input

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `linkedin_url` | string (URL) | Conditional | Full LinkedIn profile URL, e.g. `https://www.linkedin.com/in/janedoe`. Pass exactly one of `linkedin_url` or `username`. |
| `username` | string | Conditional | LinkedIn vanity username (the slug after `/in/`), e.g. `janedoe`. |
| `size` | enum: 32 \| 64 \| 128 \| 256 \| 512 | Optional | Avatar dimensions in pixels (square). Defaults to `256`. |

### Output

Alongside the inline image, the tool returns `structuredContent.imageUrl`: a durable, ready-to-use Avtrz image URL built from the workspace's publishable key (`https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=…&size=…`). This is the fast path for "give me a URL I can paste" — drop it straight into a CRM, doc, email, or `<img>` tag and it renders immediately, no base64 handling. The inline image is for showing the face in-chat; the `imageUrl` is for embedding it elsewhere. (If the workspace's publishable key can't be resolved, the tool still returns the inline image and simply omits `imageUrl`.)

- **Resolved (cache hit / ready):** an inline `image` content block with the avatar bytes, a `text` block carrying the stable CDN URL plus the embeddable URL — e.g. `Avatar for janedoe: https://cdn.avtrz.dev/…\nEmbeddable URL: https://avtrz.dev/v1/avatar?key=pk_…` — and `structuredContent.imageUrl`.
- **Enriching (queued / fallback):** an inline `image` block containing the deterministic initials SVG, plus a `text` block telling the model the avatar is being enriched and where the final image will appear shortly (the `imageUrl` resolves once enrichment lands). `structuredContent.imageUrl` is still returned. The tool never polls — the model can call again in a moment.
- **No avatar:** an error text block — `No avatar is available for … The profile picture could not be found.`
- **Quota exhausted:** an error text block — `Monthly avatar quota exceeded for this workspace. Upgrade your plan or wait for the next cycle.`

### Example

> **Prompt:** "Show me Jane Doe's photo — here's her LinkedIn: https://www.linkedin.com/in/janedoe"

```json
{
  "name": "get_profile_avatar",
  "arguments": {
    "linkedin_url": "https://www.linkedin.com/in/janedoe",
    "size": 256
  }
}
```

Returns (cache hit):

```json
{
  "content": [
    { "type": "image", "data": "<base64 bytes>", "mimeType": "image/webp" },
    {
      "type": "text",
      "text": "Avatar for janedoe: https://cdn.avtrz.dev/…\nEmbeddable URL: https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=https://www.linkedin.com/in/janedoe&size=256"
    }
  ],
  "structuredContent": {
    "imageUrl": "https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=https://www.linkedin.com/in/janedoe&size=256"
  }
}
```

## `get_profile`

Fetch enriched business-profile data — name, headline, summary, industry, location, profile picture URL, open-to-work — for a person by LinkedIn URL or username. If the profile hasn't been enriched yet, it enqueues enrichment and asks the caller to retry.

### Input

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `linkedin_url` | string (URL) | Conditional | Full LinkedIn profile URL. Pass exactly one of `linkedin_url` or `username`. |
| `username` | string | Conditional | LinkedIn vanity username (the slug after `/in/`). |

### Output

- **Profile found:** a `structuredContent` object plus a `text` block rendering it as Markdown so the model has something readable to quote.
- **Enrichment queued:** a `text` block — `Profile enrichment queued for … Data will be available shortly; try again in a moment.` Avtrz has already kicked off enrichment; the model should retry.
- **No profile:** an error text block — `No profile data is available for … The profile could not be found.`
- **Quota exhausted:** an error text block — `Monthly profile quota exceeded for this workspace. Upgrade your plan or wait for the next cycle.`

The `structuredContent` shape:

```json
{
  "linkedin_url": "https://www.linkedin.com/in/janedoe",
  "username": "janedoe",
  "first_name": "Jane",
  "last_name": "Doe",
  "headline": "VP Engineering at Acme",
  "summary": "Building reliable infra…",
  "industry": "Software",
  "location": "San Francisco, CA, United States",
  "picture_url": "https://cdn.avtrz.dev/…",
  "open_to_work": false,
  "premium": true,
  "verified": true
}
```

The shape deliberately omits internal identifiers (provider/entity URNs, raw payloads). Read-only callers never see secrets or internal IDs.

### Example

> **Prompt:** "Who is `janedoe` on LinkedIn? Give me their role and location."

```json
{
  "name": "get_profile",
  "arguments": { "username": "janedoe" }
}
```

## `get_publishable_key`

Return the workspace's publishable avatar key and a ready-to-fill URL template, so the model can build durable image URLs without calling `get_profile_avatar` per person. Read-only and **takes no input** — the key is provisioned server-side when you connect, and this tool just reads it back. Use it when a user asks for "a URL I can paste" or wants `<img>` URLs for a whole list of people at once.

### Input

None. The tool resolves the key for the workspace the connection is bound to.

### Output

A `text` block with the URL template and instructions, plus `structuredContent`:

| Field | Type | Description |
| --- | --- | --- |
| `publishable_key` | string | The workspace's unrestricted publishable key (`pk_…`). Never a secret (`sk_`) key — the server refuses to return anything that isn't publishable. |
| `url_template` | string | `https://avtrz.dev/v1/avatar?key=<pk>&linkedin_url=<LINKEDIN_URL>&size=128`. Replace `<LINKEDIN_URL>` with a full LinkedIn URL (or swap to `username=<USERNAME>`) and set `size`. |
| `sizes` | array | The accepted square sizes: `[32, 64, 128, 256, 512]`. |
| `note` | string | The plain-language caveat about how the key behaves (see below). |

```json
{
  "publishable_key": "pk_…",
  "url_template": "https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=<LINKEDIN_URL>&size=128",
  "sizes": [32, 64, 128, 256, 512],
  "note": "This is your workspace's unrestricted publishable key. Embed it in avatar URLs to render images anywhere (CRMs, docs, emails, sites). Anyone with a URL can use it against this workspace's avatar quota, so treat it like a public token. Revoke or rotate it any time from the avtrz dashboard under API keys."
}
```

### The key caveat

The MCP key is **unrestricted by design** — no domain firewall — so the avatar URLs render from any context (CRMs, server-side image fetchers, static sites) that can't send a `Referer`. The trade-off:

- Anyone holding a URL can draw on this workspace's avatar quota — treat the key like a public token, not a secret.
- It is **publishable only** (`pk_`). A secret key is never exposed over MCP.
- It is **revocable** any time from the dashboard under API keys; it does not expire on its own.

### Member-role caveat

The key is provisioned server-side under a workspace **owner or admin** (only those roles can create API keys). If a plain member connects, Avtrz provisions the shared key under an available owner/admin automatically. But if a member-only workspace has **no owner or admin** to provision under, the tool returns an actionable error instead of a key:

> This workspace's avatar key has not been set up yet, and your role cannot create it. Ask a workspace owner or admin to connect avtrz once — that provisions the shared key for everyone.

Retrying won't help — a workspace owner or admin must connect once to mint the shared key.

### Example

> **Prompt:** "Get me paste-ready avatar URLs for these three people: linkedin.com/in/janedoe, linkedin.com/in/johndoe, linkedin.com/in/samlee"

The model fetches the key once, then templates working `<img>` URLs for the whole list — no per-person tool call:

```json
{
  "name": "get_publishable_key",
  "arguments": {}
}
```

Returns the key + template, which the model fills in per person:

```html
<img src="https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=https://www.linkedin.com/in/janedoe&size=128" />
<img src="https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=https://www.linkedin.com/in/johndoe&size=128" />
<img src="https://avtrz.dev/v1/avatar?key=pk_…&linkedin_url=https://www.linkedin.com/in/samlee&size=128" />
```

## Quota and billing

The lookup tools meter the **workspace the connection is bound to** — there is no separate MCP billing:

- Every metered tool call is written to the same `avatarRequest` analytics the `GET /v1/avatar` API uses, so MCP traffic appears in your dashboard alongside API traffic.
- **Every** `get_profile_avatar` / `get_profile` call consumes one `avatar_requests` unit — whether or not a photo is resolved, since the request quota is checked and metered before the cache lookup. A profile your workspace hasn't looked up before *also* counts against `avatar_new_profiles`.
- `get_publishable_key` takes no input and doesn't hit the avatar pipeline, so it isn't metered.
- Rate limits bucket **per user**, not per connection: the limiter keys off your user identity (`mcp:<userId>`), so all of one user's connections and sessions share a single bucket. If a call is rate-limited, the tool returns the limit message and the number of seconds to wait before retrying.

## Errors and edge cases

| Situation | Behavior |
| --- | --- |
| Both or neither lookup field supplied | Validation error: `Pass exactly one of linkedin_url or username.` |
| Avatar/profile still enriching | `get_profile_avatar` returns initials art + the future CDN URL; `get_profile` returns "enrichment queued". Retry shortly. |
| Profile genuinely not found | Error text: the avatar/profile could not be found. |
| Monthly quota exhausted | Error text: quota exceeded for this workspace. The connection stays valid. |
| Rate-limited | Error text with a `Retry after Ns` hint. |
| Token expired / revoked | Tool returns `Not authenticated.` Reconnect the connector. |

## Where to go next

- [MCP server](https://avtrz.dev/docs/mcp): What the connector is, and the [Working image URLs](https://avtrz.dev/docs/mcp#working-image-urls) concept.
- [Connect Claude & ChatGPT](https://avtrz.dev/docs/mcp/connect): Add the connector in each client.
- [Get avatar (API reference)](https://avtrz.dev/docs/api/avatar): The HTTP endpoint behind the same engine.
