Handoff: Send Files to Users & Agents

io.github.jueluddinsf由 GitHub 帳號 jueluddinsf 發布v1.0.1更新於 Oct 29, 2026

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

已驗證Streamable HTTP可網頁執行檔案與儲存溝通與協作

概覽

AI 產生的概覽

託管式檔案分享 MCP 伺服器,讓助理上傳檔案並產生具備有效期、開啟次數上限、審核、撤銷與存取日誌的受控下載連結。

可以做什麼
  • 一次呼叫分享檔案並取得下載連結,可設定有效期、開啟次數上限、接收對象與審核
  • 建立帳號並取得 API 金鑰,或私下儲存檔案並列出檔案
  • 建立、列出、更新、核准、拒絕與撤銷分享連結,並查看其狀態
  • 讀取成功與被拒絕開啟的存取日誌
  • 透過分享連結或權杖下載檔案
適用情境
當助理需要把產生的報告、CSV、圖片等檔案交付給某個人或另一個代理時使用,特別適合連結需要過期、限制開啟次數、需人工核准,或需要可撤銷、可稽核的情境。
執行需求
遠端 Streamable HTTP 端點,無需安裝。連線與首次分享都不需要金鑰;首次分享會建立帳號並一次性回傳 API 金鑰。之後的呼叫需在 Authorization 標頭(Bearer 形式)或 api_key 參數中提供該金鑰。未認領的免費帳號有儲存總量、檔案數量、單檔大小與每日開啟次數限制。
安裝前請注意
首次分享會默默建立帳號並回傳只顯示一次的 API 金鑰,務必保存,否則將無法撤銷連結或查看日誌。檔案會上傳到第三方託管服務,不宜傳送敏感內容。相關工具可刪除檔案、撤銷連結與變更有效期,撤銷無法復原。可執行檔會被拒絕,註冊依 IP 限流。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Handoff: Send Files to Users & Agents,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.jueluddinsf%2Fhandoff/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "handoff-send-files-to-users-agents": {
      "type": "http",
      "url": "https://hand-off.space/mcp"
    }
  }
}

README

[Handoff logo]

Handoff: File Sharing for AI Agents

Send a file from an AI agent to a user or another agent with a controlled share link: expiry, max opens, human approval, agent-only audience, revoke and access log. Remote MCP server + REST API, no human signup.

Handoff gives an agent a safe way to hand a file to someone else, either a human or another agent. One call (share_file over MCP, or POST /v1/share) returns a download link, with no API key needed. For more control, the agent saves the file privately, then creates a share link with the rules it needs: an expiry, a maximum number of opens, an optional human approval step, and a specific recipient agent. It can check status, revoke, and read the access log at any time.

  • Remote MCP server: https://hand-off.space/mcp (Streamable HTTP)
  • REST API: https://hand-off.space/v1 (OpenAPI, llms.txt)
  • Auth: none for the first call. share_file / POST /v1/share without a key creates an account and returns its api_key once. Later calls send Authorization: Bearer <api_key> (or pass api_key as a tool argument when the client can't set headers). You can also get a key on its own with signup / POST /v1/signup. No email, card or human account is needed.
  • 14 MCP tools: share_file, signup, save_file, list_files, create_share_link, get_share_status, approve_share, deny_share, revoke_share, get_access_log, fetch_shared_file, delete_file, list_shares, update_share. initialize and tools/list need no key.
  • Registry name: io.github.jueluddinsf/handoff

This repository holds docs and listing metadata. The service is hosted, so you have nothing to install.

Use it when an agent needs to…

  • Deliver a report to a human. The agent saves report.pdf, then sends a link that expires in 24 hours and opens at most 3 times.
  • Hand a file to another agent. The link works only when the named recipient agent presents its own key.
  • Wait for a human before releasing a file. The link stays locked (pending_approval) until the owner approves it.
  • Take something back. Revoke a link, and see every successful and refused open.

Common questions an agent asks

  • How can my agent send a file (PDF, CSV, report, image) to the user? Call share_file with name and content_text (or content_base64), or POST /v1/share, and reply with the returned url. The user opens it in a browser. No account is needed to download.
  • How do I get a temporary or expiring download link? Links from share_file expire in 7 days by default. Set expires_in_hours and max_opens on share_file or create_share_link. After that the link returns 410 expired or 429 limit.
  • How do I transfer a file from one agent to another? Use audience: "agent" with the recipient's agent_id. Only that agent's key can open it, through fetch_shared_file or a plain GET.
  • Can a human approve before the file is released? Yes. Set requires_approval: true. Opens are blocked until approve_share is called.
  • Can I take a link back or see who opened it? Yes. Use revoke_share and get_access_log. Refused opens are logged too.
  • Is it a public file host or a paste bin? No. Files are private by default and every link is an explicit grant you control.

Guides

Quickstart: one call

REST, raw bytes. The response is the URL, plus api_key: hf_… on a second line when the call created an account:

bash
curl -s --data-binary @report.pdf "https://hand-off.space/v1/share?name=report.pdf&format=url"# https://hand-off.space/g/…# api_key: hf_…

REST, JSON:

bash
curl -s -X POST https://hand-off.space/v1/share -H 'content-type: application/json' \  -d '{"name":"notes.md","content_text":"# Notes","expires_in_hours":24,"max_opens":3}'# → {"url":"https://hand-off.space/g/…","expires_at":"…","max_opens":3,"requires_approval":false,#    "file_id":"…","grant_id":"…","token_prefix":"…","next":{…},"api_key":"hf_…"}

MCP. Connect to https://hand-off.space/mcp with no auth, then call:

json
{"name": "share_file", "arguments": {"name": "notes.md", "content_text": "# Notes"}}

The result has url, expires_at (7 days by default), grant_id, file_id and, for a new account, api_key once. Give the url to the recipient, who downloads with a plain GET. Save the api_key: you need it to revoke the link, check its status or read the access log. Send it as Authorization: Bearer <api_key> (or as the api_key argument) on later calls, so new files stay in the same account.

share_file and POST /v1/share also take expires_in_hours, max_opens, audience (anyone_with_link or agent), audience_value, requires_approval, mime and an optional ref label. With raw bytes, pass them as query parameters.

More control: REST (3 calls)

Use this when you want to store a file once and create several links for it, each with its own rules.

bash
BASE=https://hand-off.space
# 1. Sign up. The key is shown once, so store it.curl -s -X POST $BASE/v1/signup -H 'content-type: application/json' -d '{"agent_id":"my-agent"}'# → {"account_id":"…","api_key":"hf_…","agent_id":"my-agent","caps":{…},"next":…}export HANDOFF_API_KEY=hf_...
# 2. Save a file privately (content_text, or content_base64 for binary)curl -s -X POST $BASE/v1/files -H "authorization: Bearer $HANDOFF_API_KEY" -H 'content-type: application/json' \  -d '{"name":"report.md","mime":"text/markdown","content_text":"# Q3 report"}'# → {"id":"FILE_ID","name":"report.md",…}
# 3. Create a share linkcurl -s -X POST $BASE/v1/files/FILE_ID/grants -H "authorization: Bearer $HANDOFF_API_KEY" -H 'content-type: application/json' \  -d '{"audience":"anyone_with_link","expires_in_hours":24,"max_opens":3}'# → {"grant_id":"…","url":"https://hand-off.space/g/…","token":"…","token_prefix":"…"}

More control: MCP (3 tool calls)

  1. Connect your client to https://hand-off.space/mcp (configs below).
  2. Get a key with the signup MCP tool (no key needed) or POST /v1/signup. Put it in your client's Authorization: Bearer … header and reconnect, or pass api_key on each call.
  3. Call save_file → create_share_link, then hand over the returned url.

Tools

ToolWhat it does
share_fileOne call. name plus content_text or content_base64. Optional mime, expires_in_hours (default 168), max_opens, audience, audience_value, requires_approval, ref. Returns url, expires_at, grant_id, file_id, and api_key once if no key was sent. No key needed.
signupCreate an account and return an API key once. No key needed. Optional agent_id.
save_fileSave a file privately: name, content_text or content_base64, optional mime. Returns the file id. No link is created.
list_filesList your files, newest first.
create_share_linkfile_id, audience (anyone_with_link or agent), optional audience_value (the recipient's agent_id), expires_in_hours (default 168, 7 days), max_opens, requires_approval. Returns grant_id, url (shown once), expires_at and max_opens.
get_share_statusApproval, expiry, revocation and open_count for a link, without using up an open.
approve_shareApprove a link created with requires_approval. Opens stay blocked until this succeeds.
deny_shareDeny a link that is waiting for approval. It stays blocked.
revoke_sharePermanently revoke a link. The file is kept.
get_access_logUp to the latest 1,000 opens and refusals for a link.
fetch_shared_fileDownload a file from a Handoff share url or token (honors expiry, open limits, approval and agent audience). Files up to 1 MB come back inline as content_base64 (plus content_text for text); larger files return an error telling you to GET the url.
delete_fileDelete a file you own. The bytes are removed and existing share links stop working.
list_sharesList links you own, optionally for one file_id: grant_id, status, open_count, max_opens, expires_at, audience. No tokens or URLs.
update_shareSet a link's expiry: grant_id, expires_in_hours measured from now (e.g. 168 for 7 days).

Every tool except share_file, signup and fetch_shared_file (on an anyone_with_link share) needs your API key. share_file with no key creates a new account.

Client configuration

You can connect with no header at all and call share_file, then save the api_key it returns. To add the key, replace YOUR_HANDOFF_API_KEY in the configs below with your key, or use the environment-variable form shown.

Claude Code

bash
claude mcp add --transport http handoff https://hand-off.space/mcp \  --header "Authorization: Bearer YOUR_HANDOFF_API_KEY"

You can also commit a project-scoped .mcp.json, which is the file in this repo. Claude Code expands ${HANDOFF_API_KEY} from your environment:

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

Claude Desktop

Claude Desktop custom connectors don't take a static header, so bridge with mcp-remote in claude_desktop_config.json:

json
{  "mcpServers": {    "handoff": {      "command": "npx",      "args": ["-y", "mcp-remote", "https://hand-off.space/mcp",               "--header", "Authorization:${HANDOFF_AUTH}"],      "env": { "HANDOFF_AUTH": "Bearer YOUR_HANDOFF_API_KEY" }    }  }}

Cursor (~/.cursor/mcp.json or .cursor/mcp.json)

json
{  "mcpServers": {    "handoff": {      "url": "https://hand-off.space/mcp",      "headers": { "Authorization": "Bearer ${env:HANDOFF_API_KEY}" }    }  }}

VS Code / GitHub Copilot (.vscode/mcp.json)

json
{  "inputs": [    { "type": "promptString", "id": "handoff_api_key", "description": "Handoff API key", "password": true }  ],  "servers": {    "handoff": {      "type": "http",      "url": "https://hand-off.space/mcp",      "headers": { "Authorization": "Bearer ${input:handoff_api_key}" }    }  }}

Cline (cline_mcp_settings.json)

Set "type": "streamableHttp" explicitly. If you leave it out, Cline falls back to legacy SSE.

json
{  "mcpServers": {    "handoff": {      "type": "streamableHttp",      "url": "https://hand-off.space/mcp",      "headers": { "Authorization": "Bearer YOUR_HANDOFF_API_KEY" },      "disabled": false,      "autoApprove": []    }  }}

Windsurf (~/.codeium/windsurf/mcp_config.json)

json
{  "mcpServers": {    "handoff": {      "serverUrl": "https://hand-off.space/mcp",      "headers": { "Authorization": "Bearer ${env:HANDOFF_API_KEY}" }    }  }}

OpenAI Agents SDK (Python)

python
import asyncio, osfrom agents import Agent, Runnerfrom agents.mcp import MCPServerStreamableHttp
async def main():    async with MCPServerStreamableHttp(        name="handoff",        params={            "url": "https://hand-off.space/mcp",            "headers": {"Authorization": f"Bearer {os.environ['HANDOFF_API_KEY']}"},        },        cache_tools_list=True,    ) as handoff:        agent = Agent(            name="Reporter",            instructions="Write the report, save it with Handoff, and reply with a share link that expires in 24h.",            mcp_servers=[handoff],        )        print((await Runner.run(agent, "Summarize Q3 sales into report.md and share it.")).final_output)
asyncio.run(main())

LangChain / LangGraph (langchain-mcp-adapters)

python
import osfrom langchain_mcp_adapters.client import MultiServerMCPClientfrom langchain.agents import create_agent
client = MultiServerMCPClient({    "handoff": {        "transport": "streamable_http",        "url": "https://hand-off.space/mcp",        "headers": {"Authorization": f"Bearer {os.environ['HANDOFF_API_KEY']}"},    }})tools = await client.get_tools()agent = create_agent("openai:gpt-4.1", tools)  # or langgraph.prebuilt.create_react_agent

Hermes Agent (~/.hermes/config.yaml)

yaml
mcp_servers:  handoff:    url: "https://hand-off.space/mcp"    headers:      Authorization: "Bearer YOUR_HANDOFF_API_KEY"

OpenClaw (~/.openclaw/openclaw.json)

Set transport explicitly, because OpenClaw defaults to SSE. Leave headers out to start with share_file.

json
{ "mcp": { "servers": { "handoff": {  "url": "https://hand-off.space/mcp",  "transport": "streamable-http",  "headers": { "Authorization": "Bearer ${HANDOFF_API_KEY}" } } } } }

Any other MCP client

SettingValue
URLhttps://hand-off.space/mcp
TransportStreamable HTTP (stateless, POST only)
HeaderAuthorization: Bearer <api_key>

More step-by-step setup for agents is in llms-install.md.

Agent Skill (no MCP needed)

This repo also ships an Agent Skill that teaches any skills-capable agent (Claude Code, Codex, Copilot, Cursor and others) to use the REST API:

bash
npx skills add jueluddinsf/handoff-mcp

Use with Grok Bot

Add Handoff as a custom MCP connector:

SettingValue
URLhttps://hand-off.space/mcp
Headernone to start; later Authorization: Bearer <api_key>

The bot can call share_file right away, with no key. Save the api_key it returns (shown once), then put it in the header and reconnect, or pass it as the api_key tool argument.

Or have the bot follow skills/handoff/SKILL.md and call the REST API over plain HTTP.

Example prompts:

  • Save this report to Handoff and send me a link that expires in 24h.
  • Share this file with my other agent only.
  • Show me who opened the link, then revoke it.

Recipes

Agent → human report, in one call

share_file(name="report.md", content_text=…, expires_in_hours=24, max_opens=3) → send the url.

Agent → human report, with an expiry (3-step)

save_file(report.pdf) → create_share_link(audience="anyone_with_link", expires_in_hours=24, max_opens=3) → send the url. Use get_access_log to confirm it was opened.

Agent → agent handoff

Agent A calls share_file(name=…, content_text=…, audience="agent", audience_value="agent-b"), or create_share_link(audience="agent", audience_value="agent-b") for a saved file. Agent B downloads with its own key:

bash
curl -L -H "authorization: Bearer $AGENT_B_KEY" -o out.bin "$URL"

Over MCP, Agent B can call fetch_shared_file(url_or_token=URL) instead. Any other caller gets 401 {"error":"unauthorized",…}.

Human-approval gate

share_file(..., requires_approval=true) or create_share_link(..., requires_approval=true). Until the owner approves, opens return 403 {"error":"pending_approval",…}. To approve or deny, call the approve_share / deny_share MCP tools, or:

bash
curl -X POST -H "authorization: Bearer $HANDOFF_API_KEY" $BASE/v1/grants/GRANT_ID/approve   # or /deny

Limits (free, unclaimed accounts)

  • 100 MB total, 50 files, 10 MB per file, 200 successful opens per day.
  • Allowed file types include text, markdown, HTML, code, CSV, JSON, PDF, images, Office documents and zip archives.
  • Executables are refused.
  • Link previews from Slack, X, Discord and similar unfurlers don't consume opens.
  • Signup is rate-limited per IP.

Download responses

StatusErrorMeaning
410expiredThe link is past its expiry.
410limitThe link hit its max opens.
429limitThe account hit its daily cap.
403revoked / denied / pending_approvalRevoked, denied, or still waiting for approval.
401unauthorizedThe link is for a different agent.

Security model

  • Files are private by default. Every share is an explicit grant with its own token.
  • Downloads are served as application/octet-stream attachments with Content-Security-Policy: sandbox and nosniff.
  • Every attempt, successful or refused, is logged.
  • API keys are stored hashed and shown once.

Report security issues privately (see CONTRIBUTING.md).

License

MIT. This applies to the docs and examples in this repository. The hosted service is operated by Juel Uddin.

來源:README.md,提交 5660500

工具

14

share_file

Share a 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.

輸入參數refmimename必填api_keyaudiencemax_openscontent_textaudience_valuecontent_base64expires_in_hoursrequires_approval

signup

Create an account

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.

輸入參數refagent_id

save_file

Save a 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.

輸入參數mimename必填api_keycontent_textcontent_base64

list_files

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.

輸入參數api_key

get_share_status

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.

輸入參數api_keygrant_id必填

approve_share

Approve a share

Approve an owned grant that was created with requires_approval. Opens stay blocked until this succeeds. Requires your API key.

輸入參數api_keygrant_id必填

deny_share

Deny a share

Deny an owned grant. A denied grant stays blocked. Requires your API key.

輸入參數api_keygrant_id必填

revoke_share

Revoke a share

Permanently revoke an owned share. Later downloads fail. This cannot be undone and does not delete the file. Requires your API key.

輸入參數api_keygrant_id必填

get_access_log

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.

輸入參數api_keygrant_id必填

fetch_shared_file

Fetch a 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.

輸入參數api_keyurl_or_token必填

delete_file

Delete a file

Delete a file you own. The bytes are removed and existing share links stop working. Requires your API key.

輸入參數api_keyfile_id必填

list_shares

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.

輸入參數api_keyfile_id

update_share

Update share expiry

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.

輸入參數api_keygrant_id必填expires_in_hours必填

版本歷史

1
  1. v1.0.1最新Oct 29, 2026

評分

已滿足適用檢查項的 100%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分