Skip to content

MCP cannot read or attach files: no tool downloads an attachment (e.g. a policy PDF to analyse) and none attaches one (e.g. a photographed invoice) to a record #30892

Open

Description

@objectstack-fleet

coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e

Filing gate: ③ a task on the maintainer's word. Coordinator session, 2026-11-06: after reviewing screenshots of an MCP client (Claude app) working against a hosted environment, the maintainer said 「这些问题都需要立卡处理」, then 「你只需要立卡,不需要派发」.

Reader: domain:server (packages/mcp, with packages/services/service-storage). Cloud's half (storage quota, the bucket) follows on objectstack-ai/cloud if the design needs it. Suggested priority p1. Triage decides.

Dedupe:

维护者速读

两个真实场景:一是拍一张发票,让 AI 建报销单并把发票图片挂上去;二是系统里存着规章制度的附件,让 AI 下载下来分析。现在 MCP 两样都做不到:工具里没有任何读写文件的。AI 能看懂照片、把字段填进报销单,但挂不上原图;存成附件的 PDF 也读不到。

What is there today (objectstack origin/main)

  • The MCP tools in packages/mcp/src/mcp-http-tools.ts read and write records and run actions. None touches storage: no upload, download or presign anywhere in packages/mcp/src.
  • Records carry Field.file / Field.image (spec/src/data/field.zod.ts:3473-3474) and attachments live in service-storage (attachment-lifecycle.ts).
  • An AI client sees an image the user attaches in its own chat, but it has no bytes to send. The MCP protocol has no standard client-to-server file transfer.

Ask

  1. Read a file. A tool that, for an attachment or a file field the caller can see, returns its text for analysis (PDF and Word extracted to text; images as an image content block where the protocol allows), plus a short-lived download link for a person. Access follows the record: no record access, no file. A size cap, named in the answer.
  2. Attach a file through a one-time upload link. A tool that, for a record the caller may edit, returns a short-lived, single-use link bound to that record and field. The person opens it on their phone and picks the photo, and it lands as the record's attachment. The link expires in minutes and uploads count toward the storage quota.
  3. Direct upload through the protocol when clients can send file content; out of scope until then, noted so the design leaves room.

Acceptance

  • Tests pin access following the record, the size cap, the link's single use and expiry, and the binding to one record and field.
  • On a hosted environment: an MCP client reads a PDF attachment's text and summarises it. A photographed invoice ends up attached to the expense record it created, through the link.

🤖 Generated with Claude Code

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    area:filesFiles — upload, download, signed URLs, access derived from the parent record
    area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drives
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p1 kept · domain:server · re-queued · 2026-11-06T12:44Z

    ⛔ Not a claim.

    • MCP cannot read or attach files: no tool downloads an attachment or attaches one. The maintainer filed it from an MCP client walkthrough (「这些问题都需要立卡处理」). The coordinator was told 「只需要立卡,不需要派发」, which is about the coordinator's own stroke; lane seats pick it up from the queue.
    • p1 kept: a protocol addition (new MCP tools), within the packages/mcp plus service-storage surface.
    • Triage re-queued it at 12:28Z: the card had a grade and a lane but no pm:* state, so no seat could see it. That was a triage sweep miss, fixed this round.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

    Claim: PM loop round 1 · 2026-11-06T12:50Z
    Session: session_016WV4czvZykUqhYxYXSSDt4
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch, re-posted in a whole claim once the thread reports it
    Worktree: objectstack-issue-30892
    Domain: domain:server
    Seat: domain:server#4
    File surface: two new MCP tools and their wiring (packages/mcp/src/**: tool registration, the data-bridge members, and the MCP-served skill-md.ts lines), the runtime half of the MCP bridge where it reads attachments (packages/runtime/src/domains/mcp.ts), the storage service's existing read, presign and attach paths (packages/services/service-storage/src/**, only to call or expose an existing door, plus one single-use upload-link record if none exists), their tests and dogfood; content/docs/ai/connect-mcp.mdx; one root .changeset/ file. ⛔ packages/spec/**: if a published key or schema is needed, the dev stops after measuring and the seat files the domain:spec sub-card first. ⛔ ui/**, skills/** (stop on breach; explain in the report)
    Container & model: L, mode:thread, model: default, effort: xhigh (p1)
    Clause-②: yes (widening) (provisional: two new tools on the published MCP surface; the dev measures and reports the arm)
    Metadata-protocol: none (provisional; added only through the spec sub-card)
    Responsibility: n/a — new capability, not a regression
    Thread-read: 6685125943
    Premise: card body (maintainer 「这些问题都需要立卡处理」) and triage 6685125943 (p1 kept). Two tools follow.

    • Read a file: for an attachment or file field the caller can see, return its text (PDF and Word extracted; images as an image block where the protocol allows), plus a short-lived download link. Access follows the record, under a named size cap.
    • Attach through a one-time upload link: for a record the caller may edit, return a short-lived, single-use link bound to that record and field. The upload counts toward the storage quota.
      Direct protocol upload is out of scope. Cloud's half (quota, bucket) goes to objectstack-ai/cloud only if the design needs it.
      Serial constraints cleared: the dev re-reads open PRs touching packages/mcp/src/mcp-http-tools.ts at branch start (PR feat(mcp)!: delete_record refuses a call without confirm: true #30861 and PR fix(platform-objects)!: platform-admin actions declare their gate, read by MCP list_actions and both action doors #30849 are open on it, awaiting the maintainer's word; rebase onto whichever lands first)

    Contract review owed (a widening of the public MCP surface) before the queue.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

    Claim: PM loop round 1 (re-posted whole with the branch) · 2026-11-06T12:56Z
    Session: session_016WV4czvZykUqhYxYXSSDt4
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-s2vcdk
    Dev session: session_016WV4cyqd3A7fbCWrqM3KCz (the card thread that implements; this seat lands)
    Worktree: objectstack-issue-30892
    Domain: domain:server
    Seat: domain:server#4
    File surface: two new MCP tools and their wiring (packages/mcp/src/**: tool registration, the data-bridge members, and the MCP-served skill-md.ts lines), the runtime half of the MCP bridge where it reads attachments (packages/runtime/src/domains/mcp.ts), the storage service's existing read, presign and attach paths (packages/services/service-storage/src/**, only to call or expose an existing door, plus one single-use upload-link record if none exists), their tests and dogfood; content/docs/ai/connect-mcp.mdx; one root .changeset/ file. ⛔ packages/spec/**: if a published key or schema is needed, the dev stops after measuring and the seat files the domain:spec sub-card first. ⛔ ui/**, skills/** (stop on breach; explain in the report)
    Container & model: L, mode:thread, model: default, effort: xhigh (p1)
    Clause-②: yes (widening) (provisional: two new tools on the published MCP surface; the dev measures and reports the arm)
    Metadata-protocol: none (provisional; added only through the spec sub-card)
    Responsibility: n/a — new capability, not a regression
    Thread-read: 6685125943
    Premise: card body (maintainer 「这些问题都需要立卡处理」) and triage 6685125943 (p1 kept). Two tools follow.

    • Read a file: for an attachment or file field the caller can see, return its text (PDF and Word extracted; images as an image block where the protocol allows), plus a short-lived download link. Access follows the record, under a named size cap.
    • Attach through a one-time upload link: for a record the caller may edit, return a short-lived, single-use link bound to that record and field. The upload counts toward the storage quota.
      Direct protocol upload is out of scope. Cloud's half (quota, bucket) goes to objectstack-ai/cloud only if the design needs it.
      Serial constraints cleared: the dev re-reads open PRs touching packages/mcp/src/mcp-http-tools.ts at branch start (PR feat(mcp)!: delete_record refuses a call without confirm: true #30861 and PR fix(platform-objects)!: platform-admin actions declare their gate, read by MCP list_actions and both action doors #30849 are open on it, awaiting the maintainer's word; rebase onto whichever lands first)

    Contract review owed (a widening of the public MCP surface) before the queue.

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    To: domain:server#4
    Request: progress · #30892 · idle since 2026-11-06T12:56Z


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

    Handoff to domain:server#2 · 2026-11-06T17:02Z · server seat 4 is signing off on the maintainer's word, and the contract review is left to the next seat. The maintainer's word for this card: server seat 2 takes it over.

    State:

    • Branch claude/project-thread-s2vcdk, built by card thread session session_016WV4cyqd3A7fbCWrqM3KCz. Option B (the upload link needs the minting user's session) is at 04ff76bc: 111/111 derived gates, full dogfood 426 files 0 failed. The dev is pushing it now and opening a draft PR with Fixes #30892; the PR body states what is still open.
    • Section 1 measurements: /mnt/project-files/server-lane-seat4/30892/section1-measurements.md (project files). Seat rulings: D1 unpdf@1.9.1 pinned exact (2.x needs node 24), a dependency of @objectstack/mcp only, dynamic import. D2: the link record is the pending sys_file row. D3: judged at mint and re-resolved as the minting user at upload.

    Owed by the next seat:

    1. The maintainer's choice, which seat 4's withdrawn decision card asked: A, the link is a bearer capability, so a phone that isn't signed in can upload (needs an ADR-0138 outside-family amendment, a governed fable-tier surface); or B, the link requires sign-in (built; no ADR change, no new anonymous door). Seat 4 recommended B.
    2. The contract review (Clause-②: yes (widening): two new MCP tools and two new storage routes). Its security face covers the token entropy, hash at rest, single use, expiry, and one same-shaped answer for a used, expired or unknown link. Then decide whether to notify security#1.
    3. ACCEPT and landing. Not governed is expected; re-run check-governed-merges on the final file list.

    The claim (6685332126) is released with this note: the assignee is removed and the card returns to pm:queue for server seat 2 to claim.

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 6, 2026
  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    Ruling · 2026-11-06T17:40Z · how the shared delete link is used (seat 4 brief 6690566522)

    The maintainer ruled in the server seat 2 thread 2026-11-06T17:36Z, option B: the link works only for a signed-in user, as PR #31009 builds it. Option A (a bearer link usable without sign-in, which would need an ADR-0138 amendment) is not taken. PR #31009 now owes the seat's widening contract review (token entropy, hash at rest, single use, expiry, one same-shaped answer); the seat runs it and lands the PR on PASS.

  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-elon-musk · session_016WV4cyqd3A7fbCWrqM3KCz
    os-dev-report

    {
      "status": "done",
      "branch": "claude/project-thread-s2vcdk",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31009",
      "session": "session_016WV4cyqd3A7fbCWrqM3KCz",
      "premise_still_valid": true,
      "summary": "Two MCP tools: read_file (a record's file field or attachment, as extracted text for PDF/.docx/text, an image block, a short-lived download link, size and the 10 MiB cap; access follows the record with get_record's and the download door's own codes) and create_upload_link (a 600 s single-use link bound to one record and field or the attachments list, minted after the record's edit door; the upload lands as the record's file through engine.update / attachFileAs as the minting user, re-resolved). Built as option B: the link works only for the minting user, signed in (no session 401 AUTH_REQUIRED, another user the same 404, nothing spent), so the ADR-0138 D2 anonymous-door sweep holds unchanged. Elon's A/B sign-in choice is still open, and landing waits on it: A = bearer link plus an ADR-0138 amendment and a revert of 04ff76bc. Server seat 4 signed off; the card is handed to server seat 2 (handoff 6690566522), the claim is released, the PR has no assignee, and the Clause-② (widening) contract review is owed to that seat.",
      "tests": "Head 67847d9c. service-storage 74 files 1388/1388; mcp 58 files 711/711; runtime src/mcp-record-file-bridge.test.ts 5/5; tsc --noEmit on service-storage, mcp, runtime and dogfood exit 0; dogfood showcase-mcp-record-files + anonymous-door-sweep + authz-conformance 3 files 93/93. New suites: record-file-access.integration.test.ts 21, mcp-file-tools.test.ts 21, mcp-record-file-bridge.test.ts 5, showcase-mcp-record-files.dogfood.test.ts. Ablations (service-storage integration): spend guard `if (!spent)` -> `if (spent < 0)` turned 'of two concurrent uses exactly one lands' red (1 failed / 19 then); removing `deadline <= this.now()` turned the expiry test red; both restored, blobs equal HEAD.",
      "gates": "dispatch-gates --commands derives 111 on the merge head (same set as 04ff76bc). Run over the merged tree while two test-only commits (f30868c7, 67847d9c) landed: 111/111 exit 0; --ran: 111 derived, 111 run, 0 NOT-MEASURED. Two reruns, both prerequisites, not findings: check:type-check-debt exited 3 (scenarios dts build lacked playwright-core because pnpm install had not run after the main merge; after install + rebuild it exits 0), and on 04ff76bc check-comment-mask-corpus hit ENOENT on a dogfood temp bundle (rerun exit 0). Not in the derivation: @objectstack/dogfood#typecheck (CI 'Type Check · workspace' caught TS2322 on c5fe7df9, fixed f30868c7) and check:test-source-alias red after the merge (fixed 67847d9c). CI on 67847d9c: 66 check-runs, 59 success and 7 skipped by their own filters, 0 failed (reds on c5fe7df9 and f30868c7 were the two fixes above plus aggregators of shards cancelled by the next push).",
      "dogfood_battery": "n/a by rule (no `!` in the title, Clause-② widening), run anyway: merge tree (c5fe7df9 + f30868c7) · os-verify-lock: VERDICT command-exit 0 · Test Files 429 passed + 3 skipped / 432 · Tests 3573 passed + 41 skipped / 3614 · 0 failed. On 04ff76bc: 426 + 3 skipped / 429, 0 failed.",
      "mcp_calls": "21, all GitHub reads: issue_read x6, list_pull_requests x4, pull_request_read x8, get_job_logs x3. No MCP write. (Two check-run reads also went through gh api.)",
      "api_writes": "2 relay writes: pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft; read-back 22074 bytes identical) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/30892/comments). Plus 5 git pushes (75bc8cfa..67847d9c), which are not REST writes.",
      "open_questions": [
        {
          "question": "Does the upload link need the minting user to be signed in?",
          "options": [
            "A: bearer link, so a phone that is not signed in can upload; needs an ADR-0138 amendment admitting the two routes as an outside family, then revert 04ff76bc",
            "B: only the minting user, signed in (built; sweep unchanged)"
          ],
          "recommendation": "B, because it keeps the anonymous-door sweep closed with no ADR change, and a forwarded or leaked link works for nobody else; the cost is signing in on the phone first."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — dispatch-gates --commands does not derive @objectstack/dogfood#typecheck, so a dogfood test type error shows only in CI 'Type Check · workspace'.",
        "carrier: none · noted in the PR's Acceptance notes — an ADR-0069 hold on the minting user is not re-checked at upload; the link path follows the plugin basePath; no byte quota exists in the open tree (cloud's half)."
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drivesarea:filesFiles — upload, download, signed URLs, access derived from the parent recorddomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:queueReady for the PM dispatch looppriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions