Skip to content

Strategy

Create and update Business Model Canvas data in strategy.* — business units, the 9 canvas blocks, typed business-unit relationships, assumptions, provenance sources, draft canvas snapshots, competitors and personas.

Endpoint https://strategy.mcp.devfellowship.com/mcp
Tools 25 in 9 groups
Package packages/dfl-mcp-strategy
Auth Your dfl-auth login token as Authorization: Bearer <token>. Every call runs as you, under RLS. See Auth & security.
.mcp.json
{
"mcpServers": {
"dfl-strategy": {
"type": "http",
"url": "https://strategy.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 datastrategy.business_units, .value_propositions, .customer_segments, .customer_relationships, .channels, .revenue_streams, .key_partners, .key_activities, .resources, .expense_categories, .business_unit_relationships, .assumptions, .sources, .artifact_sources, .canvas_snapshots, .competitors, .entities, .personas.

These tools manage the canvas record of a business unit (strategy.business_units). The business unit itself (public.business_units: name, tags, image) belongs to the work host — list_business_units and its family there. A canvas row links to it through business_unit_id. The *_canvas_business_unit names replace the old *_business_unit names on this host, which collided with the work tools.

The Business Model Canvas record of each business unit: list, create, edit, archive.

list_canvas_business_units

List strategy.business_units rows — the Business Model Canvas record of a business unit.

List Canvas Business Units

List strategy.business_units rows — the Business Model Canvas record of a business unit. Not the work business unit: that is public.business_units on the work host (list_business_units there). By default excludes archived units — pass include_archived: true to see everything. business_unit_id on the row links the canvas to its public.business_units row (the work-host business unit).

ParameterTypeRequiredDescription
include_archivedbooleannoInclude is_archived=true rows. Default: false.
parent_business_unit_idstringnoFilter to the canvases linked to this public.business_units.id (the work-host business unit; column business_unit_id on the row)
limitnumbernoDefault: 50.

get_canvas_business_unit

Fetch one strategy.business_units row (the canvas record of a business unit) by id.

Get Canvas Business Unit

Fetch one strategy.business_units row (the canvas record of a business unit) by id. Not the work business unit: that is public.business_units on the work host (list_business_units there).

ParameterTypeRequiredDescription
idstringyesstrategy.business_units.id.

create_canvas_business_unit

Create a strategy.business_units row — the root entity every BM Canvas block (value propositions, customer segments, channels, etc.) hangs off of via business_unit_id.

Create Canvas Business Unit

Create a strategy.business_units row — the root entity every BM Canvas block (value propositions, customer segments, channels, etc.) hangs off of via business_unit_id. golden_circle_why/how/what are free-form jsonb (Simon Sinek framing). Not the work business unit: that is public.business_units on the work host (list_business_units there).

ParameterTypeRequiredDescription
namestringyesBusiness unit name.
descriptionstringno—
sectorstringno—
tagsstringno—
colorstringnoHex or CSS color used by the canvas UI.
logo_urlstringno—
business_unit_idstringnopublic.business_units.id (the work-host business unit) this canvas belongs to. FK, ON DELETE CASCADE.
golden_circle_whyobjectno—
golden_circle_howobjectno—
golden_circle_whatobjectno—

update_canvas_business_unit

Partially update a strategy.business_units row (the canvas record of a business unit) by id.

Update Canvas Business Unit

Partially update a strategy.business_units row (the canvas record of a business unit) by id. Only the fields provided are changed. Not the work business unit: that is public.business_units on the work host (list_business_units there).

ParameterTypeRequiredDescription
idstringyesstrategy.business_units.id.
namestringno—
descriptionstringno—
sectorstringno—
tagsstringno—
colorstringno—
logo_urlstringno—
business_unit_idstringnopublic.business_units.id (the work-host business unit) this canvas belongs to. FK, ON DELETE CASCADE.
golden_circle_whyobjectno—
golden_circle_howobjectno—
golden_circle_whatobjectno—

archive_canvas_business_unit

Set strategy.business_units.is_archived on a row (default true — archive).

Archive Canvas Business Unit

Set strategy.business_units.is_archived on a row (default true — archive). Pass archived: false to unarchive. This is a soft flag, not a delete — child block rows (value propositions, segments, etc.) are left untouched.

ParameterTypeRequiredDescription
idstringyesstrategy.business_units.id.
archivedbooleannoDefault: true.

Write one of the nine canvas blocks, for example value propositions or customer segments.

set_canvas_block

Upsert a row into one of the 9 Business Model Canvas block tables for a business_unit: value_proposition, customer_segment, customer_relationship, channel, revenue_stream, key_partner, key_activity, key_resource…

Set Canvas Block

Upsert a row into one of the 9 Business Model Canvas block tables for a business_unit: value_proposition, customer_segment, customer_relationship, channel, revenue_stream, key_partner, key_activity, key_resource, expense_category. Pass "id" to update an existing row (scoped to business_unit_id), or omit it to insert a new row. "fields" is passed through to the underlying table as-is — column names must match the table (e.g. value_proposition needs "name"; customer_relationship needs "name" + "type"; key_partner accepts free-text "name" + "description"; key_resource ("resources" table) accepts free-text "name" + "description" — both added 2026-07-15 via dfl-schema #683 to fix the "Unnamed Partner" display bug and let resources be modeled without a fixed entity/resource_category catalog FK). All 9 blocks are agent-writable end to end as of 2026-07-15 (RLS INSERT/UPDATE policies land on every block table).

ParameterTypeRequiredDescription
blockenumyesOne of: value_proposition, customer_segment, customer_relationship, channel, revenue_stream, key_partner, key_activity, key_resource, expense_category.
business_unit_idstringyesstrategy.business_units.id this block row belongs to.
idstringnoRow id to update. Omit to insert a new row.
fieldsobjectnoColumn name → value for the target table. Default: {}.

Link two business units, for example one feeds or sells to the other.

add_canvas_business_unit_relationship

Create a typed edge in strategy.business_unit_relationships between two business units (from_bu → to_bu), e.g. "the Fellowship BU funds the Studio BU" or "Itera commercializes Revera's methodology".

Add Canvas Business Unit Relationship

Create a typed edge in strategy.business_unit_relationships between two business units (from_bu → to_bu), e.g. "the Fellowship BU funds the Studio BU" or "Itera commercializes Revera's methodology". mechanics is free-form jsonb describing how the relationship actually works (revenue split, staffing %, etc).

ParameterTypeRequiredDescription
from_bustringyesstrategy.business_units.id — the source of the relationship.
to_bustringyesstrategy.business_units.id — the target of the relationship (must differ from from_bu)
relationship_typeenumyesOne of: funds, supplies_talent, commercializes, provides_methodology, shares_brand, incubates.
mechanicsobjectnoDefault: {}.
descriptionstringno—
is_activebooleannoDefault: true.
started_atstringnoISO date the relationship started.

Record a belief the strategy depends on, so it can be tested.

add_assumption

Create a strategy.assumptions row for a business_unit — a testable belief underpinning the strategy (desirability/viability/feasibility), its status, evidence gathered so far, and the risk if it turns out to be wrong.

Add Assumption

Create a strategy.assumptions row for a business_unit — a testable belief underpinning the strategy (desirability/viability/feasibility), its status, evidence gathered so far, and the risk if it turns out to be wrong.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id.
statementstringyesThe assumption, stated as a testable claim.
categoryenumyesOne of: desirability, viability, feasibility.
statusenumnoOne of: untested, testing, validated, invalidated. Default: "untested".
evidenceobject[]noDefault: [].
risk_if_wrongstringno—

Say where a strategy item came from: a call, a plan, a document.

add_source

Create a strategy.sources row — a provenance record for where a strategy artifact came from (a Fireflies call, a plans.devfellowship.com plan, a Company Brain node, or a manual note), or of a keyword-metrics collection…

Add Source

Create a strategy.sources row — a provenance record for where a strategy artifact came from (a Fireflies call, a plans.devfellowship.com plan, a Company Brain node, or a manual note), or of a keyword-metrics collection (api_run = one provider API run, browser_agent_session = one browser-agent session; pass its id as source_id to upsert_keywords / update_keyword_metrics). Use with link_artifact_source to attach it to the artifact it backs.

ParameterTypeRequiredDescription
source_typeenumyesOne of: fireflies_call, plans_app, company_brain, manual, api_run, browser_agent_session.
external_refstringnoExternal id/URL for the source (call id, plan slug, brain node id, ...)
occurred_atstringnoISO timestamp the source event occurred.
summarystringno—
Create a strategy.artifact_sources row linking any strategy artifact row (by table name + id, e.g. artifact_table: "assumptions") to a strategy.sources row created via add_source.

Link Artifact Source

Create a strategy.artifact_sources row linking any strategy artifact row (by table name + id, e.g. artifact_table: "assumptions") to a strategy.sources row created via add_source. This is how provenance ("this assumption came from the 2026-07-10 strategic call") gets recorded without a dedicated source_id column on every block table.

ParameterTypeRequiredDescription
artifact_tablestringyesThe strategy.* table the artifact lives in, e.g. "assumptions", "value_propositions".
artifact_idstringyesThe artifact row id in artifact_table.
source_idstringyesstrategy.sources.id (from add_source)
extraction_notestringnoHow/why this source backs this artifact.

Save the whole canvas of a business unit at one point in time.

create_canvas_snapshot

Assemble a business_unit's full Business Model Canvas (business_unit row + all 9 block tables + active relationships/assumptions) into a strategy.canvas_snapshots row, status=draft.

Create Canvas Snapshot

Assemble a business_unit's full Business Model Canvas (business_unit row + all 9 block tables + active relationships/assumptions) into a strategy.canvas_snapshots row, status=draft. snapshot_version auto-increments per business_unit. IMPORTANT: agents can only ever create draft snapshots — strategy.canvas_snapshots UPDATE (ratifying draft → ratified) is DB-gated to iam.is_global_admin() and there is deliberately no ratify tool here. Ratification happens in the BM Canvas app by a human global admin.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id to snapshot.
triggerenumnoOne of: manual, weekly_synthesis, pre_deck_export, post_strategic_call. Default: "manual".
diff_summary_mdstringnoOptional human-readable summary of what changed since the last snapshot.

Competitors, personas and customer segments

Section titled “Competitors, personas and customer segments”

Who the competitors and the buyers are: competitors, personas and customer segments.

add_competitor

Create a strategy.competitors row for a business_unit.

Add Competitor

Create a strategy.competitors row for a business_unit. Competitors point at a strategy.entities row (the actual company name/website/logo lives on entities, not on competitors itself, so entities can be shared across the competitor/key_partner graph). Pass entity_id to link an existing entity, or entity_name (+ optional entity_description/ entity_website) to create a new one inline.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id this competitor is tracked against.
entity_idstringnoExisting strategy.entities.id — omit if creating a new entity inline.
entity_namestringnoName for a new entity (required if entity_id is omitted)
entity_descriptionstringno—
entity_websitestringno—
market_sharenumberno—
threat_levelenumnoOne of: high, medium, low.
visiblebooleannoDefault: true.

add_persona

Create a strategy.personas row under a customer_segment — a named buyer/user persona (occupation, buying role in the deal, ICP priority tier).

Add Persona

Create a strategy.personas row under a customer_segment — a named buyer/user persona (occupation, buying role in the deal, ICP priority tier).

ParameterTypeRequiredDescription
customer_segment_idstringyesstrategy.customer_segments.id this persona belongs to.
namestringyes—
occupationstringno—
descriptionstringno—
imagestringnoImage URL.
sort_ordernumbernoDefault: 0.
buying_roleenumnoOne of: economic_buyer, champion, user, influencer, gatekeeper, blocker.
icp_priorityenumnoOne of: primary, secondary, tertiary.

list_personas

List strategy.personas rows.

List Personas

List strategy.personas rows. Pass exactly one filter: customer_segment_id (direct — personas belonging to one segment) or business_unit_id (joins through customer_segments to return every persona across all of that BU's segments).

ParameterTypeRequiredDescription
customer_segment_idstringnostrategy.customer_segments.id — direct filter.
business_unit_idstringnostrategy.business_units.id — filters personas across every segment of this BU (joins via customer_segments)
limitnumbernoDefault: 50.

update_persona

Partially update a strategy.personas row by id.

Update Persona

Partially update a strategy.personas row by id. Only the fields provided are changed.

ParameterTypeRequiredDescription
idstringyesstrategy.personas.id.
customer_segment_idstringnoRe-parent the persona to a different customer_segment_id.
namestringno—
occupationstringno—
descriptionstringno—
imagestringnoImage URL.
sort_ordernumberno—
buying_roleenumnoOne of: economic_buyer, champion, user, influencer, gatekeeper, blocker.
icp_priorityenumnoOne of: primary, secondary, tertiary.

delete_persona

Hard-delete a strategy.personas row by id.

Delete Persona

Hard-delete a strategy.personas row by id. strategy.personas has no soft-delete/archive flag today, so this is a permanent row delete — use for cleaning up misparented/duplicate personas (e.g. seeded under the wrong customer_segment).

ParameterTypeRequiredDescription
idstringyesstrategy.personas.id.

list_customer_segments

List strategy.customer_segments rows for a business_unit_id.

List Customer Segments

List strategy.customer_segments rows for a business_unit_id.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id this segment belongs to.
limitnumbernoDefault: 50.

update_customer_segment

Partially update a strategy.customer_segments row by id.

Update Customer Segment

Partially update a strategy.customer_segments row by id. Only the fields provided are changed.

ParameterTypeRequiredDescription
idstringyesstrategy.customer_segments.id.
business_unit_idstringnoRe-parent the segment to a different business_unit_id.
namestringno—
descriptionstringno—
demographicsstringno—
needsobjectno—
pain_pointsobjectno—
age_rangestringnoOne of strategy.age_range_enum.
persona_namestringnoLegacy inline-persona field (superseded by strategy.personas rows — prefer add_persona/update_persona)
persona_descriptionstringno—
persona_occupationstringno—
persona_imagestringno—

delete_customer_segment

Hard-delete a strategy.customer_segments row by id.

Delete Customer Segment

Hard-delete a strategy.customer_segments row by id. CAUTION: as of 2026-07-15, strategy.customer_segments has INSERT/UPDATE/SELECT RLS policies for authenticated members but NO DELETE policy — this call will likely fail with a Postgres RLS error (0 rows deleted, or 42501) until a dfl-schema migration adds one. If it fails for that reason, do not silently swallow it — surface it and flag a dfl-schema follow-up. Also note personas FK-reference customer_segment_id with no documented ON DELETE behavior — delete/re-parent child personas first.

ParameterTypeRequiredDescription
idstringyesstrategy.customer_segments.id.

How a business unit writes: its voice and its writing patterns.

list_writing_patterns

List strategy.writing_patterns rows for a business_unit.

List Writing Patterns

List strategy.writing_patterns rows for a business_unit. Each row is one "voice slot" — a reusable description of how a given surface should sound (e.g. slot "book", "youtube"). The free-form pattern jsonb holds the actual voice definition. Optionally filter by slot to fetch a single voice. Read this BEFORE writing a new slot so the new row follows the same pattern shape as the existing ones.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id whose writing patterns to list.
slotstringnoFilter to a single slot (e.g. "book", "youtube", "pedagogy_fala")
limitnumbernoDefault: 50.

upsert_writing_pattern

Create or update one strategy.writing_patterns row (a "voice slot") for a business_unit.

Upsert Writing Pattern

Create or update one strategy.writing_patterns row (a "voice slot") for a business_unit. Resolution order: pass id to update that exact row; else pass slot and the tool updates the existing row with that slot in the business_unit, or inserts a new one if none exists. pattern is free-form jsonb — call list_writing_patterns first and mirror the shape the other slots already use, so a consumer reading every slot does not break. On update, pattern REPLACES the stored object (no deep merge) — send the whole thing.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id this writing pattern belongs to.
idstringnoRow id to update. Omit to resolve by slot (update-or-insert).
slotstringnoStable machine key for the voice surface (e.g. "book", "youtube"). Used to resolve update-vs-insert when id is not given.
namestringnoHuman-readable label shown in the BM Canvas UI.
patternobjectnoFree-form voice definition (jsonb). Existing DFL rows use: description, toneAxes [{id,left,right,value}], vocabularyDo[], vocabularyAvoid[], examplePairs [{id,onBrand,offBrand}], notes. Call list_writing_patterns first and keep those keys so existing consumers keep working; add extra keys only when the slot genuinely needs them.
versionnumbernoVersion counter for this slot.
sort_ordernumberno—

The SEO keyword list of a business unit and its search metrics.

list_keywords

List strategy.keywords rows (the SEO keyword corpus) of one business unit, ordered by text.

List SEO Keywords

List strategy.keywords rows (the SEO keyword corpus) of one business unit, ordered by text. count is the exact number of keywords the BU holds, independent of limit — compare it before and after a test run to prove the run left no rows behind. Reads run under the caller's user-JWT (RLS applies).

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id.
limitnumbernoDefault: 200.

delete_keywords

Hard-delete strategy.keywords rows by id, scoped to one business unit.

Delete SEO Keywords

Hard-delete strategy.keywords rows by id, scoped to one business unit. Their keyword_metrics and keyword_relations rows cascade; child keywords keep their row and lose parent_keyword_id (SET NULL). The generic repair and test-cleanup path for the SEO corpus (e2e specs delete what they create). An id that is absent, belongs to another BU, or is hidden by RLS is not deleted and is listed in not_deleted_ids.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id — every id must belong to this BU.
idsstring[]yesstrategy.keywords.id values to delete.

upsert_keywords

Create strategy.keywords rows (the SEO corpus) in one business unit, with an optional keyword_metrics snapshot and an optional parent (parent_keyword_id, the mind-map tree).

Upsert SEO Keywords

Create strategy.keywords rows (the SEO corpus) in one business unit, with an optional keyword_metrics snapshot and an optional parent (parent_keyword_id, the mind-map tree). A keyword is identified by (business_unit_id, slug); the slug is the campaigns app slugifier of text. A keyword that already exists is reused and NOT changed, unless update_existing is true — then its category / cluster fields are overwritten and a new metrics snapshot is added. A metrics snapshot must name its channel (google_search, youtube_search, chatgpt_search), provider and collection_method; a browser_agent snapshot must also give source_id (the strategy.sources session row). It stores only the metric fields you give: omit a field that was not measured, and never send 0 for it. metrics.fetched_at dates the measurement. To relabel or correct existing snapshots, use update_keyword_metrics. Group keywords into a cluster by giving them the same cluster_name + cluster_color. Writes run under the caller user-JWT (RLS applies). Not a transaction: a failure after the first insert leaves the earlier rows; re-run the same batch to finish, it is idempotent.

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id.
keywordsobject[]yes—
update_existingbooleannoDefault: false.

update_keyword_metrics

Set columns on existing strategy.keyword_metrics snapshots of one business unit: relabel the typed source (channel, provider, collection_method, source_id, market, language, raw) or set a metric (search_volume, score…

Update SEO Keyword Metrics

Set columns on existing strategy.keyword_metrics snapshots of one business unit: relabel the typed source (channel, provider, collection_method, source_id, market, language, raw) or set a metric (search_volume, score, cpc, competition) to a value or to NULL ("not measured"). The generic repair and backfill path for keyword metrics; use upsert_keywords to ADD a snapshot. Select the rows with filter (AND of keyword_ids, metric_ids, the channel, provider, collection_method, only_unlabelled). Run with dry_run: true first: it returns the match count and before/after samples and writes nothing. Give expected_count to refuse the write when the match count differs. A result that would leave a browser_agent snapshot without source_id is refused. At most 5000 rows per call. Runs under the caller user-JWT (RLS applies).

ParameterTypeRequiredDescription
business_unit_idstringyesstrategy.business_units.id — only its keywords are touched.
filterobjectyesWhich snapshots to change, inside business_unit_id. All given fields must match (AND). An empty filter means every snapshot of the business unit and then requires expected_count.
patchobjectyesColumns to set on every matched snapshot. A key you omit is not changed. null sets the column to NULL (only on the nullable fields: source_id, market, language, raw and the four metrics).
dry_runbooleannotrue = report what would change, write nothing. Default: false.
expected_countnumbernoRefuse the write unless exactly this many snapshots match. Required with an empty filter.

Every tool on this page authenticates with the caller’s Supabase user JWT (RLS-scoped). None of them use service_role, per the fleet-wide rule that MCP tools carry the calling user’s own privileges — writes are exactly as permitted (or blocked) by the RLS policies above.

These old names still answer until the date shown. Call the new name.

Deprecated nameUse insteadRemoved after
list_business_unitslist_canvas_business_units2026-12-04
get_business_unitget_canvas_business_unit2026-12-04
create_business_unitcreate_canvas_business_unit2026-12-04
update_business_unitupdate_canvas_business_unit2026-12-04
archive_business_unitarchive_canvas_business_unit2026-12-04
add_business_unit_relationshipadd_canvas_business_unit_relationship2026-12-04