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. 5 remaining items

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

  8. 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

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    Claim: PM loop round 1 · take-over of PR #31009 on domain:server#4's brief 6690566522 and request 6691459016 on mailbox #30985 · 2026-11-06T18:00Z
    Session: session_01L9qibyvxbrLP4jm4PargoH
    Account: os-tesla (the card's assignee from this claim)
    Branch: claude/project-thread-s2vcdk (PR #31009 as left by domain:server#4's card thread; a server seat 2 card thread resumes it for the review's two should-fixes, merge only, no new PR)
    Worktree: objectstack-issue-30892
    Domain: domain:server
    Seat: domain:server#2
    File surface: PR #31009's files as its body lists them; the delta adds only an owner check in record-file-access.ts openLink, its pin, its module-doc sentence, and the PR body.
    Container & model: M, mode:thread, build model: TIER_DEFAULT, effort: high; contract review 6691738005 served at CONTRACT_REVIEW_TIER (security face); a delta review follows the fix.
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: as on the card; the maintainer ruled option B (sign-in required), 6691366983.
    Thread-read: 6691432611
    Premise: holds. PR #31009 head 67847d9c, 59 success / 7 skipped / 0 failed; contract review PASS with 0 must-fix.
    Serial constraints cleared: none (packages/mcp work of #30891 on this seat does not touch the link files).
    Resume-from: claude/project-thread-s2vcdk@67847d9c

    Order: /mnt/project-files/server-seat-2/r2026-11-06/dispatch-31009-fix.md (frame FRAME.md beside it).

  11. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibzuTqmB5hqFYmMATiY
    os-dev-report

    {
      "issue": 30892,
      "status": "done",
      "branch": "claude/project-thread-s2vcdk",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31009",
      "session": "session_01L9qibzuTqmB5hqFYmMATiY",
      "premise_still_valid": true,
      "summary": "Contract review 6691738005 should-fix 1 and 2, on the dispatch claim 6691779259 (resumed 67847d9c, merge only). Should-fix 1 (8d23d823): openLink in record-file-access.ts now also refuses when the row's owner_id (readonly, written at mint) differs from the binding's user_id, with the same refuseUnusableLink 404; the module doc states that name and metadata are not readonly and that the binding's other columns rest on sys_file edit being administrative. Pinned: a link row whose metadata is rewritten to name the reviewer answers the one 404 on the page and the PUT to both the reviewer and the minting user, and stays pending, open and unspent with no attachment added. Should-fix 2: the PR body now records ruling B (6691366983), drops the revert instruction, records the review and its should-fix 3 follow-up, written twice through relay issue_patch with the seat's trailer, each read back identical. The PR had gone DIRTY, so origin/main was merged at 57724d86: the tenant-audit census conflicted, resolved by taking main's artefacts, regenerating with tenant-audit-census.mjs --write (265 write call sites) and moving the page's 15 hand-stated figures. Landing class: the PR is 4063 changed lines (+3979 / -84), over the 3,000 line, so check-governed-merges --test answers HUMAN MERGE OR AUTHORIZED APPROVAL (exit 3); the body's 维护者速读 now asks the maintainer for that one approval.",
      "tests": "Head 57724d86 (after install and a full workspace build with declarations, 119/119 turbo tasks): @objectstack/service-storage 74 files 1389/1389; @objectstack/mcp 58 files 714/714; @objectstack/runtime src/mcp-record-file-bridge.test.ts 5/5; typecheck on service-storage, mcp and runtime exit 0; dogfood tsc --noEmit exit 0; dogfood showcase-mcp-record-files + anonymous-door-sweep + authz-conformance + route-ledger-live-mount-parity + mcp-system-object-action-exposure 5 files 114/114. On 8d23d823: record-file-access.integration.test.ts 22/22; service-storage 1389/1389; eslint --no-inline-config on the two files exit 0. Ablation (source-imported suite, no dist leg): scripts/ablation-replace.mjs --delete of 'if (row.owner_id !== state.user_id) refuseUnusableLink();' anchor 1 -> 0, blob e63efe2f -> 7625cc64; the new pin went red, 'AssertionError: expected 200 to be 404' (the rebound row opened for the other user), Tests 1 failed / 21 passed of 22, os-verify-lock VERDICT command-exit 1; restored, blob equals HEAD e63efe2f, git diff HEAD empty.",
      "gates": "On 57724d86, derived for this round's delta (record-file-access.ts, its integration test, the two census artefacts): 84 derived, 84 run, all exit 0; dispatch-gates --ran: 84 derived, 84 run, 0 NOT-MEASURED, 0 UNRUN. On 8d23d823 (two code files): 53 derived, 53 of 53 exit 0 and reconciled 53/53/0, after three prerequisite reruns, none a finding: check-plugin-teardown-shape --self-test exit 3 until 621a4876 was fetched into the shallow clone, check:dual-build-cjs-loads and check:i18n exit 3 until a full workspace build. check-tenant-audit-census exit 0 after the merge resolution (265 sites, 23 prose figures held). check-governed-merges --test on the final file list: no governed path; with --additions 3979 --deletions 84 it exits 3, HUMAN MERGE OR AUTHORIZED APPROVAL. CI: on 8d23d823 70 check-runs, 61 success, 9 skipped, 0 failed; 57724d86 was still running when this report was written (in_progress).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm; the five overlapping dogfood files ran on 57724d86, 114/114.",
      "mcp_calls": "9 — all GitHub reads: issue_read x2 (card get, comments), pull_request_read x7 (get x3, get_comments, get_check_runs, get_files). No MCP write.",
      "api_writes": "3 relay writes: issue_patch on PR #31009 twice (run 42271774383, read-back 23708 bytes identical; run 42280038121, read-back 25036 bytes identical) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/30892/comments). Plus 2 git pushes (67847d9c..8d23d823, 8d23d823..57724d86), not REST writes.",
      "open_questions": [
        {
          "question": "The PR is 4063 changed lines, so it lands only after an authorized approval. Does the seat request it?",
          "options": ["A: the seat requests review from os-zhuang and lands after the maintainer's approval", "B: the maintainer merges it by hand"],
          "recommendation": "A, because the seat's contract review already passed and the body's 维护者速读 already asks for that one approval."
        },
        {
          "question": "os-dev.md says the dev writes a PR body once at pr_create and never PATCHes it, while this order asked the card thread to patch the body with the seat's signed trailer. I followed the order. Is a seat-ordered, seat-signed body patch the accepted reading?",
          "options": ["A: yes, a seat-ordered patch signed with the seat's trailer is the seat's write", "B: no, the seat writes body patches itself next time"],
          "recommendation": "A, because the trailer names the seat as the author and the order named the stroke."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: domain:server#2 · contract review should-fix 3 (a signed-out browser on GET upload-links gets a JSON 401, not a sign-in page) is the seat's follow-up card per the order; not filed here."
      ]
    }
  12. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

    ACCEPT pointer · 2026-11-06T19:37Z · PR #31009 accepted at head 57724d86 in 6693670237. It is 4,063 changed lines, over the human-merge line, so it waits as a draft with pm:awaiting-merge for os-zhuang's approval; the seat lands it after that.

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

Metadata

Metadata

Assignees

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:dispatchedDispatched to a dev agent by /pm-dispatchpriority: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