Skip to main content
Glama

upsert_opportunity

Create a deal, or find the one another system already knows about and update it. Match is on the identity that system issued (externalProvider plus externalId) or on an explicit id, and never on the name: two deals with the same name are routinely two deals. Use create_opportunity when you mean to create one regardless.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "<name>"
    },
    "name": "upsert_opportunity"
  }
}

Input Schema

NameRequiredDescriptionDefault
idNo
nameYes
dryRunNoReport what this write WOULD do and commit nothing. The response carries the same change list, held fields and refusals as the real call, so it is the way to check a risky write, or a whole import, before it lands. Nothing is metered.
stageIdNoOnly records in this stage. Stage ids come from the schema call, which lists every pipeline and its stages in order.
currencyNo
sourceIdNoThe recorded source this claim came from, if one exists. It attaches evidence to the write, so a person reading the field later can see where it came from rather than only who sent it.
companyIdNo
externalIdNo
amountMinorNo
customFieldsNoValues for fields this workspace defined, keyed by the field's key. Read the schema first: a key that is not declared is REFUSED, with the available keys in the error, rather than stored where nobody will find it. Send null to clear one.
ownerActorIdNo
assertionKindNoHOW YOU KNOW. This is the field the product is built around, so say what is true rather than what is strongest: `observed` you saw it stated directly, `imported` it came from a file or another system, `inferred` you worked it out, `user_supplied` a person told you in so many words. They rank, weakest first: inferred < imported < observed < user_supplied < confirmed. A weaker claim does not overwrite a stronger one; it is reported in `held[]` and the stored value stands, so an unattended re-run converges instead of eroding what somebody checked. `confirmed` is not accepted here: it means a person checked the value, and only a signed-in person can set it.
idempotencyKeyNoYour own name for this write, so retrying it cannot do it twice. Send the same key again and you get the SAME result back rather than a second record, which is what makes a re-run safe after a timeout you never saw the answer to. Derive it from what the write is about, not from the clock: `nightly-sync-halden` retries correctly and a timestamp does not.
probabilityBpsNo
expectedVersionNoThe version you read before deciding to write. If the record has moved since, the write is refused instead of overwriting whatever changed. Send it whenever you read, reasoned, and then wrote: without it the last writer wins and neither of you finds out.
attributionLabelNoA short name for the work this write belongs to, shown beside the actor so one credential can distinguish separate jobs. It is recorded as CALLER-SUPPLIED and grants nothing: it changes no permission, no idempotency scope and no quota.
externalProviderNo
overrideStandingNoApply this write even when the value standing in the record was asserted more strongly than this one. Absent means no, which is the safe answer for a re-run. WITHOUT IT, a write can be accepted and change nothing: precedence is confirmed > user_supplied > observed > imported > inferred, and a weaker claim leaves the standing value in place and reports the field in `held[]` rather than failing. That is the whole of why a write can return 200 and not take. WITH it, this write wins regardless of what it is overruling, INCLUDING a value a person confirmed, so an agent that sets it by default has switched the provenance mechanism off. Set it per write, when the caller knows its claim supersedes what is there.
expectedCloseDateNo

Output Schema

NameRequiredDescriptionDefault
recordNoThe record as it stands. Absent on an ambiguous upsert and on a dry run.
statusYesWhether this created a record, matched an existing one on real identity evidence, or found several and committed nothing. `ambiguous` is not an error: it means the evidence you sent fits more than one record, and `candidates` lists them so a caller can choose rather than have one picked for it.
mutationNoWhat the write did. Absent when nothing was written, which is the ambiguous case.
candidatesNoThe records the evidence fits, when it fits more than one. Send the upsert again naming the `id` you mean.
matchReasonNoWhy this matched the record it matched, in words, when it matched one.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed15 schema fields changed
    • addedInput schema / properties / assertionKind / description
      Added value: +"HOW YOU KNOW. This is the field the product is built around, so say what is true rather than what is strongest: `observed` you saw it stated directly, `imported` it came from a file or another system, `inferred` you worked it out, `user_supplied` a person told you in so many words. They rank, weakest first: inferred < imported < observed < user_supplied < confirmed. A weaker claim does not overwrite a stronger one; it is reported in `held[]` and the stored value stands, so an unattended re-run converges instead of eroding what somebody checked. `confirmed` is not accepted here: it means a person checked the value, and only a signed-in person can set it."
    • addedInput schema / properties / attributionLabel / description
      Added value: +"A short name for the work this write belongs to, shown beside the actor so one credential can distinguish separate jobs. It is recorded as CALLER-SUPPLIED and grants nothing: it changes no permission, no idempotency scope and no quota."
    • changedInput schema / properties / customFields / additionalProperties
      Previous value: -trueNew value: +{
      +  "anyOf": [
      +    {
      +      "maxLength": 2000,
      +      "type": "string"
      +    },
      +    {
      +      "type": "integer"
      +    },
      +    {
      +      "type": "boolean"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ]
      +}
    • addedInput schema / properties / customFields / description
      Added value: +"Values for fields this workspace defined, keyed by the field's key. Read the schema first: a key that is not declared is REFUSED, with the available keys in the error, rather than stored where nobody will find it. Send null to clear one."
    • addedInput schema / properties / dryRun / description
      Added value: +"Report what this write WOULD do and commit nothing. The response carries the same change list, held fields and refusals as the real call, so it is the way to check a risky write, or a whole import, before it lands. Nothing is metered."
    • addedInput schema / properties / expectedVersion / description
      Added value: +"The version you read before deciding to write. If the record has moved since, the write is refused instead of overwriting whatever changed. Send it whenever you read, reasoned, and then wrote: without it the last writer wins and neither of you finds out."
    • addedInput schema / properties / idempotencyKey / description
      Added value: +"Your own name for this write, so retrying it cannot do it twice. Send the same key again and you get the SAME result back rather than a second record, which is what makes a re-run safe after a timeout you never saw the answer to. Derive it from what the write is about, not from the clock: `nightly-sync-halden` retries correctly and a timestamp does not."
    • addedInput schema / properties / overrideStanding / description
      Added value: +"Apply this write even when the value standing in the record was asserted more strongly than this one. Absent means no, which is the safe answer for a re-run. WITHOUT IT, a write can be accepted and change nothing: precedence is confirmed > user_supplied > observed > imported > inferred, and a weaker claim leaves the standing value in place and reports the field in `held[]` rather than failing. That is the whole of why a write can return 200 and not take. WITH it, this write wins regardless of what it is overruling, INCLUDING a value a person confirmed, so an agent that sets it by default has switched the provenance mechanism off. Set it per write, when the caller knows its claim supersedes what is there."
    • addedInput schema / properties / sourceId / description
      Added value: +"The recorded source this claim came from, if one exists. It attaches evidence to the write, so a person reading the field later can see where it came from rather than only who sent it."
    • addedInput schema / properties / stageId / description
      Added value: +"Only records in this stage. Stage ids come from the schema call, which lists every pipeline and its stages in order."
    • addedOutput schema / properties / candidates / description
      Added value: +"The records the evidence fits, when it fits more than one. Send the upsert again naming the `id` you mean."
    • addedOutput schema / properties / matchReason / description
      Added value: +"Why this matched the record it matched, in words, when it matched one."
    • addedOutput schema / properties / mutation / description
      Added value: +"What the write did. Absent when nothing was written, which is the ambiguous case."
    • addedOutput schema / properties / record / description
      Added value: +"The record as it stands. Absent on an ambiguous upsert and on a dry run."
    • addedOutput schema / properties / status / description
      Added value: +"Whether this created a record, matched an existing one on real identity evidence, or found several and committed nothing. `ambiguous` is not an error: it means the evidence you sent fits more than one record, and `candidates` lists them so a caller can choose rather than have one picked for it."
  2. First observed

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds valuable behavioral detail beyond the annotations: it explains that the tool may either create or update and that identity is resolved by external identifiers or id, never by name. It does not describe no-op/held precedence behavior itself, but that aspect is richly documented in parameter descriptions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences deliver the core behavior, matching rule, and a sibling alternative without filler. The most important distinction—name is not an identifier—is front-loaded and memorable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 19-parameter, high-complexity mutation tool, the description covers the central matching semantics and points to the correct create-only sibling. It lacks explicit distinction from update_opportunity and bulk_upsert, but the rich schema and output schema supply much of the remaining detail.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds high-value semantics for externalProvider, externalId, id, and name by explaining their roles in matching. Schema description coverage is only 47%, so the description partially compensates, but it does not explain the remaining parameters' meanings at the tool level.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's operation: 'Create a deal, or find the one another system already knows about and update it.' It also pinpoints the match criteria (externalProvider plus externalId, or explicit id) and explicitly rejects name-based matching, which distinguishes it from create_opportunity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly directs the agent to use create_opportunity when an unconditional create is intended and warns against matching on name. However, it does not contrast with update_opportunity or bulk_upsert, so guidance among all sibling alternatives is incomplete.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.