# Tools

Every tool works on the signed-in account only, and every item it returns links to its source on Upvera. Results come back as `structuredContent` with a short text summary.

## upvera_whoami

The Upvera account this connection acts for.

| | |
|---|---|
| Scope | `read:own` |
| Input | none |
| Returns | handle, display name, profile URL, plan name |
| Hints | read-only, idempotent |

## list_my_startups

Startups you own or are a member of, as owner, poster or launcher.

| | |
|---|---|
| Scope | `read:own` |
| Input | none |
| Returns | for each startup: name, slug, stage, your role, last post date, URL |
| Hints | read-only, idempotent |

## get_startup_status

One of your startups in one call.

| | |
|---|---|
| Scope | `read:own` |
| Input | `startup` (string, 1 to 80 characters): the id or slug from `list_my_startups` |
| Returns | identity, the 10 most recent published posts, proof sources with their verification status and source links, public value with the latest receipt |
| Hints | read-only, idempotent |

Asking about a startup that isn't yours returns: *That startup is not one of yours. Use list_my_startups to see the ones you can read.*

## get_build_trail

Recent commits from the repositories connected to your startups, newest first. Messages are trimmed and secrets are removed.

| | |
|---|---|
| Scope | `read:own` |
| Input | `since` (optional, ISO date such as `2026-10-13`): defaults to 14 days back, at most 30 |
| Returns | `since` and up to 60 `commits`, each with `repo`, `sha7`, `message` (first line, up to 200 characters), `at` and `url`. Merge commits are skipped |
| Hints | read-only, idempotent |

## create_draft

Saves a draft to your Hatch. It is never published automatically: you review it and publish it in the app.

| | |
|---|---|
| Scope | `write:drafts` |
| Input | `title` (1 to 120 characters) · `body` (1 to 8,000 characters) · `commit_sha` (optional, 7 to 40 hex characters) · `startup` (optional, id or slug) · `idempotency_key` (optional, up to 120 characters) |
| Returns | `note_id`, `created` (false when the key matched an earlier draft) and `open_url`, which opens the draft in the app |
| Hints | not read-only, not destructive, idempotent |

Send the same `idempotency_key` when you retry, and you get the same draft back instead of a second one.

## prepare_post

Prepares a post on your account and shows it in a preview card. Nothing is published by this tool.

| | |
|---|---|
| Scope | `write:drafts` |
| Input | `body` (1 to 500 characters) · `startup` (optional, id or slug; required for an update) · `kind` (`post` or `update`, default `post`) · `idempotency_key` (optional, up to 120 characters) |
| Returns | `draft_id`, `status`, `needs_site` with its `reasons`, and the preview: author, startup, kind and the locked text |
| Hints | not read-only, not destructive, idempotent |

The text is cleaned and locked on Upvera's side and bound to a content hash, so the card publishes exactly what it shows. Hidden direction-override and zero-width characters are rejected; emoji, including joined emoji, are kept.

`needs_site` is true for your first post from an assistant, a post with a link or an @mention, and while posting from assistants is switched off. Then the card offers **Confirm in Upvera**, which opens Upvera's own compose screen with the text filled in.

## publish_post and undo_post

These two run only from the preview card, when you press **Publish** or **Undo**. They are marked as app-only, so clients do not list them to the model, and they need the optional `publish:posts` scope. Posting from the card is rolling out.

| | `publish_post` | `undo_post` |
|---|---|---|
| Scope | `publish:posts` | `publish:posts` |
| Input | `draft`, plus a one-time ticket and the content hash that the card holds | `draft` |
| Returns | `post_id`, `url`, `undo_until` | `undone` |
| Hints | destructive, open world | destructive, idempotent |

A post can be undone for 10 minutes. After that it stays, and you can delete it in the app like any post.

## Errors

A tool error comes back as a normal result with `isError: true` and one sentence that says what to do next.

| Reason | Message |
|---|---|
| `not_member` | That startup is not one of yours. Use list_my_startups to see the ones you can read. |
| `not_found` | Nothing was found for that id or slug. |
| `rate_limited` | Too many requests right now. Wait a minute and try again. |
| `shed` | Upvera is shedding load right now. Try again in a few minutes. |
| `invalid` | The request was not valid. Check the arguments and try again. |
| `disabled` | Posting from AI assistants is turned off right now. The user can publish it in Upvera. |
| `needs_site` | This post has to be confirmed in Upvera by the user. |
| `expired` | This preview expired. Prepare the post again. |
| `mismatch` | This preview is no longer valid. Prepare the post again. |
| `used` | This prepared post was already used. |
| `too_late` | The undo window has passed. The post can be deleted in the Upvera app. |
| `timeout` | Upvera took too long to answer. Try again. |

## A typical session

```text
› Draft today's Kiln update from my commits

⏺ upvera · list_my_startups()
  Kiln (kiln-…) · owner · last post 2 days ago
⏺ upvera · get_build_trail(since: "2026-10-19")
  7 commits · "checkout fits on one screen" …
⏺ upvera · create_draft(title: "Checkout fits on one screen", startup: "kiln-…", commit_sha: "a3f9c1e", …)
  ✓ Draft saved to Hatch · open it in Upvera to publish
```

---
Source: https://upvera.dev/mcp/tools/ · Markdown: https://upvera.dev/mcp/tools.md
