--- name: Pathpoint description: Manage Pathpoint risks, quotes, policies, and submissions. Use when the user wants to quote, submit, or bind a risk; browse or search risks; modify submission fields; clone or renew a submission; add, update, or delete quotes; attach quote documents; cancel, reinstate, or non-renew a bound policy; bind or issue a quote; create a Vave mid-term adjustment; confirm or decline an endorsement request; review or reject inspection compliance; author bind subjectivities; or perform any operation on the Pathpoint insurance platform. --- # Pathpoint Operations Supplement the `pathpoint` MCP server tools with conversational guidance for non-technical users. Ask questions in plain English, one at a time. Never show raw JSON, GraphQL, or internal IDs to the user. ## Interaction Pattern Every operation follows the same shape: 1. **Login** — call `get_login_status`. It verifies the sign-in with the server, so its answer is live truth. Never accept a password from the user in conversation. What to do next depends on how the user connected: - **Hosted connector** (the reply mentions a bearer token): the user signed in when they added the Pathpoint connector, so there is nothing to log in to. If the sign-in is rejected or is the wrong account, ask the user to disconnect and reconnect the Pathpoint connector in their Claude settings. - **Local install** (`p` on the user's machine): "NOT logged in" with a session file present means the session expired or was revoked. Call the `login` MCP tool — it opens the browser sign-in form directly on the user's machine and returns the form URL in case the window did not appear. Ask the user to complete the form; it expires after a few minutes, and only one sign-in can be open at a time. While the form is open, `get_login_status` reports the sign-in as still in progress; once the user finishes, call it again to confirm. 2. **Find the risk** — ask for a UUID or company name, then call `search_risk`. If the user just wants to browse recent activity, use `list_risks` instead. If multiple results, present a numbered list and let the user pick. Confirm with `get_risk` to show full details. 3. **Gather intent** — determine what the user wants to do and collect the necessary inputs (see domain knowledge below). 4. **Confirm** — show a human-readable summary of what will happen. Wait for approval. 5. **Execute** — call the appropriate MCP tool. 6. **Verify** — call `get_risk` again and report what changed. ## Ops & BPO workflows Pathpoint staff work lives in the opt-in `internal` group. On a local install, an account with Pathpoint staff permission loads it automatically; otherwise use `enable_toolset internal` (or `--toolsets internal` in the `p mcp-serve` args). Where each of the common ops jobs is documented below. QA bind/MTA tools never run in prod (they hard-refuse), so they are **not** in `internal`. Testers on local / demo / review load `qa` (`enable_toolset qa`) instead; a staff QA account loads it automatically there. | Job | Tools | Section | | --------------------------------- | ----------------------------------------------------- | --------------------------------- | | Cancel a policy | `cancel_or_reinstate_policy`, `attach_quote_file` | Cancellation & non-renewal | | Reinstate a policy | `cancel_or_reinstate_policy`, `attach_quote_file` | Cancellation & non-renewal | | Amend a bound policy's dates | `amend_bound_policy_dates` | Changing policy dates | | Amend a quoted risk's dates | `update_policy_dates` | Changing policy dates | | Upload renewal quotes | `renew_risk`, `quote_risk`, `attach_quote_file` | Attaching quote documents | | Add a bind requirement | `create_subjectivity` | Subjectivity authoring | | Reject inspection compliance | `mark_compliance_insufficient` | Inspections | | Confirm or decline an endorsement | `confirm_endorsement`, `decline_endorsement_requests` | Endorsements | | Bind or issue a quoted policy | `bind_or_issue_policy` (`qa`, not `internal`) | Binding or issuing (QA, non-prod) | | Start a Vave mid-term adjustment | `create_mta_submission`, `dry_run_mta` (`qa`) | Vave MTA (QA, non-prod) | ## Not every tool is loaded (`list_toolsets`, `enable_toolset`) Tools are grouped by who uses them. The **agent** tier loads by default: everything a broker does, from finding a risk to binding it, plus the broker side of a bound policy (endorsement and cancellation requests, inspection proof, loss-history rows, e-sign, building edits, the user's own profile). Three groups are opt-in: | Group | Who | What is in it | | ---------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `internal` | Pathpoint ops, underwriting, AMs | `quote_risk`, `update_quote`, `delete_quote`, `attach_quote_file`, `decline_submission`, `clone_submission`, `renew_risk`, `resubmit_risk`, `set_risk_owner`, `create_bind_request_task`, `cancel_or_reinstate_policy`, `mark_policy_non_renew`, `remove_non_renewal`, `undo_bind`, `amend_bound_policy_dates`, `confirm_endorsement`, `decline_endorsement_requests`, `mark_compliance_insufficient`, `flag_pending_claim`, `create_subjectivity`, `update_subjectivity`, `delete_subjectivity`, `accept_subjectivities`, `order_wildfire_scores` | | `admin` | Tenant admins (also loads internal) | `list_agency_networks`, `create_agency_network`, `update_agency_network`, `search_agencies`, `set_agency_commission` | | `qa` | Testers, local / demo / review only | `bind_or_issue_policy`, `create_mta_submission`, `dry_run_mta`, `update_inspection_status`, `upload_inspection_file` | Most `internal` and `admin` tools need Pathpoint staff permission, which the server enforces: a broker who loads them gets FORBIDDEN. Sections below that use an opt-in tool say which group it is in. **If a Pathpoint capability looks missing, call `list_toolsets` before concluding it does not exist**, then `enable_toolset` to load the group for this session. Never improvise a workaround (or tell the user to go to the app) for a tool that is merely hidden. If the newly loaded tools still do not appear on the next turn, the MCP client is not honouring tool-list changes. Follow the recovery step in the `enable_toolset` reply: on a local install, the user adds `--toolsets ` (or `--toolsets all`) to the `p mcp-serve` args in their MCP config, or sets `$P_MCP_TOOLSETS`, and restarts it. The hosted connector serves the agent tier by default, and its tool list is fixed: it has no `list_toolsets` or `enable_toolset`. The server withholds staff permissions from hosted sessions, so staff work needs a local install (`p login`). ## The everyday loop ### Finding risks (`search_risk` vs `list_risks`) - `search_risk` — use when the user names a specific company, a policy number or a UUID. Accepts a single query string; matches named insured (case-insensitive substring) or policy number across submissions, renewals and bound/issued policies, most recently updated first, at most 10 results. Rows are labelled `[renewal]` or `[policy ]`. A blank query is refused rather than run: an empty search returns unfiltered recent risks that look exactly like confident matches. If the results do not contain the search term the tool says so — relay that caveat instead of presenting them as hits. - `list_risks` — use for "show me my recent submissions" or "what's in the queue". Drafts, renewals and bound/issued policies are included, with the same labels. Optionally filter by one status (`DRAFT`, `UNDER_REVIEW`, `QUOTED`, `BIND_REQUESTED`, `BOUND`, `ISSUED`, and the others the tool lists). There is no `SUBMITTED` status. A renewal Pathpoint has not yet quoted or declined is hidden from both tools (and the web dashboard); find it on its expiring policy's `renewal:` line. Defaults to the 20 most recent; max 50. - `list_recoverable_drafts` — use for "did I leave something half-finished?". It lists recent never-submitted drafts with percent complete and product. Resume one with `get_submission_questions` → `modify_submission` → `submit_risk`. **The default window excludes today.** `days_ago_start` is the OLDER bound and must be strictly GREATER than `days_ago_end` — an equal pair asks for a single instant and matches nothing, so it is refused rather than returning a misleading empty list. The defaults (7 and 1) mean "created between 7 days and 1 day ago", so a draft started this morning does not appear. Pass `days_ago_end: 0` to include the last 24 hours. Add `include_agency: true` to also see agency-shared drafts owned by colleagues. It is expensive — it rebuilds the question tree for every draft and caps at 100 rows — so call it once, not in a loop. A row showing 0% and a named insured of "New Submission" may be a genuinely empty draft or a backend hiccup; the tool says so rather than reporting 0% as fact. ### Reading a risk (`get_risk`, `get_risk_activity`, `get_action_items`) - `get_risk` — full details: status, product, dates, submissions, quotes. The verification step after every write. When the risk is a mid-term adjustment (MTA), the summary names the root policy risk it adjusts — the bound policy being changed lives on that root risk, not on the MTA. - `get_risk_activity` — use for "what's the latest on this risk?", "any notes from the team?", or before summarizing a risk's state. Returns notes, submissions, quote letters, bind requests, and declines, most recent first. Attached files show by name only — use `list_risk_files` when the user wants a download link. - `get_action_items` — use for "what's on my plate?", "anything I need to do?", or as a morning round-up. Defaults to the user's own risks; pass `scope: AGENCY` when they ask about their whole agency. Follow up on a specific item with `get_risk` / `get_risk_activity`. ### Leaving a note (`add_risk_note`) The write side of `get_risk_activity`. Use it for "leave a note on this risk" or to record what was discussed with the insured. Takes `risk_id`, `quote_id` and `text`. **`quote_id` is required, and it is doing real work.** The API authorizes this write against the quote, and the tool derives the note's submission from that same quote — which is what stops a note landing on the wrong risk. Accepts a quote number, EID or UUID. **Permanent and append-only.** A note cannot be edited or deleted by any tool or by the app, calling twice leaves two notes, and it is visible to the owning agency and to Pathpoint staff (it may also notify them). So read `get_risk_activity` first rather than retrying a call whose outcome you are unsure of. The entry is always a `COMMENT`; the tool deliberately exposes no way to write the lifecycle activity types (`BOUND`, `ISSUED`, `SUBMITTED` and friends) because those are the server's to write and minting one would fake a state change in the audit trail. ### Discovering fields (`get_submission_questions`, then `list_fields`) `get_submission_questions` is the discovery step. Use it whenever you intend to write: it returns the live question set with current labels, answer types, allowed options, prefills, required-field progress and validation errors, so you can go straight to `modify_submission`. Call `list_fields` only for a quick read-back of stored labels and values outside that flow, or with a specific `view` to narrow results (e.g. `ORGANIZATION_INFORMATION` for address/revenue, `COVERAGE_OPTIONS` for dates/limits). (`list_fields`' own tool description offers itself as the discovery step — prefer `get_submission_questions` anyway; it is the one that reports options, progress and errors.) Either way, fields vary by product type, and a draft with no product assigned may have no fields at all: - If `get_risk` shows "Product: (none assigned)", the risk needs a product type before fields can be modified. - If `list_fields` returns no fields, tell the user and suggest checking the product assignment. **Incomplete results.** Both tools may return a result that starts with `WARNING — THIS RESULT IS INCOMPLETE`. That means the server rejected one or more submission views this build of `p` asks for, so the listing is missing whole sections. Never conclude from a banner-carrying result that a field or question does not exist, and never tell the user a value cannot be set — say the tool and the server are out of sync, and work from the views that did come back. ### Modifying submissions (`modify_submission`) The `modify_submission` tool accepts human-readable field labels (e.g. "Organization Name", "Annual Revenue") and resolves them internally. Use the exact labels from `get_submission_questions` (or `list_fields`) to avoid mismatches. If the user pastes unstructured data (broker email, correction form), extract field changes, verify the labels, and confirm before executing. After a write, the tool reports any questions that appeared or disappeared as a result (the form is conditional) plus how many required answers remain — relay new questions to the user instead of re-listing everything. See "Product-specific answer rules" below for class codes, exposures, excess underlying limits, value types, and multi-building path prefixes. ### Starting a new submission conversationally (`create_risk` → `get_submission_questions` loop) The from-scratch entry point — use when there's no similar risk to clone. The question set is dynamic: answers reveal new questions, so work the loop below rather than trying to collect everything up front. 1. **Intake.** Ask what coverage the client needs and map it to a product id (`cyber`, `cglV2`, `monolinePropertyV2`, `mpl`, … — the full list is in the "Coverage ids" table below); when unsure, check an existing risk of the same flavor with `get_risk`. Then invite everything at once: "Paste whatever you have — broker email, notes, details — and attach any ACORDs or supplementals." 2. **Create** the risk with `create_risk`, then immediately call `get_submission_questions` to learn the actual question labels, types, and allowed options. For property-carrying coverages (`package*`, `monolineProperty*`), pass `tenancy_types` at creation — ask what occupies the building, find the canonical strings with `search_tenancy_types`, and pass them in the `create_risk` call. Property raters hard-require an occupancy, and some package products (`packageRestaurants` at least) never serve an occupancy question in the submission flow — so creation is the only conversational chance to set it there, and a package risk created without one is declined by every property market at submit. (`monolineProperty*` also serves a "Tenant type" question that can be set later.) Pass `class_codes` at creation for GL-family products too — some verticals (`cglManufacturing` at least) never serve a class-of-business picker in the flow, yet their raters hard-require the risk-level class codes: a risk created without them is declined by every market with no appetite reason given. For products that do serve a picker (`cglV2`, `cglVacants`), the preselect just skips it. `class_codes` are validated against the chosen coverage's own picker before anything is created, and a bad preselect fails the call with "(no risk created)": codes from a different product's picker are each named with their description, their actual group, and the coverages they ARE valid for; a code unknown to the catalog is called out as possibly mistyped. A wrong-product code usually means the coverage id is wrong for this business — reconsider it before reaching for different codes; otherwise re-run `search_class_codes` and pick codes whose products include the coverage. The check only covers coverages in the picker catalog: `bundle` (option set depends on the child coverages) and coverages without a catalog row (`monolinePropertyV2`, `cyber`) pass codes through unchanged, exactly as the server does — so codes you preselect there are stored as given, right or wrong. If the server still drops EVERY requested code after creation, `create_risk` returns an error that names the created risk: the risk exists, but its class of business is UNSET and it must not be submitted until one is set (`search_class_codes` → `modify_submission`, verify with `list_fields`) — and any `changes` passed in the same call were NOT applied, so re-apply those too. A partial drop stays a warning inside a success result, as before — still heed it. 3. **Files.** Upload each file with `upload_risk_file` and `extract: true`. Extraction runs in the background (about a minute) — keep collecting info meanwhile, and check `get_extraction_status` before the review step. If extraction fails, proceed without prefills and ask instead. 4. **Extract from text yourself.** Map what the user pasted onto the real question labels. Only use values the user actually provided — never guess or invent an answer, especially for underwriting questions. 5. **Batch review.** Present one plain-English summary of every value you intend to write, grouped by page, marking where each came from ("from your ACORD", "from your email"). Call out high-stakes fields individually: effective date, annual revenue, class of business. Get one approval, then write everything with a single `modify_submission` call. 6. **Follow the cascade.** The write response reports newly appeared questions — relay them conversationally ("that unlocked 3 questions about your kitchen operations"). Ask remaining questions a few at a time, grouped by page, offering allowed options verbatim. Answers can be option text — the tool maps them to values. For tenancy-type questions (the type hint reads "tenancy type"), ask what actually occupies the building, use `search_tenancy_types` to find candidates, offer the top matches conversationally, and write the exact returned value (for multiple tenancies, one canonical string per line) — free text fails silently at rating. On a risk that already has answers or uploaded documents, `get_recommended_tenancy_types` can suggest tenancies from the risk's own data first; it returns bare codes, so still resolve each one through `search_tenancy_types` to get the canonical string before writing it. 7. **Loop** steps 5–6 until `get_submission_questions` reports all required questions answered and no validation errors. Mid-flow corrections ("actually the effective date is June 1") are just another `modify_submission` call. 8. **Submit** via the `submit_risk` flow below: summary, explicit confirmation, then report which markets it went to and any instant quotes or declines. ### Coverage ids The released product ids `create_risk` accepts, with the display name the app and quote documents use and whether the product writes admitted or non-admitted (surplus lines) paper. All released products are non-admitted except Cyber. Where two products share a display name, the trailing parenthetical is the product vertical, not part of the name — pick the id by the applicant's business. | Coverage id | Name | Admitted? | | ----------------------------- | --------------------------------------------------------------- | ------------ | | `bundle` | General Liability & Property (name varies with child coverages) | Non-admitted | | `cgl` | General Liability: Other Segments | Non-admitted | | `cglChurches` | General Liability: Churches and Other Houses of Worship | Non-admitted | | `cglLRO` | General Liability for Lessor's Risk Only | Non-admitted | | `cglManufacturing` | General Liability: Manufacturing | Non-admitted | | `cglRestaurants` | General Liability for Restaurants | Non-admitted | | `cglRetailAndServices` | General Liability: Retail & Services | Non-admitted | | `cglV2` | General Liability for Contractors | Non-admitted | | `cglVacantBuilding` | General Liability for Vacant Buildings | Non-admitted | | `cglVacantLand` | General Liability for Vacant Land | Non-admitted | | `cglVacants` | General Liability: Vacants | Non-admitted | | `contractorsExcessStandalone` | Standalone Excess Liability (Contractors) | Non-admitted | | `cyber` | Cyber | Admitted | | `lroExcessStandalone` | Standalone Excess Liability (Lessor's Risk Only) | Non-admitted | | `monolinePropertyV2` | Monoline Property | Non-admitted | | `monolineWind` | Monoline Wind | Non-admitted | | `package` | Commercial Package (GL/Property) (Other) | Non-admitted | | `packageChurches` | Package for Churches and Other Houses of Worship | Non-admitted | | `packageLRO` | Package for Lessor's Risk Only | Non-admitted | | `packageRestaurants` | Package for Restaurants | Non-admitted | | `packageRetailAndServices` | Commercial Package (GL/Property) (Retail & Services) | Non-admitted | | `packageVacantBuilding` | Package for Vacant Buildings | Non-admitted | ### Checking appetite before submitting (`get_appetite`) Asks the PALMS rules engine which carriers want this risk and, when they don't, why not — four buckets per coverage (MAY_PROCEED, WILL_REFER, MIDDLE_MARKET_ELIGIBLE, WILL_DECLINE) with per-carrier reasons in plain English. It is the only way to see a decline reason BEFORE submitting, and the fastest explanation for "why did this come back declined". Run it after `modify_submission` and before `submit_risk` / `quote_risk`, then again after edits to confirm a fix landed. A bundle risk fans out to one set of buckets per child coverage. Three things not to get wrong when relaying the result: - **Absence is not approval.** A carrier is dropped from all four buckets when it is not configured to write that product, or when PALMS matched it but returned no reasons. A carrier that is not listed has no verdict here — never report it as "no objection". - **All buckets empty means no verdict, not a decline.** Say the engine returned nothing for this risk; do not tell the user every market declined. - **It is not the submittable-markets list.** `resubmit_risk` validates its `markets` argument against a static config lookup answering "which carriers write this product". `get_appetite` answers "which carriers want THIS risk", recomputed from the current draft answers on every call. Conflating them either submits into a decline or skips a market that would have quoted. ### Submitting a risk (`submit_risk`) Submits a drafted risk to its markets for quoting — the same action as the Submit button in the app. Before calling: 1. Confirm required fields are filled (`get_submission_questions`); fix gaps with `modify_submission`. 2. Show the user a summary of the risk (`get_risk`) and confirm they want to submit. Validation failures come back listing the missing fields — relay them in plain English and offer to fill them. After a successful submit, the tool returns the refreshed risk summary; report which markets it went to and any instant quotes or declines. **The classless-submit guard.** Before the mutation, the tool reads the risk's class-of-business question state and refuses to submit when a REQUIRED class-of-business picker is blank — including the empty-string state the server itself reports as complete, which is how classless submissions used to reach markets and land in the UW referral queue with no appetite match. The refusal names the blank question and the fix: find the right 5-digit code with `search_class_codes`, write it with `modify_submission`, then retry. The same pre-flight runs in `resubmit_risk` and in `clone_submission` when submitting — there the clone already exists, so the refusal arrives as a "NOT SUBMITTED" banner inside a success result naming the new risk id; fix the class of business, then `submit_risk` it. If the question state cannot be read at all, all three refuse with a distinct "could not be verified" message — the class may well be set, so retry or inspect with `get_submission_questions` rather than overwriting anything. Optional pickers (`cglManufacturing`) and coverages with no class-of-business question (`cyber`) never trigger the guard. ### When a submission refers (`REFERRED`, `UNDER_REVIEW`, `CARRIER_REVIEW`) A referral is not a rejection and not an error: the submission is in human underwriting review — `UNDER_REVIEW` means Pathpoint's brokerage team has it, `CARRIER_REVIEW` means the carrier's underwriters do, and `REFERRED` is the settled referral state. The reviewers come back with a quote, questions, or a decline. The risk summary prints each submission's `reason:` lines (why it referred or declined) and `note:` lines (informational caveats on quoted submissions) when the server provides them. How to respond: - **Relay the reasons verbatim** to the user — never invent a cause for a bare `REFERRED` status, and never present a referral as a decline. - **Check `get_risk_activity`** for underwriting notes and follow-ups; that is where the brokerage team's questions appear. - **Do not resubmit to shake a referral loose.** Resubmitting is only right after actually changing something a reason names (wrong exposure, wrong class code). If EVERY market referred or quoted absurdly high at once, suspect a data error in the submission — a wrong unit in an exposure value (see the percent-of-operations rule below) has caused exactly that — and re-check the figures with the user before anything else. - Set expectations: a human underwriting review takes hours-to-days, not seconds. ### Choosing markets (`select_markets`) Steers which markets a not-yet-submitted risk goes to. Accepts carrier names ("Kinsale") or market UUIDs, and takes `add_markets`, `remove_markets`, or both in one call. **It is write-only.** No query in the API returns a risk's current market selection, so this cannot show what is selected now and cannot diff before and after — and its success response is not proof the rows changed. Never tell the user you have verified the selection. A market may not appear in both lists: the server applies adds before removes, so it would silently net to removed, and the tool refuses that rather than guessing. Adds are idempotent and removing a market that was not selected does nothing, so a repeated call is harmless. This only affects where a risk goes **next** — it does not recall, cancel or unsend anything already submitted. Use `resubmit_risk` to go back out to markets. ### Quoting (`quote_risk`, `internal`) The tool routes limits into the correct typed input automatically based on the risk's product. You usually don't need to pass `product` — it's inferred from the risk. Pass it only if the user explicitly overrides (rare). Determine the product type from the risk, then ask for the right limits: | Product | Limits to ask | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | | Cyber | `aggregate_limit`, `per_occurrence_limit`, `retention` | | GL (CGL) | `aggregate_limit`, `per_occurrence_limit`, `products_completed_ops` | | Package | Same as GL — package products resolve to a general-liability line server-side | | Excess | `aggregate_limit`, `per_occurrence_limit`, `products_completed_ops` | | Property / Monoline Property | `aggregate_limit` (use the flat limit; other property fields live in the application, not the quote) | Two product families cannot be quoted through these tools at all, and the tool refuses with an explanation rather than dropping the limits: a **bundle** has no single line of insurance (quote its individual child risks instead), and **mpl** has no line-of-insurance mapping on the server at all (use the app). Relay the explanation — retrying with different limits will not help. Always ask: - **Premium** (required) - **Carrier** — can be picked from existing submissions or named freely - **Effective/expiration dates** — omit them to use the risk's policy dates (the tool fills them in and says so); confirm with the user. A passed date always wins. If only `effective_date` is passed, expiration is 1 year after it. A risk with no effective date needs `effective_date`. - **Quote number** — optional - **Fees** — agency, company, stamping, inspection (ask once, skip if none) - **TRIA** — optional - **Wind** — `includes_wind` (does the quote cover wind) and `est_cost_wind` (the wind portion of the premium), both optional - **Admitted or non-admitted** — default non-admitted (omit `admitted` to keep the default). Pass `admitted: true` for admitted. - **Comment** — optional ### Identifying a quote Tools that take `quote_identifier` accept a quote number, an EID, or a UUID. **Quote numbers are not unique.** When a number matches more than one quote the tool lists the candidates and stops instead of picking one — show the user the list and pass the EID or UUID of the one they mean. Re-sending the same quote number will just fail again. ### Viewing quote details (`get_quote`) When the user asks about a specific quote, use `get_quote` to show the full picture — cost breakdown (premium, fees, taxes), limits, wind coverage and its estimated cost, subjectivities, and status flags. If the user picks a quote from the `get_risk` summary, use the EID shown there. On the package segments (LRO, restaurants, retail & services, vacant building, churches), monoline property/wind, and Churches-GL quotes it also lists the carrier's coverage adjustments ("modifications") with the buildings each one affects, or states that none were recorded — so silence is never ambiguous. Other coverages — including the legacy generic package product, which the server never records modifications for — don't carry them, and the tool doesn't fetch there. ### Updating quotes (`update_quote`, `internal`) Show the existing quotes from `get_risk` as a numbered list with carrier, quote number, premium, and status. Let the user pick one. Use `get_quote` to show full current details, then ask what to change. Accept free-form input — "change premium to $4,200 and add a $500 agency fee" — and map to the tool parameters. Limit parameters are routed by product the same way as `quote_risk`, including package products. To flip an admitted quote to non-admitted, pass `admitted: false` explicitly (omitting it leaves the current value alone). Wind behaves the same way: pass `includes_wind: false` explicitly to drop wind coverage, and `est_cost_wind: 0` explicitly to clear a previously-set wind cost. Both are sent as written — unlike the fee parameters, where a 0 reads as "not supplied" and is omitted — so omitting them is what leaves the current values alone. Show a before/after summary before executing. ### Bind readiness (`check_bind_readiness`) Run before `request_bind` or whenever the user asks "can I bind this yet?" — it reports validation blockers and incomplete subjectivities without changing anything, and names the tool that clears each one (`upload_subjectivity_file` for FILES, `answer_subjectivity` for TEXT / DATE / CHECKBOX / CONTACT_INFO). Walk the user through fixing each blocker, then re-check. A missing quote letter comes back as a `QUOTE_FILE_MISSING` blocker — that one is fixed with `attach_quote_file` (`internal`), not in the app. On the hosted connector (and for non-staff accounts) the server refuses the validation step, so the report lists only subjectivities and says so; never call that "ready to bind". ### Requesting to bind (`request_bind`) The conversion action — tells the Pathpoint team the agent wants to bind a quote. Identify the quote the same way as `update_quote` (number, EID or UUID, plus `risk_id`). The tool selects the quote on the risk automatically if it isn't already selected. Always confirm before calling — show carrier, quote number, premium, and effective dates, and get an explicit "yes". After requesting, check `list_subjectivities` for outstanding requirements and tell the user what's still needed to complete the bind. Optional `licensed_agent_email` routes the bind through a specific licensed agent, for agencies that require one. If the quote carries a `SELECT_LICENSED_AGENT` subjectivity, that is the signal to ask who the licensed agent is; resolve the exact address with `list_agency_users` and pass it here. Two things it won't do on its own: - It refuses to run on a quote with no quote letter ("Quote letter is not ready" / `QUOTE_FILE_MISSING`). Manual quotes made with `quote_risk` never auto-generate one — attach a PDF with `attach_quote_file` (`file_type: QUOTE_LETTER`) and retry. A rated quote's letter may still be generating — re-check shortly instead of uploading one. - It does not put the quote in front of Pathpoint ops. Follow it with `create_bind_request_task`. ### Ops bind queue (`create_bind_request_task`, `internal`) `request_bind` runs the bind validations and notifies the team, but it does not create the ops task, so a bind requested through `request_bind` alone never reaches the queue ops works off. Call `create_bind_request_task` afterwards to complete the handoff — pass `risk_id` plus `quote_identifier` (quote number, EID, or UUID). It is idempotent: when a task is already queued it reports that one and creates nothing. `cancel_bind_request` cancels the task. ## Rules that apply to every tool **Writing to production.** Every tool that changes anything refuses to run against a prod session unless it is called with `confirm_prod: true`. Treat the refusal as designed: state in plain English what is about to change, get an explicit "yes", then re-call with the flag. Never set it pre-emptively on a call the user hasn't approved. **A timeout means UNKNOWN, not failed.** Long operations get longer budgets — 300s for bind, issue and document generation, 120s for quoting and submission, 30s for everything else. If one times out, the server may well have completed it after the client stopped waiting. Do not report a failure and do not retry blindly: re-read the object first (`check_bind_readiness` or `get_quote` after a bind, `get_risk` otherwise) and retry only if it did not take effect. Never retry a bind on a timeout without checking — that is how a policy gets bound twice. **Server errors.** Common server rejections come back with a plain-English explanation ahead of the raw message. Relay the explanation, not the GraphQL text. **Download links.** `list_risk_files` and `get_quote_documents` return names, types and sizes only; presigned URLs are omitted unless you pass `include_urls: true`. List first, then re-call with the flag once you know which document the user actually wants. ## Product-specific answer rules ### GL / class-of-business products (cglV2 and friends) GL-family submissions classify the business with 5-digit class codes instead of (or alongside) tenancies: - **Class of business page.** The vertical picker (type hint mentions class codes, e.g. "What type of contractor is your applicant?") stores comma-separated 5-digit codes. On a risk that already has answers or uploaded documents, try `get_recommended_class_codes` first — it suggests codes from that risk's own data, ranked by confidence, with no search wording needed. Otherwise (or if it comes back empty) ask what the business actually does and use `search_class_codes` to find candidates. Either way, offer the top matches in plain English, then write the code(s) comma-separated (e.g. `"91560,91580"`). Free text fails classification silently at rating — never invent codes. Class codes are also product-scoped: `search_class_codes` prints each hit's products, and picking codes whose products include the risk's coverage is now enforced, not just advised — `create_risk` rejects a preselect whose codes belong to a different coverage's picker before creating anything, and the submit tools refuse a risk whose required class of business is still blank (see the classless-submit guard under `submit_risk`). Both recommendation tools only return matches scoring 0.8 or above, so **an empty result is ambiguous** — no completed aggregation job, or nothing confident enough (and for tenancy, a third cause: the recommended tenancy is no longer in the catalog and was silently dropped). It never means the risk cannot be classified; fall back to keyword search rather than reporting a dead end. - **Tenancy recommendations.** `get_recommended_tenancy_types` is the tenancy equivalent, and has one extra trap: it returns tenancy **codes only**. Resolve each code to the full canonical `": "` string with `search_tenancy_types` before writing it — a bare code fails tenancy parsing silently at rating, exactly like an invented class code. - **Per-location exposures.** Each location's "Class of business and exposure values" question stores a JSON array with one entry per selected class code: `[{"id":"91560","selected":true,"value":"100"}]` — `selected` marks the classes operating at that location, `value` is that class's exposure. The unit depends on the product and class, and getting it wrong is silent and catastrophic. On contractor GL products (`cglV2`, `contractorsExcessStandalone`) `value` is the **percent of the applicant's operations** (0–100; the selected percent classes must total exactly 100 across ALL locations on the risk together — see the next bullet) — NOT payroll, revenue, or any dollar amount; the platform derives the dollar exposure internally, so a $100,000 figure written here rates the risk at ~1000x and every market quotes absurdly or refers (this happened live). The exception on those products: subcontracted-work classes (91581/91583/91585/91591) take the annual $ cost of that subcontracted work. Other products take a real amount in the class's own basis (gross sales for retail/restaurants, area for some premises classes, acres for vacant land) — and the SAME code can rate on a different basis per product: 61212 takes annual rents on `cglLRO` but building AREA on `lroExcessStandalone`, where a rents-sized number reads as square footage and hard-declines on appetite. Ask the user for the figure that fits the class and product. Restaurant classes split food and liquor sales as two newline-separated numbers in one `value` string (`"400000\n50000"`). - **The percents must total exactly 100 across the whole risk.** On the percent-basis products (`cglV2`, `contractorsExcessStandalone`) the selected non-subcontractor percents are summed across EVERY location on the risk together — not per location — and that one cross-location sum must be exactly 100 before the risk can go to markets. Subcontracted-work classes (91581/91583/91585/91591) are dollar-basis and never count toward it. Exposure writes keep you oriented: a write that leaves the projected cross-location total off 100 opens with an advisory stating the running total. The write itself still succeeds — a multi-location risk is filled one location at a time and legitimately passes through partial totals — so keep working until the last write lands the total on exactly 100. The one write that IS refused outright is a single location whose own selected percents already exceed 100: other locations can only add to the sum, so it can never become valid — fix the split and re-send. At submit time the total is enforced fail-closed: `submit_risk`, `resubmit_risk`, and `clone_submission` (when submitting) refuse when the cross-location total is not exactly 100, when no countable percent exposure exists at all, or when the exposure state or product cannot be verified. The web app disables its Submit button until the total is 100, but the server never checks at submit time — this guard is the only gate, so expect the refusal rather than fighting it (`clone_submission`'s arrives as a success-shaped "NOT SUBMITTED" banner: the clone exists, it just was not sent). The fix loop is always the same: `get_submission_questions` to see each location's exposure values and the gap, `modify_submission` to correct the split, then `submit_risk`. - **Subcontractors page (contractors).** "Does applicant hire subcontractors…" is a yes/no; a yes cascades follow-ups including "What type of work is subcontracted?". That question does NOT take the trade's own class code — it only accepts the "Contractors – Subcontracted Work" codes listed in its type hint (91581/91583/91585/91591, by what the subcontractors build). Writing a trade code there silently adds a phantom class to the submission and EXPOSURES starts demanding an exposure for it. - **Exposure follow-through.** Every class code selected anywhere (vertical picker AND subcontracted work) must get an entry in each location's exposure JSON — validation errors name any code left without one. - **Middle-markets business mix (cglV2).** Middle-markets contractor submissions carry a business-mix grid: the Residential and Commercial sections each break into operation rows, and each row has two percent cells — "% new" and "% repair / remodel". `get_submission_questions` renders every cell with its full section path (e.g. "Commercial › Industrial › Industrial (% new)") and a hint carrying the rule the grid enforces: each cell is a whole percent 0–100 of the overall operations mix, and ALL cells across BOTH sections and BOTH columns must together total exactly 100. Answer cells with `modify_submission` using the pathed label (or the bare cell label when it is unambiguous). Writes are validated to the unit the cell stores: an int, an exact-integer float, `"40"`, and `"40 %"` all normalize to the integer 40; 0 and 100 are fine; `null` clears a cell. An empty string, non-numeric text, a fractional percent, or an out-of-range value is rejected with an error restating the rule — a dollar figure written into a percent cell is the classic mistake. A grid whose percents do not total 100 is unsubmittable, and its blank cells surface as missing questions in `get_submission_questions` — treat them as unanswered work, not optional noise. - **Percent-total groups.** The same submissions carry other percentage groups (operations-details percentage rows among them) whose hint reads "this group's percentages must together total exactly 100": each row is a whole percent 0–100, the group's rows must sum to exactly 100, and writes follow the same normalization and rejection rules as business-mix cells. ### Excess standalone products (`contractorsExcessStandalone`, `lroExcessStandalone`) Excess submissions add an underlying-policy block: carrier (a huge validated select — search it by what the user says, offer close matches), A.M. Best confirmation, underlying premium, dates, and limits. Two of the limit questions ("General Aggregate Limit", "Products-Completed Operations Limit") list NO options — they store integers, so write plain numbers (`2000000`), never `"$2,000,000"` text (it passes validation but fails server-side). Their siblings that do list options ("Per Occurrence Limit") take the option text as usual. ### Value types and look-alike checkbox groups - Write numbers as numbers: year, count, and money questions reject numeric strings server-side, and a `modify_submission` batch is all-or-nothing — one bad value rejects every change in the call. - When several checkbox groups share the same truncated header (e.g. three different "Select all of the following that apply to any location: › None of the above" rows), a colliding write errors and lists the full disambiguated paths — copy one back verbatim, including any `:_suffix` in the group text. If a bare write silently satisfies only one of the look-alike groups, the progress counts reveal the ones still missing. ### Multi-location risks (`list_properties`, `add_property`, `duplicate_property`, `delete_property`) `create_risk` seeds exactly one property, so a second building or address only exists if you create it. All four tools are in the agent tier. - `list_properties` — every building on the risk with its `property_id`, name, address, and the exact field-path prefix `modify_submission` needs for it. No query returns property ids, so this is the only way to find the id the other two tools take. - `add_property` — a NEW location (a different address). Pass the address in the same call if you have it, but all of street, city, state and ZIP together or none at all. The new building is auto-named "Building N" and has **no Location Number**, which most property products ask for as a required question — set it afterwards with `modify_submission`. - `duplicate_property` — a SECOND BUILDING AT THE SAME ADDRESS. Copies every answer including the address and Location Number, clears the building description, and derives the portal's own next name (max+1 over buildings sharing the source's base name) unless you pass one. - `delete_property` — removes a building and everything stored on it. A risk must keep at least one, so deleting the last is refused. **Addressing a specific building.** Once a risk has more than one, a bare label like "Building Limit" is ambiguous and gets rejected. Prefix it with the building's ordinal path, which differs per view — `Property 2 › Building Limit` in PROPERTY_DETAILS, `Location 2 › Address` in EXPOSURES. `list_properties` prints the exact prefix for each one. **The numbering shifts.** Any add, duplicate or delete renumbers the remaining buildings (the server decrements Location Numbers above a deleted one), so every previously copied `Property N` / `Location N` prefix may now point at a different building. Re-read `list_properties` (or `get_submission_questions`) after any of the three before the next write — never reuse a prefix from before the change. "Three buildings at two addresses" is `add_property` for the second address, then `duplicate_property` for the extra building. ### Verifying an address (`search_addresses`, `set_property_geocode`) An address written with `modify_submission` is stored exactly as typed. Nothing validates it, and nothing derives the county or the coordinates that carrier appetite and hazard scoring read. Resolve it first, in this order: `search_addresses` → confirm the candidate with the user → `modify_submission` for street/city/state/ZIP → `set_property_geocode`. - `search_addresses` — AWS address autocomplete. Needs a street number and a street name; a bare city is refused locally, the same rule the web client applies. Returns full candidates each carrying a `place_id`. **An empty result is ambiguous** — the server swallows AWS errors and returns nothing — so it is never proof the address is wrong. - `set_property_geocode` — takes a `place_id` plus the `risk_id` and `entity_id` of the property (from `list_properties`) and stores latitude and longitude on it. It then writes the County question the way the web does and reads it back; the result says whether the county landed. Two things about `set_property_geocode` that its name does not tell you. It **writes**, even though the underlying API call is shaped like a read, so it takes `confirm_prod` on prod. And the coordinates write is best-effort — failures, including a permissions failure on the property, are logged and discarded server-side while the geocode still comes back — so treat the coordinates as "the lookup happened", not "the values persisted". ## Working the rest of the risk ### Selecting a quote & TRIA (`select_quote`, `set_tria`) `select_quote` marks the quote the agent intends to bind and generates its bind checklist — use it when the user picks a quote but isn't ready to request bind (request_bind selects automatically). `set_tria` toggles terrorism coverage on a quote. TRIA starts opted out: `get_quote` shows the TRIA premium as "opted out, not in total" until it is included. `set_tria` reports the total cost before and after the change. ### Subjectivities (`list_subjectivities`, `answer_subjectivity`, `upload_subjectivity_file`) Subjectivities are the bind requirements on a quote. `list_subjectivities` shows them with EIDs, response types, subjectivity types, and completion status — by default just the selected quote's (pass `all_quotes: true` for everything). An INSURED_PAY item is annotated "not required for your agency" when the agency is not insured-pay: bind readiness skips it, so never chase it. To answer one, pass `answer_subjectivity` the EID (or an unambiguous title fragment) and the ONE parameter matching the response type — a mismatched parameter is refused, because bind readiness only consults the field matching the type: - **TEXT** — `text`. - **DATE** — `date` (YYYY-MM-DD). For the EFFECTIVE_DATE item specifically, prefer `update_policy_dates`: it writes the real policy dates AND this subjectivity in one call. - **CHECKBOX** — `checkbox: true` (false clears it). - **CONTACT_INFO** — the `contact_full_name` / `contact_email` / `contact_phone` trio, all three at once. Partial contacts are refused: readiness requires all three fields non-empty. - **SELECT_LICENSED_AGENT** (a TEXT item) — `licensed_agent_email`; it resolves against the agency's licensed agents with an NPN on file and stores the full agent record. No or ambiguous match errors with the selectable agents. - **FILES** — use `upload_subjectivity_file` with a local file path; it uploads, attaches, and marks the subjectivity complete in one step. Confirm the file is the right document first. `mark_complete: true` can accompany any answer or stand alone, but note bind readiness ignores it — completion comes from the answer itself (text/date/checkbox/files/e-signature). Read back the saved answer before marking complete when the answer matters (payment info, inspection contacts). ### The insured contact (`get_insured_contact`, `set_insured_contact`) The named person Pathpoint emails the signing documents to, saved against a quote. It is the real record: writing contact JSON into a CONTACT_INFO subjectivity sets the subjectivity text but leaves this empty. The web app copies the contact into the Inspection Contact / Insured Pay / Audit Contact subjectivities; these tools do not, so answer those with `answer_subjectivity`. The contact also gates the e-sign flow: after the agent signs a packet, Pathpoint emails the insured their signing turn ONLY when this contact exists (see The e-sign packet). Both tools take the risk UUID plus any quote identifier — number, EID, or UUID — resolved through the risk. - `get_insured_contact` — the saved contact, any contacts on the risk's other quotes, and the prefill candidates the app would offer. Having none is a normal state, not an error: the form only shipped in May 2025, so older risks never collected one. - `set_insured_contact` — saves all four fields (first name, last name, email, US phone) at once; there is no partial update. **Replacing a contact that already exists invalidates the e-sign packet**: issued signing URLs expire, e-signed subjectivities revert to incomplete, and prefilled manual-sign documents are voided, so documents must be regenerated and re-sent to the insured. The tool performs those invalidations itself and says so. Creating a contact where there was none has no such effect. Warn the user before replacing, and re-check the Inspection Contact / Insured Pay / Audit Contact subjectivities afterwards — this tool does not rewrite them the way the app does. ### Deleting quotes (`delete_quote`, `internal`) This is a soft delete — the quote is marked deleted and hidden from normal queries, but the action is not typically reversible from the UI. Always confirm with the user before calling the tool: ``` Delete quote KIN-001 from Acme Cyber Corp (Kinsale, $4,200)? This cannot be undone from the app. ``` Only call `delete_quote` after an explicit "yes". Never default to delete when the user says "remove" or "cancel" — ask if they mean delete vs. unbind vs. decline. ### Canceling a bind request (`cancel_bind_request`) Cancels the queued bind-request task that `create_bind_request_task` opened (the app's "Make changes"). It does **not** withdraw a bind request made with `request_bind`: the quote stays bind requested, and only the Pathpoint team can withdraw it. No broker tool can. The result reads the quote back and says which case applies. Never tell the user the bind request is withdrawn unless the result says the quote is not bind requested. Always collect a reason and confirm before calling. The tool writes the reason as a permanent note in the risk's activity feed. ### Changing policy dates (`update_policy_dates`, `amend_bound_policy_dates`) The fix when a quoted risk's effective date has slipped — the classic blocker between "quote selected" and "bind requested". Writes the new effective/expiration dates on both the application and the quote, recalculates the quote's grace period, and queues a Novidea update. Three things to keep straight: - **It does not re-rate.** Premium and terms are untouched; for a large date move the carrier may require a fresh quote (`resubmit_risk`) instead. Before any quote exists this tool is the wrong one anyway — pre-quote, the effective date is submission data and feeds rating, so use `modify_submission`. - Both dates are required. Don't silently assume an annual term: confirm the expiration with the user (most policies are effective + 1 year, but not all). - Documents generated earlier (quote letter, ACORDs) keep the old dates until regenerated. - When the selected quote carries an EFFECTIVE_DATE subjectivity, the tool also writes its date response to match (the app does the same) — so a date fix clears that bind requirement in the same call. It operates on the risk's **selected quote only** — the mutation rewrites the application's policy dates along with the quote's, so pointing it at a non-selected quote would leave the application and the quote being bound disagreeing. If nothing is selected yet, `select_quote` first. `quote_identifier` is accepted as confirmation but must name the selected quote. Bound quotes are refused outright: those dates belong to an active policy, and changing them is endorsement work (`create_endorsement_request`), not a pre-bind fix — the refusal names `amend_bound_policy_dates` (`internal`) as the in-place correction path if that is what is actually needed. **`amend_bound_policy_dates`** (`internal`) is that path — the exact inverse of `update_policy_dates`: it only accepts a **BOUND** quote, where the agent tool only accepts a non-bound one. It sends the IDENTICAL `updatePolicyDates` mutation, and the server allows it on a bound or issued quote **only for Pathpoint staff (`GLOBAL_ACCESS_PROTECTED_RESOURCE`)** — anyone else gets FORBIDDEN and nothing is written. It isn't available over the hosted MCP connector, which never carries staff permissions; use a local `p login` session. On top of that, it needs the opt-in `internal` group and a required `reason`. It exists because Pathpoint's ops team corrects a bound policy's recorded dates in place every day, today in a third-party Retool app, and Retool does it worse: it writes only the EAV submission choice (`setUserChoice`) and never the quote row, so every Retool date amend leaves the quote and the application disagreeing. This tool writes both via the same mutation, so it cannot introduce that disagreement. It requires a `reason`, written verbatim to the risk's activity feed as the audit trail — an unexplained date change on an active policy must never happen silently, and that entry is permanent and visible there to the owning agency and to Pathpoint staff, same as `add_risk_note`. The product-correct fix for a real mid-term date change is still an endorsement (`create_endorsement_request`); this tool is for correcting the record in place on a policy that is already bound, and it says so every time, along with what it does not fix: it does **not** re-rate (premium and terms are unchanged); policy documents, the binder, and ACORDs are **not** regenerated and keep the old dates; the quote's **grace period** is recalculated, which has billing implications on an active policy, and Ascend invoicing is not touched; and the **carrier** is not told — this changes Pathpoint's records only (except Vave). One limitation worth knowing: both policy-dates tools resolve their target exclusively through the risk's `selectedQuote` (`resolvePolicyDatesQuote`), and that never changes just because the quote later binds. A policy whose risk no longer reports that bound quote as selected — a later rebind on a sibling quote, a cleared selection — cannot be corrected with `amend_bound_policy_dates` at all, and the refusal it gets back names `select_quote`, which is not a sensible instruction for a policy already in force. The common case is unaffected: a bound quote is normally still the risk's selected one. ## Subjectivity authoring (`internal`) These four tools are in the opt-in `internal` group. Everything above is reading and answering the bind requirements a quote already has; these change what the requirements ARE — the underwriter/ops side that Retool's Quoting-BT app has had all along. - **`create_subjectivity`** adds a new bind requirement to a quote — admin only (requires `GLOBAL_ACCESS_PROTECTED_RESOURCE`). Takes `risk_id`, `quote_id` (UUID/EID/quote number), `title`, `description`, `response_type`, and `lifecycle_stage`. "Binding subjectivity" / "bind requirement" means `lifecycle_stage: PRE_BIND` — the enum has no `BINDING` value, only `PRE_BIND` (blocks `request_bind`) and `PRE_ISSUANCE` (blocks issuance instead). A created subjectivity has no `SubjectivityType` — that field is the app's internal routing for special items (AUDIT_CONTACT, SELECT_LICENSED_AGENT, INSURED_PAY, and the rest) — so bind readiness and `list_subjectivities` treat it as a plain item with none of that special handling. Answer it afterward with `answer_subjectivity` (TEXT/DATE/CHECKBOX/CONTACT_INFO) or `upload_subjectivity_file` (FILES/FILES_AND_TEXT). - **`delete_subjectivity`** permanently removes one — admin only, same permission as create. This cannot be undone from the app: the requirement and any answer already saved on it are both gone. Identify it by EID or an unambiguous title fragment, same resolution as `answer_subjectivity`. Always confirm with the user before calling it. - **`update_subjectivity`** edits title, description, `response_type`, `lifecycle_stage`, `subjectivity_type`, or the underwriter comment — never the answer. It refuses answer-shaped arguments outright (`text`, `date`, `checkbox`, the `contact_full_name`/`contact_email`/ `contact_phone` trio, `licensed_agent_email`, `mark_complete`, files) and points at `answer_subjectivity` / `upload_subjectivity_file` / `accept_subjectivities` instead, and refuses a call that changes nothing, including one whose values already match. **Changing `response_type` keeps a saved answer counting** — bind readiness accepts any saved answer whatever the type, except CONTACT_INFO, where only the contact JSON counts. So a change TO CONTACT_INFO makes a free-text answer incomplete (the tool warns), and a change between other types leaves the item complete even if the new type's web input does not show the old answer (the tool notes it). Unlike create/delete, **this is NOT admin-gated server-side** — it's only an ownership check (`@ResourceCheck(BySubjectivity)`), so any session that can see the risk can retitle or restage one of its bind requirements. - **`accept_subjectivities`** bulk-stamps `acceptedAt` on outstanding requirements (`bulkUpdateSubjectivities`), also **not admin-gated server-side** (`@ResourceCheck(ByCustomFunction)`). Accepting is the underwriter's sign-off on a response: the web shows the item as Accepted and stops most edits, and once every subjectivity on a bind-requested quote is accepted the quote can move to Ready for Payment. **Accepting does NOT satisfy `request_bind`** — bind readiness counts only saved answers, so an accepted but unanswered item still blocks it. By default the tool only accepts items that already have an answer, on the selected quote (or every quote, if none is selected); pass `include_unanswered: true` to accept unanswered ones too. `quote_identifier` scopes to one quote; `identifiers` (an array of EIDs or title fragments) takes priority over `quote_identifier`, but every one named must be on the SAME quote — a mixed-quote list (and the every-quote fall-through, when more than one of those quotes has something outstanding) is refused before anything is sent, since `bulkUpdateSubjectivities` rejects a batch spanning more than one quote wholesale. Per-item failures come back as a 200 with no GraphQL errors, so a partial failure is reported as an error result even when some items succeeded — check the message for which ones actually went through. ## The e-sign packet These three tools are in the agent tier. Most FILES-type bind requirements (ACORDs, the TRIA form, the Pathpoint supplemental, no-known-loss letters, per-state diligent-effort and affidavit forms) are normally satisfied in one stroke by the **e-sign packet**: a bundle of pre-filled documents both the licensed agent and the named insured sign electronically. The alternative — collecting each signed document and uploading it with `upload_subjectivity_file` — always works, but the packet is how the app expects these to clear. The flow, on the risk's **selected quote**: 1. **`get_state_subjectivity_fields`** — lists the fields the packet's state form requires for this quote's state and coverage: names, meanings, choice options, and prefills (quote dates, the stored licensed agent, the insured contact). It also lists which open subjectivities the signed packet is expected to satisfy. Ask the user only for fields without a prefill. States differ a lot: Georgia needs only the universal four (signing agent, dates, insured signatory name and title); New York and California add declination details against their admitted-carrier lists. 2. **`generate_esign_packet`** — takes the answers as a `field_values` JSON object and returns the **licensed agent's signing URL**. It mirrors the app form's submit exactly: it saves the licensed-agent subjectivity, and when the dates changed it updates the policy dates (application - quote + EFFECTIVE_DATE subjectivity) and regenerates the supplemental first, so the packet embeds the right dates. 3. **Signing order matters.** The agent signs first via the returned URL (valid 20 days). When the agent finishes, Pathpoint automatically emails the insured their own signing link — **but only if an insured contact is saved on the quote** (`set_insured_contact`); without one, nobody emails the insured and the agent must arrange their signature. Both tools warn when the contact is missing. 4. When both parties have signed, the covered subjectivities complete on their own — confirm with `list_subjectivities` rather than assuming. 5. **`invalidate_esign_packet`** — expires the outstanding signing URLs and reopens any e-signed subjectivities. Use it when an answer changes (dates, signatory, declination details) before regenerating. Note that replacing the insured contact invalidates the packet as a side effect too. Two boundaries: quotes without a subjectivity state have no state form (satisfy items individually), and the interactive signing ceremony itself stays in the browser — these tools produce and manage the links, they cannot sign. ### Resubmitting (`resubmit_risk`, `internal`) Use after quotes expire, markets decline, or the submission changed — "get me fresh quotes" or "resubmit to Vave". Targets specific carriers by name or all submittable markets when omitted. Carrier names are validated against the risk's submittable markets and the error lists the options, so on a mismatch just relay the choices. The classless-submit guard (see `submit_risk`) runs here too — a resubmit re-notifies markets, so a risk with a blank required class of business is refused with the same remediation. ### Declining a submission (`decline_submission`, `internal`) Records a manual decline on a market's behalf with a free-text reason, which shows on the activity log. Identify the submission by its UUID, or by `risk_id` plus the carrier's display name (matched case-insensitively as a substring). On a package risk, `decline_all_lines: true` also declines every sibling negotiation that shares this market across the child lines. Confirm the carrier and the reason with the user first — this is what the agent will see as the market's answer. ### Renewals (`renew_risk` is `internal`, `get_renewal_changes`) Use `renew_risk` when the user wants to renew an expiring policy/term. It creates a renewal risk linked to the original and carries data forward — it refuses to duplicate an existing renewal and reports it instead. The server usually submits the new renewal to its markets at once, so the reply gives the renewal's status: call `submit_risk` only when that status is `DRAFT`. Otherwise follow its markets and quotes with `get_risk`. `get_renewal_changes` shows field-by-field diffs against the expiring term with who changed what and when — useful for "what's different on the renewal?". The output leads with the expiring term's carrier of record; "not recorded" there means the prior term never bound, not a data error. ### Cloning submissions (`clone_submission`, `internal`) Show a summary of the source risk. Ask: 1. "Do you want to change anything on the new submission?" — collect changes as field/value pairs 2. "Submit for quoting right away, or leave as draft?" The tool handles creating the risk, copying values, applying changes, and optionally submitting — all in one call. If the source risk has no product assigned, clone will fail with a clear error; have the user fix that on the source before retrying. When submitting, the classless-submit guard (see `submit_risk`) runs after the clone is created: a blank — or unreadable — required class of business leaves the clone intact but NOT submitted, and the result says so with the new risk id. Copying the answers needs Pathpoint staff permission, which the hosted connector never carries. For any other account the tool refuses before it creates anything; then use `create_risk` and `modify_submission` instead. ### Excess cross-sale (`create_excess_from_quote`) Turns an existing quote on an underlying GL risk into a standalone excess (umbrella) submission. It creates a NEW risk: the underlying risk's answers are cloned onto the excess product and the underlying-policy block (carrier, premium, occurrence/aggregate/PCO/personal-injury limits, dates, operations state and ZIP) is seeded from the quote, so it starts mostly filled in. The original risk and quote are untouched. Follow it with `get_submission_questions` on the new risk, fill the gaps with `modify_submission`, then `submit_risk`. Eligibility is the server's call: contractors GL (`cglV2`) becomes `contractorsExcessStandalone`, LRO (`cglLRO` / `packageLRO`) becomes `lroExcessStandalone`, and a bundle qualifies through whichever of those it offers. Any other coverage comes back with an error naming the coverage — that error is the answer, so relay it rather than retrying. ### Sharing & ownership (`share_risk`, `set_risk_owner` is `internal`) - `share_risk` — "share with my team" → scope AGENCY; "make this private" → PERSONAL. - `set_risk_owner` — hand a submission to a teammate by email. Confirm the email before calling; the user must already exist in Pathpoint. ### Agency directory (`list_agency_users`) Lists names and emails at the user's agency. Use it to resolve "assign this to Sarah" into an exact email for `set_risk_owner`, or to pick a licensed agent for `request_bind`. Pass `query` with part of the name or email (`query: "sarah"`) — large agencies have hundreds of users, and without a query the list stops at `limit` (default 25, max 100). ## Files & documents ### Risk files (`upload_risk_file`, `delete_risk_file`, `list_risk_files`) `upload_risk_file` attaches a local file (loss runs, supplementals, ACORDs, signed docs) to a risk — ask for the file's path, confirm name and target risk, then upload and verify with `list_risk_files`. For submission documents (ACORDs, supplementals), pass `extract: true` so extracted values prefill the application — track progress with `get_extraction_status`. `delete_risk_file` is a soft remove by file EID; confirm before deleting, same as quotes. `list_risk_files` returns names, types and sizes; add `include_urls: true` only when the user wants to download something. ### Loss runs (`request_loss_runs`) For bound/issued risks when the user asks for loss runs. The documents arrive asynchronously — check `list_risk_files` later rather than promising instant results. ### Quote documents (`get_quote_documents`) Use when the user wants the quote letter, proposal, invoice, binder, or policy paperwork. Returns the document list; pass `include_urls: true` to get presigned download links, and share the link, not the raw URL string dump. If nothing is attached yet, say so; quote letters can take a few minutes to generate after quoting. ### Attaching quote documents (`attach_quote_file`, `internal`) The write side of `get_quote_documents` — uploads a local file and attaches it to a quote as a typed document. Admin-only: the underlying mutation requires `GLOBAL_ACCESS_PROTECTED_RESOURCE`. | Parameter | Required | Notes | | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `quote_id` | one of\* | Quote UUID or EID. A quote number is not accepted — resolve it with `get_quote` first | | `quote_ids` | one of\* | Array of quote UUIDs/EIDs to attach the SAME file to in one upload — the renewal-quote case, one letter across several option quotes. Duplicates are deduped; an empty array is refused | | `file_path` | yes | Absolute path of the local file | | `file_type` | yes | `APPLICATION`, `COVER_LETTER`, `POLICY_DOC`, `QUOTE_LETTER`, `SIGNED_APP_FORM`, `SUPPLEMENTAL_DOC`, `BINDER`, `INVOICE`, `LOSS_RUNS_DOC`, `NOTICE_OF_POLICY_CANCELLATION`, `NOTICE_OF_REINSTATEMENT`, `CANCELLATION_INVOICE`, `REINSTATEMENT_INVOICE` | | `file_name` | no | Override the stored name (defaults to the file's base name) | | `confirm_prod` | on prod | `true` is required when the session points at prod | \* Pass exactly one of `quote_id` or `quote_ids`, never both. **The bind unblock.** Manual quotes created with `quote_risk` never auto-generate a quote letter, so `request_bind` hard-fails with "Quote letter is not ready" (`QUOTE_FILE_MISSING`) until one exists. Attaching a `QUOTE_LETTER` sets the quote's letter pointer and clears the blocker: ``` quote_risk → attach_quote_file (QUOTE_LETTER) → check_bind_readiness → request_bind → create_bind_request_task ``` Re-attaching replaces that slot — the previous file stays on the risk, only the quote's pointer moves. Verify with `get_quote_documents`. **Renewal quotes (SYS).** The BPO team's renewal workflow produces several option quotes on one risk that all share a single carrier quote letter — the same document, several targets. Use `quote_ids` rather than calling `attach_quote_file` once per quote: the file is uploaded once and the same S3 object is attached to every id in the array. ``` renew_risk → quote_risk (per option) → attach_quote_file (QUOTE_LETTER, quote_ids=[...]) → check_bind_readiness ``` The file uploads once regardless of how many ids are passed. Every quote gets its own `addFileToQuote` call, and a failure on one does not stop the rest — the result names which quotes attached and which did not, so a partial failure is never reported as a plain success. ## After the bind: servicing a policy `get_inspection_status` and `upload_proof_of_inspection` are in the agent tier. Everything else here is in the opt-in `internal` group, except the QA tools, which are in `qa`. ### Inspections (`get_inspection_status`, `upload_proof_of_inspection`, `mark_compliance_insufficient`) Inspection is a mandatory timeline stage between Payment and Policy for any policy bound on a quote carrying an inspection fee. - `get_inspection_status` — answers "where is the inspection, what is attached, and what do I owe?". Each inspection comes back with its status spelled out in plain terms, the carrier's recorded discrepancies, and the attached documents. On a bundle, the PARENT risk id expands to every child risk's inspection. Most risks have no inspection at all and the tool says so explicitly — that is not an error. - `upload_proof_of_inspection` — files the signed recommendations letter and proof the carrier's recommendations were carried out. `risk_ids` is an ARRAY, because one compliance packet routinely covers several policies on the same account. Three things to hold onto: - **A bundle parent does NOT expand here** (unlike `get_inspection_status`). Pass the child risk ids the inspections actually sit on, or the parent is silently skipped. - **It is not idempotent server-side.** A second successful call files a second compliance submission, a second Novidea sync and a second email to ops. The tool derives file ids from the file contents so the documents themselves won't duplicate, and refuses by default when a byte-identical document is already attached. - **On a timeout the outcome is UNKNOWN, not failed.** Re-run `get_inspection_status` first; only re-send if the documents are genuinely absent. Never retry blindly. - `mark_compliance_insufficient` — the ops/compliance **rejection** of a submitted proof-of-compliance packet. Moves the risk from `INSPECTION_COMPLIANCE_IN_REVIEW` back to `INSPECTION_COMPLIANCE_REQUIRED` and pushes `Compliance Insufficient` to Novidea. `comment` is what the broker/agent reads next to learn what still needs fixing, so be specific. Calling it twice in a row records nothing the second time — the server only writes a new activity when the latest inspection activity is a different type — and this tool reports that up front as a success rather than claiming a fresh rejection. Admin-only (`GLOBAL_ACCESS_PROTECTED_RESOURCE`). The web app only shows this button to QA-flagged users (`viewer.qa`), a UI condition unrelated to the API's permission check — so an ops session with admin access can do this through the tool even where the app hides the button. - **Only meaningful in `INSPECTION_COMPLIANCE_IN_REVIEW`** — a packet must actually be submitted and awaiting review; on any other status (nothing submitted yet, already cleared, still processing, etc.) this refuses instead of writing. - **A bundle PARENT id is refused, not expanded** (the same trap `upload_proof_of_inspection` warns about above). If the risk's inspections are all keyed to different (child) risk ids, this tool names those child ids in the refusal — pass one of them instead of the parent. ### Inspections (QA, non-prod) From-scratch setup for a risk that is in any inspection state, on **local / review apps / demo only**. The prod API is unchanged; these tools refuse against a prod session before any network call. Sign in with a QA account (e.g. `mferguson@pathpoint.com`), not a `+broker` variant. The inspections row is born in exactly one place: `request_bind`, gated on `inspection_fee > 0` (the same `Boolean(quote.inspectionFee)` check as prod). Status then arrives from Novidea and documents from Heron — an external loop we cannot and should not simulate. Most inspection UI needs that row (`Risk.inspectionStatuses` iterates it), so without it InspectionPage shows "No inspection needed" and the Documents Hub Inspections tab is empty, even if a status-only write flips the chip. Create the row via the real path, then set status. **Chain** (one conversation: "get me a new quote with an inspection in compliance-required"): 1. `create_risk` — property or GL coverage (`cglV2`, `monolinePropertyV2`, a `package*` product) for realism. Fill with `get_submission_questions` / `modify_submission`, then `submit_risk`. 2. `quote_risk` with `inspection_fee` **greater than 0** — that is the exact prod gate. Zero or omitted means no inspections row at bind. 3. Manual quotes never auto-generate a quote letter. Attach one with `attach_quote_file` (`file_type: QUOTE_LETTER`) or `request_bind` fails `QUOTE_FILE_MISSING`. 4. `select_quote`, then clear pre-bind subjectivities (`check_bind_readiness`). Use the typed `answer_subjectivity` parameters — `date`, the `contact_*` trio, `licensed_agent_email` — never raw `text` on SELECT_LICENSED_AGENT (that stores an email string the subjectivities page cannot parse). FILES items need `upload_subjectivity_file`. Then `request_bind`. That last call runs the actual prod kickoff — inspections row plus `INSPECTION_PROCESSING`. The risk landing in `BIND_REQUESTED` is prod-faithful. For a bundle, subsequent inspection writes take the **coverage/child** risk id, not the parent. 5. `enable_toolset` `qa` if the QA tools are hidden, then `update_inspection_status`. **Setting status (`update_inspection_status`).** Params: `risk_id`, `status`, optional `comment`. Settable values: `INSPECTION_PROCESSING`, `INSPECTION_COMPLIANCE_REQUIRED`, `INSPECTION_COMPLIANCE_IN_REVIEW`, `INSPECTION_CLEARED`, `INSPECTION_NON_PRODUCTIVE`, `INSPECTION_DNOC`. `INSPECTION_PROCESSED` is rejected — Go-side reads omit it, and prod only writes it paired inside the Heron worker. - Only `INSPECTION_COMPLIANCE_REQUIRED` / `INSPECTION_COMPLIANCE_IN_REVIEW` derive risk statuses (`COMPLIANCE_REQUIRED` / `COMPLIANCE_IN_REVIEW`, above ISSUED). Only the former is a dashboard action item. The other four are annotations. - `COMPLIANCE_REQUIRED` pushes to Novidea; local, review, and demo all point Novidea at the pcsb Salesforce sandbox, so the push is inert (may log a Sentry error if the risk has no sandbox application — acceptable). - Same-status calls are no-ops (the server dedupes). - A broker session gets a permission error (`GLOBAL_ACCESS_PROTECTED_RESOURCE`), not a silent failure. An unquoted / unselected risk comes back "No selected quote found". - Verify each state with `get_inspection_status` (chip, InspectionPage, `Risk.inspectionStatuses`). After `INSPECTION_COMPLIANCE_REQUIRED`, `get_action_items` should show the Compliance-required card. **Ops documents (`upload_inspection_file`).** Optional; use when the Documents Hub Inspections tab needs content. Wraps the ops `uploadInspectionFile` (`INSPECTION_REPORT` / `INSPECTION_LETTER` into `inspections_files`). Requires the inspection row. Same non-prod allowlist and QA permission. Brokers submit proof of compliance with `upload_proof_of_inspection`, not this tool. Do not invent a create-inspection mutation or fake Novidea. File-driven agent flow (`uploadProofOfInspection` from the app) stays app-only. ### Cancellation & non-renewal (`cancel_or_reinstate_policy`, `mark_policy_non_renew`, `remove_non_renewal`) Admin only — these require `GLOBAL_ACCESS_PROTECTED_RESOURCE`, and a broker-only session gets Forbidden. They are the EXECUTE half of ending a policy; `request_policy_cancellation` (below) is the broker intake half, which only records a request and emails the servicing team. For every carrier except Vave, these tools are the only in-product way to end a policy at all. - `cancel_or_reinstate_policy` — stamps the cancellation (or reinstatement) on a BOUND quote and logs the activity. `canceled: true` cancels and requires an effective date; `canceled: false` reinstates and defaults to today. The reason is free text, but use one of the wordings the app's dropdown offers so ops reads the same thing it sees in the app. For Vave policies this also makes a live outbound call to the carrier; for everyone else it changes Pathpoint's records only, so the carrier must be told separately. A cancellation dated in the future shows as requested but not yet in force — that is correct, and worth explaining to the user. - `mark_policy_non_renew` — records that the policy will not be offered a renewal, with an effective date (normally the expiration date) and a reason. It does not end coverage mid-term. This has no user interface anywhere in the web app. - `remove_non_renewal` — undoes the above. Safe to run when nothing was marked, though it logs an activity every time. It does NOT undo a cancellation — use `cancel_or_reinstate_policy` with `canceled: false` for that. Identify the policy with `risk_id`; the single bound quote is used by default, and `quote_identifier` picks one when a risk has several. The cancellation/reinstatement documents (`NOTICE_OF_POLICY_CANCELLATION`, `CANCELLATION_INVOICE`, `NOTICE_OF_REINSTATEMENT`, `REINSTATEMENT_INVOICE`) are attached with `attach_quote_file` — see _Attaching quote documents_. ### Binding or issuing (QA, non-prod) Load with `enable_toolset` `qa`. Ops BIND/ISSUE for a quoted policy, on **local / review apps / demo only**. The prod API is unchanged; `bind_or_issue_policy` refuses against a prod session before any network call. `confirm_prod` cannot unblock it. Sign in with a QA account, not a `+broker` variant (`GLOBAL_ACCESS_PROTECTED_RESOURCE`). The EXECUTE half of bind/issue — wraps `bindQuoteIssuePolicy`. `request_bind` is the agent handoff (notifies the team); this is what actually stamps the quote bound or issued. - `bind_or_issue: BIND` puts coverage in force. For Vave this is a live outbound carrier call. BIND is enough to unlock Vave MTA (`create_mta_submission`). - `bind_or_issue: ISSUE` stamps the policy issued. Attach a `POLICY_DOC` with `attach_quote_file` first, the same way the python script did. Identify the quote with `risk_id` plus `quote_identifier` (UUID, EID, or number). Prerequisites match `bindQuoteIssuePolicy`, not `request_bind`: Vave needs an `INVOICE` (`attach_quote_file`); other markets need `BINDER` + `INVOICE` and `policy_id` (required except Vave/Coterie). A quote letter is not a gate. On a bundle, pass the coverage child risk, not the parent. Local Vave shortcut to an MTA-ready policy (quoted Vave risk already in the session): ``` enable_toolset qa → select_quote → attach_quote_file INVOICE → bind_or_issue_policy BIND → create_mta_submission ``` A cloned issued Vave risk (`risk-clone --stage issued`) is already bound — skip straight to `create_mta_submission`. Clones do not copy `vave_building_id_mappings`; a local PriceNew/bind writes those rows. ### Undoing a mistaken bind (`undo_bind`) Admin only (`GLOBAL_ACCESS_PROTECTED_RESOURCE`). The correction for a bind that should never have happened — wrong quote bound, ops error, a bind recorded against the wrong risk. It clears the quote's bound/issued state (the bind request is kept), and it DELETES the Bound/Issued activity rows, which cannot be restored. That deletion is why this needs an explicit, confirmed user request — never suggest it as a routine step backwards. The server does not re-sync the risk status after an undo, so the tool reads the quote and risk back and reports what is true, a stale status included. Two boundaries to respect: - A policy that legitimately existed and now needs to end is a **cancellation** (`cancel_or_reinstate_policy`), which preserves the record that coverage was in force. Undo is only for binds that were mistakes from the start. - The bind request survives the undo. If the agent no longer wants to bind at all, withdraw it with the Retool `revertBindRequestedAt` action. `cancel_bind_request` only cancels a queued task. Same targeting as the other policy tools: `risk_id`, with the single bound quote picked by default. The tool shows the state before and after so what changed is on the record. ## Endorsements Bound policies only. `create_endorsement_request`, `list_endorsement_requests`, `get_confirmed_endorsements` and `request_policy_cancellation` are in the agent tier; `confirm_endorsement` and `decline_endorsement_requests` are in the opt-in `internal` group. Broker side: - `create_endorsement_request` — asks Pathpoint to change a bound policy: additional insureds, named insured or DBA, add/remove/move a premises, mailing address, operations, exposure or limits. It records the request, emails the servicing team and opens the endorsement in Salesforce; it does not change the policy. Takes `risk_ids` (UUIDs — more than one only for sibling risks split off the same parent submission), `endorsement_requests` (each an `action` + `sub_action` + free-text `answers` whose keys are the question labels the web form shows), `requested_effective_date`, optional `additional_information` (max 1000 characters — Salesforce rejects longer) and `files`. The tool's own schema lists every valid action → sub-action pair; read it rather than guessing. - `request_policy_cancellation` — ends a policy instead of changing it. Takes `risk_id`, `cancellation_effective_date` and a `reason` (Property sold | Replacement coverage obtained by insured | No longer in business | Complaint | Other); `reason: "Other"` needs `reason_details`. A signed ACORD 35 is always required, plus proof of sale for "Property sold" or the replacement policy for "Replacement coverage obtained by insured". `create_endorsement_request` refuses the "Cancel policy" action and points here. - `list_endorsement_requests` — every endorsement and cancellation request on a risk with each requested change's state and the choice ids the ops tools take. State is derived, not stored: PENDING until ops resolves it, then ACCEPTED or DECLINED. Optional `status` filter (pending, accepted, declined). Ops side (admin — requires `GLOBAL_ACCESS_PROTECTED_RESOURCE`): - `confirm_endorsement` — accepts pending choices and records the confirmed endorsement that results; it can decline the rest in the same call, and choices left out stay pending. `choices` describes what the endorsement actually does, and the server validates each one as an ordered tuple — the field keys AND their order must match the action/sub-action pair exactly. - `decline_endorsement_requests` — declines choices outright with a reason each, creating no endorsement. Use it when nothing on the request is going through; the reasons are emailed to the agent. - `get_confirmed_endorsements` — the endorsements actually applied to the policy, with effective date and accepted/unbound timestamps. Three things to know before driving any of this: - **The four write tools need a BOUND quote with a policy id.** They load it before doing anything else, so a risk that was never bound fails with "Quote not found for risk". The two read tools do not. - **Action/sub-action pairing and required forms are enforced client-side only.** The API and the database accept any combination and accept a request with no documents at all, so a mismatch would be stored and shown to ops as an unrecognised request rather than rejected. That's why the tools reject pairs the web form cannot produce (the error names the action the sub-action really belongs to) and refuse a request missing its ACORDs. `skip_validation: true` relaxes exactly those two checks — it does NOT accept out-of-enum actions, sub-actions or file types, which are always rejected. - **Unmappable products.** `bundle` and `mpl` have no line-of-insurance mapping, so the required-forms check downgrades to a warning instead of blocking. On those products the per-choice `file_types` can't be derived either — pass it explicitly on each change, or uploaded files attach to nothing. ## Vave mid-term adjustments (QA, non-prod) Load with `enable_toolset` `qa`. Same allowlist as the inspection QA tools: **local / review apps / demo only**. The prod API is unchanged; these tools refuse against a prod session before any network call. `confirm_prod` cannot unblock them. They are not in `internal`. Distinct from `create_endorsement_request`, which files an ops-mediated request and does not price the change. Vave MTA clones the bound policy into a child risk, edits it, then dry-runs Vave `PriceAdjustment`. The bound Vave policy is the starting point. Get one without walking quote → subjectivities → ops queue: attach an `INVOICE` then `bind_or_issue_policy` BIND on a quoted Vave risk, or clone an issued Vave risk locally. Then: 1. **`create_mta_submission`** — the web "Edit submission for MTA" button. Copies the bound policy into a new MTA child (`migrateRiskValuesToNewProduct`, `childType: MTA`). Always clones onto `monolinePropertyV2`, same as the app (`turnOffWind` handles wind on that child). Pass the **property child** `risk_id` on a bundle, not the parent or the wind leg. If earlier MTAs were accepted, the latest accepted one becomes the parent — same as the app. The mutation is ownership-gated; the web button is QA-only. Returns the MTA risk id and `/risk//edit/review`. 2. Edit buildings / address on that MTA risk with `modify_submission` (or in the app). Cosmetic address edits keep the building's stable key; a real move is Vave **1507** and rejects the whole request. 3. **`dry_run_mta`** — the Request MTA modal's preview. Calls Vave and returns premium delta, per-building `BuildingId`s, and issues. Requires `GLOBAL_MANAGE_MTA`. Does **not** save an MTA quote (`saveDryRunMTA` in the app does that), but it does write an `mta_diff_snapshots` row and Vave creates a submission row. Discard in the app to drop those leftovers; leaving the preview leaves the rows. Takes `original_risk_id` (the bound policy), `mta_risk_id` (the child), plus the form fields (`transaction_reason`, `effective_date`, `change_description`, `coverage_parts_affected`). Saving, accepting, discarding, and undoing an MTA stay in the app. ## Administration The agency-network and agency tools are in the opt-in `admin` group. The profile tools are in the agent tier. ### Agency networks (`list_agency_networks`, `create_agency_network`, `update_agency_network`) Partner/network records, not risk work. All three require the `GLOBAL_MANAGE_AGENCY` permission. - `list_agency_networks` — every network with its id, name, `type` (`STANDARD_NETWORK`, `CENTRALIZED_NETWORK`, `AGENCY`), `payer` (`INSURED` or `AGENCY`), commission percentages, Ascend account id and flags. Read it first: the id it prints is what the update tool takes. - `create_agency_network` — needs at least a name and a type; the commission percentages, Ascend account id, `appointed` and `hide_commission` are optional. - `update_agency_network` — identify the network by that id. Only the fields you pass are changed, so read the record back to the user before and after. ### Agencies & commissions (`search_agencies`, `set_agency_commission`) The level below networks: the individual agency, whose commission percentages are what the agency is actually paid per bind. Both require `GLOBAL_MANAGE_AGENCY`. - `search_agencies` — find agencies by name and read their commission setup. Names are SQL LIKE patterns, so `"Acme%"` matches every agency starting with Acme; a plain name must match exactly. Each hit shows the `groupId` (what the setter takes), both commission percentages, network membership and flags. - `set_agency_commission` — write `commission_percent` and/or `mm_commission_percent` on one agency, by `groupId`. Values are whole percents (`9.0` = 9%), above 0 and at most 15 — the database constrains agency commissions to that range, and the tool refuses out-of-range values before the mutation because the server would half-apply them (the unconstrained legacy row is written first). Zero is additionally refused because the server's Novidea sync drops falsy values — a 0 would be stored in Pathpoint but silently kept at the old rate in Novidea. Only what you pass changes, and the change syncs to Novidea. Show the user the current record from `search_agencies` and get a clear "yes" before writing — this changes real payouts. Network membership is deliberately NOT settable here: reassigning an agency to a network makes the server reset these percentages to the network's defaults, so that stays in the app. Network-level default commissions live on `update_agency_network` above. ### Your own profile (`get_my_profile`, `update_my_profile`, `set_risk_sharing_scope`) - `get_my_profile` — the logged-in user's own record: id, email, name, work phone (the API never returns the stored extension, so it can't be read back), cell phone, NPN, license, email/SMS/terms consent, the default sharing scope applied to new risks, and their agency. Read-only and scoped to the session — there is no way to read another user's profile. - `update_my_profile` — self-service edits: `name`, `phone`, `cell_phone`, `npn`, `agreed_to_sms_notifications`, `share_risks`. All optional, at least one required. Omitted fields are preserved — the tool reads the profile first and re-sends it, because the underlying `updateMe` mutation otherwise nulls work phone, extension and SMS consent. Returns a before/after diff. Confirm the new values with the user first, and pass `confirm_prod: true` on prod. - `license` is admin-only and cannot be set here. - Put an extension inline: `phone: "+1 212-555-0142 ext. 12"`. A bare number CLEARS a stored extension; `phone: ""` removes the work phone entirely. The extension is the one field not preserved: any call without `phone` clears it, so ask the user for it and pass it inline. - `name`, `npn` and `cell_phone` cannot be cleared — the server coalesces an empty value back to the stored one, so the tool rejects it rather than reporting a change that didn't happen. - Any call that keeps SMS consent on re-stamps the consent timestamp to now. - If the stored work phone is unparseable, re-sending it fails with "Not a valid US phone number". Supply a valid `phone` (or `phone: ""`) alongside whatever else is being changed. - `set_risk_sharing_scope` — BULK ACTION: rewrites the scope on EVERY risk the user currently owns (`access_scope: AGENCY` or `PERSONAL`). No per-risk selection and no undo beyond re-running with the other value, so a PERSONAL run un-shares work colleagues may be relying on — get an explicit "yes" first, and pass `confirm_prod: true` on prod. It does NOT change the default for risks created afterwards (that is `update_my_profile(share_risks=…)`), and for a single risk use `share_risk`. ## Loss history `flag_pending_claim` is in the opt-in `internal` group. The other three tools are in the agent tier. **Two unrelated things are both called "claim" here.** Getting them confused will produce a very wrong answer, so establish which the user means before touching anything: - A **property claim** is a row on a building's loss-history questionnaire — submission data, entered while drafting. `add_property_claim`, `delete_property_claims`. - A **pending claim** is a marker saying a NEW claim has been reported since a quote was issued — a post-quote underwriting trigger with side effects far beyond the risk. `list_pending_claims`, `flag_pending_claim`. ### Property claims - `add_property_claim` — adds one **empty** loss-history row to a building. Takes `property_id` (from `list_properties`) and `risk_id`. It carries no claim data and does not return the new row's id, so it is only half the job: fill it in afterwards with `modify_submission` against the labels `Claim Amount`, `Claim Type`, `Claim Date`, `Claim Description` and `Claim Fully Repaired`. The building's own gate question ("Has this location had any claims in the last 5 years?") must be answered yes for the group to appear. Calling twice adds two empty rows. It refuses a building that has no gate question at all (products without per-building loss history, such as cglV2). The tool enumerates the risk's buildings first and refuses unless `property_id` is one of them — including when that list cannot be read. The server does not make that check itself (it authorizes against `risk_id` and then writes to whatever `property_id` it is handed), so this is the only thing standing between a mistyped id and a claim landing on someone else's risk. - `delete_property_claims` — removes loss-history rows by id. **There is no undo through any API** — the row is orphaned rather than erased, so its values survive in the database, but nothing exposed re-attaches it and `add_property_claim` only ever creates a new empty row. Treat it as permanent and expect the data to be re-entered. The batch is all-or-nothing, so one unknown id fails the call and deletes nothing, which at least makes retry-after-fix safe. An already-deleted id is refused like a foreign one, because a deleted row drops out of the risk. Remaining rows renumber positionally, so never echo "Claim 2" back without re-reading. **Finding the ids is the hard part.** No query returns claim ids as data, and no tool lists them up front. A refused `delete_property_claims` call lists every claim id on the risk, but not which "Claim N" each one is. If a user wants a specific claim removed and you cannot tell which id it is, say so plainly and point them at the app rather than guessing. The tool does still check ownership before deleting: it enumerates the risk's own loss-history rows and refuses the whole batch if any `claim_id` is not among them, including when that list cannot be read. That check is the only thing standing in the way — the server authorizes against `risk_id` and then deletes whatever `claim_ids` it is handed, so ids from another risk would otherwise be orphaned under this risk's authorization. ### Pending claims - `list_pending_claims` — pending claims on a quote. Defaults to active only; pass `include_disabled: true` to see cleared ones too. An unknown or foreign `quote_id` is a permissions error, not an empty list. Both pending-claim tools take `quote_id` as the EID `get_quote` prints, a quote UUID, or a quote number with `risk_id`. - `flag_pending_claim` — records that a new claim has been reported against a quote. **Read the blast radius to the user before calling.** If the quote's risk has renewals, one call also syncs Novidea (Salesforce), clears the broker's selected quote on every renewal, hides every **VAVE** renewal quote from the broker, and writes a carrier-worded `REFERRED` activity saying underwriting review is required. Quotes already bound, issued, or with a live bind request are spared. **Clearing any of this is only possible in the app**, so confirm before, not after. The API itself does not de-duplicate, but the tool does: it reads the live markers first and refuses if the quote is already flagged, so a repeated call is safe and its refusal is not an error to route around. ## Hazard data (`internal`) This tool is in the opt-in `internal` group. - `order_wildfire_scores` — orders CoreLogic wildfire scores for a risk's western-state locations and files the report into Novidea. **Internal ops/QA work, not part of the broker flow** — the web app hides its only button behind a QA permission flag, so do not offer it to a broker unprompted. **It only queues.** Success means the job was accepted onto a queue and nothing more: no scores fetched, no report filed, nothing in Novidea changed yet. There is no query anywhere for the status of a wildfire order, so completion can only be confirmed in Novidea or by ops — never from here. Never call it a second time to check whether it worked: every call is a fresh **billable** vendor order (two live CoreLogic requests per location, no dedupe). And two silent no-ops are normal rather than bugs — the worker orders nothing if no carrier on the risk requires wildfire scores, or if the risk has no western-state locations. ## Not covered by these tools (use the app) Check `list_toolsets` before telling a user something is unavailable — most of what looks missing is merely a hidden group. What genuinely is not here: - **The signing ceremony itself** — the e-sign tools generate and manages the packet and its signing URLs, but the interactive signing happens in the browser, and the manual-sign fallback documents are app-only. - **Saving / accepting / undoing an MTA** — `create_mta_submission` and `dry_run_mta` cover creating the child and previewing premium; persisting the dry-run, accepting, and undoing stay in the app. - **Premium financing** — financing estimates and payment programs. - **Extracted document field values** — `get_extraction_status` reports how extraction is going, but the extracted values themselves, and cancelling a running extraction, are app-only. - **Creating broker users** — the `admin` group covers agency-network and agency records, and the profile tools cover the logged-in user, not adding people. - **Importing a risk from JSON.** Endorsement requests, policy cancellation, non-renewal, inspection compliance, Vave mid-term adjustments, loss history, wildfire orders and the e-sign packet (including the per-state subjectivity forms) ARE covered — in the agent tier or the `internal` and `qa` groups. ## Reporting problems When a tool misbehaves — wrong result, unexpected error, something the web app can do that these tools get wrong — offer to help the user report it: the intake is https://github.com/outline-insurance/mcp/issues, and the output of `p doctor` (run in a terminal) gives the maintainers version, session and install state in one paste. Two cautions: that issue tracker is PUBLIC, so never include policyholder names, premiums, or any submission data in a report; and a missing capability is usually a hidden toolset — check `list_toolsets` before reporting a gap. ## Display Formatting When showing risk details to the user, format as: ``` Acme Cyber Corp Status: QUOTED | Effective: 2026-05-01 — 2027-05-01 Product: Cyber (cyber) Submissions: 1. At-Bay — DECLINED (no quotes) 2. Kinsale — QUOTED Quote KIN-001: $4,200 (admitted) Limits: $1M aggregate / $1M occurrence / $10K retention ``` Risk IDs and EIDs are internal — keep them as the parameters you pass back to the next tool call, not as user-facing output. When confirming actions, show before/after: ``` Updating quote KIN-001 (Kinsale): Premium: $4,200 → $4,800 Agency Fee: (none) → $500 Proceed? ```