Work
Project management: projects, epics, deliveries, tasks, business units, and member placement against the work schema.
| Endpoint | https://work.mcp.devfellowship.com/mcp |
|---|---|
| Tools | 35 in 9 groups |
| Package | packages/dfl-mcp-work |
| Auth |
Your dfl-auth login token as Authorization: Bearer <token>. Every call runs as
you, under RLS. See Auth & security.
|
{ "mcpServers": { "dfl-work": { "type": "http", "url": "https://work.mcp.devfellowship.com/mcp", "headers": { "Authorization": "Bearer YOUR_ACCESS_TOKEN" } } }}Other clients (Cursor, VS Code, codex, the Anthropic SDK): see Getting started, step 3.
| Backing data | work schema (projects, epics, deliveries, tasks, business units, placements). |
Projects
Section titled “Projects”Find, create and edit projects.
list_projects
List all projects with optional filters.
list_projectsList Projects
List all projects with optional filters.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum number of projects to return (default: 50, max: 100) |
offset | number | no | Number of projects to skip (for pagination) |
business_unit_id | string | no | Filter by business unit ID. |
search | string | no | Search by project name. |
get_project
Get a specific project by ID with its epics.
get_projectGet Project
Get a specific project by ID with its epics.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the project. |
create_project
Create a new project.
create_projectCreate Project
Create a new project.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Project name. |
business_unit_id | string | no | Business unit ID. |
update_project
Update an existing project.
update_projectUpdate Project
Update an existing project.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the project to update. |
name | string | no | Project name. |
business_unit_id | string | no | Business unit ID. |
delete_project
Delete a project. This will fail if the project has associated epics.
delete_projectDelete Project
Delete a project. This will fail if the project has associated epics.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the project to delete. |
The epics of a project, with their deliveries and tasks.
list_epics
List all epics with optional filters.
list_epicsList Epics
List all epics with optional filters.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum number of epics to return (default: 50, max: 100) |
offset | number | no | Number of epics to skip (for pagination) |
project_id | string | no | Filter by project ID. |
status | enum | no | Filter by status. One of: pending, in_progress, done, no_longer_needed. |
search | string | no | Search by epic name. |
get_epic
Get a specific epic by ID with its deliveries and tasks.
get_epicGet Epic
Get a specific epic by ID with its deliveries and tasks.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the epic. |
create_epic
Create a new epic. 🔴 AN EPIC CANNOT BE SHOWN ON A PLAN — do not tell anyone you linked one.
create_epicCreate Epic
Create a new epic. 🔴 AN EPIC CANNOT BE SHOWN ON A PLAN — do not tell anyone you linked one. The plans-app renders bound TASKS only (it reads work.entity_connections filtered to target_type='task'), so an epic connection would be a row nothing displays. What to do instead: create the epic here, create its tasks with create_task, then bind THOSE tasks to the plan with set_plan_tasks on the PLANS MCP (plans.mcp.devfellowship.com) — that is what makes the work visible on the plan. Name the epic in the plan body if a reader needs to know which epic holds the tasks.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Epic name. |
project_id | string | no | Project ID. |
notes | string | no | Epic notes/description. |
status | enum | no | Epic status. One of: pending, in_progress, done, no_longer_needed. |
is_long_lived | boolean | no | Whether this is a long-lived epic. |
update_epic
Update an existing epic.
update_epicUpdate Epic
Update an existing epic.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the epic to update. |
name | string | no | Epic name. |
project_id | string | no | Project ID. |
notes | string | no | Epic notes/description. |
status | enum | no | Epic status. One of: pending, in_progress, done, no_longer_needed. |
is_long_lived | boolean | no | Whether this is a long-lived epic. |
delete_epic
Delete an epic. This will fail if the epic has associated deliveries or tasks.
delete_epicDelete Epic
Delete an epic. This will fail if the epic has associated deliveries or tasks.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the epic to delete. |
Deliveries
Section titled “Deliveries”The deliveries of an epic, with their tasks.
list_deliveries
List all deliveries with optional filters.
list_deliveriesList Deliveries
List all deliveries with optional filters.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum number of results (default 50) |
offset | number | no | Number of results to skip. |
epic_id | string | no | Filter by epic ID. |
owner_id | string | no | Filter by owner (member) ID. |
status | enum | no | Filter by status. One of: pending, in_progress, completed, canceled, no_longer_needed. |
search | string | no | Search in name and notes. |
get_delivery
Get a specific delivery by ID with its epic and tasks.
get_deliveryGet Delivery
Get a specific delivery by ID with its epic and tasks.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the delivery. |
create_delivery
Create a new delivery.
create_deliveryCreate Delivery
Create a new delivery.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Delivery name. |
notes | string | no | Delivery notes. |
epic_id | string | yes | Epic ID (required) |
owner_id | string | no | Owner (member) ID. |
status | enum | no | Delivery status. One of: pending, in_progress, completed, canceled, no_longer_needed. |
price | number | no | Delivery price. |
price_per_point | number | no | Price per story point. |
total_points | number | no | Total story points. |
transaction_id | string | no | Transaction ID. |
update_delivery
Update an existing delivery.
update_deliveryUpdate Delivery
Update an existing delivery.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the delivery to update. |
name | string | no | Delivery name. |
notes | string | no | Delivery notes. |
epic_id | string | no | Epic ID. |
owner_id | string | no | Owner (member) ID. |
status | enum | no | Delivery status. One of: pending, in_progress, completed, canceled, no_longer_needed. |
price | number | no | Delivery price. |
price_per_point | number | no | Price per story point. |
total_points | number | no | Total story points. |
number_of_tasks | number | no | Number of tasks. |
number_of_completed_tasks | number | no | Number of completed tasks. |
transaction_id | string | no | Transaction ID. |
delete_delivery
Delete a delivery.
delete_deliveryDelete Delivery
Delete a delivery.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the delivery to delete. |
Find tasks by owner, status, epic or delivery; each task carries its estimated date, so you can see who is late and what is overdue. Get the Git branch name of a task.
list_tasks
List all tasks with optional filters.
list_tasksList Tasks
List all tasks with optional filters. Each task row carries its owner_id, status and estimated_date (the planned completion date). To answer "who is late" or "which tasks are overdue", list the open tasks (status not done / no_longer_needed) and keep the rows whose estimated_date is before today. There is no overdue filter: compare the dates yourself.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum number of tasks to return (default: 50, max: 100) |
offset | number | no | Number of tasks to skip (for pagination) |
epic_id | string | no | Filter by epic ID. |
delivery_id | string | no | Filter by delivery ID. |
owner_id | string | no | Filter by owner (member) ID. |
status | enum | no | Filter by status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked. |
search | string | no | Search by task name. |
get_task
Get a specific task by ID with its epic and delivery.
get_taskGet Task
Get a specific task by ID with its epic and delivery.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the task. |
create_task
Create a new task. Requires a context package (why/what) so the task is readable by whoever was not in the conversation it came from.
create_taskCreate Task
Create a new task. Requires a context package (why/what) so the task is readable by whoever was not in the conversation it came from. 🔴 IF THIS TASK BELONGS TO A PLAN, CREATING IT IS ONLY HALF THE JOB. This tool writes work.tasks and NOTHING ELSE — it does not attach the task to a plan, and no tool on this server does. A plan renders its execution checklist from work.entity_connections, so the task stays invisible to the plan until you call set_plan_tasks on the PLANS MCP (plans.mcp.devfellowship.com) with the plan slug and EVERY task id the plan should show: set_plan_tasks({ slug, tasks: [{ task_id }, ...] }). It REPLACES the plan's task set, so pass the full desired list, not just the new ids. The failure mode is silent and has already happened: 18 tasks created under an epic for a plan, reported as linked, and the plan page showed nothing (2026-08-12). A created task is a complete, valid row on its own, so there is no error to notice — the only signal is an empty rail on the plan, which reads as "no work started". Verify by reading the plan back and seeing your tasks in its links.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Task name. |
description | string | no | Task description. |
epic_id | string | no | Epic ID. |
delivery_id | string | no | Delivery ID. |
owner_id | string | no | Owner (public.members.id). Omitir atribui a task a quem chamou a tool. Uma task sem dono some do board, entao nao existe caminho que grave null aqui. |
status | enum | no | Task status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked. |
points | number | no | Story points. |
priority | number | no | Priority (lower = higher priority) |
estimated_date | string | no | Estimated completion date (ISO format) |
attachments | string[] | no | Array of attachment URLs. |
acceptance_criteria | string[] | no | What has to be true for this task to be done, ONE criterion per array entry. The column is text[], not text — do not pass a single blob with numbered lines, because nothing can then render or check a criterion on its own. Write each entry so it can be verified without asking the author: name the command, the endpoint, or the observation that settles it. |
stage_id | enum | yes | What kind of work this is. Required: a task without a stage is invisible on the kanban board, and 1160 of them accumulated that way while this was optional. This is a DIFFERENT AXIS from status — pick where the work starts, not how far along it is. design (board: Ideation) for exploring references and alternatives, decision (board: Design Review) for a proposal waiting on someone to call it, qa (board: QA / Test) for finish work like copy and edge states, spec for shaping the requirement, execution for work ready to be built, review (board: QA / Test) for final validation of work already built. qa and review share the QA / Test column and are told apart by a badge on the card: qa shows Design, review shows Engineering. One of: spec, design, execution, review, decision, qa. |
context | object | yes | Context package: why the task exists, what was done, and the links. |
actor_slug | string | no | The agent/service slug that DID this work (e.g. 'claude-main'). ABSENCE = human task (owner is the calling member, no actor attribution). PRESENT = the task is attributed to that actor via public.actor_links; the slug MUST resolve to an existing actor or the call is REJECTED and nothing is written. |
update_task
Update an existing task.
update_taskUpdate Task
Update an existing task.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the task to update. |
name | string | no | Task name. |
description | string | no | Task description. |
epic_id | string | no | Epic ID. |
delivery_id | string | no | Delivery ID. |
owner_id | string | no | Owner (member) ID. |
status | enum | no | Task status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked. |
points | number | no | Story points. |
priority | number | no | Priority (lower = higher priority) |
estimated_date | string | no | Estimated completion date (ISO format) |
attachments | string[] | no | Array of attachment URLs. |
acceptance_criteria | string[] | no | What has to be true for this task to be done, ONE criterion per array entry. REPLACES the whole list — send every criterion you want to keep, not only the new one. The column is text[], not text. |
stage_id | enum | no | Stage ID (a task without one is invisible on the kanban board) One of: spec, design, execution, review, decision, qa. |
delete_task
Delete a task.
delete_taskDelete Task
Delete a task.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the task to delete. |
get_task_branch_name
Returns the suggested Git branch name for a task based on its identifier and name.
get_task_branch_nameGet Task Branch Name
Returns the suggested Git branch name for a task based on its identifier and name. Format: feat/DFL-XXXX-slug-of-name.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the task. |
Business Units
Section titled “Business Units”Find, create and edit business units.
list_business_units
List all business units with optional filters.
list_business_unitsList Business Units
List all business units with optional filters. Requires finance/admin/owner role.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | no | Maximum number of business units to return (default: 50, max: 100) |
offset | number | no | Number of business units to skip (for pagination) |
search | string | no | Search by business unit name. |
tag | string | no | Filter by tag. |
get_business_unit
Get a specific business unit by ID.
get_business_unitGet Business Unit
Get a specific business unit by ID. Requires finance/admin/owner role.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Business unit ID (UUID) |
create_business_unit
Create a new business unit.
create_business_unitCreate Business Unit
Create a new business unit. Requires finance/admin/owner role.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Business unit name. |
profile_image_url | string | no | Profile image URL. |
tags | string[] | no | Array of tags. |
update_business_unit
Update an existing business unit.
update_business_unitUpdate Business Unit
Update an existing business unit. Requires finance/admin/owner role.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Business unit ID (UUID) |
name | string | no | Business unit name. |
profile_image_url | string | no | Profile image URL, null to remove. |
tags | string[] | no | Array of tags, null to remove. |
delete_business_unit
Delete a business unit by ID.
delete_business_unitDelete Business Unit
Delete a business unit by ID. Requires finance/admin/owner role.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | Business unit ID (UUID) |
Placements
Section titled “Placements”Record where a fellow got placed (job, freelance, internal), and map a speaker name to a member.
create_placement
Record a DFL fellow placement (job/freelance/internal) in work.placements.
create_placementCreate Placement
Record a DFL fellow placement (job/freelance/internal) in work.placements. Tier A = external employment (vaga em empresa terceira). Tier B = external paid freelance/project. Tier C = internal DFL/Revera paid project. Provide member_id directly, or resolve a fellow name to its public.members.id with lookup_member (in the learn MCP) first. All fields (incl. reason_we_helped, is_internal_hire_promoted, had_prior_tech_background, consent_to_report) are applied in the current prod schema. Runs under the caller JWT — RLS enforces global-admin write access.
| Parameter | Type | Required | Description |
|---|---|---|---|
member_id | string | yes | public.members.id of the fellow (resolve a name via lookup_member in the learn MCP). FK -> public.members(id). |
company | string | yes | Employer / company / org name (column: company). |
role | string | yes | Role / job title (column: role). |
type | enum | no | Kind of placement. DB default: employed. (column: type / work.placement_type) One of: employed, freelance, founded, internship. |
started_at | string | yes | Start date (ISO date, e.g. 2025-03-01). (column: started_at) |
ended_at | string | no | End date (ISO date); omit if still active. (column: ended_at) |
placement_tier | enum | no | A = external employment; B = external paid freelance; C = internal DFL/Revera paid project. Optional (nullable in schema), but should be set for the UNICEF-headline slicing. One of: A, B, C. |
source_of_record | enum | no | Confidence of the record source (low→high). Defaults to self_report (DB default). (column: source_of_record / work.placement_source) One of: self_report, mentor_confirmed, contract_doc, employer_confirmed. |
reason_we_helped | enum[] | no | How DFL helped the fellow land this placement (enum array). ⚠️ Requires pending migration dfl-schema #397 — omit until applied or the insert will error. |
is_internal_hire_promoted | boolean | no | TRUE for the Samuel/William case: DFL hired a non-dev as a full-time dev. (column: is_internal_hire_promoted; NOT NULL default false) |
had_prior_tech_background | boolean | no | Whether the fellow already had a tech background before this placement. FALSE for internal hires who "não eram da área" (Samuel/William). (column: had_prior_tech_background; nullable) |
consent_to_report | boolean | no | LGPD consent to use this placement in external/UNICEF reports. Defaults to false (DB default) — rows exist but are excluded from external reports until consent is recorded. (column: consent_to_report; NOT NULL default false) |
evidence_url | string | no | Evidence link (LinkedIn, offer letter, contract photo, etc.). (column: evidence_url) |
notes | string | no | Free-form notes / context. (column: notes) |
created_by | string | no | auth.users.id of who logged this record (FK -> auth.users.id). Optional. (column: created_by) |
create_member_label_alias
Map a raw speaker/label string to a member by upserting into work.member_label_aliases.
create_member_label_aliasCreate Member Label Alias
Map a raw speaker/label string to a member by upserting into work.member_label_aliases. Used to resolve attendance/meeting_participants raw_label values (transcript spellings, first-name-only, etc.) back to a fellow. Idempotent: upserts on the UNIQUE (alias, member_id) constraint, so re-running the same mapping is a no-op (refreshes source/confidence/context). Provide member_id directly, or resolve a fellow name to its id with lookup_member (in the learn MCP) first. Match the alias to the EXACT raw_label string used in work.meeting_participants.
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | yes | The raw label / speaker string to map (e.g. the exact raw_label in work.meeting_participants). (column: alias) |
member_id | string | yes | work.members.id the alias maps to (resolve a name via lookup_member in the learn MCP). FK -> work.members(id). |
source | string | no | Origin of the mapping: manual | extractor | fireflies | gmail_signature | ... DB default: 'manual'. (column: source) |
confidence | number | no | Confidence of the mapping, 0..1. DB default: 1.00. (column: confidence) |
context | string | no | Free-text traceability note, e.g. "Tainan TG msg 6515" or "attendance audit 2026-06-14 — HIGH confidence". (column: context) |
Meetings
Section titled “Meetings”Fix recurring meetings: merge duplicates, set their time window and their template.
merge_meetings
Dedup a duplicate recurring-meeting cluster by MERGING a skeleton meeting into a canonical meeting, then removing the skeleton.
merge_meetingsMerge Duplicate Recurring Meetings
Dedup a duplicate recurring-meeting cluster by MERGING a skeleton meeting into a canonical meeting, then removing the skeleton. LOSSLESS on duplicate-date collisions: it NEVER drops a transcription-bearing occurrence in favor of an empty one. For a date both meetings have, it compares transcripts on BOTH sides — if the canonical occurrence is EMPTY but the skeleton one carries a real transcript, it RE-PARENTS the skeleton occurrence onto the canonical and DELETES the empty canonical duplicate (the real transcript survives). If BOTH carry transcripts it reports a CONFLICT, keeps both, and does NOT remove the skeleton row (manual review needed). Empty skeleton duplicates of a transcript-bearing (or empty) canonical date are dropped; skeleton occurrences on a NEW date are re-parented. Other meeting_id child rows (participants/concepts/expertise/tools/underlines) are re-parented too, guarding the meeting_participants UNIQUE constraint. The skeleton meetings row is removed only when there are ZERO unresolved conflicts. Transcriptions live per-occurrence so nothing is concatenated. It also PRESERVES the work.meetings.template field: if the canonical is null/"default" and the skeleton carries a non-default template (e.g. "weekly_leaders"), the canonical is updated to the skeleton's template (carry the more-specific value forward; never downgrade). ALWAYS run with dry_run:true first on prod data to preview the change.
| Parameter | Type | Required | Description |
|---|---|---|---|
canonical_meeting_id | string | yes | work.meetings.id of the CANONICAL meeting that SURVIVES (the Fireflies-sourced row that usually holds the real transcriptions on its occurrences). |
skeleton_meeting_id | string | yes | work.meetings.id of the SKELETON meeting to merge in and then REMOVE (the empty Google-Calendar-invite row, e.g. "Updated invitation: …" or a title-variant with no cal-id). If a duplicate-date collision is resolved in the skeleton's favor (its occurrence carries the real transcript), that occurrence is re-parented onto the canonical before the skeleton is removed. |
dry_run | boolean | no | When true (recommended first), report what WOULD change without writing anything. Default: false. |
set_meeting_recurrence_time
Set the time-of-day window (recurrence_start_time / recurrence_end_time) on a single recurring meeting series in work.meetings, so its recurring occurrences render at the right hour on the /meetings time-grid.
set_meeting_recurrence_timeSet Meeting Recurrence Time Window
Set the time-of-day window (recurrence_start_time / recurrence_end_time) on a single recurring meeting series in work.meetings, so its recurring occurrences render at the right hour on the /meetings time-grid. Times are 24h wall-clock "HH:MM" or "HH:MM:SS" (Postgres time). The meeting must already be a recurring series (is_recurring = true); recurrence_day_of_week is set separately. Returns the updated row.
| Parameter | Type | Required | Description |
|---|---|---|---|
meeting_id | string | yes | work.meetings.id of the recurring series to update. |
recurrence_start_time | string | yes | Series start time-of-day, 24h "HH:MM" or "HH:MM:SS" (e.g. "12:30"). |
recurrence_end_time | string | yes | Series end time-of-day, 24h "HH:MM" or "HH:MM:SS" (e.g. "13:30"). |
set_meeting_template
Set the UI template (work.meetings.template) on a single meeting in work.meetings.
set_meeting_templateSet Meeting Template
Set the UI template (work.meetings.template) on a single meeting in work.meetings. The template selects which structured-underline UI the dfl-learn /meetings view renders — the weekly-leaders structured form is gated on template = "weekly_leaders". Allowed values: "default" | "weekly_leaders" (mirrors the dfl-learn UnderlineTemplate enum). Unknown values are rejected. Returns the updated row (id, title, template). Primary use: restore a meeting's template after a dedup-merge collapsed it to "default".
| Parameter | Type | Required | Description |
|---|---|---|---|
meeting_id | string | yes | work.meetings.id of the meeting whose template to set. |
template | enum | yes | The UI template to set. One of: "default", "weekly_leaders". One of: default, weekly_leaders. |
Meeting series data-ops
Section titled “Meeting series data-ops”Clean up recurring meeting series without losing their transcripts.
list_meeting_series
READ-ONLY discovery of recurring meeting SERIES PARENTS in work.meetings (is_recurring = true), so an operator can find duplicate/dedup targets without psql.
list_meeting_seriesList Recurring Meeting Series
READ-ONLY discovery of recurring meeting SERIES PARENTS in work.meetings (is_recurring = true), so an operator can find duplicate/dedup targets without psql. Filter by creation window (created_after / created_before), title substring, or fireflies_calendar_id substring. Each result carries the series id, title, fireflies_calendar_id, meeting_date, created_at and an occurrence accounting: occurrence_count, occurrences_with_transcript (real transcript TEXT) and occurrences_with_transcript_id (a Fireflies transcript id). Set include_occurrences:true to also get the per-occurrence rows (id, occurrence_date, status, has_transcript, fireflies_transcript_id). Transcript TEXT is NEVER returned — it is tens/hundreds of KB per occurrence. Results are sorted by created_at ascending. Use this before remove_meeting_series to confirm exactly which series you are about to remove and how much real content hangs off it.
| Parameter | Type | Required | Description |
|---|---|---|---|
created_after | string | no | Only series created at or after this ISO-8601 timestamp (e.g. "2026-07-28T00:00:00Z"). The canonical way to answer "which series were created today?". |
created_before | string | no | Only series created at or before this ISO-8601 timestamp. |
title_contains | string | no | Case-insensitive substring match on work.meetings.title. |
fireflies_calendar_id_contains | string | no | Case-insensitive substring match on fireflies_calendar_id. Useful to group the "_R<instance>" forks Google mints for the same base calendar id (e.g. pass the base id without the suffix). |
include_occurrences | boolean | no | When true, include the per-occurrence rows for each series (id, occurrence_date, status, has_transcript, fireflies_transcript_id). Never includes transcript text. Default: false. |
limit | number | no | Maximum number of series to return. Default: 100. |
remove_meeting_series
Remove ONE recurring meeting SERIES PARENT from work.meetings, promoting its occurrences to standalone ("avulsa") meetings FIRST so the ON DELETE CASCADE can never destroy a transcript.
remove_meeting_seriesRemove Recurring Meeting Series (data-ops)
Remove ONE recurring meeting SERIES PARENT from work.meetings, promoting its occurrences to standalone ("avulsa") meetings FIRST so the ON DELETE CASCADE can never destroy a transcript. Transcripts live PER-OCCURRENCE and all 7 FKs onto work.meetings(id) are ON DELETE CASCADE, so the order promote -> verify -> delete IS the safety mechanism (an MCP tool cannot hold a Postgres transaction across calls). Guards, all of which ABORT rather than guess: (a) UNTRACKABLE — any occurrence with transcript text but no fireflies_transcript_id; (b) ALREADY-PROMOTED — occurrences whose transcript id already exists on a meetings row are skipped, not re-inserted (partial UNIQUE uq_meetings_fireflies_transcript_id), which also makes the tool idempotent on retry; (c) CASCADE — refuses when meeting_participants / meeting_concepts / meeting_expertise / meeting_tools / meeting_transcript_segments / meeting_underlines have rows on this parent or its occurrences (use merge_meetings instead, it knows how to re-parent those); (d) a post-promotion ASSERT that every surviving occurrence has a standalone counterpart before anything is deleted. Promoted rows get is_recurring:false and fireflies_calendar_id:null (deliberately detached from any series), the parent's title, the occurrence's content, and a meeting_date rebuilt from the occurrence date plus the parent's time-of-day. SIDE EFFECT: work.meetings has an AFTER INSERT trigger (meetings_to_n8n) that POSTs each new row to the n8n process_devfellowship_meetings webhook; a user-JWT tool cannot disable it, so every promotion fires one n8n workflow — the count is reported as n8n_webhook_inserts. ALWAYS run with dry_run:true first on prod: it returns the exact same result shape with nothing written.
| Parameter | Type | Required | Description |
|---|---|---|---|
series_meeting_id | string | yes | work.meetings.id of the recurring SERIES PARENT to remove. Find it with list_meeting_series. |
occurrence_policy | enum | no | How to treat the series occurrences. "promote_real_discard_empty" (DEFAULT): occurrences carrying transcript TEXT or a fireflies_transcript_id are PROMOTED to standalone meetings; occurrences with neither (empty materializer placeholders, typically future-dated) are DELETED — recreating those would only manufacture junk rows. "promote_all": every occurrence is promoted, even the empty placeholders. "require_empty": pure safety mode — refuse to do anything unless the series has ZERO occurrences. One of: promote_real_discard_empty, promote_all, require_empty. |
allow_non_recurring | boolean | no | By default the tool REFUSES a meeting with is_recurring = false, so it can never be pointed at a standalone ("avulsa") meeting by mistake. Set true to override deliberately. Default: false. |
dry_run | boolean | no | When true, NOTHING is written — returns the exact same result shape describing what WOULD happen (including n8n_webhook_inserts). ALWAYS run dry_run:true first on prod. Default: false. |
promote_occurrence_to_standalone
Promote ONE work.meeting_occurrences row to a standalone ("avulsa") work.meetings row and then detach/delete the occurrence.
promote_occurrence_to_standalonePromote Meeting Occurrence to Standalone Meeting
Promote ONE work.meeting_occurrences row to a standalone ("avulsa") work.meetings row and then detach/delete the occurrence. The generic primitive underneath remove_meeting_series; use it to pull a single meeting out of a recurring series. The new meeting gets is_recurring:false, fireflies_calendar_id:null (deliberately detached), the parent series' title (or title_override), the occurrence's content, and a meeting_date rebuilt from the occurrence date plus the parent's time-of-day. Guards: it refuses an occurrence carrying transcript text with NO fireflies_transcript_id unless keep_occurrence:true (the copy could not be verified by transcript id, so deleting the source would be unsound); it never mints a second meetings row for a transcript id that already exists (partial UNIQUE uq_meetings_fireflies_transcript_id) and reports the existing meeting instead, which makes it idempotent; and it refuses when meeting_participants / meeting_underlines rows FK on this occurrence, since those are ON DELETE CASCADE and would be destroyed. Order is the safety mechanism: INSERT and confirm, then delete. SIDE EFFECT: the meetings_to_n8n AFTER INSERT trigger POSTs each new row to the n8n process_devfellowship_meetings webhook and a user-JWT tool cannot disable it, so one n8n workflow fires per promotion (reported as n8n_webhook_inserts). Run dry_run:true first on prod.
| Parameter | Type | Required | Description |
|---|---|---|---|
occurrence_id | string | yes | work.meeting_occurrences.id to promote. Find it with list_meeting_series({include_occurrences:true}). |
title_override | string | no | Title for the new standalone meeting. Defaults to the parent series' title (occurrences have no title of their own). |
keep_occurrence | boolean | no | When true, create the standalone meeting but LEAVE the occurrence in place — a copy-then-review flow where nothing is destroyed. Also the escape hatch for an occurrence whose transcript has no fireflies_transcript_id, or one that still has participant/underline rows attached. Default: false (the occurrence is deleted after the copy is confirmed). |
dry_run | boolean | no | When true, NOTHING is written — returns the same result shape describing what WOULD happen. ALWAYS run dry_run:true first on prod. Default: false. |
Weekly updates
Section titled “Weekly updates”Read-only. What one member did in the last N days, per source, with a daily text ready to paste. Mirrors the Weekly updates page of dfl-learn (/weekly-updates).
What one person did in the last days, ready to paste as a daily update.
get_weekly_updates
Read-only. What one member did in the last N days (Brasília calendar days, today included): tasks moved to done/dev_completed, tasks in progress, merged PRs (review_request.review_requests), Core Daily lines they said…
get_weekly_updatesGet weekly updates for a member
Read-only. What one member did in the last N days (Brasília calendar days, today included): tasks moved to done/dev_completed, tasks in progress, merged PRs (review_request.review_requests), Core Daily lines they said (resolved per meeting: segment attribution, then the top alias, then the exact name; any ambiguity leaves the line out), and plans whose status changed (plans app, with your session). Each source degrades on its own: see unavailable and truncated. daily_text is ready to paste in the "Bom dia / Ontem / Hoje / No blockers" format, one line per item, with no PR numbers, repos, task identifiers or links. Defaults to you when member_id is omitted.
| Parameter | Type | Required | Description |
|---|---|---|---|
member_id | string | no | public.members id; defaults to the caller. |
days | number | no | Window in Brasília calendar days, 1–14. Default: 7. |