# Handoff: Send Files to Users & Agents

> Send a file to a user or agent as a share link with expiry, max opens, approval, revoke and logs.

- name: io.github.jueluddinsf/handoff
- version: 1.0.1
- connection class: R0 (autonomous)
- trust: 97/100
- quality: 72/100 (needs work)
- owner: not claimed
- descriptor: https://api.protogrid.dev/v1/servers/io.github.jueluddinsf%2Fhandoff
- tools: https://api.protogrid.dev/v1/servers/io.github.jueluddinsf%2Fhandoff/tools

**An agent can connect right now, no human step.** Remote endpoint, no authentication, reachable on the last probe.

## Connection (mcpServers)

```json
{
  "mcpServers": {
    "handoff": {
      "type": "http",
      "url": "https://hand-off.space/mcp",
      "headers": {
        "Authorization": "Bearer ${HANDOFF_TOKEN}"
      }
    }
  }
}
```

Placeholders to fill: `${HANDOFF_TOKEN}`.

## Trust

- hygiene: 100
- liveness: 100
- freshness: 100
- provenance: 90
- drivers: +repository +namespace-matches-repo +website +reachable +uptime-100% +updated-9d-ago
- Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed.

## Quality 72/100 (needs work)

### protocol: 75 (weight 25)

- warn `protocol.modern`: newest supported version is 2025-11-25; 2026-07-28 not supported, older versions are deprecated until 2027-07-28
- n/a `protocol.stateless`: only applies to 2026-07-28 servers
- pass `protocol.transport`: streamable HTTP
- n/a `protocol.list_ttl`: only applies to 2026-07-28 servers

### authorization: n/a (weight 15)

- n/a `auth.prm`, `auth.as_metadata`, `auth.cimd`: no remote uses OAuth
- n/a `auth.secret_in_url`: no templated URL

### tool hygiene: 93 (weight 30)

- pass `tools.descriptions`: every tool has a description
- pass `tools.description_length`: descriptions are concise
- pass `tools.schemas`: every tool has an object input schema
- pass `tools.annotations`: 14 of 14 tools declare readOnlyHint or destructiveHint
- warn `tools.directory_hints`: 2 of 14 tools declare all four hints; missing: signup (openWorldHint); save_file (openWorldHint); create_share_link (openWorldHint); …
- pass `tools.token_cost`: about 3,503 tokens to load every tool
- pass `tools.api_dump`: tools are not a one-to-one API dump

### stability: 25 (weight 15)

- warn `stability.changes`: 14 tool changes in 30 days
- fail `stability.rug_pull`: 1 tools kept their name but changed most of their description in 30 days; review before trusting them

### dependencies: n/a (weight 15)

- n/a `deps.known_vulns`, `deps.mcp_sdk_version`, `deps.resolvable`: no npm or PyPI package

- tools: 14, about 3,503 tokens to load them all
- tool set hash: e2d10f4d1fb6df0b0bfed4f5b1b5d74d
- tools last changed: 2026-10-30T00:43:45.258Z
- badge: [![protogrid quality](https://protogrid.dev/badge/io.github.jueluddinsf/handoff.svg)](https://protogrid.dev/servers/io.github.jueluddinsf/handoff)
- Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed.

### Tool changes

- 2026-10-30 `update_share` added
  - Set when an owned share expires. expires_in_hours is measured from now. Example: 168 for 7 days. Does not return the token. Requires your API key.
- 2026-10-30 `list_shares` added
  - List grants you own. Pass file_id to limit the list to one file. Returns grant_id, status, open_count, max_opens, expires_at, and audience. Does not return tokens or download urls. Requires your API key.
- 2026-10-30 `delete_file` added
  - Delete a file you own. The bytes are removed and existing share links stop working. Requires your API key.
- 2026-10-30 `fetch_shared_file` changed (title, annotations)
- 2026-10-30 `get_access_log` changed (title, inputSchema, annotations)
- 2026-10-30 `revoke_share` changed (title, inputSchema, annotations)
- 2026-10-30 `deny_share` changed (title, inputSchema, annotations)
- 2026-10-30 `approve_share` changed (title, inputSchema, annotations)
- 2026-10-30 `get_share_status` changed (title, inputSchema, annotations)
- 2026-10-30 `create_share_link` changed (title, description, inputSchema, outputSchema, annotations)
  - before: Create a controlled download link for a file you own. Use this when a person or another agent should fetch the file. The response includes url and token ONCE. Save the url immediately. Later status calls do not return it. Download with GET on that url. Requires your API key.
  - after: Create a controlled download link for a file you own. Use this when a person or another agent should fetch the file. The response includes url and token ONCE, plus expires_at and max_opens. Save the url immediately. Later status calls do not return it. When expires_in_hours is omitted, the link expires in 7 days. Download with GET on that url. Requires your API key.

Full history: https://api.protogrid.dev/v1/servers/io.github.jueluddinsf%2Fhandoff/changes

## Remotes

- https://hand-off.space/mcp (streamable-http, auth none, reachable true, uptime30d 1)

## Packages

_none_

## Tools (14)

- `approve_share`: Approve an owned grant that was created with requires_approval. Opens stay blocked until this succeeds. Requires your API key.
- `create_share_link`: Create a controlled download link for a file you own. Use this when a person or another agent should fetch the file. The response includes url and token ONCE, plus expires_at and max_opens. Save the url immediately. Later status calls do not return it. When expires_in_hours is omitted, the link expires in 7 days. Download with GET on that url. Requires your API key.
- `delete_file`: Delete a file you own. The bytes are removed and existing share links stop working. Requires your API key.
- `deny_share`: Deny an owned grant. A denied grant stays blocked. Requires your API key.
- `fetch_shared_file`: Download a file from a share url or token. Use this when you were given a Handoff url and need the bytes. Honors expiry, open limits, approval, and agent audience. anyone_with_link needs no key. An agent audience uses your API key. Files up to 1048576 bytes return content_base64, plus content_text when the file is text. Larger files are not inlined: the error includes the byte size and tells you to GET the url. A successful inline return counts as an open. A file over the inline limit does not.
- `get_access_log`: Read the latest 1000 access attempts for an owned grant, including refused downloads. Use this to see whether a share was opened. Requires your API key.
- `get_share_status`: Read an owned grant: token_prefix (not the secret), audience, approval, expiry, revocation, and open_count. Does not download the file and does not return the share URL. Requires your API key.
- `list_files`: List this account's files, newest first, up to 1000. Use this to find a file_id before sharing. Returns metadata only, never bytes or download URLs. Requires your API key.
- `list_shares`: List grants you own. Pass file_id to limit the list to one file. Returns grant_id, status, open_count, max_opens, expires_at, and audience. Does not return tokens or download urls. Requires your API key.
- `revoke_share`: Permanently revoke an owned share. Later downloads fail. This cannot be undone and does not delete the file. Requires your API key.
- `save_file`: Save a private file for this account. Pass name and content_text or content_base64. Returns the file id and metadata. Does not share the file. Requires your API key.
- `share_file`: Share a file in one call and get a download link. Pass name and content_text (or content_base64). Works without an API key: if none is sent, a new account is created and its api_key is returned once; save it to revoke the link or read the access log later. Default link: anyone with the link, expires in 7 days.
- `signup`: Create a Handoff account and return an API key once. No Authorization header is required. Save api_key. On later calls send Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass api_key only if you cannot set headers. share_file can create this key for you. The key is a secret. Do not put it in a file you share.
- `update_share`: Set when an owned share expires. expires_in_hours is measured from now. Example: 168 for 7 days. Does not return the token. Requires your API key.
