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.
|
{ "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 data | strategy.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. |
Business units
Section titled “Business units”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_unitsList 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
include_archived | boolean | no | Include is_archived=true rows. Default: false. |
parent_business_unit_id | string | no | Filter to the canvases linked to this public.business_units.id (the work-host business unit; column business_unit_id on the row) |
limit | number | no | Default: 50. |
get_canvas_business_unit
Fetch one strategy.business_units row (the canvas record of a business unit) by id.
get_canvas_business_unitGet 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.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_unitCreate 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Business unit name. |
description | string | no | — |
sector | string | no | — |
tags | string | no | — |
color | string | no | Hex or CSS color used by the canvas UI. |
logo_url | string | no | — |
business_unit_id | string | no | public.business_units.id (the work-host business unit) this canvas belongs to. FK, ON DELETE CASCADE. |
golden_circle_why | object | no | — |
golden_circle_how | object | no | — |
golden_circle_what | object | no | — |
update_canvas_business_unit
Partially update a strategy.business_units row (the canvas record of a business unit) by id.
update_canvas_business_unitUpdate 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.business_units.id. |
name | string | no | — |
description | string | no | — |
sector | string | no | — |
tags | string | no | — |
color | string | no | — |
logo_url | string | no | — |
business_unit_id | string | no | public.business_units.id (the work-host business unit) this canvas belongs to. FK, ON DELETE CASCADE. |
golden_circle_why | object | no | — |
golden_circle_how | object | no | — |
golden_circle_what | object | no | — |
archive_canvas_business_unit
Set strategy.business_units.is_archived on a row (default true — archive).
archive_canvas_business_unitArchive 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.business_units.id. |
archived | boolean | no | Default: true. |
Canvas blocks
Section titled “Canvas blocks”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_blockSet 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
block | enum | yes | One of: value_proposition, customer_segment, customer_relationship, channel, revenue_stream, key_partner, key_activity, key_resource, expense_category. |
business_unit_id | string | yes | strategy.business_units.id this block row belongs to. |
id | string | no | Row id to update. Omit to insert a new row. |
fields | object | no | Column name → value for the target table. Default: {}. |
Relationships
Section titled “Relationships”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_relationshipAdd 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
from_bu | string | yes | strategy.business_units.id — the source of the relationship. |
to_bu | string | yes | strategy.business_units.id — the target of the relationship (must differ from from_bu) |
relationship_type | enum | yes | One of: funds, supplies_talent, commercializes, provides_methodology, shares_brand, incubates. |
mechanics | object | no | Default: {}. |
description | string | no | — |
is_active | boolean | no | Default: true. |
started_at | string | no | ISO date the relationship started. |
Assumptions
Section titled “Assumptions”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_assumptionAdd 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id. |
statement | string | yes | The assumption, stated as a testable claim. |
category | enum | yes | One of: desirability, viability, feasibility. |
status | enum | no | One of: untested, testing, validated, invalidated. Default: "untested". |
evidence | object[] | no | Default: []. |
risk_if_wrong | string | no | — |
Provenance
Section titled “Provenance”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_sourceAdd 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
source_type | enum | yes | One of: fireflies_call, plans_app, company_brain, manual, api_run, browser_agent_session. |
external_ref | string | no | External id/URL for the source (call id, plan slug, brain node id, ...) |
occurred_at | string | no | ISO timestamp the source event occurred. |
summary | string | no | — |
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.
link_artifact_sourceLink 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
artifact_table | string | yes | The strategy.* table the artifact lives in, e.g. "assumptions", "value_propositions". |
artifact_id | string | yes | The artifact row id in artifact_table. |
source_id | string | yes | strategy.sources.id (from add_source) |
extraction_note | string | no | How/why this source backs this artifact. |
Snapshots
Section titled “Snapshots”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_snapshotCreate 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id to snapshot. |
trigger | enum | no | One of: manual, weekly_synthesis, pre_deck_export, post_strategic_call. Default: "manual". |
diff_summary_md | string | no | Optional 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_competitorAdd 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id this competitor is tracked against. |
entity_id | string | no | Existing strategy.entities.id — omit if creating a new entity inline. |
entity_name | string | no | Name for a new entity (required if entity_id is omitted) |
entity_description | string | no | — |
entity_website | string | no | — |
market_share | number | no | — |
threat_level | enum | no | One of: high, medium, low. |
visible | boolean | no | Default: 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_personaAdd Persona
Create a strategy.personas row under a customer_segment — a named buyer/user persona (occupation, buying role in the deal, ICP priority tier).
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_segment_id | string | yes | strategy.customer_segments.id this persona belongs to. |
name | string | yes | — |
occupation | string | no | — |
description | string | no | — |
image | string | no | Image URL. |
sort_order | number | no | Default: 0. |
buying_role | enum | no | One of: economic_buyer, champion, user, influencer, gatekeeper, blocker. |
icp_priority | enum | no | One of: primary, secondary, tertiary. |
list_personas
List strategy.personas rows.
list_personasList 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_segment_id | string | no | strategy.customer_segments.id — direct filter. |
business_unit_id | string | no | strategy.business_units.id — filters personas across every segment of this BU (joins via customer_segments) |
limit | number | no | Default: 50. |
update_persona
Partially update a strategy.personas row by id.
update_personaUpdate Persona
Partially update a strategy.personas row by id. Only the fields provided are changed.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.personas.id. |
customer_segment_id | string | no | Re-parent the persona to a different customer_segment_id. |
name | string | no | — |
occupation | string | no | — |
description | string | no | — |
image | string | no | Image URL. |
sort_order | number | no | — |
buying_role | enum | no | One of: economic_buyer, champion, user, influencer, gatekeeper, blocker. |
icp_priority | enum | no | One of: primary, secondary, tertiary. |
delete_persona
Hard-delete a strategy.personas row by id.
delete_personaDelete 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.personas.id. |
list_customer_segments
List strategy.customer_segments rows for a business_unit_id.
list_customer_segmentsList Customer Segments
List strategy.customer_segments rows for a business_unit_id.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id this segment belongs to. |
limit | number | no | Default: 50. |
update_customer_segment
Partially update a strategy.customer_segments row by id.
update_customer_segmentUpdate Customer Segment
Partially update a strategy.customer_segments row by id. Only the fields provided are changed.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.customer_segments.id. |
business_unit_id | string | no | Re-parent the segment to a different business_unit_id. |
name | string | no | — |
description | string | no | — |
demographics | string | no | — |
needs | object | no | — |
pain_points | object | no | — |
age_range | string | no | One of strategy.age_range_enum. |
persona_name | string | no | Legacy inline-persona field (superseded by strategy.personas rows — prefer add_persona/update_persona) |
persona_description | string | no | — |
persona_occupation | string | no | — |
persona_image | string | no | — |
delete_customer_segment
Hard-delete a strategy.customer_segments row by id.
delete_customer_segmentDelete 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | strategy.customer_segments.id. |
Writing patterns (voice slots)
Section titled “Writing patterns (voice slots)”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_patternsList 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id whose writing patterns to list. |
slot | string | no | Filter to a single slot (e.g. "book", "youtube", "pedagogy_fala") |
limit | number | no | Default: 50. |
upsert_writing_pattern
Create or update one strategy.writing_patterns row (a "voice slot") for a business_unit.
upsert_writing_patternUpsert 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id this writing pattern belongs to. |
id | string | no | Row id to update. Omit to resolve by slot (update-or-insert). |
slot | string | no | Stable machine key for the voice surface (e.g. "book", "youtube"). Used to resolve update-vs-insert when id is not given. |
name | string | no | Human-readable label shown in the BM Canvas UI. |
pattern | object | no | Free-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. |
version | number | no | Version counter for this slot. |
sort_order | number | no | — |
SEO keywords
Section titled “SEO keywords”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_keywordsList 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id. |
limit | number | no | Default: 200. |
delete_keywords
Hard-delete strategy.keywords rows by id, scoped to one business unit.
delete_keywordsDelete 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id — every id must belong to this BU. |
ids | string[] | yes | strategy.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_keywordsUpsert 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id. |
keywords | object[] | yes | — |
update_existing | boolean | no | Default: 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_keyword_metricsUpdate 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
business_unit_id | string | yes | strategy.business_units.id — only its keywords are touched. |
filter | object | yes | Which 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. |
patch | object | yes | Columns 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_run | boolean | no | true = report what would change, write nothing. Default: false. |
expected_count | number | no | Refuse the write unless exactly this many snapshots match. Required with an empty filter. |
user-JWT only — never service_role
Section titled “user-JWT only — never service_role”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.
Deprecated names
Section titled “Deprecated names”These old names still answer until the date shown. Call the new name.
| Deprecated name | Use instead | Removed after |
|---|---|---|
list_business_units | list_canvas_business_units | 2026-12-04 |
get_business_unit | get_canvas_business_unit | 2026-12-04 |
create_business_unit | create_canvas_business_unit | 2026-12-04 |
update_business_unit | update_canvas_business_unit | 2026-12-04 |
archive_business_unit | archive_canvas_business_unit | 2026-12-04 |
add_business_unit_relationship | add_canvas_business_unit_relationship | 2026-12-04 |