Skip to content

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.
.mcp.json
{
"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 datawork schema (projects, epics, deliveries, tasks, business units, placements).

Find, create and edit projects.

list_projects

List all projects with optional filters.

List Projects

List all projects with optional filters.

ParameterTypeRequiredDescription
limitnumbernoMaximum number of projects to return (default: 50, max: 100)
offsetnumbernoNumber of projects to skip (for pagination)
business_unit_idstringnoFilter by business unit ID.
searchstringnoSearch by project name.

get_project

Get a specific project by ID with its epics.

Get Project

Get a specific project by ID with its epics.

ParameterTypeRequiredDescription
idstringyesThe UUID of the project.

create_project

Create a new project.

Create Project

Create a new project.

ParameterTypeRequiredDescription
namestringyesProject name.
business_unit_idstringnoBusiness unit ID.

update_project

Update an existing project.

Update Project

Update an existing project.

ParameterTypeRequiredDescription
idstringyesThe UUID of the project to update.
namestringnoProject name.
business_unit_idstringnoBusiness unit ID.

delete_project

Delete a project. This will fail if the project has associated epics.

Delete Project

Delete a project. This will fail if the project has associated epics.

ParameterTypeRequiredDescription
idstringyesThe 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 Epics

List all epics with optional filters.

ParameterTypeRequiredDescription
limitnumbernoMaximum number of epics to return (default: 50, max: 100)
offsetnumbernoNumber of epics to skip (for pagination)
project_idstringnoFilter by project ID.
statusenumnoFilter by status. One of: pending, in_progress, done, no_longer_needed.
searchstringnoSearch by epic name.

get_epic

Get a specific epic by ID with its deliveries and tasks.

Get Epic

Get a specific epic by ID with its deliveries and tasks.

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

ParameterTypeRequiredDescription
namestringyesEpic name.
project_idstringnoProject ID.
notesstringnoEpic notes/description.
statusenumnoEpic status. One of: pending, in_progress, done, no_longer_needed.
is_long_livedbooleannoWhether this is a long-lived epic.

update_epic

Update an existing epic.

Update Epic

Update an existing epic.

ParameterTypeRequiredDescription
idstringyesThe UUID of the epic to update.
namestringnoEpic name.
project_idstringnoProject ID.
notesstringnoEpic notes/description.
statusenumnoEpic status. One of: pending, in_progress, done, no_longer_needed.
is_long_livedbooleannoWhether this is a long-lived epic.

delete_epic

Delete an epic. This will fail if the epic has associated deliveries or tasks.

Delete Epic

Delete an epic. This will fail if the epic has associated deliveries or tasks.

ParameterTypeRequiredDescription
idstringyesThe UUID of the epic to delete.

The deliveries of an epic, with their tasks.

list_deliveries

List all deliveries with optional filters.

List Deliveries

List all deliveries with optional filters.

ParameterTypeRequiredDescription
limitnumbernoMaximum number of results (default 50)
offsetnumbernoNumber of results to skip.
epic_idstringnoFilter by epic ID.
owner_idstringnoFilter by owner (member) ID.
statusenumnoFilter by status. One of: pending, in_progress, completed, canceled, no_longer_needed.
searchstringnoSearch in name and notes.

get_delivery

Get a specific delivery by ID with its epic and tasks.

Get Delivery

Get a specific delivery by ID with its epic and tasks.

ParameterTypeRequiredDescription
idstringyesThe UUID of the delivery.

create_delivery

Create a new delivery.

Create Delivery

Create a new delivery.

ParameterTypeRequiredDescription
namestringyesDelivery name.
notesstringnoDelivery notes.
epic_idstringyesEpic ID (required)
owner_idstringnoOwner (member) ID.
statusenumnoDelivery status. One of: pending, in_progress, completed, canceled, no_longer_needed.
pricenumbernoDelivery price.
price_per_pointnumbernoPrice per story point.
total_pointsnumbernoTotal story points.
transaction_idstringnoTransaction ID.

update_delivery

Update an existing delivery.

Update Delivery

Update an existing delivery.

ParameterTypeRequiredDescription
idstringyesThe UUID of the delivery to update.
namestringnoDelivery name.
notesstringnoDelivery notes.
epic_idstringnoEpic ID.
owner_idstringnoOwner (member) ID.
statusenumnoDelivery status. One of: pending, in_progress, completed, canceled, no_longer_needed.
pricenumbernoDelivery price.
price_per_pointnumbernoPrice per story point.
total_pointsnumbernoTotal story points.
number_of_tasksnumbernoNumber of tasks.
number_of_completed_tasksnumbernoNumber of completed tasks.
transaction_idstringnoTransaction ID.

delete_delivery

Delete a delivery.

Delete Delivery

Delete a delivery.

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

ParameterTypeRequiredDescription
limitnumbernoMaximum number of tasks to return (default: 50, max: 100)
offsetnumbernoNumber of tasks to skip (for pagination)
epic_idstringnoFilter by epic ID.
delivery_idstringnoFilter by delivery ID.
owner_idstringnoFilter by owner (member) ID.
statusenumnoFilter by status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked.
searchstringnoSearch by task name.

get_task

Get a specific task by ID with its epic and delivery.

Get Task

Get a specific task by ID with its epic and delivery.

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

ParameterTypeRequiredDescription
namestringyesTask name.
descriptionstringnoTask description.
epic_idstringnoEpic ID.
delivery_idstringnoDelivery ID.
owner_idstringnoOwner (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.
statusenumnoTask status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked.
pointsnumbernoStory points.
prioritynumbernoPriority (lower = higher priority)
estimated_datestringnoEstimated completion date (ISO format)
attachmentsstring[]noArray of attachment URLs.
acceptance_criteriastring[]noWhat 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_idenumyesWhat 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.
contextobjectyesContext package: why the task exists, what was done, and the links.
actor_slugstringnoThe 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 Task

Update an existing task.

ParameterTypeRequiredDescription
idstringyesThe UUID of the task to update.
namestringnoTask name.
descriptionstringnoTask description.
epic_idstringnoEpic ID.
delivery_idstringnoDelivery ID.
owner_idstringnoOwner (member) ID.
statusenumnoTask status. One of: to_do, in_progress, dev_completed, done, no_longer_needed, blocked.
pointsnumbernoStory points.
prioritynumbernoPriority (lower = higher priority)
estimated_datestringnoEstimated completion date (ISO format)
attachmentsstring[]noArray of attachment URLs.
acceptance_criteriastring[]noWhat 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_idenumnoStage 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 Task

Delete a task.

ParameterTypeRequiredDescription
idstringyesThe 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 Name

Returns the suggested Git branch name for a task based on its identifier and name. Format: feat/DFL-XXXX-slug-of-name.

ParameterTypeRequiredDescription
idstringyesThe UUID of the task.

Find, create and edit business units.

list_business_units

List all business units with optional filters.

List Business Units

List all business units with optional filters. Requires finance/admin/owner role.

ParameterTypeRequiredDescription
limitnumbernoMaximum number of business units to return (default: 50, max: 100)
offsetnumbernoNumber of business units to skip (for pagination)
searchstringnoSearch by business unit name.
tagstringnoFilter by tag.

get_business_unit

Get a specific business unit by ID.

Get Business Unit

Get a specific business unit by ID. Requires finance/admin/owner role.

ParameterTypeRequiredDescription
idstringyesBusiness unit ID (UUID)

create_business_unit

Create a new business unit.

Create Business Unit

Create a new business unit. Requires finance/admin/owner role.

ParameterTypeRequiredDescription
namestringyesBusiness unit name.
profile_image_urlstringnoProfile image URL.
tagsstring[]noArray of tags.

update_business_unit

Update an existing business unit.

Update Business Unit

Update an existing business unit. Requires finance/admin/owner role.

ParameterTypeRequiredDescription
idstringyesBusiness unit ID (UUID)
namestringnoBusiness unit name.
profile_image_urlstringnoProfile image URL, null to remove.
tagsstring[]noArray of tags, null to remove.

delete_business_unit

Delete a business unit by ID.

Delete Business Unit

Delete a business unit by ID. Requires finance/admin/owner role.

ParameterTypeRequiredDescription
idstringyesBusiness unit ID (UUID)

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

ParameterTypeRequiredDescription
member_idstringyespublic.members.id of the fellow (resolve a name via lookup_member in the learn MCP). FK -> public.members(id).
companystringyesEmployer / company / org name (column: company).
rolestringyesRole / job title (column: role).
typeenumnoKind of placement. DB default: employed. (column: type / work.placement_type) One of: employed, freelance, founded, internship.
started_atstringyesStart date (ISO date, e.g. 2025-03-01). (column: started_at)
ended_atstringnoEnd date (ISO date); omit if still active. (column: ended_at)
placement_tierenumnoA = 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_recordenumnoConfidence 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_helpedenum[]noHow 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_promotedbooleannoTRUE 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_backgroundbooleannoWhether 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_reportbooleannoLGPD 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_urlstringnoEvidence link (LinkedIn, offer letter, contract photo, etc.). (column: evidence_url)
notesstringnoFree-form notes / context. (column: notes)
created_bystringnoauth.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 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.

ParameterTypeRequiredDescription
aliasstringyesThe raw label / speaker string to map (e.g. the exact raw_label in work.meeting_participants). (column: alias)
member_idstringyeswork.members.id the alias maps to (resolve a name via lookup_member in the learn MCP). FK -> work.members(id).
sourcestringnoOrigin of the mapping: manual | extractor | fireflies | gmail_signature | ... DB default: 'manual'. (column: source)
confidencenumbernoConfidence of the mapping, 0..1. DB default: 1.00. (column: confidence)
contextstringnoFree-text traceability note, e.g. "Tainan TG msg 6515" or "attendance audit 2026-06-14 — HIGH confidence". (column: context)

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

ParameterTypeRequiredDescription
canonical_meeting_idstringyeswork.meetings.id of the CANONICAL meeting that SURVIVES (the Fireflies-sourced row that usually holds the real transcriptions on its occurrences).
skeleton_meeting_idstringyeswork.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_runbooleannoWhen 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 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.

ParameterTypeRequiredDescription
meeting_idstringyeswork.meetings.id of the recurring series to update.
recurrence_start_timestringyesSeries start time-of-day, 24h "HH:MM" or "HH:MM:SS" (e.g. "12:30").
recurrence_end_timestringyesSeries 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 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".

ParameterTypeRequiredDescription
meeting_idstringyeswork.meetings.id of the meeting whose template to set.
templateenumyesThe UI template to set. One of: "default", "weekly_leaders". One of: default, weekly_leaders.

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

ParameterTypeRequiredDescription
created_afterstringnoOnly 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_beforestringnoOnly series created at or before this ISO-8601 timestamp.
title_containsstringnoCase-insensitive substring match on work.meetings.title.
fireflies_calendar_id_containsstringnoCase-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_occurrencesbooleannoWhen true, include the per-occurrence rows for each series (id, occurrence_date, status, has_transcript, fireflies_transcript_id). Never includes transcript text. Default: false.
limitnumbernoMaximum 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 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.

ParameterTypeRequiredDescription
series_meeting_idstringyeswork.meetings.id of the recurring SERIES PARENT to remove. Find it with list_meeting_series.
occurrence_policyenumnoHow 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_recurringbooleannoBy 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_runbooleannoWhen 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 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.

ParameterTypeRequiredDescription
occurrence_idstringyeswork.meeting_occurrences.id to promote. Find it with list_meeting_series({include_occurrences:true}).
title_overridestringnoTitle for the new standalone meeting. Defaults to the parent series' title (occurrences have no title of their own).
keep_occurrencebooleannoWhen 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_runbooleannoWhen true, NOTHING is written — returns the same result shape describing what WOULD happen. ALWAYS run dry_run:true first on prod. Default: false.

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

ParameterTypeRequiredDescription
member_idstringnopublic.members id; defaults to the caller.
daysnumbernoWindow in Brasília calendar days, 1–14. Default: 7.