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.
Should I use this
Quality & Safety
Based on automated analysis of tool definitions and protocol compliance.
Context Cost
This is the approximate number of tokens consumed each time the server's tools are loaded into a model's context. Higher counts reduce the attention available for other tasks.
Install
One-Click Install
Add this to your `claude_desktop_config.json` file:
{
"mcpServers": {
"handoff": {
"url": "https://hand-off.space/mcp"
}
}
}Remote endpoints
https://hand-off.space/mcpstreamable-httpWhat it can do
Tool inventory
Tools (14)
🟡share_file(name, content_text, content_base64, mime, expires_in_hours, ...)
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.
Input Schema
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[^/\\\\\\x00-\\x1f]+$",
"description": "File name with an extension. No slashes or control characters."
},
"content_text": {
"type": "string",
"maxLength": 70000000,
"description": "UTF-8 text. Pass exactly one of content_text or content_base64."
},
"content_base64": {
"type": "string",
"maxLength": 70000000,
"description": "Standard or url-safe base64. A data:*;base64, prefix is stripped. Example: aGk="
},
"mime": {
"type": "string",
"maxLength": 200,
"pattern": "^[\\w.+-]+\\/[\\w.+-]+$",
"description": "MIME type. Inferred from the file name when omitted."
},
"expires_in_hours": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 87600,
"description": "Hours until the link expires. Default 168 (7 days)."
},
"max_opens": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 2147483647,
"description": "Successful downloads allowed. Omit for no limit."
},
"audience": {
"type": "string",
"enum": [
"anyone_with_link",
"agent"
],
"description": "Default anyone_with_link."
},
"audience_value": {
"type": "string",
"maxLength": 320,
"description": "Required when audience is agent."
},
"requires_approval": {
"type": "boolean",
"description": "Default false. When true, downloads wait until the owner approves."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
},
"ref": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9._-]+$",
"description": "Optional attribution label. Lowercase letters, digits, dot, underscore, or hyphen. At most 64 characters."
}
},
"required": [
"name"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Output Schema
{
"type": "object",
"properties": {
"url": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"max_opens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"requires_approval": {
"type": "boolean"
},
"file_id": {
"type": "string"
},
"grant_id": {
"type": "string"
},
"token_prefix": {
"type": "string"
},
"api_key": {
"type": "string"
},
"next": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"header": {
"type": "string"
},
"mcp_url": {
"type": "string"
},
"if_no_headers": {
"type": "string"
},
"configs": {
"type": "object",
"properties": {
"claude_code": {
"type": "string"
},
"cursor_json": {
"type": "object",
"properties": {
"mcpServers": {
"type": "object",
"properties": {
"handoff": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"headers": {
"type": "object",
"properties": {
"Authorization": {
"type": "string"
}
},
"required": [
"Authorization"
],
"additionalProperties": false
}
},
"required": [
"url",
"headers"
],
"additionalProperties": false
}
},
"required": [
"handoff"
],
"additionalProperties": false
}
},
"required": [
"mcpServers"
],
"additionalProperties": false
}
},
"required": [
"claude_code",
"cursor_json"
],
"additionalProperties": false
},
"first_call": {
"type": "string"
}
},
"required": [
"header",
"mcp_url",
"if_no_headers",
"configs",
"first_call"
],
"additionalProperties": false
}
]
}
},
"required": [
"url",
"expires_at",
"max_opens",
"requires_approval",
"file_id",
"grant_id",
"token_prefix",
"next"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡signup(agent_id, ref)
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.
Input Schema
{
"type": "object",
"properties": {
"agent_id": {
"type": "string",
"minLength": 1,
"maxLength": 80,
"pattern": "^[a-zA-Z0-9_-]+$",
"description": "Optional label, unique across Handoff. Letters, numbers, underscore, and hyphen."
},
"ref": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9._-]+$",
"description": "Optional attribution label. Lowercase letters, digits, dot, underscore, or hyphen. At most 64 characters."
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Output Schema
{
"type": "object",
"properties": {
"account_id": {
"type": "string"
},
"api_key": {
"type": "string"
},
"agent_id": {
"type": "string"
},
"caps": {
"type": "object",
"properties": {
"total_mb": {
"type": "number"
},
"files": {
"type": "number"
},
"opens_per_day": {
"type": "number"
},
"file_mb": {
"type": "number"
}
},
"required": [
"total_mb",
"files",
"opens_per_day",
"file_mb"
],
"additionalProperties": false
},
"next": {
"type": "object",
"properties": {
"header": {
"type": "string"
},
"mcp_url": {
"type": "string"
},
"if_no_headers": {
"type": "string"
},
"configs": {
"type": "object",
"properties": {
"claude_code": {
"type": "string"
},
"cursor_json": {
"type": "object",
"properties": {
"mcpServers": {
"type": "object",
"properties": {
"handoff": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"headers": {
"type": "object",
"properties": {
"Authorization": {
"type": "string"
}
},
"required": [
"Authorization"
],
"additionalProperties": false
}
},
"required": [
"url",
"headers"
],
"additionalProperties": false
}
},
"required": [
"handoff"
],
"additionalProperties": false
}
},
"required": [
"mcpServers"
],
"additionalProperties": false
}
},
"required": [
"claude_code",
"cursor_json"
],
"additionalProperties": false
},
"first_call": {
"type": "string"
}
},
"required": [
"header",
"mcp_url",
"if_no_headers",
"configs",
"first_call"
],
"additionalProperties": false
}
},
"required": [
"account_id",
"api_key",
"agent_id",
"caps",
"next"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡save_file(name, mime, content_text, content_base64, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[^/\\\\\\x00-\\x1f]+$",
"description": "File name with an extension. No slashes or control characters."
},
"mime": {
"type": "string",
"maxLength": 200,
"pattern": "^[\\w.+-]+\\/[\\w.+-]+$",
"description": "MIME type. Inferred from the file name when omitted."
},
"content_text": {
"type": "string",
"maxLength": 70000000,
"description": "UTF-8 text. Pass exactly one of content_text or content_base64."
},
"content_base64": {
"type": "string",
"maxLength": 70000000,
"description": "Standard or url-safe base64. A data:*;base64, prefix is stripped. Example: aGk="
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"name"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢list_files(api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡create_share_link(file_id, audience_value, expires_in_hours, max_opens, requires_approval, ...)
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.
Input Schema
{
"type": "object",
"properties": {
"file_id": {
"type": "string",
"minLength": 1,
"description": "File id returned by save_file or list_files."
},
"audience_value": {
"type": "string",
"maxLength": 320,
"description": "Required for agent: the recipient's registered agent_id. Example: their-agent"
},
"expires_in_hours": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 87600,
"description": "Hours until the link expires. Default 168 (7 days) when omitted."
},
"max_opens": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 2147483647,
"description": "Successful downloads allowed. The next one is refused."
},
"requires_approval": {
"type": "boolean",
"default": false,
"description": "When true, downloads wait until the owner approves the grant."
},
"audience": {
"type": "string",
"enum": [
"anyone_with_link",
"agent"
],
"description": "anyone_with_link: whoever has the saved url. agent: only the registered agent_id in audience_value."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"file_id",
"audience"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Output Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string"
},
"url": {
"type": "string"
},
"token": {
"type": "string"
},
"token_prefix": {
"type": "string"
},
"expires_at": {
"type": "string"
},
"max_opens": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
}
},
"required": [
"grant_id",
"url",
"token",
"token_prefix",
"expires_at",
"max_opens"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢get_share_status(grant_id, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1,
"description": "grant_id returned when the share was created."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}⚪approve_share(grant_id, api_key)
Approve an owned grant that was created with requires_approval. Opens stay blocked until this succeeds. Requires your API key.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1,
"description": "grant_id returned when the share was created."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🔴deny_share(grant_id, api_key)
Deny an owned grant. A denied grant stays blocked. Requires your API key.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1,
"description": "grant_id of the share to deny."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🔴revoke_share(grant_id, api_key)
Permanently revoke an owned share. Later downloads fail. This cannot be undone and does not delete the file. Requires your API key.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1,
"description": "grant_id of the share to revoke."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢get_access_log(grant_id, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1,
"description": "grant_id of the share to audit."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢fetch_shared_file(url_or_token, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"url_or_token": {
"type": "string",
"minLength": 1,
"description": "Share url returned by create_share_link, or the 64-character token."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"url_or_token"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🔴delete_file(file_id, api_key)
Delete a file you own. The bytes are removed and existing share links stop working. Requires your API key.
Input Schema
{
"type": "object",
"properties": {
"file_id": {
"type": "string",
"minLength": 1,
"description": "File id returned by save_file, list_files, or share_file."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"file_id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Output Schema
{
"type": "object",
"properties": {
"file_id": {
"type": "string"
},
"deleted": {
"type": "boolean",
"const": true
}
},
"required": [
"file_id",
"deleted"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢list_shares(file_id, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"file_id": {
"type": "string",
"minLength": 1,
"description": "Optional file id. Omit to list every grant on this account."
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡update_share(grant_id, expires_in_hours, api_key)
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.
Input Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string",
"minLength": 1
},
"expires_in_hours": {
"type": "number",
"exclusiveMinimum": 0,
"maximum": 87600
},
"api_key": {
"type": "string",
"minLength": 1,
"description": "Secret API key from signup. Prefer Authorization: Bearer <api_key>, the bare key in Authorization, or X-Handoff-Api-Key. Pass this only when you cannot set headers. Do not write it into a file you share."
}
},
"required": [
"grant_id",
"expires_in_hours"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Output Schema
{
"type": "object",
"properties": {
"grant_id": {
"type": "string"
},
"file_id": {
"type": "string"
},
"status": {
"type": "string"
},
"open_count": {
"type": "number"
},
"max_opens": {
"type": [
"number",
"null"
]
},
"expires_at": {
"type": [
"string",
"null"
]
},
"audience": {
"type": "string"
}
},
"required": [
"grant_id",
"file_id",
"status",
"open_count",
"max_opens",
"expires_at",
"audience"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}Community
Evidence