You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Serve move and copy that wait for Basecamp in basecamp mcp - #988
Agents on basecamp mcp could only start a move or copy with move_recording or copy_recording. They then had to poll get_filing and look up the new recording themselves. The CLI's own move and copy commands already wait and answer with the new recording. This change gives the MCP server the same behavior through basecamp-sdk v0.32.0's MoveAndWait and CopyAndWait.
What changed
basecamp_recordings move and copy take a recording id and type and a destination. The destination is project_id (its one enabled tool of the recording's kind, or its one to-do list) or container_id, with folder_id for Docs & Files and include_comments for a copy. Each call starts the filing once, waits up to a minute, and returns the new recording with source_id.
A failure carries error.type and error.retry_safe. A project with the tool turned off names the tool_id to enable. A project with several candidates lists them for container_id. A filing still running when the wait ends carries the filing_id to check with get_filing. An id that now redirects to a moved recording is refused as recording_moved, so a retried move never files the recording twice.
move_recording and copy_recording are removed. get_filing moves from basecamp_automation to basecamp_recordings, so the check is always next to the actions that need it.
The SDK is bumped to v0.32.0 and the vendored MCP model is synced to it.
Scope
Covers the 14 kinds the web's Move… and Copy… dialogs file: cards, card columns, messages, documents, uploads, cloud files, Google documents, folders, schedule entries, check-in questions, email forwards, to-dos, to-do lists and to-do list groups.
Leaves out whole dock tools (a project's Docs & Files or card table), as the CLI does. basecamp_cards move_card (column moves and wormholes) uses a different endpoint and is unchanged.
A recurring event files as its whole series, as in the CLI. The answer names the series and keeps the occurrence Basecamp read as occurrence_id.
One recording per call. No bulk form.
Tradeoffs
recording_type is required. Basecamp has no generic recording read, and the type selects the typed read that reports the parent, which tells a to-do list from a group and a folder from a whole Docs & Files.
No start-only twin stays beside the waiting actions. The gateway does not need one, and an agent shown both would have to pick.
Blast Radius
A client that calls move_recording, copy_recording or basecamp_automation get_filing now gets an unknown-action error and must use basecamp_recordings. basecamp_recordings now has write actions, so its tool-level read-only hint is off. --read-only drops move and copy and keeps summarize and get_filing.
Verification
bin/ci green on this head.
TestFilingServesEveryTypeTheSDKFiles asks FilingContainerFor about every recordable type (bc3's models, the SDK's constants and every type name in the vendored model, with each as a parent) and drives a move and a copy through the MCP wire for each of the 14 routes it accepts. It fails when the SDK files a type this server cannot read or a container kind it cannot resolve. It fails with Inbox::Forward removed from the read table, which was checked.
Live matrix: 64 of 64 rows passed through the built binary over stdio (initialize, then tools/call basecamp_recordings) against a dev bc3 with a job worker. Each kind ran move within its project, move to another project, copy in place and copy to another project. Extra rows covered folder placement in another project, include_comments, three refusals (tool turned off, two candidate message boards, a folder passed as the container), a recurring event moved and copied to another project, and a retried move refused. An independent API read proved each row: the new id is in the expected project and container, a moved id answers 302, and a copied source is unchanged. A filing_unsettled answer was not provoked live, because bc3 finishes in under 2 seconds and the wait is a minute; unit tests cover it. The driver is in the first comment below.
basecamp_recordings move and copy take a recording and a destination (a
project, or a container plus an optional folder), start the filing once
through the SDK's MoveAndWait or CopyAndWait, wait for Basecamp to finish,
and answer with the new recording read back at its new id. A failure says
how far it got and whether calling again is safe.
They replace the model's start-only move_recording and copy_recording,
which answered with a filing to poll. get_filing moves into
basecamp_recordings beside them, as the check for a filing that was still
running when the wait ended.
Live matrix driver, for rerunning the rows against a dev bc3 with a job worker (API on 127.0.0.1:4411, as the verify-basecamp-cli skill launches it).
# env: BASECAMP_BASE_URL=http://127.0.0.1:4411 BASECAMP_ACCOUNT_ID=181900405, and BASECAMP_TOKEN read at# runtime from bc3's `pat` fixture (test/fixtures/oauth/access_tokens.yml). `basecamp` v0.20.1 on PATH seeds# the upload and forward the API cannot create.
go build -o /tmp/mcp-matrix/basecamp ./cmd/basecamp
python3 setup.py fixtures.json # MCP CLI Lab A/B/C projects, second tools in A, seeds
python3 matrix.py /tmp/mcp-matrix/basecamp fixtures.json matrix # writes matrix.md and matrix.json
The table from this head (e846a39). Rows 62 to 64 ran by hand on the same head: a recurring event moved and copied to project B with the released CLI seeding it, and a completed move retried with its old id:
#
kind
operation
source
new
API checks
verdict
1
card
move within project
1069485280
1069485282
GET new 200; bucket 1042979315; in 1069484017; GET old 302
PASS
2
card
move to other project
1069485285
1069485287
GET new 200; bucket 1042979316; in 1069483972; GET old 302
PASS
3
card
copy in place
1069485290
1069485292
GET new 200; bucket 1042979315; in 1069483956; source unchanged
PASS
4
card
copy to other project
1069485290
1069485294
GET new 200; bucket 1042979316; in 1069483972; source unchanged
PASS
5
column
move within project
1069485296
1069485297
GET new 200; bucket 1042979315; in 1069484017; GET old 302
PASS
6
column
move to other project
1069485298
1069485299
GET new 200; bucket 1042979316; in 1069483972; GET old 302
PASS
7
column
copy in place
1069485300
1069485301
GET new 200; bucket 1042979315; in 1069483956; source unchanged
PASS
8
column
copy to other project
1069485300
1069485302
GET new 200; bucket 1042979316; in 1069483972; source unchanged
PASS
9
message
move within project
1069485303
1069485305
GET new 200; bucket 1042979315; in 1069484016; GET old 302
PASS
10
message
move to other project
1069485308
1069485310
GET new 200; bucket 1042979316; in 1069483967; GET old 302
PASS
11
message
copy in place
1069485313
1069485315
GET new 200; bucket 1042979315; in 1069483951; source unchanged
PASS
12
message
copy to other project
1069485313
1069485317
GET new 200; bucket 1042979316; in 1069483967; source unchanged
PASS
13
document
move within project
1069485319
1069485321
GET new 200; bucket 1042979315; in 1069484030; GET old 302
PASS
14
document
move to other project
1069485324
1069485326
GET new 200; bucket 1042979316; in 1069483969; GET old 302
PASS
15
document
copy in place
1069485328
1069485331
GET new 200; bucket 1042979315; in 1069483953; source unchanged
PASS
16
document
copy to other project
1069485328
1069485333
GET new 200; bucket 1042979316; in 1069483969; source unchanged
PASS
17
upload
move within project
1069485335
1069485337
GET new 200; bucket 1042979315; in 1069484030; GET old 302
PASS
18
upload
move to other project
1069485339
1069485341
GET new 200; bucket 1042979316; in 1069483969; GET old 302
PASS
19
upload
copy in place
1069485343
1069485345
GET new 200; bucket 1042979315; in 1069483953; source unchanged
PASS
20
upload
copy to other project
1069485343
1069485347
GET new 200; bucket 1042979316; in 1069483969; source unchanged
PASS
21
cloud file
move within project
1069485349
1069485351
GET new 200; bucket 1042979315; in 1069484030; GET old 302
PASS
22
cloud file
move to other project
1069485353
1069485355
GET new 200; bucket 1042979316; in 1069483969; GET old 302
PASS
23
cloud file
copy in place
1069485357
1069485359
GET new 200; bucket 1042979315; in 1069483953; source unchanged
PASS
24
cloud file
copy to other project
1069485357
1069485361
GET new 200; bucket 1042979316; in 1069483969; source unchanged
PASS
25
google document
move within project
1069485363
1069485365
GET new 200; bucket 1042979315; in 1069484030; GET old 302
PASS
26
google document
move to other project
1069485368
1069485370
GET new 200; bucket 1042979316; in 1069483969; GET old 302
PASS
27
google document
copy in place
1069485373
1069485375
GET new 200; bucket 1042979315; in 1069483953; source unchanged
PASS
28
google document
copy to other project
1069485373
1069485377
GET new 200; bucket 1042979316; in 1069483969; source unchanged
PASS
29
folder
move within project
1069485379
1069485380
GET new 200; bucket 1042979315; in 1069484030; GET old 302
PASS
30
folder
move to other project
1069485382
1069485383
GET new 200; bucket 1042979316; in 1069483969; GET old 302
PASS
31
folder
copy in place
1069485385
1069485386
GET new 200; bucket 1042979315; in 1069483953; source unchanged
PASS
32
folder
copy to other project
1069485385
1069485387
GET new 200; bucket 1042979316; in 1069483969; source unchanged
PASS
33
schedule entry
move within project
1069485388
1069485390
GET new 200; bucket 1042979315; in 1069484023; GET old 302
PASS
34
schedule entry
move to other project
1069485393
1069485395
GET new 200; bucket 1042979316; in 1069483970; GET old 302
PASS
35
schedule entry
copy in place
1069485398
1069485400
GET new 200; bucket 1042979315; in 1069483954; source unchanged
PASS
36
schedule entry
copy to other project
1069485398
1069485402
GET new 200; bucket 1042979316; in 1069483970; source unchanged
PASS
37
check-in question
move within project
1069485404
1069485406
GET new 200; bucket 1042979315; in 1069484024; GET old 302
PASS
38
check-in question
move to other project
1069485409
1069485411
GET new 200; bucket 1042979316; in 1069483978; GET old 302
PASS
39
check-in question
copy in place
1069485414
1069485416
GET new 200; bucket 1042979315; in 1069483962; source unchanged
PASS
40
check-in question
copy to other project
1069485414
1069485418
GET new 200; bucket 1042979316; in 1069483978; source unchanged
PASS
41
email forward
move within project
1069485420
1069485423
GET new 200; bucket 1042979315; in 1069484025; GET old 302
PASS
42
email forward
move to other project
1069485426
1069485429
GET new 200; bucket 1042979316; in 1069483979; GET old 302
PASS
43
email forward
copy in place
1069485432
1069485435
GET new 200; bucket 1042979315; in 1069483963; source unchanged
PASS
44
email forward
copy to other project
1069485432
1069485438
GET new 200; bucket 1042979316; in 1069483979; source unchanged
PASS
45
to-do list
move within project
1069485441
1069485442
GET new 200; bucket 1042979315; in 1069484026; GET old 302
PASS
46
to-do list
move to other project
1069485443
1069485444
GET new 200; bucket 1042979317; in 1069484003; GET old 302
PASS
47
to-do list
copy in place
1069485445
1069485446
GET new 200; bucket 1042979315; in 1069483952; source unchanged
PASS
48
to-do list
copy to other project
1069485445
1069485447
GET new 200; bucket 1042979317; in 1069484003; source unchanged
PASS
49
to-do list group
move within project
1069485448
1069485449
GET new 200; bucket 1042979315; in 1069484028; GET old 302
PASS
50
to-do list group
move to other project
1069485450
1069485451
GET new 200; bucket 1042979316; in 1069484029; GET old 302
PASS
51
to-do list group
copy in place
1069485452
1069485453
GET new 200; bucket 1042979315; in 1069484027; source unchanged
PASS
52
to-do list group
copy to other project
1069485452
1069485454
GET new 200; bucket 1042979316; in 1069484029; source unchanged
PASS
53
to-do
move within project
1069485455
1069485457
GET new 200; bucket 1042979315; in 1069484028; GET old 302
PASS
54
to-do
move to other project
1069485459
1069485462
GET new 200; bucket 1042979316; in 1069484029; GET old 302
PASS
55
to-do
copy in place
1069485465
1069485467
GET new 200; bucket 1042979315; in 1069484027; source unchanged
PASS
56
to-do
copy to other project
1069485465
1069485469
GET new 200; bucket 1042979316; in 1069484029; source unchanged
PASS
57
document
move into folder in other project
1069485471
1069485473
GET new 200; bucket 1042979316; in 1069484031; GET old 302
PASS
58
message
copy with include_comments
1069485475
1069485480
GET new 200; bucket 1042979316; in 1069483967; source unchanged; copy has 1 comment(s)
answer names series 1069485492 (occurrence 1069485493); GET new follows 302 to series 1069485492 in bucket 1042979316; GET old follows 302 to the same series in 1042979316
PASS
63
recurring event
copy to other project
1069485495
1069485498
answer names series 1069485498; GET new: series 1069485498 in bucket 1042979316; source: series 1069485495 still in bucket 1042979315
PASS
64
document
retry a completed move with the old id
(fresh document)
1069485503
first call moved it; second call error.type recording_moved, nothing filed
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Preflight failures omit error classification and retry-safety fields, and recurring schedule series are rejected because occurrence reads are not normalized to the series.
Adds waiting move and copy actions to the MCP recordings tool, bringing its filing workflow in line with the CLI.
Changes:
Replaces start-only actions with move and copy, returning the new recording and reporting filing status and retry safety.
Moves get_filing into basecamp_recordings and adds routing, failure-handling, and read-only tests.
Updates the Basecamp SDK to v0.32.0 and refreshes dependency and model provenance.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
…iling
Basecamp answers a recurring event's read with one occurrence, so the type check refused every recurring event. The read now names the series, with the occurrence kept as occurrence_id, as the CLI's own schedule move does.
A failed read of the recording or the destination project now answers with error.type (recording_unreadable, destination_unreadable) and retry_safe, like every other failure after the arguments are accepted.
A moved recording's old id reads as its new one. Filing what that read found would move the recording again when a lost answer to a move is retried. The call is now refused as recording_moved, naming the new id, and nothing is filed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The copy-action documentation returned by describe incorrectly says “Copys a recording” instead of “Copy a recording.”
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Fix incorrect “Copys” spelling in describe documentation
internal/mcpserver/filing.go:164
Minor spelling fix: for the copy action, verb + "s" produces “Copys a recording” in the documentation returned by describe. Using the imperative form (“Move a recording” / “Copy a recording”) avoids the incorrect inflection.
Copilot, recurring events read as an occurrence: fixed in a739380 (thread resolved).
Copilot, untyped failures from reads before filing: fixed in a739380 (thread resolved).
Copilot, "Copys" in the copy action's describe doc (review body, no thread): fixed in 5637b0d.
Independent review (gpt-6.1-sol): found that a to-do's ambiguity listed To-dos tools instead of lists (fixed before opening), and that a retried move could file a redirected recording again (fixed in e846a39 as recording_moved). The last two rounds reported no findings.
CI is green and Copilot approved this head. No thread is open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Agents on
basecamp mcpcould only start a move or copy withmove_recordingorcopy_recording. They then had to pollget_filingand look up the new recording themselves. The CLI's own move and copy commands already wait and answer with the new recording. This change gives the MCP server the same behavior through basecamp-sdk v0.32.0'sMoveAndWaitandCopyAndWait.What changed
basecamp_recordings moveandcopytake a recording id and type and a destination. The destination isproject_id(its one enabled tool of the recording's kind, or its one to-do list) orcontainer_id, withfolder_idfor Docs & Files andinclude_commentsfor a copy. Each call starts the filing once, waits up to a minute, and returns the new recording withsource_id.error.typeanderror.retry_safe. A project with the tool turned off names thetool_idto enable. A project with several candidates lists them forcontainer_id. A filing still running when the wait ends carries thefiling_idto check withget_filing. An id that now redirects to a moved recording is refused asrecording_moved, so a retried move never files the recording twice.move_recordingandcopy_recordingare removed.get_filingmoves frombasecamp_automationtobasecamp_recordings, so the check is always next to the actions that need it.Scope
basecamp_cards move_card(column moves and wormholes) uses a different endpoint and is unchanged.occurrence_id.Tradeoffs
recording_typeis required. Basecamp has no generic recording read, and the type selects the typed read that reports the parent, which tells a to-do list from a group and a folder from a whole Docs & Files.Blast Radius
A client that calls
move_recording,copy_recordingorbasecamp_automation get_filingnow gets an unknown-action error and must usebasecamp_recordings.basecamp_recordingsnow has write actions, so its tool-level read-only hint is off.--read-onlydropsmoveandcopyand keepssummarizeandget_filing.Verification
bin/cigreen on this head.TestFilingServesEveryTypeTheSDKFilesasksFilingContainerForabout every recordable type (bc3's models, the SDK's constants and every type name in the vendored model, with each as a parent) and drives a move and a copy through the MCP wire for each of the 14 routes it accepts. It fails when the SDK files a type this server cannot read or a container kind it cannot resolve. It fails withInbox::Forwardremoved from the read table, which was checked.tools/call basecamp_recordings) against a dev bc3 with a job worker. Each kind ran move within its project, move to another project, copy in place and copy to another project. Extra rows covered folder placement in another project,include_comments, three refusals (tool turned off, two candidate message boards, a folder passed as the container), a recurring event moved and copied to another project, and a retried move refused. An independent API read proved each row: the new id is in the expected project and container, a moved id answers 302, and a copied source is unchanged. Afiling_unsettledanswer was not provoked live, because bc3 finishes in under 2 seconds and the wait is a minute; unit tests cover it. The driver is in the first comment below.