Skip to main content

MCP Server

CVD Portal exposes a Model Context Protocol (MCP) server so AI agents can act on the platform directly, the same way a script would use the REST API. An agent connected to it can submit a vulnerability report, read a company's CRA compliance status, and generate a CSAF advisory, without any custom integration code.

There are two servers, one for each side of a disclosure.

ServerEndpointWho it is forAuth
Manufacturer/api/mcpThe company operating a portal, acting inside its own workspaceOAuth 2.1 token or API key
Researcher/api/mcp/publicAnyone reporting a vulnerability to a manufacturerNone

The rest of this page covers the manufacturer server. The researcher server is documented under Public MCP server below.

The manufacturer server accepts companies on the Reporting plan or higher. Each tool also checks the plan.

  • Reporting. get_getting_started, get_next_deadline, get_next_task, the Article 14 tools record_article14_decision, prepare_article14_stage and open_incident, record_remediation_decision, upload_sbom, list_products with the id and name only, and submit_job and get_job_status for drafting jobs.
  • Compliance. All other tools. These are the CRA product tools, get_conformity_gaps, get_clause_mapping, record_assessment_result, classification and conformity jobs, submission triage, advisories, get_compliance_status and ask_expert.

On the Reporting plan, an API key can carry only the read scope, so it reaches only the read tools. Use browser sign-in (OAuth) for the Article 14 and remediation tools. On the Compliance plan, an API key can carry any MCP scope. The REST API under /api/v1 still needs the Enterprise plan, also for a key issued on Compliance. No MCP tool files a notification with ENISA. You file on the Single Reporting Platform yourself.

The server accepts either an OAuth 2.1 access token obtained via authorization code grant with PKCE, or a legacy Bearer API key. When accessed without credentials, it returns an HTTP 401 status with an RFC 9728 WWW-Authenticate header pointing to /.well-known/oauth-protected-resource so compatible agents can discover the authorization server and register automatically.

Endpoint​

https://cvdportal.com/api/mcp

The transport is streamable HTTP. There are two ways to authenticate.

MethodUse it forWhat happens
Browser sign-in (OAuth 2.1)Claude Code, the Claude apps, and other MCP clients that a person usesThe client opens your browser. You sign in to CVD Portal and approve the access. The client stores and renews the token.
API keyScripts, CI/CD pipelines, and servers with no browserYou send the key in the Authorization header on every request.

Use browser sign-in when a person runs the agent. The token is bound to you, so the audit log names you, and it is the only credential that can act for a client company (see Acting for a client company). An API key belongs to the company, not to a person.

Connecting with browser sign-in​

Claude Code

Add the server without a header:

claude mcp add --transport http cvd-portal https://cvdportal.com/api/mcp

Then, in an interactive claude session, run /mcp, select cvd-portal, and select Authenticate. Your browser opens.

Claude desktop app and claude.ai

Add a custom connector with the URL https://cvdportal.com/api/mcp and connect it. Your browser opens.

What you see in the browser

  1. The CVD Portal sign-in page, if you are not signed in already. Sign in with your password or with a sign-in provider such as Google, Microsoft or GitHub. If your account at that provider uses a passkey, you confirm with your fingerprint or face there.
  2. The consent screen. It names the client, your company, and the permissions (scopes) the client asks for. The client chose its own name, so the screen marks it Unverified name. The box Authorization is sent to shows the host that receives the authorization. Check that host before you select Authorize Access.
  3. The browser returns to the client, and the client is connected.

How it works

The client needs no configuration beyond the URL. A request without a token gets HTTP 401 with a WWW-Authenticate header that points to /.well-known/oauth-protected-resource. From there the client finds the authorization server, registers itself (RFC 7591), and starts the authorization code flow with PKCE. An access token lasts 1 hour. The client renews it with a refresh token, which lasts 30 days. After 30 days without use, you sign in again.

The consent screen asks only for the MCP scopes: read, submissions:write, submissions:triage, submissions:communicate, products:write, sbom:write, article14:prepare, advisory:draft and conformity:prepare. MCP clients name the MCP server as the resource of the sign-in. For such a sign-in, the platform removes any REST-only scope such as gdpr:export, even when the client asks for it.

A client gets only the scopes that it registered. The platform removes each requested scope that the client did not register, and the consent screen does not show it. A client that asks for no scopes gets the MCP scopes that it registered. A client that asks only for scopes that it did not register gets the error invalid_scope and no consent screen.

The token can only call tools whose scope you approved on the consent screen. If every tool call fails with "OAuth token lacks the required scope", remove the connection, add it again, and check the permissions on the consent screen.

See Authentication for the endpoints and the standards they follow.

Connecting with an API key​

Create and scope keys under Settings → API Keys in the dashboard. On the Reporting plan, you can create a key with only the read scope for the MCP server. On the Compliance plan, you can create a key with any combination of the MCP scopes: read, submissions:write, submissions:triage, submissions:communicate, products:write, sbom:write, article14:prepare, advisory:draft and conformity:prepare. Keys with other scopes need the Enterprise plan. A key with no scopes has full access. A scoped key can only call tools whose scope it carries.

Claude Code

claude mcp add --transport http cvd-portal https://cvdportal.com/api/mcp \
--header "Authorization: Bearer <your-api-key>"

Any MCP client

Point the client at https://cvdportal.com/api/mcp with the Authorization: Bearer <your-api-key> header. Clients that support the Model Context Protocol streamable HTTP transport discover the tools automatically.

Rolling out to your organisation​

Use this section when you add CVD Portal for all people in a Claude Team or Claude Enterprise organisation. Each person signs in to CVD Portal with their own account. CVD Portal does not support central sign-in for the whole organisation.

Add the connector once

  1. An Owner or Primary Owner of the Claude organisation opens Organization settings, then Connectors. On Claude Enterprise, a person with a custom role that includes Manage access to Libraries can also do this.
  2. Select Add, then Custom, then Web. Enter a name and the URL https://cvdportal.com/api/mcp.
  3. Leave the OAuth Client ID and Client Secret empty. Claude registers itself with CVD Portal.
  4. Tell each person to find the connector, select Connect, and sign in to CVD Portal.

Invite each person under Settings → Team before they connect. A person who signs up without an invitation gets a new, empty company, and the connector then works in that company, not in yours. Each person approves the scopes on the consent screen. At each tool call, CVD Portal checks the scope and the role of that person. A MEMBER cannot call the tools that need ADMIN. The audit log names each person.

Do not put an API key in an organisation connector

An API key belongs to the company, not to a person. Each person who uses a shared key gets the full access of that key, and the role of the person is not checked. The audit log does not name the person. An API key cannot act for a client company. Use API keys only for scripts and pipelines.

Remove access for one person

Remove the person under Settings → Team. This revokes all OAuth tokens of the person immediately. A password change or a password reset also revokes them.

If you disable the person only in your identity provider, the connector continues to work. CVD Portal has no SCIM, and a refresh token stays valid for up to 30 days. Always remove the person in CVD Portal too.

What CVD Portal does not support

  • A list of connections. No page shows which people have connected which clients.
  • Revocation of one client. You cannot revoke one client, for example Claude, and keep the other connections of the same person. Removing the person revokes all of their tokens.
  • An allowlist of clients. Any MCP client can register itself. The consent screen marks the client name as Unverified name, and each person decides whether to approve it.
  • A pre-registered client. You cannot create an OAuth client ID and secret for your organisation in the dashboard.

Scopes​

Scoped API keys and OAuth tokens restrict which tools an agent can call.

ScopeDescription
readRead vulnerabilities, products, compliance and SBOM metadata
submissions:writeSubmit vulnerability reports
submissions:triageTriage and progress vulnerability reports
submissions:communicateDraft messages to reporters for approval
article14:preparePrepare CRA Article 14 notification dossiers
advisory:draftDraft CSAF and OpenVEX security advisories
products:writeRegister products, record classifications, and build the threat model
conformity:prepareRun conformity jobs, record assessments, and sign off Annex I requirements
sbom:writeUpload an SBOM with upload_sbom. On the REST API this scope also needs the Enterprise plan

Tools​

ToolScope requiredWhat it does
list_submissionsreadLists vulnerability reports with optional stage or status filtering.
get_submissionreadReturns decrypted case details excluding reporter contact information.
get_submission_journeyreadReturns the active journey stages, current status, and next step.
acknowledge_submissionsubmissions:triageAcknowledges a report and sends the standard template message.
triage_submissionsubmissions:triageSets severity, CVSS vector and score, and affected components.
link_submission_productsubmissions:triageLinks a vulnerability report to a workspace product.
record_article14_decisionarticle14:prepareRecords whether a vulnerability is actively exploited under CRA Article 14.
prepare_article14_stagearticle14:prepareSaves draft narrative fields and generates copy-ready SRP text.
assign_coordinatorsubmissions:triageAssigns a vulnerability coordinator from your team.
record_remediation_decisionsubmissions:triageRecords the remediation decision and target deadline.
record_fix_verificationsubmissions:triageConfirms that a deployed fix has been verified in production.
close_submissionsubmissions:triageCloses a vulnerability report as RESOLVED or DISMISSED with justification.
draft_reporter_messagesubmissions:communicateEnqueues a message to the vulnerability reporter for human approval.
request_advisory_publicationadvisory:draftEnqueues publication of an advisory draft for human approval.
open_incidentsubmissions:writeCreates a severe incident report and starts CRA Article 14 tracking. Severity is HIGH or CRITICAL, and HIGH when omitted.
submit_vulnerabilitysubmissions:writeFiles a disclosure report in your inbox, or in a client inbox with onBehalfOfCompanyId. Requires a description. The response names the company that received the report. The audit log of that company records the report with the OAuth client or the API key.
get_compliance_statusreadReturns the verification status of your company account, ACCOUNT_VERIFIED or ACCOUNT_NOT_VERIFIED, with recommendations. It checks two account flags and reads no product, so it is not a verdict on a product. For a product, read readyForDocAssembly from get_conformity_gaps.
generate_csaf_advisoryreadReturns the machine-readable advisory document for an existing advisory.
assess_responsereadEvaluates an incident or vulnerability response plan under CRA Article 14.
get_getting_startedreadReturns the ordered first steps for the company, and the portal slug, the published portal URL and whether the portal is on. Call it first.
get_next_deadlinereadReturns the soonest open Article 14 deadline.
get_next_taskreadReturns the next open CRA obligation.
submit_jobdepends on the job typeRuns a long job in the background. See Background jobs.
get_job_statusreadReturns the status, progress and result of a job. The plan check follows the job type, the same as submit_job.
ask_expertreadAnswers a CRA question with citations. answer is plain text. When the model returns more fields beside its answer, they are in details. Advisory only. A rule check reads each answer for article numbers of the 2022 CRA proposal and for wrong statements of Article 13(8) and Article 14. When it finds one, citationCheck lists each finding with its sentence, and a note is added to the end of answer.
lookup_regulatory_referencereadSearches the regulatory reference library by topic, document code, clause number or CRA product category. With standard, only the documents of that standard are searched. The tool ignores case, spaces and hyphens in the code, so FprEN 40000-1-2 and fpren-40000-1-2 find the same documents. A standard that matches no document returns count: 0 and a message with the known codes of the same publisher.

link_submission_product, record_article14_decision, prepare_article14_stage, assign_coordinator and close_submission need the ADMIN role, as in the dashboard. With a browser sign-in token, the platform reads your role in the token's company at each call. That is your account role in your own company, or your membership role in a client company where you hold a seat. A MEMBER gets an error even when the token has the scope. A plain API key belongs to the company, so its scopes alone decide.

CRA product tools​

These tools cover the product journey, from registration to the technical file. They need the Compliance plan features of the company they act for.

ToolScope requiredWhat it does
list_productsreadLists active products with their ids, the decided classification, the unconfirmed suggestion, and progress counts. On the Reporting plan it returns only the id and the name, and detail is basic.
create_productproducts:writeRegisters a product. Records a class suggestion only. Refuses a duplicate name and returns the existing id.
set_product_classificationproducts:writeRecords the classification decision (Annex III or IV category, so the Article 32 route) and its legal basis. Send categoryId: null for a default-class product. An unknown categoryId is refused, and the error lists each valid id with its label, one per line. The response gives classificationDecidedAt, the justification, the standards applied and the Article 32 route in conformityRoute. Needs an OAuth token.
set_notified_bodyconformity:prepare or products:writeRecords the notified body name and 4-digit identification number for a route that needs third-party assessment. This closes the notified_body_missing gap. The certificate number is optional. Returns required: false when the route allows Module A. Needs an OAuth token.
generate_product_threatsproducts:writeGenerates STRIDE threat candidates. Does not save. Check source: catalogue means generic entries, not an analysis. Each threat proposes initialMisconfiguration (true, false or null). Can take more than a minute.
propose_threat_treatmentproducts:writeProposes a mitigation and the residual risk for one threat. Does not save. Needs description (1 to 2000 characters), likelihood and impact. Both accept the five-level treatment scale and the scale that generate_product_threats returns, so a generated threat passes in as it is. Likelihood rare, unlikely, possible, likely or almost_certain, or low, medium, high, which map to unlikely, possible, almost_certain. Impact negligible, minor, moderate, major or catastrophic, or low, medium, high, critical, which map to minor, moderate, catastrophic, catastrophic. Each three-level word takes the highest level in its range. The response's inherent gives the mapped values. Write the same values on the risk row.
get_clause_mappingreadReturns the mapping in force for one CRA clause. clauseRef.part is 1 for Part I or 2 for Part II, and clauseRef.point is a number with an optional letter, for example {instrument:'CRA', annex:'I', part:'1', point:'2b'} for Annex I Part I (2)(b). The tool reads II as 2 and (2)(b) as 2b. clauseRef can also be a string: Annex I, Part I, (2)(b), the ref that get_threat_model returns, or the key CRA:I:1:2b.
get_threat_modelreadReturns the saved assets, threats and risks, and every Annex I requirement with its key, evidence and sign-off. A requirement whose sign-off was revoked has lastRevokedBy and lastRevokedAt. Also returns the evidence attached to the product (attachedEvidence), and for each requirement the attached evidence that can support it (suggestedControlIds). annexIApplicability lists the 13 Part I(2) letters with the recorded decision. article13Duties lists the Article 13 paragraphs that take a record, with the stored status, evidence, owner and allowedStatuses.
save_threat_modelproducts:writeSaves assets, threats and risks row by row. null deletes a row. Sets risk treatment. Any validation failure refuses the whole save. A row that has no value for a required field, or a value outside its type, is saved, and warnings names it, for example assets.a1: missing category, classification. An asset needs name, category and classification. A threat needs category, asset, description and status. A risk needs description. A row with no productId gets the id of the product, because the gap analysis reads only the rows of the product. A row counts in the export gate only when it is confirmed: an asset with confirmed: true, and a threat with source set to manual (or catalog for a catalogue threat). A threat that the agent confirms is recorded as confirmed by the person who owns the token, at the time of the save, with confirmedVia: "mcp". The server writes confirmedAt, confirmedBy and confirmedVia, and ignores the values that the agent sends. A token that does not belong to a person cannot confirm a threat, so the row is stored as a proposal (source: "ai") and warnings names it. A source other than manual, catalog or ai is also stored as a proposal. A catalogue baseline threat stays a baseline row until its description is rewritten for the product. A threat's initialMisconfiguration is stored as a proposal with initialMisconfigurationProposed: true. A cause that a person recorded stays, whatever the agent sends.
set_annex_i_evidenceconformity:prepare or products:writeRecords the evidence for Annex I requirements: a free-text note, links to evidence attached to the product, or both. This closes the no_requirement_evidenced gap. A link must name evidence that the product holds. It does not sign off. The row is marked as written by an agent, and reviewRequired lists these keys. The technical file status lists each one as "Review the agent evidence" until a named person signs the requirement off again, in the dashboard or with signoff_annex_i_requirements. A person's edit of the evidence does not close it.
set_annex_i_applicabilityconformity:prepare or products:writeRecords whether each Annex I Part I(2) requirement, letters a to m, applies. not_applicable needs a justification. See Applicability and Article 13 duties over MCP. Needs an OAuth token.
set_article13_dutiesconformity:prepare or products:writeRecords the status, evidence reference and owner of the Article 13 duties that CVD Portal does not record elsewhere. See Applicability and Article 13 duties over MCP. Needs an OAuth token.
set_product_description_factsconformity:prepare or products:writeRecords the software versions that affect compliance and whether the product has hardware, Annex VII point 1(b) and 1(c). See Annex VII facts over MCP. Needs an OAuth token.
set_support_periodconformity:prepare or products:writeRecords the Article 13(8) support period, the expected time in use and how the period was determined, Annex VII point 4. See Annex VII facts over MCP. Needs an OAuth token.
signoff_annex_i_requirementsconformity:prepare or products:writeSigns off or revokes Annex I requirements. See Annex I sign-off over MCP.
list_artifactsreadLists the 88 technical-file artifacts of a product, or the artifacts of one clause with clause (for example 6.2). Each row has the id, the clause, the type, the description, the stored status (missing when the product has no entry), generatedBy (null when a person wrote the text), the content length and the last update. It returns no content. exportGaps lists the open export gates.
get_artifactreadReturns the text of one technical-file artifact, its status (missing when the product has no entry), generatedBy and the last update. revision tells what save_artifact does with it. See Technical-file artifacts over MCP.
save_artifactproducts:writeWrites the text of one technical-file artifact as a draft for review. See Technical-file artifacts over MCP. Needs ADMIN.
get_conformity_gapsreadLists open gaps across Annex I, technical documentation and evidence, and tells whether the technical file is ready. See Is the technical file ready?. An evidence gap on a requirement that is already signed off has signedOffWithoutEvidence: true. The product_context_missing gap names each empty Clause 6.2 input and the call that fills it.
get_release_gatereadReturns the CI/CD release gate verdict for one product, PASS or BLOCK, with the reason for each of the six checks. It stores nothing and leaves out the approver email. A PASS is not a statement of conformity. See Release gate.
record_assessment_resultconformity:prepare or products:writeRecords a dated assessment of one CRA clause. The acceptance rules of the mapping give the verdict. The tool has no verdict input. To change the verdict, add the evidence that the rules ask for and call it again. clauseRef takes the same forms as get_clause_mapping.
upload_sbomsbom:writeStores an SBOM for one product, or for the whole company when productId is omitted, and replaces the stored one. With the optional part, for example firmware, it replaces only that part of the product SBOM. part needs productId. Accepts CycloneDX or SPDX JSON or a dependency manifest. Scans the components against OSV.dev and returns the CRA minimum-elements check. Needs an ADMIN and the Reporting plan.
get_declaration_of_conformityreadBuilds the technical file with the Annex V Declaration of Conformity through the export gates. Returns the gaps when the file is not ready. ready: true means that the export gate is open. The gate is a minimum. doc.fileStatus tells whether the file is complete. Does not sign the declaration.

Is the technical file ready?​

Use readyForDocAssembly from get_conformity_gaps. It is true only when gaps is empty and the technical file of each product is complete as recorded.

  • gaps lists the export gate, classification and Annex I evidence and sign-off checks, each with the call that closes it.
  • fileStatus has one entry per product. It is the status that get_declaration_of_conformity prints in doc.fileStatus, with every open gap in the file. These include Annex I applicability decisions, Article 13 duties, the Annex V and Annex VII points, and the machine drafts that a gate counts and no person has reviewed (id review:<artifactId>, area draft_review). Each gap has an action and the product tab where you close it.
  • An incomplete file also adds one entry to gaps with ref: "technical_file_status". So an empty gaps list always means a complete file.
  • With no matching product, readyForDocAssembly is false.

get_declaration_of_conformity can return ready: true while doc.fileStatus says "Incomplete". The export gate opens on a minimum, for example one requirement signed off. get_compliance_status checks the company account only.

Background jobs​

submit_job takes a type and an input object. The input must match the type. The platform checks the input when you submit the job. A bad input returns an error at once and queues no job.

TypeInputResult
CLASSIFY_PRODUCTproductId, or text, or productName and description. With productId alone, the platform uses the stored product name and description.A class suggestion. It does not change the product. Use set_product_classification to record the decision.
IMPORT_PREFILLdocumentContent (text, required), productId, optional documentName and documentRefExtracts facts from the document and prefills the product assessment, each field with provenance. It drafts the Clause 6.2 inputs except C6.2-IN-03, which an agent writes with save_artifact. Send the text of a PDF, not the file.
AUTO_ENRICHproductId (required)Fills the fixed risk methodology, the SBOM entry, the issue log and the monitoring record. Lists candidate standards for the product category in candidateStandards. These are suggestions only. The job never records a standard as applied. Record the standards you applied in the product's Classification. vulnerabilitiesMonitoredCount is the number of components that the last complete vulnerability check covered. sbomCheck gives the component count, the number the check can query, and the reason when it covered fewer than all. The check asks OSV.dev about a component with a version and a package ecosystem, and NVD about a component with a CPE that has a version. It skips the others, for example a component whose package URL type is github or generic and that has no CPE.
ASSEMBLE_DOSSIERproductId (required)Assembles the technical dossier index and requests approval.
DRAFT_ARTIFACTartifactId and productId (required), optional inputs. Or a free-form prompt, or system with user.Drafts one CRA artifact.

Examples:

{ "type": "CLASSIFY_PRODUCT", "input": { "productId": "prod_123" } }
{ "type": "DRAFT_ARTIFACT", "input": { "artifactId": "C6.3-OUT-02", "productId": "prod_123" } }
{ "type": "IMPORT_PREFILL", "input": { "productId": "prod_123", "documentContent": "Section 1 ...", "documentName": "User manual" } }

POST /api/v1/jobs uses the same check and returns HTTP 400 for a bad input.

A job for a product that is not in the job's company fails with "Product not found in this workspace". It does not report success.

Filing with the ENISA Single Reporting Platform stays with a person. Tools prepare draft text and calculate deadlines. Authorized personnel review the draft, file on the SRP, and record the reference in the portal.

Tool annotations​

Every tool on both servers sends the MCP annotations readOnlyHint, destructiveHint, idempotentHint and openWorldHint. Claude uses them to decide when to ask you. A read-only tool can run without a confirmation. A destructive tool always asks first.

AnnotationTools
Read-only. Changes no dataget_getting_started, get_compliance_status, generate_csaf_advisory, assess_response, get_next_deadline, get_next_task, get_job_status, ask_expert, get_conformity_gaps, get_release_gate, get_clause_mapping, lookup_regulatory_reference, list_products, generate_product_threats, propose_threat_treatment, get_threat_model, list_artifacts, get_artifact, get_declaration_of_conformity, list_submissions, get_submission, get_submission_journey, find_vendor_portal, track_vulnerability_report
Destructive. Can replace or remove data that existssubmit_job, set_product_classification, set_notified_body, save_threat_model, set_annex_i_evidence, set_annex_i_applicability, set_article13_duties, set_product_description_facts, set_support_period, save_artifact, signoff_annex_i_requirements, upload_sbom, acknowledge_submission, triage_submission, link_submission_product, record_article14_decision, prepare_article14_stage, assign_coordinator, record_remediation_decision, record_fix_verification, close_submission
Write that only adds datasubmit_vulnerability, record_assessment_result, create_product, draft_reporter_message, request_advisory_publication, open_incident, submit_vulnerability_to_vendor, subscribe_to_report
Open world. Reaches outside your workspaceask_expert, generate_product_threats, propose_threat_treatment and submit_job (AI models), upload_sbom (OSV.dev), acknowledge_submission, triage_submission and close_submission (email or status push to the reporter), find_vendor_portal, submit_vulnerability_to_vendor and subscribe_to_report (other manufacturers' portals and your callback URL)

generate_product_threats and propose_threat_treatment need the products:write scope but save nothing, so they are read-only. A read tool that writes an audit log entry is still read-only.

Acting for a client company​

A consultancy can act for a client company where the consultancy user holds a seat (a partner membership). Pass the client's company id as onBehalfOfCompanyId on any tool that reads or changes company data.

  • Only a user-bound OAuth token can act for another company. A plain API key stays in its own company.
  • A read needs a membership in the client company. A write needs ADMIN there.
  • The membership is checked again on every call. When the client removes the seat, access stops at once.
  • The plan check uses the client's plan, not the consultancy's. A refusal names the client and its plan, for example "This tool requires the sbom feature, and Acme is on the Free plan. Upgrade the plan of Acme."
  • Background jobs run in the client company, so the job worker finds the client's products.

Without onBehalfOfCompanyId, every tool works in the company of the key or token. The company always comes from the verified credential and the membership check, never from a free tool argument.

Annex I sign-off over MCP​

signoff_annex_i_requirements records a legal attestation in the technical file.

  • The signatory is the person who owns the OAuth token. The platform takes the name and email from that user account, never from tool arguments.
  • A plain API key cannot sign.
  • The signatory must be ADMIN in the company the call acts for.
  • Each signed entry records that it came through MCP and through which OAuth client. The audit log records the same.
  • A requirement can be signed off with no evidence. The tool saves the sign-off and returns a line in warnings for each applicable requirement it signed with no evidence note and no linked evidence. Record the evidence with set_annex_i_evidence, or revoke the sign-off.
  • Until the evidence is recorded, get_conformity_gaps keeps the evidence gap of that requirement open with signedOffWithoutEvidence: true, and its summary counts these requirements.
  • A sign-off also counts as the review of evidence that an agent recorded with set_annex_i_evidence, and closes its "Review the agent evidence" gap. Sign only after the signatory has read that evidence. A sign-off from before the evidence does not cover it, so sign the requirement again.
  • A revoke removes the sign-off and records who revoked it and when, in lastRevokedBy, lastRevokedByEmail and lastRevokedAt. The technical file prints "Withdrawn by" with the name and the date on that row. A later sign-off keeps these fields. A revoke on a row that has no sign-off records nothing.

This differs from the dashboard. In the dashboard, a partner who drills into a client workspace cannot sign. Over MCP, a partner user who is ADMIN in the client company can sign for that client. Call the tool only when the signatory has reviewed the requirement and its evidence and has asked for the signature.

Applicability and Article 13 duties over MCP​

The technical file asks for a decision on each Annex I Part I(2) requirement and a record for each Article 13 duty that CVD Portal does not record elsewhere. get_declaration_of_conformity lists each open one in doc.fileStatus, as "Decide whether Part I(2)(x) applies" and "Put Article 13(n) in place". Two tools close them. Read the keys and the current values with get_threat_model first, in annexIApplicability and article13Duties.

Both tools follow the provenance rules of the sign-off.

  • Only a user-bound OAuth token can write. A plain API key cannot.
  • The person must be ADMIN in the company the call acts for.
  • The audit log records the change, the user, and the OAuth client as mcp:<clientId>. The response gives the same value in recordedVia.
  • A row you leave out stays as it is. A change made at the same time in the dashboard is not lost.
  • A refused call writes nothing.

set_annex_i_applicability​

Send a decision per letter, applicable or not_applicable, for example {"a":{"decision":"applicable"},"m":{"decision":"not_applicable","justification":"The kettle stores no user data or settings."}}.

  • not_applicable needs a justification. Article 13(4) requires the reason in the technical file. The tool refuses the decision without one.
  • Part I(1) and Part II always apply. The tool refuses a decision on them.
  • The security objectives under a letter follow the decision in the same write. An objective that a person answered differently stays as it is. objectivesUpdated lists the objectives that changed.
  • A stored "does not apply" decision with no reason blocks the save, because the file cannot print it. The error names that letter. Send a decision with a justification for it in the same call.
  • A letter that is signed off and that you mark not_applicable keeps its sign-off. warnings names it.

set_article13_duties​

Send a record per paragraph number, for example {"6":{"status":"in_place","evidence":"PSIRT-PROC-04 rev 2","owner":"PSIRT lead"}}.

FieldValues
statusnot_started, in_progress, in_place or not_relied_on
evidenceWhere the evidence lives, for example a document id, a URL or a ticket. 1000 characters at most. null clears it
ownerThe person or team that owns the duty. null clears it
  • in_place needs an evidence reference, sent in the same call or stored before.
  • not_relied_on is only for 13(10) and 13(11), which apply only to a manufacturer that relies on them. The tool refuses it for any other paragraph.
  • A paragraph that CVD Portal records elsewhere, for example 13(12), takes no record. The tool refuses it and lists the paragraphs that do.
  • A field you leave out stays as it is.
  • A company duty (scope: "company") is stored on each product, the same as in the dashboard. Record it for each product. warnings reminds you.

Annex VII facts over MCP​

The technical documentation index marks each Annex VII point present or missing. get_declaration_of_conformity lists each missing point in doc.fileStatus as "Supply Annex VII point n". Most points rest on artifacts, which save_artifact drafts. Points 1 and 4 also need facts that only the manufacturer can state. Two tools record them.

PointWhat it needsOver MCP
1The product context (C6.2-OUT-01), the software versions, the hardware decision, photographs of a hardware product, and the user instructions (C7.5-OUT-04)save_artifact for the two artifacts. set_product_description_facts for the versions and the hardware decision. Attach the photographs in the dashboard.
2The architecture (C7.4-OUT-01), the SBOM and a contact address for vulnerability reportssave_artifact and upload_sbom. The contact address comes from the security contact email and the security.txt scan of the company.
3The evaluated risks (C6.4-OUT-02) and the Annex I Part I(2) applicability table (C6.4-OUT-03)save_artifact
4How the support period was determinedset_support_period
5The risk treatment decisions that describe the solutions adopted (C6.5-OUT-01)save_artifact
6The verification and validation results (C7.6-OUT-01) or an attached test reportsave_artifact. Attach a test report in the dashboard.

Both tools need a user-bound OAuth token and an ADMIN. The audit log records the change, the user, and the OAuth client as mcp:<clientId>. The response gives the same value in recordedVia. Record only facts and decisions that the manufacturer confirmed.

set_product_description_facts​

  • placedOnMarketVersion lists the software versions that affect compliance with the essential requirements, for example app 4.2.0, firmware 1.8.3. 300 characters at most.
  • isHardware is true for a product with hardware and false for software only.
  • A field you send replaces the stored value. null clears it. A field you leave out stays.
  • photographsNeeded in the response is true for a hardware product. Point 1 stays open until image files of the external features, marking and internal layout are attached to C6.2-IN-05. No tool attaches them. The missing text of the point 1 gap in fileStatus names each open sub-point, the photographs included.

set_support_period​

Send the period, the expected time in use and the basis, for example {"supportPeriodMonths":96,"expectedUseTimeMonths":96,"supportPeriodBasis":"Kettles of this class stay in use for about eight years.","placedOnMarketAt":"2026-03-01"}.

  • The tool runs the same Article 13(8) checks as the dashboard. Five years is the floor, not a default.
  • It refuses a period shorter than the expected time in use, a period under 60 months when the expected time in use is 60 months or more, and an implausible year of placing on the market. A refused call writes nothing.
  • factors holds the evidence per Article 13(8) criterion. A factor you send replaces the stored one, and the others stay.
  • placedOnMarketAt is the date of placing on the market, YYYY-MM-DD. The period runs from it, and the tool computes the end date. Left out, the stored date stays.
  • warnings gives the checks that do not refuse the save, for example start_not_recorded when no date of placing on the market is known.

Technical-file artifacts over MCP​

The technical file holds 88 artifacts, for example the six Clause 6.2 inputs that describe the product. list_artifacts shows which artifacts hold text, get_artifact returns the text of one, and save_artifact writes one.

  • save_artifact stores the text with status needs_review and generatedBy: "mcp". A person approves it in the dashboard with "Mark reviewed". The tool never sets verified and never writes a sign-off.
  • The export gate counts the draft at once. Until a person reviews it, the file status lists it as a gap, for example "Review the agent draft of C6.2-IN-03", so readyForDocAssembly stays false. Drafts from IMPORT_PREFILL and other machine writers that a gate counts are listed the same way. An edit by a person in the dashboard does not close the gap. Only "Mark reviewed" does.
  • By default the tool writes only an empty artifact. With overwrite: "machine" it also replaces text that a generator wrote, for example import_prefill or an earlier mcp draft.
  • The tool never replaces text that a person wrote, or an artifact that a person approved. It returns an error that tells the agent to ask that person.
  • The response has exportGaps, the export gates that stay open after the save.

To revise a draft, read it with get_artifact first. Its revision field tells what save_artifact does with the artifact.

revisionMeaning
{ "allowed": true, "overwrite": "empty_only" }The artifact is empty. save_artifact writes it with the default overwrite.
{ "allowed": true, "overwrite": "machine" }A generator wrote the text. Send overwrite: "machine" to replace it.
{ "allowed": false, "reason": "..." }A person wrote or approved the text. save_artifact refuses. Ask that person to change it in the dashboard.

The export gate product_context_missing waits for all six Clause 6.2 inputs, C6.2-IN-01 to C6.2-IN-06. The IMPORT_PREFILL job can draft five of them from a product description. No job drafts C6.2-IN-03 (market segments and the state of the art). An agent fills it with save_artifact:

{ "productId": "prod_123", "artifactId": "C6.2-IN-03", "content": "Market segment: consumer kitchen appliances in the EU. Comparable products: ..." }

Public MCP server​

The researcher side needs no account and no API key. It exposes the same actions a researcher already performs in a browser, so an agent can find a vendor's portal, file a report, and follow it afterwards.

https://cvdportal.com/api/mcp/public

Claude Code

claude mcp add --transport http cvd-portal-public https://cvdportal.com/api/mcp/public
ToolWhat it does
find_vendor_portalLooks up a manufacturer's portal by slug or custom domain. Returns the portal URL, the security.txt URL, the policy URL, and the disclosure contact.
submit_vulnerability_to_vendorFiles a report to a manufacturer's portal. Takes a slug plus the report fields. Returns a reference number and a tracking token.
track_vulnerability_reportReads the current status of a filed report from its tracking token.
subscribe_to_reportSubscribes a public HTTPS callback URL to real-time status push notifications using the tracking token. Callbacks receive signed HTTP POST webhooks with status-only payloads.

Three things are worth knowing before an agent uses this.

Record the tracking token. It is the only route back to an anonymous report and it cannot be recovered.

Filing is limited to 5 reports per minute, per portal, per IP, the same ceiling as the web form. The limit is shared, so switching between the form and MCP does not raise it.

track_vulnerability_report returns status, vendor, product, and dates. It never returns the report body, the reproduction steps, the impact, or the contact details. A tracking token proves possession, not authorship.

A disclosure is a permanent record on the vendor's side and cannot be retracted through the API. Confirm the vendor and the finding with the person you are acting for before filing.

Request limits​

Both servers refuse an oversized request before they process it.

ServerLargest request bodyLargest tool input
/api/mcp8 MiB2,000 array elements and object members
/api/mcp/public256 KiB200 array elements and object members

A body over the limit gets HTTP 413. A tool input over the limit gets an error result. An SBOM sent to upload_sbom is one text value, so its size counts against the body limit only. upload_sbom takes up to 5 MB of SBOM text.

Privacy​

Tool-call analytics record which tool ran, its outcome, and timing. Argument and result content, which can include vulnerability details, is not recorded by default. This applies to both servers.

On the manufacturer server, each call is linked to the internal id of the company and, for a browser sign-in, of the user. No name or email address is sent. Calls to the researcher server stay anonymous.

See also​