Connect an assistant
Point Claude, or any assistant that speaks MCP, at your own figures. It reads the same P&L you do, and it can propose a change to your cost model that you approve in the conversation.
- Connect it
- What it is allowed to do
- Reading tools
- Changing things
- What a change can be
- Without a sign-in
- If it does not work
Connect it
In Claude: Settings, Connectors, Add custom connector, and paste this address.
https://vecksapp.com/api/mcpClaude reads the address, works out that it needs to sign you in, and shows you two settings it has already detected. Leave both as they are and press Connect. Either OAuth client option works: Vecks supports both the hosted client metadata Claude recommends and registering a client of its own.
You land on a Vecks page. Sign in if you are not already, choose which company the assistant should reach, decide whether it may change costs as well as read them, and approve. That is the whole setup. The connection then appears under Configure, Assistants, where you can end it at any time.
Two things worth knowing before you start. Only an owner can connect an assistant, because what it reaches includes supplier rates and individual orders. And one connection reaches one company: if you run several, add one connection each, which is also what keeps it unambiguous which figures an answer is about.
What it is allowed to do
A connection is either read only or read and change, and you choose which when you approve it. A read-only connection is not even shown the tools that change things, so it cannot spend a turn trying.
Reading reaches everything you can read in Vecks: the statement, the breakdowns, your cost rules, your products, your settings, and individual orders. Opening an order is written to the access log, exactly as it is when you open one yourself, so there is a record of what was read and by whom.
Changing is bounded, and the bound is structural rather than a promise. A change is always proposed first: it is run against your real orders inside a transaction that is rolled back, and what comes back is the whole P&L before and after it. Committing takes the id of that proposal and nothing else, so there is no way to commit a change whose effect was not produced first. A proposal can be used once and expires after fifteen minutes, and every commit is recorded permanently, outliving the connection that made it.
The reason for that shape, said plainly: Vecks reads text it does not control, such as invoice lines, spreadsheet cells and campaign names. A model holding a write tool is a model that can be argued with. Preview-then-commit is what keeps an argument from becoming a silent change to your numbers.
Reading tools
14 tools, available to every connection. The indented text under each is what the assistant itself is told, written for the model choosing between them.
None of them changes anything, and each says so to your assistant, so most assistants let you allow reading once instead of asking before every question. If yours asks each time, that is its setting rather than ours: look for an always-allow option on the approval it shows you.
Every Meta ad that spent in the period with its spend in your currency, Meta's purchases and value on their attribution window, cost per Meta purchase, hook and hold or link CTR, its launch date, its verdict against its own past, whether it is readable yet, and the three equal periods before it; with the account median, the campaigns and the market strip beside them.
An ad's spend is its share of what the P&L booked for its campaign and day, so the ads add up to the statement. There is no profit per ad: no order names its ad, and the answer says so rather than allocating one.
| Input | Type | Meaning |
|---|---|---|
| campaign | string, optional | only the ads of one campaign, by its id or a part of its name |
| period | string, optional | '7d', '14d' or '30d' (the last N closed days), a month 'YYYY-MM' (a running month is its closed days), or 'YYYY-MM-DD..YYYY-MM-DD'; defaults to '7d' |
| verdict | "fatiguing" | "limited" | "holding" | "not readable yet" | "never delivered", optional | only the ads with this verdict; the counts, median and strip stay whole |
Results per Meta ad for a period, read beside the P&L and never booked: per ad its spend in the reporting currency (its share of what the P&L booked for its campaign-day, so the ads of a campaign sum to the statement), Meta's purchase count and value on the ad set's attribution window, cost per Meta purchase, hook and hold rate for a video or link CTR for a static, launch date and days live, the verdict (fatiguing, limited, holding, not readable yet, never delivered) with the multiple over the ad's own past and the past it was read against, whether it is readable and the spend still needed, and the three equal periods before; plus the counts behind the page's sentence, the account median with its n, the campaigns with their booked spend, and the market strip (contribution before and after ads per market, with the per-order figure). Every figure says whether Meta reported it or Vecks derived it. It refuses a profit, margin or return per ad, because no order names its ad and a per-ad margin would be an allocation nobody decided; the answer carries the refusals and their reasons, so say them rather than computing one.
The orders, campaigns or monthly amounts of a cost that a market, product or cost center does not reach in a month, each with what it booked and why, biggest first.
The same rows the cost model's allocation footer counts as a shortfall, so the two never disagree.
| Input | Type | Meaning |
|---|---|---|
| dimension | "markets" | "products" | "costCenters" | |
| item_key | string | the P&L line's key (from get_layout) |
| month | string | YYYY-MM |
The rows of a cost that a dimension does not reach in a month: the orders (with ids), campaigns or monthly amounts behind the cost model's "X of Y allocated to products / markets / cost centers" shortfall, each with what it booked and why the money cannot reach a product, market or cost center (a charge per order not split by product, an order with no product to split by, a campaign with no market, a monthly amount). Use it to name the exact orders behind a gap instead of guessing.
The same statement split by one dimension: market, product, sales channel, campaign, cost center, or customer (new, returning, or unknown where the channel supplies no customer, as Amazon never does), with every stage per value.
Costs that carry no value for the dimension you chose are reported as unallocated rather than spread, so a market figure is never flattered by a cost that was never attributed to it.
| Input | Type | Meaning |
|---|---|---|
| dimension | "market" | "sku" | "channel" | "campaign" | "cost_center" | "customer_type" | |
| from | string | ISO date YYYY-MM-DD |
| to | string | ISO date YYYY-MM-DD |
P&L broken down by one dimension (market, sku, channel, campaign, cost_center, customer_type) for a date range. Amounts per dimension value and stage. customer_type is new, returning or unknown, from the order's rank in its customer's history; Amazon orders are always unknown.
The per-unit cost of every product in every market, broken into its components, and the per-order rules beside it.
The cost coverage matrix: per-unit cost per SKU and market with per-component breakdown, plus the per-order rules. Shows exactly which SKU/market combinations have missing or partial cost rules.
Stock per product: on hand as the last count plus what arrived minus what sold since, units sold per day over the last 30 days, days left with the run-out date, and the shipments on their way.
On hand is derived from a count and the ledger, never kept as a running figure. A product nobody has counted reads as not counted rather than zero; a refund covering a whole order is read as the goods coming back.
Stock per product: on hand (last count + received - sold + returned since it; null = never counted), the last count (with its id), units sold and returned in the pace window, the selling pace in units per day, days left and the run-out date, open shipments (incoming units, next expected date, ids). Counts and shipments are recorded through propose_change (stock_count, inbound_create, inbound_receive, inbound_delete); none of them touches the ledger.
The structure you have arranged: your subtotals in order, the sections under each, and the lines in each section, with the ids a change refers to.
Every one of these is yours to rename, move or remove. The statement is not a fixed shape we impose.
The statement's structure as the merchant has arranged it: subtotals in order (with ids), the sections under each (ids, labels), and the cost lines in each (item keys, labels). Everything here is the merchant's own and can be renamed, moved, reordered, added or deleted through propose_change (section_update, subtotal_update, cost_move, section_assign, item_create ...). Read it before saying something cannot be changed.
One order as the ledger booked it: every revenue and cost row, the rule that produced each, quantities, the exchange rate, and anything flagged. An order number is only unique within its channel, so give the channel when you know it.
Opening an order is written to the access log, exactly as it is when you open one in Vecks.
| Input | Type | Meaning |
|---|---|---|
| channel | "woocommerce" | "shopify" | "amazon", optional | the channel the order was placed on; without it, an order number shared by two channels reads as one order |
| order_id | string |
One order exactly as booked in the ledger: every revenue and cost row with the matched rate (scope, note), quantities, FX, and any data-quality flags. Use to answer 'why does this order cost X'. An order number is only unique within its channel, so give the channel whenever it is known.
Every line of your statement for the dates you give, in your reporting currency, with the order count. The same figures the dashboard shows, from the same ledger.
Refund timing is a choice: 'actions' puts a refund on the day the order was placed, 'reconciliation' on the day the money went back.
| Input | Type | Meaning |
|---|---|---|
| from | string | ISO date YYYY-MM-DD |
| timing | "actions" | "reconciliation", optional | |
| to | string | ISO date YYYY-MM-DD |
P&L summary for a date range: every line (revenue, costs, profit stages) in EUR, plus the order count. timing 'actions' books refunds on the order date, 'reconciliation' on the refund date.
Your start date and reporting currency, the market map, and the rules that decide what each ad campaign counts for.
Tenant settings and mappings: start date, reporting currency, the market map (which ship countries are broken out as their own market; everything absent books as 'Others'), and the campaign rules (conditions on a campaign's platform, name, delivery country or tag → the markets it counts for, with a spread, a product and a cost center).
How completely the month's orders carry their costs, per category, plus the gaps that are blocking and the data-quality issues by kind.
Four states: loaded, awaiting actuals, a rule exists but nothing matched, and no cost logic at all. The last two are why a profit figure can be too high.
| Input | Type | Meaning |
|---|---|---|
| period | string, optional | YYYY-MM or YYYY; defaults to the current month |
How completely costs are attributed for a period (YYYY-MM or YYYY): per-category states (green = loaded, amber = estimates awaiting actuals, red = a rule exists but something didn't match, grey = no cost logic at all), the blocking gaps, and data-quality issue counts by kind.
Every monthly amount with its P&L line, value, currency and the months it runs.
A monthly amount is spread daily, so a month is never a step at the end of it.
Every fixed (monthly) cost line with id, P&L line, label, monthly amount, currency and validity. Fixed costs spread daily: monthly amount divided by days in the month.
Registered products and bundles with what a bundle is made of, and any product seen in an order that nobody has registered.
The product (SKU) registry: single SKUs and bundles with their composition, plus any unknown SKUs seen in orders that are not registered.
Every cost rule with its scope, basis, amount, currency and dates, split into what is in force and what it replaced.
Rules are versioned rather than overwritten, so the history is here too, and the ids are what a change refers to.
Every cost rate, grouped by P&L line and component, split into active and history versions. Includes each rate's id (needed for rate_update/rate_end/rate_delete), scope (sku/country/market/channel, null = all), basis, amount, currency and validity. For pct_of_net rates the amount is a percentage.
The cost stack that would resolve for a product, country, market and channel you name, component by component.
It answers from the rules without touching an order, so it is the way to check a rule before anything is booked against it.
| Input | Type | Meaning |
|---|---|---|
| channel | "woocommerce" | "shopify" | "amazon" | |
| country | string, optional | ISO-2 ship country, optional |
| date | string, optional | defaults to today |
| market | string | e.g. US, CA, GB, Others |
| sku | string |
Resolve the live cost stack for a hypothetical order line: which rate wins for every component given a SKU, ship country, market and channel. The same resolution the ledger uses (most specific rate wins).
Changing things
2 tools, and only on a connection you allowed to change costs.
The change committed, and the ledger rebuilt so every figure moves with it.
It takes a proposal id and nothing else, so there is always a preview behind a commit; a proposal can be used once and expires after fifteen minutes.
| Input | Type | Meaning |
|---|---|---|
| proposal_id | string | The proposal_id returned by propose_change. |
Commit a change that propose_change already previewed, by its proposal_id. Call this only after you have shown the person the impact and they have agreed to it. A proposal can be applied once and expires fifteen minutes after it was made; there is no way to apply a change that was not previewed first.
The change run against your real orders inside a transaction that is rolled back, and the whole P&L before and after it. Nothing is written.
This is the only way a change is ever prepared, and it can never commit. What it hands back is a proposal id.
| Input | Type | Meaning |
|---|---|---|
| change_json | string | The ConfigChange object as a JSON string, with an 'action' field. |
| summary | string | One plain-language sentence describing the change for the user's approval card. |
Propose one config change to the user. This DOES NOT apply anything: the change is test-run in a rolled-back transaction and you get back its P&L impact; the user sees a card with the impact and an Apply button, and only their click commits it. Use real ids from list_rates / list_fixed_costs / get_layout / get_settings. Everything the admin UI can change is here, including the statement's own structure (sections, subtotals, the order of lines).
What a change can be
One proposal is one of the following. It is the same list the Vecks admin screens act through, so an assistant can do what you can do and nothing else. Everything about the shape of your statement is in here too: the subtotals, the sections and the order of the lines are yours, not ours.
| account_map_clear | take the mapping off an account: account (its lines go back to unmapped and the months they booked come off) |
| account_map_set | map one bookkeeping account (as the accounting prints it on every invoice line, Netvisor) so every line past and future that carries it maps itself: account, item_key, component, or fixed_cost_id (a monthly amount instead of a charge), scope? (the rule under the charge, as on charge_actual_set), role (compare | use = books | ignore), source? (invoices = the month books the sum of the invoice lines on the account; ledger = the month books the general ledger's total on the account less what the shop's own lines explain, see item_ledger_less_set; the invoice lines are then evidence only). A vendor's own memory for a line still wins; a line a person mapped by hand is not moved |
| ad_rule_create | an ad spend rule: conditions {platform, account, campaign_id, name_contains|name_starts|name_pattern, country ('*' known, '-' unknown, or ISO-2), tag}, targets {markets:'country'|'all'|[...], spread:'cost'|'orders'|'even'|{market:share}, product (a SKU), product_group (a group key, spread across its SKUs), cost_center_id} |
| ad_rule_delete | remove an ad spend rule: rule_id |
| ad_rule_reorder | the order of ad rules (ties in specificity): ids |
| ad_rule_update | change an ad spend rule: rule_id, conditions, targets |
| batch | several changes as one: changes [ ... ] (one transaction, one impact) |
| campaign_assign | one campaign counts for a market and/or product: platform, campaign_id, market, product (a SKU) or product_group (a group key) |
| campaign_tag_set | tag a campaign for ad rules: platform, campaign_id, tag |
| cc_rule_create | a cost center rule: warehouse/country/market/channel/item_key and conditions {sku|currency|payment_method|coupon}, cost_center_id, or shares [{cost_center_id, share}] to split |
| cc_rule_delete | remove a cost center rule: rule_id |
| cc_rule_reorder | the order of cost center rules (first match files): ids |
| cc_rule_update | change a cost center rule: rule_id plus the same fields as cc_rule_create |
| charge_actual_set | book one month of a charge from a document instead of the rules: item_key, component, month (YYYY-MM), amount, currency, source, ref, scope? (the rule the document is about, in the rule's own fields: warehouse, country, market, channel, sku, conditions; {default: true} for the rule with no conditions; absent = the whole charge, and then only the rows no rule's document covers) |
| charge_hierarchy_set | the order a charge's break-outs nest in: item_key, component, hierarchy (fields outermost first) |
| charge_min_set | a monthly minimum on a charge (the greater of the rules' total and the floor books): item_key, component, min_monthly (null removes), currency |
| charge_priority_set | whether documents or rules win on a charge: item_key, component, priority (documents|rules) |
| cost_center_create | a new cost center: label, optional currency |
| cost_move | move a cost line into another section (across a subtotal it changes that subtotal): item_key, section_id |
| fixed_actual_set | book one month of a monthly amount from a document: fixed_cost_id, period_month (YYYY-MM), amount (null = the typed amount again), currency, source, source_ref, invoice_line_id |
| fixed_cost_center_set | file a monthly amount to a cost center: fixed_cost_id, cost_center_id |
| fixed_cost_create | a monthly amount spread over its days: item_key, label, monthly_amount, currency, valid_from, valid_to, note |
| fixed_cost_delete | delete a monthly amount with every version of it: fixed_cost_id |
| fixed_cost_end | stop a monthly amount on a date (versions after it go, history before it stays): fixed_cost_id, end_date (null re-opens it) |
| fixed_cost_priority_set | when a document and the typed amount differ, which one books: fixed_cost_id, priority (documents|typed); the other is checked against it |
| fixed_cost_rename | rename a monthly amount, every version of it: fixed_cost_id, label |
| fixed_cost_source_set | where a monthly amount reads its value from: fixed_cost_id, source {kind:'sheet',tabId,cell} (re-read nightly) | {kind:'documents'} (the month's invoice line mapped to it) | null (typed). The typed amount stays as the fallback |
| fixed_cost_start_set | the day a monthly amount's first version starts: fixed_cost_id, valid_from (not after its next period) |
| fixed_cost_update | a new monthly amount from a date, history kept: fixed_cost_id, monthly_amount, effective_from |
| inbound_create | a shipment on its way (a purchase order): sku, quantity, expected_on?, reference?, note?, warehouse? (the location it lands in; the default location when omitted, so ask which one when more than one location is open), from_country? (ISO-2, where it ships from) or from_warehouse? (a transfer from another location, which leaves that location's stock the day it ships), mode? (air | sea | train), shipped_on? (the day it left, today by default), unit_cost?, currency?, invoice_line_id? (the invoice line it came from); counts towards on hand once received |
| inbound_delete | remove an incoming shipment: id |
| inbound_receive | mark a shipment received: id, received_on? (today), quantity? (what actually arrived) |
| inbound_update | correct a shipment on its way: id, and any of quantity, expected_on, shipped_on, from_country, from_warehouse, mode (air | sea | train), reference; only the fields given change |
| invoice_line_map | which charge an invoice line is evidence about: line_ids (from the invoice screen), item_key, component, or fixed_cost_id (a monthly amount instead of a charge), scope? (the rule under the charge, as on charge_actual_set; absent = the whole charge), role (compare = rules already produce this cost | use = the amount comes from the invoice | ignore). Remembered for that vendor and line description. |
| item_create | a new cost or income line in a section: key, label, section_id |
| item_delete | delete a cost or income line of the merchant's own with every charge under it (lines the connectors book, such as gross sales or ad spend, cannot be deleted): item_key |
| item_ledger_less_set | for a line that books from the general ledger (account_map_set with source 'ledger'): the shop's lines whose booked money each month's figure subtracts, so the line is the rest of the company: item_key, less (line keys; [] books the accounts' total in full) |
| item_product_split | how a line's per-order money is attributed to products (shipping revenue, a fee per order): item_key, split = none (it carries no product) | units (each product's share of the units on the order) | revenue (each product's share of the order's line revenue). It moves no total, only where the money sits in the product view. |
| item_reorder | the order of the costs inside a section: ids (every item id in the section, in order) |
| market_breakout | make a country its own market, or file it into a named one: country (ISO-2), market |
| market_create | a market of the merchant's own: label, regions (keys from the region list, e.g. europe, nordics, north_america), countries (ISO-2). A named country beats a region; the smallest region wins; unmatched = Others |
| market_delete | remove a market: market_id (its countries fall to the next market or Others) |
| market_remove | fold a country back into Others: country |
| market_update | change a market: market_id, label, regions, countries, except (countries the market's regions must not take), sort_order |
| product_group_create | a product group (category) of SKUs: label, optional key, skus [sku, ...]; ad spend counting for a group is spread across its SKUs by cost share |
| product_group_delete | remove a product group: key (its SKUs stay, ungrouped; ad rules lose the group target) |
| product_group_update | rename a product group or set its members: key, label?, skus? (the full list) |
| rate_cost_center_set | file one rule's rows to a cost center: rate_id (+ rate_ids), cost_center_id (null = as the rules say) |
| rate_create | a new cost rule: item_key, component (the charge), basis (per_unit|per_order|pct_of_net|per_refund), amount, currency, valid_from, optional scope sku/warehouse/country/market/channel, conditions {currency|payment_method|coupon}, stacks (a rule on top), note |
| rate_delete | delete a rule and its history as if it never existed: rate_id (+ rate_ids), valid_from (the rule's own) |
| rate_end | stop a rule from a date, history kept: rate_id (+ rate_ids), end_date (the last valid day) |
| rate_source_set | read a rule's value from somewhere: rate_id, source {kind:'sheet',tabId,cell} | {kind:'connector',field} | null (typed); a connector field is 'amazon.fee_total' or a number a WooCommerce plugin keeps on the order, 'woocommerce.meta:<key>' (e.g. 'woocommerce.meta:_stripe_fee', nested 'woocommerce.meta:_ppcp_paypal_fees/paypal_fee/value'), on a per_order or pct_of_net rule; an order without the figure books the typed amount |
| rate_update | a new value for a rule from a date, history kept: rate_id (+ rate_ids for a combined row), amount, effective_from, optional note, source |
| section_assign | move a section under another subtotal: section_id, subtotal_id |
| section_create | a new section (a group of lines under a subtotal): label, stage, optional subtotal_id, optional parent_id (inside another section) |
| section_delete | delete an empty section: section_id |
| section_reorder | the order of sections: ids |
| section_update | rename, restage or nest a section: section_id, label and/or stage, sort_order, parent_id (null lifts it to the top level) |
| sku_group_set | put one SKU in a group, or take it out: sku, group_key (null = none) |
| sku_rename | rename a product wherever it is shown: sku, label (a name only, nothing is rebuilt) |
| sku_upsert | register or rename a product: sku, label, kind (single|bundle), bundle_components for a bundle |
| stock_count | record a stock count for a single product (the stock at the end of that day; on hand is derived from the newest count and the ledger's sales since): sku, quantity, counted_on, note?, warehouse? (a storage location key; the default location when omitted) |
| stock_count_delete | remove a stock count: id (from get_inventory) |
| subtotal_create | a new subtotal line of the merchant's own (EBITDA, Net revenue): label |
| subtotal_delete | delete a subtotal that holds no sections: subtotal_id |
| subtotal_reorder | the order of subtotals: ids (the opening one stays first) |
| subtotal_update | rename or reorder a subtotal, Gross profit and Net profit included: subtotal_id, label, sort_order |
Without a sign-in
Some assistants take a header instead of signing in. For those, an owner can make a token under Configure, Assistants. It is shown once and stored only as a hash, so a copy of our database is not a usable credential. In Claude Code:
claude mcp add --transport http vecks https://vecksapp.com/api/mcp \
--header "Authorization: Bearer <your token>"A token made this way does not expire, which is why the sign-in path is the better one where a client offers it: those tokens are short and refresh themselves, and either can be revoked from the same panel.
If it does not work
- It says you are not an owner. Ask the owner of the company to connect it, or to make you one under Configure, Users.
- It signs you in and returns to the sign-in page. The address you signed in with has no access to any company here. Access is attached to the address, so check it is the one you were invited on.
- It answers that the assistant is not registered. The connector was added with a client id we have never seen. Remove it in Claude and add it again, which registers it fresh.
- Answers are about the wrong company. A connection is bound to the company chosen when it was approved. Revoke it under Configure, Assistants and connect again.
- Anything else: contact@vecksapp.com, or open a ticket from the assistant panel inside Vecks.
Privacy
A connected assistant reaches your company's figures and nothing outside them, under the address that approved it. It cannot reach another company, and it cannot reach anything you could not reach yourself. What the connectors store is described in the privacy policy: order payloads are stripped of personal data before they are stored, so what an assistant can read about an order is money, products, country and dates.