Skip to main content

MCP Use Cases

These workflows show how an AI agent chains Copera MCP tools to get real work done. Because the server is stateless, every board/table/row tool needs explicit hex ObjectIds — so most flows start with discovery (list_boardslist_tablesget_table_schema) before reading or writing.

Each example shows the tool calls in order, with the key arguments. Tool results are JSON; the agent reads ids and values out of one result to feed the next call.

Find a board and read its rows

The most common pattern: locate a board by name, find a table, then read rows.

Find the board
list_boards({ query: "Roadmap" })
// → [{ id: "66ab…b01", name: "Q3 Roadmap", … }]
Find the table
list_tables({ boardId: "66ab…b01", query: "Features" })
// → [{ id: "66ab…t02", name: "Features", columns: [...] }]
Read the schema, then the rows
get_table_schema({ boardId: "66ab…b01", tableId: "66ab…t02" })
// → column ids + STATUS/DROPDOWN option ids

list_rows({ boardId: "66ab…b01", tableId: "66ab…t02", sort: "66ab…date:desc" })
// → rows with cell values keyed by columnId
Filter instead of scanning

list_rows is not paginated and can be large. Narrow it with query, a structured filter ({ match, conditions: [{ column_id, operator, value }] }), and sort rather than reading every row.

Update a row's status

Reading the schema first is required so you use the real columnId and a valid option id.

get_table_schema({ boardId, tableId })
// find the STATUS column id and the "Done" option id

update_row({
boardId,
tableId,
rowId: "66ab…r07",
columns: [{ columnId: "66ab…status", value: "66ab…doneOption" }],
})

To edit a row's long-text description or a RICH TEXT column cell, use set_row_markdown instead — update_row does not touch long text. Writes to markdown are queued (HTTP 202), so re-read with get_row_markdown to confirm.

Search docs and summarize

Pull the most relevant document for a keyword, fetch its body, and let the model summarize it.

Search
search_docs({ query: "onboarding checklist", limit: 5 })
// → ranked hits with highlights showing what matched
Read the top hit's content
get_doc_content({ docId: "66ab…d11" })
// → full markdown body (can be large — only fetch when you need it)
Summarize

The agent summarizes the returned markdown in its own response. To browse rather than search, use get_docs_tree to walk the hierarchy.

Capture a summary back into a new doc

Combine reading with writing — research across the workspace, then persist the result.

search({ query: "Q3 launch", types: ["document", "channelMessage"], limit: 20 })
// gather context across docs and chat

create_doc({ title: "Q3 Launch Summary", content: "# Summary\n\n…" })
// → { id: "66ab…d99" }

// append more later (async — re-read to confirm)
set_doc_content({ docId: "66ab…d99", content: "\n\n## Risks\n…", operation: "append" })

Post a channel message

Notify a channel, or direct-message a specific person. Provide exactly one of channelId or userId.

Find the channel (or the user)
list_channels({ query: "engineering", type: "text" })
// → [{ id: "66ab…c01", name: "engineering" }]

// or resolve a DM target:
list_workspace_members({ query: "alex@" })
// → [{ id: "66ab…u22", name: "Alex", email: "alex@…" }]
Send
// post to a channel (synchronous)
send_message({ channelId: "66ab…c01", message: "Deploy is green ✅" })

// or direct-message a user (queued, may not appear immediately)
send_message({ userId: "66ab…u22", message: "Can you review the PR?" })
Sender name override

The optional name (display-name override) is channel-only — it is rejected when sending a direct message.

Triage notifications

Read the inbox, mark items handled, and clear noise.

list_notifications()
// → { notifications: [...], unreadCount, count }

update_notification({ notificationId: "66ab…n05", status: "read" })

delete_notification({ notificationId: "66ab…n06" }) // no undo

Pagination uses notification ObjectIds as cursors: pass the oldest returned id as after to page back through history.

Comment on a row for a customer

Add an externally-visible comment to a board row — use external deliberately, only when people outside the workspace should see it.

list_row_comments({ boardId, tableId, rowId, visibility: "all" })
// review the thread (cursor-paginated via pageInfo.endCursor)

add_row_comment({
boardId,
tableId,
rowId,
content: "We shipped the fix in today's release.",
visibility: "external",
})

Export a view and save it to the drive

Render a table view to a file. For PDF/ZIP or large exports, prefer saveToDrive: true so the file lands in the drive instead of an inline payload.

// viewId comes from list_tables / get_table_schema
export_table({
boardId,
tableId,
viewId: "66ab…v01",
format: "PDF",
saveToDrive: true,
})
// → async job snapshot + a drive reference

// later, fetch the file
get_drive_download_url({ fileId: "66ab…f44" })
// → presigned CloudFront url (fetch directly, no auth)

Tips for reliable agent runs

  • Discover before you write. Call get_table_schema to get real columnIds and option ids before create_row / update_row / set_row_markdown — unsupported column types in create_row are silently ignored.
  • Re-read after async writes. set_row_markdown, set_doc_content, and direct messages are eventually consistent.
  • Keep request volume reasonable. The API rate-limits; 429s are retried automatically with backoff, but agents that fan out can still hit limits. Narrow searches and lists with query/filter/limit.
  • Mind the scopes. A 403 almost always means the token is missing a scope for that tool — see Authentication.

See also