Skip to content

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 24 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.
.mcp.json
{
"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 dataproposals schema (companies + vault, answers, opportunities, submissions).

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

ParameterTypeRequiredDescription
relationshipenumnoFilter by relationship: "own" (DFL entity) or "partner" (external, e.g. B42) One of: own, partner.
searchstringnoCase-insensitive substring match on legal_name, trade_name, or cnpj.
limitnumbernoMax rows (default 50, max 100)
offsetnumbernoRows 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 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.

ParameterTypeRequiredDescription
idstringyesThe UUID of the company.
include_relatedbooleannoAlso 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 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).

ParameterTypeRequiredDescription
idstringnoUUID of an existing company to UPDATE. Omit to CREATE a new one.
relationshipenumnoown = DFL entity (Revera/Itera/devfellowship); partner = external. REQUIRED when creating. One of: own, partner.
business_unit_idstringnoCanonical public.business_units id for own entities; NULL for partners (soft reference, no FK).
cnpjstringnoBrazilian company tax id (unique across companies)
legal_namestringnoRazão social (legal name)
trade_namestringnoNome fantasia (trade name)
incorporated_atstringnoIncorporation date (YYYY-MM-DD)
legal_formstringnoNatureza jurídica (legal form, e.g. LTDA)
tax_regimestringnoRegime tributário (e.g. Simples Nacional, Lucro Presumido)
primary_cnaestringnoPrimary CNAE code.
share_capitalnumbernoCapital social (numeric)
state_registrationstringnoInscrição estadual.
municipal_registrationstringnoInscrição municipal.
fiscal_addressobjectnoFiscal address as a JSON object.
company_sizeenumnoPorte (legal size classification) One of: MEI, ME, EPP, other.
extraobjectnoCatch-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 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.

ParameterTypeRequiredDescription
company_idstringyesUUID of the company this document belongs to.
file_contentstringyesBase64-encoded file content.
file_namestringyesFile name with extension (e.g. "contrato_social.pdf")
mime_typestringyesMIME type (e.g. "application/pdf")
document_typestringyesFree-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado.
labelstringnoHuman label for the document.
notesstringnoFreeform notes.
issued_atstringnoIssue date (YYYY-MM-DD)
valid_untilstringnoValidity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar.
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 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.

ParameterTypeRequiredDescription
company_idstringyesUUID of the company this document belongs to.
media_refstringyesReference 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_typestringyesFree-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado.
labelstringnoHuman label for the document.
notesstringnoFreeform notes.
issued_atstringnoIssue date (YYYY-MM-DD)
valid_untilstringnoValidity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar.
supersedesstringnoUUID 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 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.

ParameterTypeRequiredDescription
within_daysnumbernoLook-ahead window in days (default 30). Documents expiring within this many days (or already expired) are returned.
company_idstringnoRestrict to one company.
include_expiredbooleannoInclude already-expired documents (default true)

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

ParameterTypeRequiredDescription
querystringnoCase-insensitive substring across question_canonical, short_answer, long_answer_md.
tagsstring[]noOnly answers carrying ALL of these facet tags.
statusenumnoFilter by governance status. One of: draft, approved, stale, retired.
languageenumnoFilter by language. One of: pt, en.
company_idstringnoCompany-specific answers for this company id.
shared_onlybooleannoOnly ecosystem-shared answers (company_id IS NULL)
limitnumbernoMax rows (default 50, max 100)
offsetnumbernoRows to skip (pagination)

create_answer

Add a reusable answer to the library.

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

ParameterTypeRequiredDescription
question_canonicalstringyesThe canonical question/prompt this answer responds to.
short_answerstringnoOne/two-line summary.
long_answer_mdstringnoFull answer in Markdown.
languageenumnoLanguage (default pt) One of: pt, en.
company_idstringnoCompany id, or NULL/omit for ecosystem-shared.
translation_ofstringnoUUID of the source-language answer this one translates.
statusenumnoGovernance status (default draft) One of: draft, approved, stale, retired.
expires_atstringnoWhen this answer should be re-reviewed (ISO timestamp)
tagsstring[]noFacet tags (e.g. institutional, impact, safeguarding)
sourceobjectnoProvenance JSON (plan slug, submission id, vault doc)
sheet_idstringnoUUID 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_kindenumnoWhich 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 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.

ParameterTypeRequiredDescription
idstringyesUUID of the answer to update.
question_canonicalstringnoUpdate the canonical question.
short_answerstringnoUpdate the short answer.
long_answer_mdstringnoUpdate the long answer (Markdown)
languageenumnoUpdate language. One of: pt, en.
company_idstringnoReassign company (NULL = ecosystem-shared)
translation_ofstringnoUpdate the translation pairing.
expires_atstringnoUpdate the re-review date.
sourceobjectnoReplace the provenance JSON.
tagsstring[]noIf provided, FULLY REPLACES the answer's tags with this set.
record_revisionbooleannoIf true, append a row to answer_revisions capturing the new long_answer_md PLUS sheet_id and answer_kind (default false)
sheet_idstringnoAttach 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_kindenumnoWhich 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 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.

ParameterTypeRequiredDescription
idstringyesUUID of the answer.
statusenumyesNew governance status. One of: draft, approved, stale, retired.
expires_atstringnoOptionally (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 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.

ParameterTypeRequiredDescription
within_daysnumbernoAlso include answers expiring within this many days (default 0 = only already-expired + status=stale)
company_idstringnoRestrict to one company.
limitnumbernoMax rows (default 100, max 200)

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

ParameterTypeRequiredDescription
kindenumyesgrant_notice=edital, rfp, public_tender=licitação, incentive_program=incentivo, award=prêmio. One of: grant_notice, rfp, public_tender, incentive_program, award.
titlestringyesHuman title of the opportunity.
notice_numberstringnoNúmero do edital / notice number.
urlstringnoPublic URL of the opportunity notice.
funder_idstringnoUUID of an existing funder.
funder_namestringnoIf no funder_id, create a funder with this name and link it.
funder_kindenumnoKind of the on-the-fly funder (only used with funder_name) One of: federal, state, municipal, multilateral, foundation, corporate.
source_media_idstringnopublic.media id of the notice PDF in the vault.
published_atstringnoPublication timestamp (ISO)
questions_deadlinestringnoClarification-questions deadline (ISO)
submission_deadlinestringnoSubmission deadline (ISO)
total_valuenumbernoTotal value of the opportunity.
currencystringnoCurrency code (default BRL)
statusenumnoPipeline status (default scouted) One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled.
go_no_go_notesstringnoGo/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 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.

ParameterTypeRequiredDescription
opportunity_idstringyesUUID of the opportunity.
requirementsobject[]yesOne or more requirements to insert.

check_eligibility

Cross a company against an opportunity's requirements and return a per-requirement verdict + gap list.

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

ParameterTypeRequiredDescription
opportunity_idstringyesUUID of the opportunity.
company_idstringyesUUID of the applying company.

list_opportunities

List editais/RFPs/public tenders/incentive programs/awards already registered in the pipeline.

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

ParameterTypeRequiredDescription
statusenumnoFilter by pipeline status. One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled.
funder_idstringnoFilter by funder UUID.
kindenumnoFilter 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.
limitnumbernoMax rows (default 50, max 100)
offsetnumbernoRows 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 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.

ParameterTypeRequiredDescription
querystringnoCase-insensitive substring across title, notice_number, and funder name.
statusenumnoFilter by pipeline status. One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled.
kindenumnoFilter by opportunity kind. One of: grant_notice, rfp, public_tender, incentive_program, award.
funder_idstringnoFilter by funder UUID.
limitnumbernoMax rows (default 50, max 100)
offsetnumbernoRows to skip (pagination)

set_opportunity_status

Move an opportunity through the pipeline: scouted → analyzing → go/no_go → preparing → submitted → won/lost, or cancelled.

Set Opportunity Status

Move an opportunity through the pipeline: scouted → analyzing → go/no_go → preparing → submitted → won/lost, or cancelled. Use cancelled to archive an opportunity that is no longer pursued (there is no separate archived status). Pass notes to set go_no_go_notes (replaces the previous text); omit it to leave the notes untouched. Use list_opportunities to find the id.

ParameterTypeRequiredDescription
opportunity_idstringyesUUID of the opportunity.
statusenumyesNew pipeline status. One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled.
notesstringnoOptionally set go_no_go_notes (the rationale for the decision; null clears it)

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

ParameterTypeRequiredDescription
opportunity_idstringyesUUID of the opportunity.
company_idstringyesUUID of the primary applying company (recorded as lead)
statusenumnoPipeline status (default draft) One of: draft, internal_review, submitted, clarifications, won, lost, withdrawn.
plan_slugstringnoplans-app plan slug used as the authoring workspace.
consortiumobject[]noAdditional consortium companies beyond the lead.

upsert_section

Create or update a section of a submission.

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

ParameterTypeRequiredDescription
submission_idstringyesUUID of the submission.
idstringnoUUID of an existing section to UPDATE (omit to CREATE)
titlestringnoSection title.
sort_ordernumbernoOrdering within the submission.
answer_idstringnoLibrary answer this section reuses (provenance FK; also gives usage-tracking for free)
content_mdstringnoThe final, opportunity-adapted text (Markdown)
statusenumnoSection drafting status (default todo on create) One of: todo, drafted, reviewed, final.
sheet_idstringnoUUID of the work.sheets spreadsheet that IS this section (typically a copy_sheet of the opportunity budget template). Pass null to detach it.
section_kindenumnoWhich 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 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.

ParameterTypeRequiredDescription
submission_idstringyesUUID of the submission.
company_document_idstringyesUUID of the vault document (proposals.company_documents) that satisfies the requirement.
requirement_idstringnoUUID of the opportunity_requirement this document fulfills (optional)
statusenumnoAttachment 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.

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

ParameterTypeRequiredDescription
submission_idstringyesUUID of the submission.

harvest_answers

The loop that compounds: promote reusable text written in a past submission back into the answer library.

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

ParameterTypeRequiredDescription
submission_idstringyesUUID of the submission to harvest from.
section_idsstring[]noRestrict to these section UUIDs (default: all qualifying sections)
only_unlinkedbooleannoOnly harvest sections with no answer_id yet (default true)
company_scopedbooleannoIf true, set the new answers' company_id to the submission's company (default false = ecosystem-shared)

set_submission_status

Move a submission through its lifecycle: draft → internal_review → submitted → clarifications → won/lost, or withdrawn.

Set Submission Status

Move a submission through its lifecycle: draft → internal_review → submitted → clarifications → won/lost, or withdrawn. Moving to submitted stamps submitted_at=now when it is still empty. Use list_submissions to find the id. This changes only the submission, not its opportunity: call set_opportunity_status for that.

ParameterTypeRequiredDescription
submission_idstringyesUUID of the submission.
statusenumyesNew submission status. One of: draft, internal_review, submitted, clarifications, won, lost, withdrawn.

list_submissions

List submissions (one company applying to one opportunity).

List Submissions

List submissions (one company applying to one opportunity). Filter by opportunity_id, company_id, or status. Use it to find the submissions of an opportunity before get_submission_checklist or set_submission_status.

ParameterTypeRequiredDescription
opportunity_idstringnoFilter by opportunity UUID.
company_idstringnoFilter by applying company UUID.
statusenumnoFilter by submission status. One of: draft, internal_review, submitted, clarifications, won, lost, withdrawn.
limitnumbernoMax rows (default 50, max 100)
offsetnumbernoRows to skip (pagination)