Proposals
The operational memory + assembly line for competing in editais / RFPs / public tenders / incentive programs / awards. Multi-company (Revera / Itera / devfellowship + partners like B42): a company registry with a fiscal/legal profile, a governed answer library that composes across submissions by reference (never copy-paste), opportunities with an extracted requirements checklist, and submissions that assemble answers + vault documents for one applying company. Reads strategy/public, writes only its own proposals schema.
| Endpoint | https://proposals.mcp.devfellowship.com/mcp |
|---|---|
| Tools | 21 in 4 groups |
| Package | packages/dfl-mcp-proposals |
| Auth |
Your dfl-auth login token as Authorization: Bearer <token>. Every call runs as
you, under RLS. See Auth & security.
|
{ "mcpServers": { "dfl-proposals": { "type": "http", "url": "https://proposals.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 | proposals schema (companies + vault, answers, opportunities, submissions). |
Companies + Document Vault
Section titled “Companies + Document Vault”The companies that apply to tenders, and their documents and certificates, including the ones that expire soon.
list_companies
List the companies that can apply to an opportunity — own entities (Revera/Itera/devfellowship) and external partners (e.g. B42).
list_companiesList Companies
List the companies that can apply to an opportunity — own entities (Revera/Itera/devfellowship) and external partners (e.g. B42). Filter by relationship (own|partner) or search legal_name / trade_name / cnpj.
| Parameter | Type | Required | Description |
|---|---|---|---|
relationship | enum | no | Filter by relationship: "own" (DFL entity) or "partner" (external, e.g. B42) One of: own, partner. |
search | string | no | Case-insensitive substring match on legal_name, trade_name, or cnpj. |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
get_company
Get one company by id, optionally with its fiscal-year history (revenue/headcount) and contacts (accountant/legal/admin/partner).
get_companyGet Company
Get one company by id, optionally with its fiscal-year history (revenue/headcount) and contacts (accountant/legal/admin/partner). Note: the underlying vault documents are admin-gated and NOT returned here — use list_expiring_documents / upload_company_doc for the vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the company. |
include_related | boolean | no | Also return company_fiscal_years and company_contacts (default false) |
upsert_company
Create a new company, or update an existing one when id is given.
upsert_companyUpsert Company
Create a new company, or update an existing one when id is given. relationship (own|partner) is required when creating. Partners (e.g. B42) keep business_unit_id NULL; own entities may point at the canonical public.business_units roster (soft reference, app-enforced).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | no | UUID of an existing company to UPDATE. Omit to CREATE a new one. |
relationship | enum | no | own = DFL entity (Revera/Itera/devfellowship); partner = external. REQUIRED when creating. One of: own, partner. |
business_unit_id | string | no | Canonical public.business_units id for own entities; NULL for partners (soft reference, no FK). |
cnpj | string | no | Brazilian company tax id (unique across companies) |
legal_name | string | no | Razão social (legal name) |
trade_name | string | no | Nome fantasia (trade name) |
incorporated_at | string | no | Incorporation date (YYYY-MM-DD) |
legal_form | string | no | Natureza jurídica (legal form, e.g. LTDA) |
tax_regime | string | no | Regime tributário (e.g. Simples Nacional, Lucro Presumido) |
primary_cnae | string | no | Primary CNAE code. |
share_capital | number | no | Capital social (numeric) |
state_registration | string | no | Inscrição estadual. |
municipal_registration | string | no | Inscrição municipal. |
fiscal_address | object | no | Fiscal address as a JSON object. |
company_size | enum | no | Porte (legal size classification) One of: MEI, ME, EPP, other. |
extra | object | no | Catch-all JSON for fields not yet promoted to columns. |
upload_company_doc
Upload a SMALL document into the company vault by sending its bytes inline: base64 file_content is POSTed to the upload-file edge function as PRIVATE (public.media row, folder proposals/<company_id>/) and a…
upload_company_docUpload Company Document (Vault)
Upload a SMALL document into the company vault by sending its bytes inline: base64 file_content is POSTed to the upload-file edge function as PRIVATE (public.media row, folder proposals/<company_id>/) and a proposals.company_documents metadata row is recorded pointing at it. The read path is always share.devfellowship.com/<media_id> or an MCP link — never a raw S3 URL. ⚠️ The base64 body is capped by the server bodyLimit (~10 MB body ≈ ~7.5 MB file); for LARGER files, upload to the private bucket first (upload-file edge function, visibility=private) and use link_company_doc with the returned media_id/URL — no bytes travel through the MCP body. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | string | yes | UUID of the company this document belongs to. |
file_content | string | yes | Base64-encoded file content. |
file_name | string | yes | File name with extension (e.g. "contrato_social.pdf") |
mime_type | string | yes | MIME type (e.g. "application/pdf") |
document_type | string | yes | Free-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado. |
label | string | no | Human label for the document. |
notes | string | no | Freeform notes. |
issued_at | string | no | Issue date (YYYY-MM-DD) |
valid_until | string | no | Validity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar. |
link_company_doc
The definitive large-file path: record a proposals.company_documents vault row that points at a file ALREADY uploaded to the private bucket — NO file bytes travel through the MCP body, so there is no size limit.
link_company_docLink Pre-Uploaded Company Document (Vault)
The definitive large-file path: record a proposals.company_documents vault row that points at a file ALREADY uploaded to the private bucket — NO file bytes travel through the MCP body, so there is no size limit. Upload the file first via the upload-file edge function (visibility=private) — that returns a media_id + a stable https://media.devfellowship.com/<id> link — then pass that reference here. media_ref accepts a bare media UUID, a media.devfellowship.com/<id> or share.devfellowship.com/<id> URL, or a private/<id>-<name> storage path. Optionally pass supersedes to replace an existing vault row (e.g. swap a compressed stopgap for the byte-exact original): the old row is marked status=superseded + superseded_by=<new id>. The read path is always share.devfellowship.com/<media_id> — never a raw S3 URL. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | string | yes | UUID of the company this document belongs to. |
media_ref | string | yes | Reference to an ALREADY-uploaded PRIVATE file: a media UUID, a media.devfellowship.com/<id> or share.devfellowship.com/<id> URL, or a private/<id>-<name> storage path. The file must have been uploaded via the upload-file edge function with visibility=private first. |
document_type | string | yes | Free-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado. |
label | string | no | Human label for the document. |
notes | string | no | Freeform notes. |
issued_at | string | no | Issue date (YYYY-MM-DD) |
valid_until | string | no | Validity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar. |
supersedes | string | no | UUID of an existing proposals.company_documents row this document REPLACES. When set, that row is marked status=superseded and superseded_by=<new row id> after the new row is inserted (e.g. replacing a compressed stopgap with the byte-exact original). |
list_expiring_documents
The certificate-renewal radar: vault documents whose valid_until falls within within_days from today (already-expired included).
list_expiring_documentsList Expiring Documents
The certificate-renewal radar: vault documents whose valid_until falls within within_days from today (already-expired included). Order by soonest expiry. VAULT is admin-gated (iam.is_global_admin) — a non-admin caller gets an empty list, not an error.
| Parameter | Type | Required | Description |
|---|---|---|---|
within_days | number | no | Look-ahead window in days (default 30). Documents expiring within this many days (or already expired) are returned. |
company_id | string | no | Restrict to one company. |
include_expired | boolean | no | Include already-expired documents (default true) |
Answer Library
Section titled “Answer Library”Reusable answers for proposals: search, write, review and retire them.
search_answers
Search the reusable answer library by free text (question_canonical / short_answer / long_answer_md) and/or facet tags.
search_answersSearch Answers (Library)
Search the reusable answer library by free text (question_canonical / short_answer / long_answer_md) and/or facet tags. This is the reuse entry point — find an approved answer, then reference it from a submission section (never copy-paste). Returns each answer with its tags. Every row also reports its BODY TYPE: answer_kind is "markdown" or "sheet". A "sheet" answer keeps its content in the work.sheets row named by sheet_id, NOT in long_answer_md — open it on the engineering MCP with get_sheet, and use copy_sheet when a submission needs its own fillable copy. Free-text search reads the markdown columns only, so find a budget template by its question_canonical or its tags.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | no | Case-insensitive substring across question_canonical, short_answer, long_answer_md. |
tags | string[] | no | Only answers carrying ALL of these facet tags. |
status | enum | no | Filter by governance status. One of: draft, approved, stale, retired. |
language | enum | no | Filter by language. One of: pt, en. |
company_id | string | no | Company-specific answers for this company id. |
shared_only | boolean | no | Only ecosystem-shared answers (company_id IS NULL) |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
create_answer
Add a reusable answer to the library.
create_answerCreate Answer
Add a reusable answer to the library. company_id NULL = ecosystem-shared (DFL narrative); set = company-specific fact. New answers default to status="draft". Optionally attach facet tags and link a translation (translation_of) to pair PT/EN. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown.
| Parameter | Type | Required | Description |
|---|---|---|---|
question_canonical | string | yes | The canonical question/prompt this answer responds to. |
short_answer | string | no | One/two-line summary. |
long_answer_md | string | no | Full answer in Markdown. |
language | enum | no | Language (default pt) One of: pt, en. |
company_id | string | no | Company id, or NULL/omit for ecosystem-shared. |
translation_of | string | no | UUID of the source-language answer this one translates. |
status | enum | no | Governance status (default draft) One of: draft, approved, stale, retired. |
expires_at | string | no | When this answer should be re-reviewed (ISO timestamp) |
tags | string[] | no | Facet tags (e.g. institutional, impact, safeguarding) |
source | object | no | Provenance JSON (plan slug, submission id, vault doc) |
sheet_id | string | no | UUID of a work.sheets spreadsheet that IS this answer (e.g. the "Orçamento detalhado" budget). Create it on the engineering MCP with create_sheet or copy_sheet first. Passing it sets answer_kind="sheet" on its own. |
answer_kind | enum | no | Which body this answer carries (default markdown, inferred as "sheet" when sheet_id is given). "sheet" without a sheet_id is refused here, before the database CHECK. One of: markdown, sheet. |
update_answer
Edit an answer in the library.
update_answerUpdate Answer
Edit an answer in the library. Optionally records an append-only revision snapshot (record_revision) and/or fully replaces the answer's facet tags (tags). Use set_answer_status for status transitions. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown. A recorded revision snapshots sheet_id and answer_kind together with long_answer_md, so the history of a sheet answer stays complete.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | UUID of the answer to update. |
question_canonical | string | no | Update the canonical question. |
short_answer | string | no | Update the short answer. |
long_answer_md | string | no | Update the long answer (Markdown) |
language | enum | no | Update language. One of: pt, en. |
company_id | string | no | Reassign company (NULL = ecosystem-shared) |
translation_of | string | no | Update the translation pairing. |
expires_at | string | no | Update the re-review date. |
source | object | no | Replace the provenance JSON. |
tags | string[] | no | If provided, FULLY REPLACES the answer's tags with this set. |
record_revision | boolean | no | If true, append a row to answer_revisions capturing the new long_answer_md PLUS sheet_id and answer_kind (default false) |
sheet_id | string | no | Attach a work.sheets spreadsheet as this answer's body (create it on the engineering MCP with create_sheet or copy_sheet first), or pass null to detach it and go back to Markdown. |
answer_kind | enum | no | Which body this answer carries. Inferred from sheet_id, so you rarely set it: "sheet" needs a sheet_id in this call or already on the row, and "markdown" alongside a new sheet_id is refused. One of: markdown, sheet. |
set_answer_status
Transition an answer through its governance lifecycle: draft → approved → stale → retired.
set_answer_statusSet Answer Status
Transition an answer through its governance lifecycle: draft → approved → stale → retired. Approving stamps last_reviewed_at=now. This is the governance lever that keeps the library from rotting into a wiki.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | UUID of the answer. |
status | enum | yes | New governance status. One of: draft, approved, stale, retired. |
expires_at | string | no | Optionally (re)set the re-review date (ISO timestamp; null clears it) |
list_stale_answers
The library review queue: answers explicitly marked status="stale" PLUS answers whose expires_at falls within within_days from now (already-expired included).
list_stale_answersList Stale Answers
The library review queue: answers explicitly marked status="stale" PLUS answers whose expires_at falls within within_days from now (already-expired included). Retired answers are excluded. Order by soonest expiry. Each row reports answer_kind and sheet_id, so a spreadsheet answer up for review is visible as one: its content lives in work.sheets, not in long_answer_md.
| Parameter | Type | Required | Description |
|---|---|---|---|
within_days | number | no | Also include answers expiring within this many days (default 0 = only already-expired + status=stale) |
company_id | string | no | Restrict to one company. |
limit | number | no | Max rows (default 100, max 200) |
Opportunities
Section titled “Opportunities”Public tenders, RFPs and grants: register one, list its requirements, and check if a company is eligible.
create_opportunity
Register an edital/RFP/public tender/incentive program/award as a structured object (deadlines, value, status).
create_opportunityCreate Opportunity
Register an edital/RFP/public tender/incentive program/award as a structured object (deadlines, value, status). Optionally attach a funder by id, or by name (a funder row is created on the fly). Optionally point source_media_id at the notice PDF already in the vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | enum | yes | grant_notice=edital, rfp, public_tender=licitação, incentive_program=incentivo, award=prêmio. One of: grant_notice, rfp, public_tender, incentive_program, award. |
title | string | yes | Human title of the opportunity. |
notice_number | string | no | Número do edital / notice number. |
url | string | no | Public URL of the opportunity notice. |
funder_id | string | no | UUID of an existing funder. |
funder_name | string | no | If no funder_id, create a funder with this name and link it. |
funder_kind | enum | no | Kind of the on-the-fly funder (only used with funder_name) One of: federal, state, municipal, multilateral, foundation, corporate. |
source_media_id | string | no | public.media id of the notice PDF in the vault. |
published_at | string | no | Publication timestamp (ISO) |
questions_deadline | string | no | Clarification-questions deadline (ISO) |
submission_deadline | string | no | Submission deadline (ISO) |
total_value | number | no | Total value of the opportunity. |
currency | string | no | Currency code (default BRL) |
status | enum | no | Pipeline status (default scouted) One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
go_no_go_notes | string | no | Go/No-Go decision notes. |
add_requirements
Bulk-add the extracted checklist for an opportunity: document/eligibility/content/form/budget requirements, each with provenance (source_excerpt + source_page) back to the notice PDF.
add_requirementsAdd Opportunity Requirements
Bulk-add the extracted checklist for an opportunity: document/eligibility/content/form/budget requirements, each with provenance (source_excerpt + source_page) back to the notice PDF. document requirements should set required_document_type so check_eligibility can match them against a company vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity. |
requirements | object[] | yes | One or more requirements to insert. |
check_eligibility
Cross a company against an opportunity's requirements and return a per-requirement verdict + gap list.
check_eligibilityEligibility Check
Cross a company against an opportunity's requirements and return a per-requirement verdict + gap list. document requirements are auto-checked against the company vault (present + current + not-expired); eligibility criteria like {"min_revenue","min_years","min_headcount"} are auto-checked against fiscal years / incorporation date; content/form/budget requirements are flagged manual_review. NOTE: the vault is admin-gated — a non-admin caller sees no documents, so every document requirement reports "missing".
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity. |
company_id | string | yes | UUID of the applying company. |
list_opportunities
List editais/RFPs/public tenders/incentive programs/awards already registered in the pipeline.
list_opportunitiesList Opportunities
List editais/RFPs/public tenders/incentive programs/awards already registered in the pipeline. Filter by status, funder_id, or kind. This is the discovery entry point — check here before create_opportunity to avoid registering a duplicate. Each row includes its resolved funder (id/name/kind).
| Parameter | Type | Required | Description |
|---|---|---|---|
status | enum | no | Filter by pipeline status. One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
funder_id | string | no | Filter by funder UUID. |
kind | enum | no | Filter by opportunity kind (grant_notice=edital, public_tender=licitação, incentive_program=incentivo, award=prêmio) One of: grant_notice, rfp, public_tender, incentive_program, award. |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
search_opportunities
Text search over opportunities by title, notice_number (número do edital), or funder name — the dedup entry point before create_opportunity (find it first, don't create it blind).
search_opportunitiesSearch Opportunities
Text search over opportunities by title, notice_number (número do edital), or funder name — the dedup entry point before create_opportunity (find it first, don't create it blind). Combine with status/kind/funder_id filters. Each row includes its resolved funder.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | no | Case-insensitive substring across title, notice_number, and funder name. |
status | enum | no | Filter by pipeline status. One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
kind | enum | no | Filter by opportunity kind. One of: grant_notice, rfp, public_tender, incentive_program, award. |
funder_id | string | no | Filter by funder UUID. |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
Submissions (Assembly)
Section titled “Submissions (Assembly)”Put one application together: sections, attached documents, the compliance checklist, and reuse of good answers.
create_submission
Open a submission = one company applying to one opportunity (the multi-company anchor).
create_submissionCreate Submission
Open a submission = one company applying to one opportunity (the multi-company anchor). Seeds the applying company as "lead" in submission_companies. Pass consortium to add partner companies (e.g. B42 as consortium_member). plan_slug ties it to the plans-app authoring workspace.
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity. |
company_id | string | yes | UUID of the primary applying company (recorded as lead) |
status | enum | no | Pipeline status (default draft) One of: draft, internal_review, submitted, clarifications, won, lost, withdrawn. |
plan_slug | string | no | plans-app plan slug used as the authoring workspace. |
consortium | object[] | no | Additional consortium companies beyond the lead. |
upsert_section
Create or update a section of a submission.
upsert_sectionUpsert Submission Section
Create or update a section of a submission. answer_id is the reuse/provenance link into the answer library (ADR-3: reference, never copy-paste); content_md is the opportunity-tailored final text ADAPTED from that answer. Pass id to update an existing section, omit to create. SHEETS: a section may BE a spreadsheet instead of Markdown — a budget table, for instance. Fill a template for one submission on the engineering MCP with copy_sheet (or build a new one with create_sheet), then pass the new sheet id as sheet_id here. section_kind is inferred from sheet_id; pass sheet_id: null to detach the sheet and go back to Markdown. answer_id stays the provenance link either way.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission. |
id | string | no | UUID of an existing section to UPDATE (omit to CREATE) |
title | string | no | Section title. |
sort_order | number | no | Ordering within the submission. |
answer_id | string | no | Library answer this section reuses (provenance FK; also gives usage-tracking for free) |
content_md | string | no | The final, opportunity-adapted text (Markdown) |
status | enum | no | Section drafting status (default todo on create) One of: todo, drafted, reviewed, final. |
sheet_id | string | no | UUID of the work.sheets spreadsheet that IS this section (typically a copy_sheet of the opportunity budget template). Pass null to detach it. |
section_kind | enum | no | Which body this section carries (default markdown, inferred as "sheet" when sheet_id is given). "sheet" without a sheet_id is refused here, before the database CHECK. One of: markdown, sheet. |
attach_document
The compliance join: record that a specific vault document (company_document_id) satisfies a submission — optionally tied to the exact requirement it fulfills (requirement_id).
attach_documentAttach Document to Submission
The compliance join: record that a specific vault document (company_document_id) satisfies a submission — optionally tied to the exact requirement it fulfills (requirement_id). This is what builds the submission checklist. The team can see "cartão CNPJ: attached" here even without vault read access to open the file itself.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission. |
company_document_id | string | yes | UUID of the vault document (proposals.company_documents) that satisfies the requirement. |
requirement_id | string | no | UUID of the opportunity_requirement this document fulfills (optional) |
status | enum | no | Attachment status (default pending) One of: pending, attached, needs_renewal. |
get_submission_checklist
Assemble the full compliance + drafting state of a submission: every opportunity requirement with the documents attached to satisfy it (submission_documents), plus each submission section and its status.
get_submission_checklistSubmission Checklist
Assemble the full compliance + drafting state of a submission: every opportunity requirement with the documents attached to satisfy it (submission_documents), plus each submission section and its status. Readable at member tier (the checklist is team-visible even when the underlying vault files are admin-gated). Each section reports its BODY TYPE: section_kind is "markdown" or "sheet", and a sheet section carries the work.sheets id in sheet_id — read or edit it on the engineering MCP (get_sheet / write_sheet_cells). sections_sheet counts them.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission. |
harvest_answers
The loop that compounds: promote reusable text written in a past submission back into the answer library.
harvest_answersHarvest Answers from Submission
The loop that compounds: promote reusable text written in a past submission back into the answer library. For each qualifying section, create a draft library answer (question_canonical = section title, long_answer_md = section content, source = provenance to this submission) and back-link the section to the new answer via answer_id. By default only sections NOT already linked to a library answer are harvested. SHEETS: a section whose body is a spreadsheet (section_kind="sheet") qualifies on its sheet_id alone, with or without content_md, and the promoted answer carries the SAME sheet_id with answer_kind="sheet". The sheet itself is never copied — the library answer and the submission section point at one work.sheets row, so editing it later shows in both.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission to harvest from. |
section_ids | string[] | no | Restrict to these section UUIDs (default: all qualifying sections) |
only_unlinked | boolean | no | Only harvest sections with no answer_id yet (default true) |
company_scoped | boolean | no | If true, set the new answers' company_id to the submission's company (default false = ecosystem-shared) |
Deprecated names
Section titled “Deprecated names”These old names still work until their removal date. Each one calls the same handler as its new name. Use the new name in new code.
These old names still answer until the date shown. Call the new name.
| Deprecated name | Use instead | Removed after |
|---|---|---|
eligibility_check | check_eligibility | 2026-12-04 |
submission_checklist | get_submission_checklist | 2026-12-04 |