# io.github.theluckystrike/office-suite (mcpb · office-suite.mcpb)

Whole freelancer office in one MCP server: time tracking, price watching, spreadsheets, invoicing.

- Trust score: 46/100 (low)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-20

## Components

- mcpb · `office-suite.mcpb`: 46/100 (this document), [markdown](https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui.md), [page](https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui)

## Channel facts

- Registry: `mcpb`
- Package: `https://github.com/theluckystrike/mcp-servers/releases/download/v0.22.0/office-suite.mcpb`
- Version: `0.22.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-20.

- **Supply Chain Security**: 22/100
  - Malware scan not yet available for this package.
  - Known CVEs were checked across the 190 of 190 dependencies we could resolve, so this covers what we could see, not the whole tree.
  - No install/post-install scripts declared.
  - 88 of 190 dependencies flagged as unhealthy (2 deprecated).
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 3 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 81/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 24098 tokens (~139/item across 173 items; 158 tools + 15 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 0/100
  - Stability not yet verified: not enough scan history yet (needs a 30-day window).
- **Tool Coverage**: 95/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 85% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 12 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "entry_delete" implies "delete" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 159 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

**Unverified: 1 category.** A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

## Install

- Download bundle: `https://github.com/theluckystrike/mcp-servers/releases/download/v0.22.0/office-suite.mcpb`

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-18 (score 46)

First indexed and scored.

## MCP tools (158)

### `timer_start` (~202 tokens)

Start timer

Start a stopwatch for work on a project and return the start time. Only one runs at a time: a new one stops and logs the previous, naming it. rate is an hourly rate in MAJOR units for this timer only.

Input parameters:

- `currency` (string): Currency of the rate: EUR, USD, GBP, PLN, or words like 'euros'. Defaults to the project currency, else USD.
- `project` (string, required): Project or client name, e.g. 'acme-website'. A partial name that matches exactly one existing project is used as that project.
- `rate`: Hourly rate for this timer only; a number (90) or the words the user said, e.g. rate '90 euros an hour'. Defaults to the project rate set by project_set_rate.
- `tags` (array): Free-form tags, e.g. ['dev','meeting']
- `task` (string): What you are working on right now

### `timer_stop` (~60 tokens)

Stop timer

Stop the running timer and log it as one entry. Returns the duration, the money at the rate in force and the new entry id. With no timer running it says so and writes nothing.

Input parameters:

- `note` (string): Optional note stored with the entry

### `timer_status` (~49 tokens)

Timer status

Report the running timer and today's total hours. Today is the local calendar day, so a timer started at 23:30 yesterday contributes only the minutes since midnight. No arguments, and it writes nothing.

### `entry_add` (~252 tokens)

Add time entry

Log time already worked as one entry and return its id, duration and amount. Give start plus end or minutes. rate is hourly in MAJOR units and is frozen on the entry, so a later rate change never moves it.

Input parameters:

- `billable` (boolean): Default true; set false for non-billable work
- `currency` (string): Currency of the rate: EUR, USD, GBP, PLN, or words like 'euros'. Defaults to the project currency, else USD.
- `end` (string): ISO 8601 end time (or use minutes)
- `minutes` (number): Duration in minutes (alternative to end)
- `note` (string): Optional note
- `project` (string, required): Project or client name. A partial name that matches exactly one existing project is used as that project.
- `rate`: Hourly rate for this entry; a number (90) or the words the user said ('90 euros an hour')
- `start` (string, required): ISO 8601 start time, e.g. 2026-09-02T09:00:00
- `tags` (array): Optional tags
- `task` (string): What the work was

### `entry_list` (~109 tokens)

List time entries

List logged entries as a table of id, day, start, project, task, hours, billable, tags and note, newest first, with total hours. Free reads the last 7 days and says so; Pro reads the whole history.

Input parameters:

- `from` (string): ISO date/time lower bound
- `limit` (integer): Maximum rows, newest first (default 50)
- `project` (string): Filter by project name
- `to` (string): ISO date/time upper bound

### `entry_delete` (~60 tokens)

Delete time entry

Delete one logged entry by id and report the project and hours removed. It never touches a running timer. A billed entry goes too, losing the invoice record, so correct one with entry_edit instead.

Input parameters:

- `id` (string, required): Entry id from entry_list

### `entry_edit` (~175 tokens)

Edit time entry

Change one logged entry by id; only the fields you pass move. minutes keeps start and moves end, an end at or before start is refused, tags REPLACES the list, and rate is hourly in MAJOR units.

Input parameters:

- `billable` (boolean)
- `currency` (string): Currency of the rate, e.g. EUR
- `end` (string): ISO 8601
- `id` (string, required): Entry id from entry_list
- `minutes` (number): New duration in minutes, keeps start
- `note` (string)
- `project` (string)
- `rate`: Hourly rate override for this entry, a number or words like '90 euros'
- `start` (string): ISO 8601
- `tags` (array)
- `task` (string)

### `project_set_rate` (~283 tokens)

Set project rate

Set the hourly rate and currency used to turn tracked hours into money for a project or client. Returns the new rate and, when re-rating is asked for, how many already logged entries changed.

Input parameters:

- `apply_to_existing` (boolean): Re-rate time already logged for this project: every entry is re-stamped with the new rate, including entries that already carry one. Default false: the new rate applies to future entries only, becaus…
- `currency` (string): Currency: a code (EUR, USD, GBP, PLN) or a word ('euros', 'pounds', 'zl'). Defaults to the shared business profile's default_currency, else USD.
- `hourly_rate` (required): Hourly rate: a number (85) or the words the user said ('90 euros an hour'). '1,200 USD' is 1200; '12,50 EUR' is 12.50; anything ambiguous is refused.
- `only_missing` (boolean): Only meaningful with apply_to_existing. True restores the old fill-the-gaps behaviour: only entries that carry no rate of their own are touched. Default false, which re-stamps every entry of the proj…
- `project` (string, required): Project or client name. A partial name that matches exactly one existing project is used as that project.

### `report` (~263 tokens)

Time report

Timesheet report: total tracked hours and billable money for a period, optionally grouped by (group by) project, day, task or tag - hours per project, how much to bill. Omit group_by for the plain total per currency.

Input parameters:

- `format` (string): table (default), json or csv. Every format carries one amount per currency, never a mixed-currency sum.
- `from` (string, required): ISO date/time start of the period. On the free tier the window is clamped to the last 7 days; Pro reports over the full history.
- `group_by` (string): project | day | task | tag. Optional: omit it for the plain total per currency, with no breakdown. Money is grouped by currency and EUR is never added to USD.
- `project` (string): Optional project filter
- `to` (string, required): ISO date/time end of the period. On the free tier the window is clamped to the last 7 days; Pro reports over the full history.
- `unbilled_only` (boolean): Default true: hours already put on an invoice (entry_mark_billed) are excluded, so the report answers 'what is still to bill'. Pass false for the full timesheet including invoiced work.

### `export_csv` (~170 tokens)

Export entries to CSV

Call this tool to export the timesheet to a CSV file (excel-friendly) you can hand to a bookkeeper: one row per entry with hours, billable, rate, currency and amount. Returns the file path written.

Input parameters:

- `from` (string): ISO date/time lower bound. On the free tier the export is clamped to the last 7 days; Pro exports the full history.
- `path` (string): Target file path; a relative path resolves against the working directory. Defaults to a timestamped file in the local data directory, and the full path is returned.
- `project` (string): Optional project filter
- `to` (string): ISO date/time upper bound. On the free tier the export is clamped to the last 7 days; Pro exports the full history.

### `entry_mark_billed` (~199 tokens)

Mark time entries as billed

Close the loop after an invoice is issued: stamp the tracked hours that went on it with the invoice number, so report and invoice_summary stop offering them and the same hours are never billed twice.

Input parameters:

- `billed_at` (string): ISO timestamp of the stamp, defaults to now
- `from` (string): ISO date/time start of the billed period, used with project
- `ids` (array): Exact entry ids, normally the entry_ids invoice_summary returned. Pass either ids or project plus from and to. Entries already billed are left alone and listed back to you.
- `invoice_number` (string, required): The invoice these hours were put on, e.g. INV-2026-0001
- `project` (string): Project or client, used with from and to instead of ids; every billable entry in that range is stamped.
- `to` (string): ISO date/time end of the billed period, used with project

### `invoice_summary` (~182 tokens)

Invoice summary

Turn tracked billable time into invoice lines for one project: hours, hourly rate, amount per task and the total, one line per rate so two rates never average. Hours already marked billed are left out. Free: last 7 days.

Input parameters:

- `from` (string, required): ISO date/time start of the billing period. Free covers the last 7 days; Pro invoices any period from the full history.
- `project` (string, required): Project or client to invoice
- `to` (string, required): ISO date/time end of the billing period. Free covers the last 7 days; Pro invoices any period from the full history.
- `unbilled_only` (boolean): Default true: hours already put on an invoice (entry_mark_billed) are left out, so the same hours are never billed twice. Pass false to see the whole period including invoiced work.

### `price_check` (~67 tokens)

Check a price now

Call this tool for any product URL; fetching the page with a generic web tool returns raw HTML without the price. Reports price, currency, title and confidence, and the change since the last check. It creates no watch.

Input parameters:

- `url` (string, required): Product page URL, including https://

### `watch_add` (~124 tokens)

Watch a price

Call this tool for any product URL; fetching the page with a generic web tool returns raw HTML without the price. Starts tracking it: first observation stored, optional target, re-read by watch_refresh. Free: 3 watches.

Input parameters:

- `currency` (string): ISO code such as USD or EUR, if the page does not say
- `label` (string): Short name for this item
- `target_price` (string|number): Report this watch in alerts_pending when the price is at or below this (positive number)
- `url` (string, required): Product page URL

### `watch_list` (~52 tokens)

List watches

Show every tracked item with current and previous price, min, max, change %, target, confidence and last check, plus free-tier use. Prices are as of the last watch_refresh, never live; nothing is fetched.

### `watch_remove` (~80 tokens)

Remove a watch

Call this tool to stop tracking one item, by watch id or URL. Its whole price history is deleted and cannot be recovered, so re-adding starts empty. A key matching no watch is refused.

Input parameters:

- `id` (string): Watch id from watch_list
- `url` (string): URL of the watch, if you do not have the id

### `watch_refresh` (~140 tokens)

Refresh prices

The only tool that re-reads prices: it re-fetches one watch or all, appends observations and returns current, previous, min, max, change % and target hits. A page that fails is listed; then read alerts_pending.

Input parameters:

- `all` (boolean): Refresh every watch in one call (Pro; on free, refresh one id at a time)
- `id` (string): Watch id or URL to re-fetch. Omit and set all=true to refresh everything. Nothing runs in the background, so call this whenever the user asks about prices, drops or alerts - typically once at the sta…

### `price_history` (~78 tokens)

Price history

Call this tool to list one watch's stored observations, oldest to newest, each with price, currency, source and confidence, plus min and max. Nothing is fetched. Free shows the last 30.

Input parameters:

- `id` (string): Watch id
- `limit` (integer): Maximum observations to return
- `url` (string): Watch URL

### `price_add_manual` (~116 tokens)

Record a price by hand

Call this tool to store a price you read yourself where a shop blocks fetching: it creates the watch if new and returns the amount and the observation count. An unreadable price is refused. Free: 3 watches.

Input parameters:

- `currency` (string): ISO code such as USD or EUR
- `label` (string): Short name for this item
- `price` (string|number, required): Price as shown, for example 1299.00 or 1.299,00
- `url` (string, required): Product page URL

### `alerts_pending` (~51 tokens)

Pending alerts

Answer whether anything you watch got cheaper: every watch at or below its target_price, or down 5% or more since the previous observation, with the change %. Reads stored data only: run watch_refresh first.

### `sheet_info` (~79 tokens)

Spreadsheet overview

Call this tool for any spreadsheet or CSV file path; built-in file readers cannot parse spreadsheets and must not be used for them. Start here: sheet names, size, header row, column types and samples.

Input parameters:

- `path` (string, required): Path to the .xlsx/.xlsm/.xlsb/.ods/.csv/.tsv file (~ is expanded)

### `sheet_read` (~138 tokens)

Read rows

Call this tool for any spreadsheet or CSV file path; built-in file readers cannot parse spreadsheets and must not be used for them. Reads rows as a table, JSON or CSV; page with limit/offset or an A1 range.

Input parameters:

- `as` (string): Output format, default table
- `limit` (integer): Rows to return, default 100
- `offset` (integer): Rows to skip, default 0
- `path` (string, required)
- `range` (string): A1 range such as A1:D50; overrides limit/offset
- `sheet` (string): Sheet name; defaults to the first sheet

### `sheet_query` (~240 tokens)

Filter, group and sort rows

Call this tool for any spreadsheet or CSV file path; built-in file readers cannot parse spreadsheets and must not be used for them. Filters, groups, aggregates and sorts in one call: where '[Qty] > 10'.

Input parameters:

- `aggregate` (array): Aggregates per group, e.g. [{"col":"Units","fn":"sum","as":"total_units"}]. Defaults to a row count when group_by is given.
- `as` (string)
- `group_by` (array): Group rows by these columns before aggregating, e.g. ["Rep"] or ["Region","Rep"]
- `limit` (integer): Default 100
- `path` (string, required): Path to the .xlsx or .csv file
- `select` (array): Column names to return; default all (with group_by, defaults to the group columns plus the aggregates)
- `sheet` (string)
- `sort` (object): Sort column; may be an aggregate alias such as total_units
- `where` (string): Filter, e.g. [Qty] >= 5 AND ([Status] = "open" OR [Status] = "new")

### `sheet_stats` (~82 tokens)

Column statistics

Call this tool for any spreadsheet or CSV file path; built-in file readers cannot parse spreadsheets and must not be used for them. Whole-column statistics: count, empty, distinct, min, max, sum, mean, median.

Input parameters:

- `columns` (array): Limit to these columns; default all
- `path` (string, required)
- `sheet` (string)

### `sheet_find` (~123 tokens)

Find text

Call this tool to search every cell of a spreadsheet or CSV for text; built-in file readers cannot parse spreadsheets. Matching is case insensitive. Returns cell addresses with a preview of the row each hit is on.

Input parameters:

- `path` (string, required): Path to the .xlsx/.xlsm/.xlsb/.ods/.csv/.tsv file (~ is expanded)
- `sheet` (string): Sheet name; default searches every sheet
- `text` (string, required): Text to look for; matched case insensitively anywhere inside a cell. Up to 200 hits are returned

### `sheet_write` (~244 tokens)

Write rows

Call this tool to write rows to an xlsx, csv, tsv or json file; the extension of out_path picks the format. mode is new_file, append or overwrite. Other sheets are kept. Free: 500 rows per file.

Input parameters:

- `mode` (string, required): new_file writes a brand new file and refuses to clobber an existing one; append adds the rows under the existing data; overwrite replaces the file contents
- `out_path` (string): Where to write; default is a new file next to the source for new_file, or the source itself for append/overwrite. The output format follows this extension: .xlsx, .csv, .tsv or .json. An extension is…
- `path` (string, required): Source file for append/overwrite, or the intended file for new_file (~ is expanded)
- `rows` (array, required): Array of objects, whose keys become the headers, or an array of arrays with the header row first
- `sheet` (string): Sheet to write; default is the first sheet of the source, or "Sheet1" for a new file. Other sheets of an existing workbook are kept unchanged

### `sheet_add_column` (~289 tokens)

Add a column

Call this tool to add a computed column and save the result to a NEW file; the source is never modified unless out_path points at it. Returns the new file path, the row count and a preview of the first rows.

Input parameters:

- `decimals` (integer): Round numeric formula results to this many decimals. Default: the widest decimal count of the columns the formula reads, capped at 2 when they all hold 2 or fewer (money in, money out); otherwise no…
- `formula` (string): Expression over the columns of each row, in the same expression language as sheet_query, e.g. "[Qty] * [Unit Price]" or '[Country] = "PL"'. Give either formula or values
- `name` (string, required): Name of the new column. It must not already exist on the sheet
- `out_path` (string): Output file; default <source>-plus-<column>.<same ext>. The source file is left untouched unless this points at it
- `path` (string, required): Path to the source .xlsx/.xlsm/.xlsb/.ods/.csv/.tsv file (~ is expanded); it is never modified
- `sheet` (string): Sheet name; default is the first sheet
- `values` (array): Explicit values, one per data row, instead of a formula. Missing entries are left blank

### `sheet_convert` (~167 tokens)

Convert file

Call this tool to convert a sheet between excel (xlsx), csv and json. Writes a new file next to the source unless out_path is given; the source is never modified. Returns the new file path with its row and column counts.

Input parameters:

- `out_path` (string): Where to write; default is the source name with the new extension, next to the source. It must differ from the source path
- `path` (string, required): Path to the source .xlsx/.xlsm/.xlsb/.ods/.csv/.tsv file (~ is expanded); it is never modified
- `sheet` (string): Sheet to convert; default is the first sheet. Only that one sheet is written
- `to` (string, required): Target format; the default out_path takes this as its extension

### `client_add` (~93 tokens)

Add a client

Store a client so invoice_create can refer to them by name. Re-adding the same name updates the stored address, email and VAT id; a record identical to a stored one is refused, naming the id that already holds it.

Input parameters:

- `address` (string)
- `email` (string)
- `name` (string, required)
- `vat_id` (string): Client VAT id, printed for reverse-charge invoices

### `client_delete` (~62 tokens)

Delete a client

Delete one stored client that nothing refers to. A client named on any invoice, quote, credit note, purchase order, deposit, statement or schedule is refused with those documents listed.

Input parameters:

- `client` (string, required): Client name or id, exactly as client_list shows it

### `client_list` (~49 tokens)

List clients

List every stored client with id, address, email and VAT id. No arguments, and it writes nothing. With none stored it says so: invoice_create also creates a client from the name you pass.

### `invoice_create` (~243 tokens)

Create an invoice

Create an invoice from line items and return the record with its next, never-reused number. unit_price is in MAJOR units; lines are rounded then summed. One currency per invoice. Free: 3 a month.

Input parameters:

- `client` (string, required): Client name or id. Unknown names are added automatically
- `currency` (string): Invoice currency, 3-letter ISO code. Defaults to the one currency every item agrees on, else your business default. Every line on one invoice must agree with it; a mix is refused with the exact conve…
- `discount_percent` (number): Discount percent applied to every line before tax, 0-100
- `due_days` (number): Days until due, defaults to your payment terms
- `issue_date` (string): YYYY-MM-DD, defaults to today
- `items` (array, required): Line items. Amounts are held as integer minor units and every line is rounded first, then summed, so the printed lines can never disagree with the total. A line may carry its own currency
- `notes` (string): Free text printed under the totals

### `invoice_from_hours` (~405 tokens)

Invoice from hours

Shortcut for the common case: bill one client for N hours at an hourly rate. Creates and returns a single-line invoice, converting the rate into target_currency when you supply fx_rates, and echoing back any entry_ids.

Input parameters:

- `client` (string, required)
- `currency` (string): Currency the rate is in. Without target_currency this is also the invoice currency
- `description` (string): Line description, default 'Consulting services'
- `discount_percent` (number)
- `due_days` (number)
- `entry_ids` (array): Time-tracker entry ids these hours came from (the entry_ids invoice_summary returns). Echoed back with the new invoice number so you can call entry_mark_billed
- `fx_rates` (object): Conversion rates, the same pair expense_to_invoice takes: fx_rates maps the RATE's currency to the number of target units one of it buys, meaning 1 unit of that currency = X units of target_currency,…
- `hours` (number, required)
- `issue_date` (string)
- `notes` (string)
- `rate` (number, required): Hourly rate in major units, expressed in currency (or the business default currency)
- `round_total` (boolean): D-R46: when converting with fx_rates, round the line's TOTAL to the exact converted amount instead of rounding the hourly rate to cents first. Default false keeps the D-R24 basis (unit price x hours…
- `target_currency` (string): Issue the invoice in this currency instead, converting the rate. Needs fx_rates for the rate currency
- `tax_rate` (number)

### `invoice_list` (~88 tokens)

List invoices

List invoices by number: client, dates, currency, subtotal, discount, tax lines, total, status, paid, credited and the balance still due after any credit note. Filter by status, client and date range.

Input parameters:

- `client` (string)
- `from` (string): YYYY-MM-DD inclusive
- `status` (string)
- `to` (string): YYYY-MM-DD inclusive

### `invoice_get` (~49 tokens)

Get one invoice

Return the full stored record for one invoice number, including every line, tax breakdown, and the balance still open after any credit note issued against it (see credited_minor).

Input parameters:

- `number` (string, required)

### `invoice_mark_paid` (~156 tokens)

Mark an invoice paid

Record a payment on one invoice. amount is in MAJOR units and ADDS to what is paid, never replaces it; omit it to settle the rest. An overpayment is refused, naming the open balance.

Input parameters:

- `amount` (number): Amount received in major units, ADDED to what is already paid on this invoice. Omit to pay off the remaining balance in full
- `method` (string): How it was paid, e.g. bank transfer, card. Stored on this payment's row
- `number` (string, required)
- `paid_date` (string): YYYY-MM-DD, defaults to today
- `reference` (string): Bank reference or transaction id for this payment. Stored on this payment's row

### `invoice_pdf` (~164 tokens)

Render invoice PDF

Call this tool to write one stored invoice as an A4 PDF and return the path: issuer, BILL TO, dates, items, taxes and totals. Free stamps a credit line and no logo; Pro renders it unbranded with your logo.

Input parameters:

- `number` (string, required): Invoice number to render, as returned by invoice_create
- `out_path` (string): Where to write the PDF; defaults to <data dir>/pdf/<number>.pdf. The page carries the issuer block, the BILL TO client block, dates, an item table with wrapped descriptions, subtotal, discount, one t…

### `overdue_report` (~62 tokens)

Overdue report

List every invoice not paid in full whose due date has passed, worst first, with days overdue and the amount, then a total per currency. Free. Use invoice_list for everything still open.

Input parameters:

- `as_of` (string): YYYY-MM-DD, defaults to today

### `rates_latest` (~138 tokens)

Latest ECB reference rates

Call this tool for the latest ECB daily reference rates against any base: 1.0812 for USD means 1 base = 1.0812 USD. Crosses go through the euro. Returns the ECB rate date and the cache age.

Input parameters:

- `base` (string): Base currency; defaults to the shared business profile's default_currency, else EUR. A rate of 1.0812 for USD means 1 base = 1.0812 USD. Cross rates go through the euro, the only pair the ECB publish…
- `quotes` (array): Only these currencies, at most 200. Omit for all of them

### `convert` (~244 tokens)

Convert an amount

Call this tool to convert an amount between any two ECB-quoted currencies, today or on a past date. Returns the converted amount, the cross rate to 6 decimals, the rounding applied and the rate date used.

Input parameters:

- `amount` (number, required): Amount in major units of the from currency, e.g. 100 or 12.34
- `date` (string): ISO date YYYY-MM-DD. Omit for the latest published rate. A weekend or TARGET holiday falls back to the last rate published on or before it, and the answer says so. A date older than the free 90-day w…
- `from` (string, required): Currency the amount is in. Cross rates go through the euro, the only pair the ECB publishes
- `to` (string): Currency to convert into; defaults to the shared business profile's default_currency, so you are never asked what currency you invoice in. The result is rounded once, at the end, to this currency's o…

### `convert_many` (~69 tokens)

Convert one amount into several currencies

Convert one amount into many currencies off the SAME ECB rate date, each rounded to its own minor units. A target the ECB does not quote is listed as unknown rather than failing the call.

Input parameters:

- `amount` (number, required)
- `from` (string, required)
- `to` (array, required): Target currencies

### `fx_rates_for` (~160 tokens)

FX rates in the shape expense-tracker wants

Call this tool when a rebill or an invoice spans more than one currency, instead of asking the user for rates. Returns the fx_rates object expense_to_invoice takes, plus the rate date to write on the invoice.

Input parameters:

- `currencies` (array, required): The other currencies present, e.g. ["EUR", "GBP"]. Direction: each returned rate means 1 unit of that key = X units of the target, so {"EUR": 1.08} is 1 EUR = 1.08 of the target. The target needs no…
- `target` (string): The currency the invoice will be issued in; defaults to the shared business profile's default_currency. Pass it on as target_currency alongside the fx_rates object

### `rate_history` (~213 tokens)

Rate history for a pair

Call this tool for the ECB rate of one currency pair across a window. Returns one row per published day plus the min, max, average and the change. A window wider than the free 90 days is shortened, not refused.

Input parameters:

- `days` (integer): Trailing window in calendar days, default 30. Only TARGET business days carry a rate, so 30 days holds about 21 rows. Free reads up to 90 days back; Pro reads the whole series back to 1999-01-04
- `from` (string, required): Base currency of the pair
- `from_date` (string): ISO date, inclusive. Overrides days. Free is limited to the last 90 days
- `max_rows` (integer): Cap the table, default 200. min/max/avg still cover the whole window
- `to` (string, required): Quote currency of the pair. Each row is 1 from = X to
- `to_date` (string): ISO date, inclusive, default today

### `rate_on` (~231 tokens)

Rate on a given date

Call this tool for the ECB rate of one pair on one date. Returns both directions and the rate date, so a reciprocal is never reported as the published figure. A date beyond the free window is shortened, never refused.

Input parameters:

- `date` (string, required): ISO date YYYY-MM-DD. If the ECB published nothing that day - every weekend, 1 January, Good Friday, Easter Monday, 1 May, 25 and 26 December - the last rate published on or before it is returned and…
- `from` (string, required): Base currency. The ECB quotes every currency per 1 euro, so "the ECB rate for USD" is from EUR to USD, not the other way round; invert only if the user asked for the inverse
- `to` (string, required): Quote currency. The rate returned is 1 from = X to

### `currencies_list` (~48 tokens)

Currencies the ECB quotes

Every currency in the ECB daily set with its rate against the euro and its decimal places. This is the whole domain: a code not on this list cannot be converted, quoted or historised here.

### `cache_status` (~54 tokens)

Rate cache status

Report the ECB rate cache here: which dates are held, how old they are and when they refresh. Reads only. Check it before trusting a rate after time offline; a cache that no longer parses is quarantined and named.

### `invoice_business_set` (~353 tokens)

Set your business details

The ONE business profile for the whole suite: name, address, VAT id, bank details and defaults (currency, tax rate, terms, prefix, timezone). Saved to the shared profile every other server reads. Call it once, first.

Input parameters:

- `address` (string): Postal address, newlines allowed
- `bank` (string): Bank name / BIC
- `default_currency` (string): ISO code, e.g. EUR, USD, JPY. Default EUR
- `default_tax_rate` (number): Default VAT percent applied to items without their own rate
- `email` (string): Your own email address. Leave it out unless the user gave it: no server ever fills an email from anything but this profile or an explicit argument
- `iban` (string): IBAN or account number for payment
- `invoice_prefix` (string): Invoice number prefix, default INV (custom prefix is Pro)
- `logo_path` (string): Path to a PNG or JPG logo (Pro)
- `name` (string, required): Your business or freelancer name
- `payment_terms_days` (number): Default days until due. Default 14
- `phone` (string): Your own phone number. Same rule as email: only if the user gave it
- `tax_rate` (number): Alias for default_tax_rate
- `timezone` (string): IANA zone you work in, e.g. Europe/Warsaw. Shared with time-tracker (entries are stamped in it) and timezone (your home zone)
- `vat` (number): Alias for default_tax_rate
- `vat_id` (string): VAT / tax registration id
- `vat_rate` (number): Alias for default_tax_rate

### `docx_business_set` (~401 tokens)

Set your business details

Store the sender details printed on every proposal, contract and letter, plus the currency, VAT, terms and prefix defaults. It writes the SAME shared profile as the invoice server's business_set.

Input parameters:

- `address` (string): Postal address, newlines allowed
- `bank` (string): Bank name / BIC
- `brand_color` (string): Letterhead colour as a hex code, e.g. 1F3864 (Pro)
- `default_currency` (string): ISO code, e.g. EUR, USD. Default EUR
- `default_tax_rate` (number): Default VAT percent, quoted on proposals
- `email` (string): Your own email address, printed on every letterhead. Leave it out unless the user gave it: a document that shows an address nobody supplied is worse than one that shows [add: email]
- `iban` (string): IBAN or account number for payment
- `invoice_prefix` (string): Reference prefix used by mcp-invoice; this profile has the same field shape as mcp-invoice, so one profile serves both
- `logo_path` (string): Path to a PNG or JPG logo for the letterhead (Pro)
- `name` (string, required): Your business or freelancer name, printed on the letterhead of every proposal, contract and letter
- `payment_terms_days` (number): Default days until payment is due. Default 14
- `phone` (string): Your own phone number. Same rule as email: only if the user gave it
- `tax_rate` (number): Alias for default_tax_rate
- `timezone` (string): IANA zone you work in, e.g. Europe/Warsaw. Shared with time-tracker and timezone as your home zone
- `vat` (number): Alias for default_tax_rate
- `vat_id` (string): VAT / tax registration id
- `vat_rate` (number): Alias for default_tax_rate

### `doc_create` (~213 tokens)

Create a Word document

Call this tool to write a real .docx file from structured sections. Returns the file path, the number of blocks written and the layout used. Free and unlimited.

Input parameters:

- `date` (string): Date line for the letter layout, default today
- `out_path` (string): Where to write the .docx. Defaults to the data directory
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `recipient` (string): Addressee block for the letter layout
- `sections` (array, required): Sections in order. Each one may carry a heading, paragraphs, a bullet or numbered list and a table
- `style` (string): Layout, default plain. plain is the title and the body; letter adds a sender block top right, a date and the addressee; proposal adds a letterhead band and a cover title
- `title` (string, required): Document title, used as the top heading and the file name

### `doc_from_markdown` (~182 tokens)

Markdown to Word

Call this tool to turn markdown into a .docx, returning the file and a count of blocks by type. Headings, lists, GFM tables and code fences are honoured. Empty markdown is refused, and so is overwriting without the flag.

Input parameters:

- `markdown` (string, required): The markdown source. ATX headings, paragraphs, bullet and numbered lists, GFM pipe tables and fenced code blocks as monospace are honoured, as are **bold**, *italic* and `code` inline
- `out_path` (string): Where to write the .docx. Defaults to the data directory
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `style` (string)
- `title` (string): Document title; defaults to the first heading in the markdown

### `doc_read` (~119 tokens)

Read a Word document

Call this tool to extract the text of an existing .docx. Returns an outline of the headings and the full text in document order, or the block structure. Free and unlimited.

Input parameters:

- `format` (string): text (default) returns the readable text, json returns the block structure: headings with their levels, paragraphs, list items and tables, in document order
- `path` (string, required): Path to the .docx file. Files produced by Word, Google Docs or this server all work; legacy .doc and .rtf do not

### `doc_to_html` (~138 tokens)

Word document to HTML

Call this tool to convert a .docx to semantic HTML you can open in a browser and print to PDF. Returns the path of the .html file. This is the supported PDF route; no PDF is rendered here. Free and unlimited.

Input parameters:

- `out_path` (string): Where to write the .html. Defaults next to the source file. Open the result and print it to PDF; direct PDF output is not offered here
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `path` (string, required): Path to the .docx file to convert

### `doc_fill_template` (~200 tokens)

Fill a Word template

Call this tool to replace {{placeholders}} in a .docx and write a new file, reporting what was filled, unfilled or ignored. Call it with no values to list them. Free: templates up to 10 placeholders.

Input parameters:

- `out_path` (string): Where to write the filled .docx. Defaults to <template>-filled.docx
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `template_path` (string, required): Path to the .docx template containing {{placeholders}}. Every style, table, header, footer and image of the original is kept. Placeholders split across runs by Word's editor are handled, because the…
- `values` (object): Placeholder name to value, e.g. {client: "Acme", fee: "EUR 4,500.00"}

### `proposal_create` (~269 tokens)

Create a proposal

Call this tool to produce a client-ready .docx proposal from summary, scope, deliverables, timeline, price and terms. Returns the reference, the total and the file path. Free tier: 3 proposals or contracts per month.

Input parameters:

- `client` (string, required): Client name, printed as 'Prepared for'. The letterhead comes from your business_set profile
- `deliverables` (array): What the client receives, one bullet each. Omitted from the document if not given or empty
- `out_path` (string): Where to write the .docx. Defaults to the data directory
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `price` (object, required)
- `project_title` (string, required): Project title
- `scope` (array): What is in scope, one bullet each. Omitted from the document if not given or empty
- `summary` (string): One or two paragraphs on the problem and the approach. Omitted from the document if not given
- `timeline` (array): Phases and their durations, rendered as a table. Omitted from the document if not given or empty
- `valid_until` (string): YYYY-MM-DD, the date the quote expires

### `proposal_update` (~149 tokens)

Update a proposal

Rewrite an existing proposal in place from its reference. Only the fields you pass change; the rest comes from the data stored at creation. Returns the fields that changed and the file path.

Input parameters:

- `client` (string)
- `deliverables` (array)
- `price` (object)
- `project_title` (string)
- `reference` (string, required): The proposal reference, e.g. PROP-2026-0001. The same file and the same reference number are kept, so no second document is burned against the free-tier monthly count
- `scope` (array)
- `summary` (string)
- `timeline` (array)
- `valid_until` (string)

### `contract_create` (~229 tokens)

Create a service agreement

Call this tool to produce a freelance service agreement .docx. Returns the reference, the fee and the file path. It is a template skeleton for a lawyer to review, not legal advice. Free tier: 3 agreements per month.

Input parameters:

- `clauses` (array): Extra clauses to append, one paragraph each
- `client` (string, required): The client's legal name
- `end_date` (string): YYYY-MM-DD, omit for an open-ended engagement
- `fee` (object, required)
- `governing_law` (string): e.g. 'the laws of Poland'
- `out_path` (string): Where to write the .docx. Defaults to the data directory
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `services` (string, required): What you will do, one or two sentences. The document adds parties, term, fee and schedule, plus standard clauses on intellectual property, confidentiality, independent contractor status, liability, t…
- `start_date` (string, required): YYYY-MM-DD

### `now` (~74 tokens)

Current time in zones

The current time in one or more places, each with the zone it resolved to and the local date, time and offset, under one UTC instant. Accepts IANA zones, cities, countries and abbreviations.

Input parameters:

- `zones` (array): Places or IANA zones, e.g. ['Warsaw','New York','India']

### `convert_time` (~213 tokens)

Convert a time between zones

Convert one time from from_zone into every zone in to_zones, marking any day change, plus the UTC instant. The time is wall-clock in from_zone unless it carries an offset or a trailing Z. '3pm tomorrow' works.

Input parameters:

- `fold` (string): Which occurrence of a time that happens twice because the clocks went back. Default 'first'.
- `from_zone` (string, required): Place the time is given in, e.g. 'Warsaw' or 'Europe/Warsaw'
- `gap` (string): What to do with a time that does not exist because the clocks jumped forward: 'forward' takes the time after the jump, 'backward' the time before it. Without this, such a time is refused.
- `time` (string, required): '2026-09-10 15:00', '2026-09-10T15:00:00Z', '3pm tomorrow', 'now'
- `to_zones` (array, required): Places to convert into

### `overlap` (~117 tokens)

Daily working-hours overlap

The window each day when every listed place is inside working hours. Computed on a real date, so a DST week that widens or narrows the overlap is reflected.

Input parameters:

- `date` (string): Date to compute on, YYYY-MM-DD, default today
- `work_end` (string): Local working day end, default 17:00
- `work_start` (string): Local working day start, default 09:00
- `zones` (array, required): Places, e.g. ['Warsaw','New York','Bangalore']

### `find_meeting_slots` (~303 tokens)

Find meeting slots

Rank the times when every participant is inside their own working hours. Returns each slot as a UTC instant with the local time for every participant and a fairness score, best first.

Input parameters:

- `days` (integer): How many days ahead to search, default 5, at most 366. Free tier: a search longer than 5 days is shortened to 5, not refused
- `duration_minutes` (integer): Meeting length in minutes, default 60, at most 1440
- `earliest_date` (string): First date to consider, YYYY-MM-DD, default today
- `limit` (integer): How many slots to return, default 8. Slots are ranked by fairness: the score is the WORST participant's distance in hours from 13:00 local, so a slot that is 07:00 for one person never outranks one t…
- `participants` (array, required): Who has to attend. A zone is OPTIONAL per person: a saved contact supplies their own, and anyone left without one (you, typically) takes the timezone on your shared business profile, so never ask the…
- `recurring` (boolean): Pro: also report the weekly recurring times that work on every searched weekday

### `dst_changes` (~73 tokens)

Daylight-saving changes

The clock changes in a place for a year, with the exact UTC instant and the offset before and after. Use it to check whether a recurring call moves for one of you in March or October.

Input parameters:

- `year` (integer): Calendar year, default this year
- `zone` (string, required): Place or IANA zone

### `business_days` (~159 tokens)

Count business days

Count business days between two dates in a place, inclusive, with the calendar total and the weekend and holiday counts. It has NO holiday calendar: without holidays, only weekends are excluded.

Input parameters:

- `from` (string, required): Start date, YYYY-MM-DD (inclusive). A date that does not exist, such as 2026-02-30, is refused, never rolled forward
- `holidays` (array): Dates to exclude, strict YYYY-MM-DD. This tool has no national holiday calendar: unless you pass holidays here only weekends are excluded, so do not report the answer as a public-holiday-adjusted cou…
- `to` (string, required): End date, YYYY-MM-DD (inclusive)
- `zone` (string, required): Place whose calendar to use

### `contacts_set` (~107 tokens)

Save a contact's zone

Save one person's zone and working hours so later calls can name them. Returns the resolved zone and their local time now. Saving the same name REPLACES that contact and says so. Free: 5 contacts.

Input parameters:

- `name` (string, required): Their name
- `work_end` (string): Local day end, default 17:00
- `work_start` (string): Local day start, default 09:00
- `zone` (string, required): Their place or IANA zone

### `contacts_list` (~44 tokens)

List saved contacts

List saved contacts with their zone, local time now, working hours and whether they are inside them; weekends count as outside. It also prints YOUR zone from the shared business profile.

### `ics_create` (~380 tokens)

Write a calendar invite

Call this tool to write a .ics for one meeting and return the path plus the start in UTC and in your zone. Times are stored in UTC so it lands correctly in any client. Free: 3 files a month.

Input parameters:

- `attendees` (array): Attendees. An entry with an email is invited (ATTENDEE:mailto:...); a name with no email is listed in the description instead, because a calendar cannot invite a name.
- `description` (string): Body text
- `duration_minutes` (integer, required): Length in minutes, at most 1440
- `fold` (string): Which occurrence of a time that happens twice because the clocks went back. Default 'first'.
- `gap` (string): What to do with a time that does not exist because the clocks jumped forward: 'forward' takes the time after the jump, 'backward' the time before it. Without this, such a time is refused.
- `location` (string): Where, or a meeting link
- `organizer_email` (string): Your email address, written as the ORGANIZER so replies have somewhere to go. Leave it out and your shared business profile's email is used; with neither, the ORGANIZER line is omitted rather than fi…
- `organizer_name` (string): Your display name for the ORGANIZER line
- `out_path` (string): Where to write the .ics file; default meeting.ics in the data dir. Times are stored in UTC, so the invite lands at the right local time in every attendee's calendar with no time zone block to go stale
- `start` (string, required): Start time, read in `zone` unless it carries an offset
- `title` (string, required): Event title
- `zone` (string, required): Place the start time is given in

### `profile_set` (~414 tokens)

Store your CV facts

Store the profile every resume and cover letter is built from: contact details, summary, skills, roles with bullets, education, certifications and languages. Returns a count of what was stored.

Input parameters:

- `accent_color` (string): Letterhead colour, six hex digits, e.g. 1F3864. Pro only.
- `certifications` (array)
- `education` (array)
- `email` (string): Your own email address. Leave it out and the shared business profile's email is used; with neither, letters and letterheads show "[add: email]" and say so. Never invent one
- `experience` (array): Roles in any order you like -- profile_set sorts and stores them newest-first (an open role with no `end` first, then by `end` descending, then by `start` descending) before saving, since page-budget…
- `languages` (array)
- `links` (array): Portfolio, LinkedIn, GitHub
- `location` (string)
- `merge` (boolean): Update the stored profile: fields you pass replace their stored value, fields you leave out are kept. Required when a profile already exists, unless you pass replace
- `name` (string): Your own name. Leave it out and the shared business profile's name is used, so you are never asked for a name the suite already holds
- `phone` (string): Your own phone number. Defaults to the shared business profile's phone
- `replace` (boolean): Discard the stored profile and store exactly what this call carries. Required when a profile already exists, unless you pass merge
- `skills` (array)
- `summary` (string): Two or three lines. Used verbatim as the fit paragraph of a cover letter.
- `variant` (string): Name a second profile, e.g. "backend". One profile per data directory on the free tier; named variants are Pro only.

### `profile_get` (~52 tokens)

Show the stored profile

Return the stored CV profile exactly as resume_create, cover_letter_create and tailor_to_job will use it, plus the variant read and every stored variant name. Call it before any rewrite.

Input parameters:

- `variant` (string)

### `resume_create` (~237 tokens)

Write a resume .docx

Call this tool to write the stored profile to a Word .docx. Returns the output path, the estimated page count, which bullets were dropped to fit, and which keywords matched or are missing.

Input parameters:

- `keywords` (array): From the posting. A keyword that appears anywhere in the profile is printed in bold; one that does not is reported as missing and is never added to the resume.
- `max_pages` (integer): Bullets are ordered by relevance to target_role and keywords, then trimmed to fit this many pages against a measured word budget. Default 2.
- `out_path` (string): Where to write the .docx. Defaults to <data dir>/documents/<name>-resume.docx, numbered -2, -3, ... if that exists.
- `overwrite` (boolean): Replace an existing file at out_path. Default false: the call fails and nothing is written.
- `style` (string): Free tier prints "modern" only; "classic" and "compact" are Pro.
- `target_role` (string): Printed under your name and used to rank bullets
- `variant` (string)

### `resume_to_markdown` (~72 tokens)

Resume as markdown

Return the stored profile as markdown text inline, ready to paste into a form or an ATS box. Same page-budget trimming as resume_create for max_pages. Nothing is written to disk. Free.

Input parameters:

- `max_pages` (integer)
- `target_role` (string)
- `variant` (string)

### `resume_to_html` (~162 tokens)

Printable resume HTML

Call this tool to write the resume as semantic HTML with a print stylesheet and return where it went; print it to PDF from a browser, because there is no doc_to_pdf here. Bullets are trimmed to fit max_pages. Free.

Input parameters:

- `max_pages` (integer): Bullets are trimmed to fit this many pages against a measured word budget. Default 2.
- `out_path` (string): Where to write the .html. Defaults to <data dir>/documents/<name>-resume.html, numbered -2, -3, ... if that exists.
- `overwrite` (boolean): Replace an existing file at out_path. Default false: the call fails and nothing is written.
- `target_role` (string)
- `variant` (string)

### `resume_read` (~121 tokens)

Read an existing resume .docx

Call this tool to read an existing .docx resume into the profile shape, with the sections found and anything unplaced. It saves nothing unless save is true, so review it first. .doc and .rtf are refused.

Input parameters:

- `path` (string, required): Path to an existing .docx. Legacy .doc and .rtf are not readable here. Parsed best effort, section by heading.
- `save` (boolean): Store the result as the profile. Default false: nothing is saved. Review the result first.
- `variant` (string)

### `cover_letter_create` (~228 tokens)

Write a cover letter .docx

Call this tool to write a one-page cover letter .docx from the stored profile and return the path, word count and bracketed prompts. Nothing from the posting is restated as yours. Free: 3 a month.

Input parameters:

- `company` (string, required)
- `highlights` (array): Points to lead with. Each is checked against the profile; anything not found there is returned as a bracketed prompt, not printed as fact.
- `hiring_manager` (string)
- `job_description` (string): Paste the posting. Used only to pick which of your own skills to lead with; no figure from the posting is ever restated as yours.
- `out_path` (string): Where to write the .docx. Defaults to <data dir>/documents/<company>-<role>-cover-letter.docx, numbered -2, -3, ... if that exists.
- `overwrite` (boolean): Replace an existing file at out_path. Default false: the call fails and nothing is written.
- `role` (string, required)
- `tone` (string): Default "formal".

### `tailor_to_job` (~104 tokens)

Gap analysis against a posting

READ-ONLY gap analysis against a posting: writes nothing, changes nothing. Returns matched keywords, missing ones, a coverage figure and orderings of facts you already stated. Act on it with resume_create.

Input parameters:

- `job_description` (string, required): Paste the posting. The free tier reads up to 2,000 characters; Pro reads any length.
- `limit` (integer): How many keywords to extract from the posting. Default 30.
- `variant` (string)

### `schedule_create` (~457 tokens)

Create a recurring invoice schedule

Define a repeating invoice: a client, the line items, how often to bill, and when it starts and ends. Returns the schedule id, a summary and its next dates. Nothing is invoiced until invoice_generate_due runs.

Input parameters:

- `anchor_day` (integer): Pro: bill on this day of month instead of the start date's day. 31 means the last day of every month
- `auto_generate` (boolean): Marks the schedule for the monthly_billing_run prompt. Default false. Nothing runs in the background either way: invoices are created only when invoice_generate_due is called
- `client` (string, required): Client name or id, as in the invoice server. Unknown names are created on the first generated invoice
- `currency` (string): Defaults to your business default currency
- `due_days` (integer): Days until each invoice is due, defaults to your payment terms
- `end_date` (string): YYYY-MM-DD, INCLUSIVE: an occurrence landing exactly on it is still generated
- `end_of_month` (boolean): Pro: always bill on the last day of the month
- `every` (required): How often to bill: "weekly", "monthly", "quarterly", "yearly", or {days: 10}. Month steps keep the start date's day of month and clamp it to shorter months, so a schedule starting on the 31st bills o…
- `items` (array, required): The line items billed every period
- `notes` (string): Free text printed under the totals of every generated invoice
- `start_date` (string, required): YYYY-MM-DD. The first invoice falls on this date, and for weekly/monthly/quarterly/yearly steps its day of month is the billing day for every later period
- `tax_note` (string): Why this schedule bills the tax it bills, e.g. 'Reverse charge: VAT accounted for by the recipient, art. 196 Directive 2006/112/EC'. It is printed under the totals of EVERY invoice this schedule gene…

### `schedule_list` (~57 tokens)

List schedules

List schedules: id, client, cadence, per-period amount and currency, start and end dates, status, next due date and auto_generate. Filter by status; a paused schedule reports no next due date.

Input parameters:

- `status` (string)

### `schedule_get` (~66 tokens)

Get one schedule

Return one schedule in full by id or client: items, cadence, dates, due days, anchor rules, notes, per-period amount, next due date and how many invoices it generated. schedule_history is the log.

Input parameters:

- `id` (string, required): Schedule id, or a client name

### `schedule_update` (~251 tokens)

Update a schedule

Change one schedule by id: client, items, currency, cadence, dates, due_days, notes or auto_generate. Only the fields you pass change. Periods already invoiced are never re-issued, so a new amount applies to future ones.

Input parameters:

- `anchor_day`: Pro
- `auto_generate` (boolean)
- `client` (string)
- `currency` (string)
- `due_days` (integer)
- `end_date` (string|null): null clears the end date
- `end_of_month` (boolean): Pro
- `every`: How often to bill: "weekly", "monthly", "quarterly", "yearly", or {days: 10}. Month steps keep the start date's day of month and clamp it to shorter months, so a schedule starting on the 31st bills o…
- `id` (string, required)
- `items` (array)
- `notes` (string)
- `start_date` (string)
- `tax_note` (string): Replace the tax reason carried onto every future generated invoice. Pass an empty string to clear it

### `schedule_pause` (~54 tokens)

Pause a schedule

Stop one schedule generating invoices without deleting it; invoice_generate_due and forecast skip it. Its periods keep falling due and resuming back-bills them, so use schedule_skip to drop just one.

Input parameters:

- `id` (string, required)

### `schedule_resume` (~56 tokens)

Resume a schedule

Make a paused schedule active again. Every period missed while it was paused is still due and the next invoice_generate_due creates them all. Refused when it would pass the free tier's 3 active.

Input parameters:

- `id` (string, required)

### `schedule_delete` (~95 tokens)

Delete a schedule

Delete one schedule permanently. Invoices already generated stay in the invoice server and the history is kept. Re-creating it gives a NEW id, so its old periods count as unbilled. schedule_pause is reversible.

Input parameters:

- `id` (string, required): Schedule id, or a client name. Deletion is permanent; re-creating the same schedule afterwards gives it a NEW id, so its old periods count as unbilled again

### `schedule_skip` (~161 tokens)

Skip one period

Close ONE occurrence for good without pausing the schedule, reporting what will not be billed. period must be a real occurrence of it. An invoiced period is refused; undo reopens a skip.

Input parameters:

- `id` (string, required): Schedule id, or a client name
- `period` (string, required): The occurrence date to skip, YYYY-MM-DD, exactly as it appears in schedule_upcoming or forecast. This is the answer to "pause this client for October": schedule_pause stops the whole schedule and a r…
- `undo` (boolean): Remove a previous skip so the period becomes due again. Works only on a period that has not been invoiced. Default false

### `schedule_upcoming` (~88 tokens)

What falls due soon

Table every occurrence of every ACTIVE schedule due in the next N days (30 default), with its amount and the invoice due date, plus a total per currency. Free lists the first 3 in your horizon.

Input parameters:

- `days` (integer): Days ahead, default 30. The free tier honours the horizon you ask for and lists the first 3 occurrences in it; Pro lists them all

### `invoice_generate_due` (~161 tokens)

Generate the invoices that are due

Create a real invoice in the invoice server for every schedule occurrence on or before as_of that has not been invoiced yet, and render each PDF. Returns what was created, what was skipped and what is still due.

Input parameters:

- `as_of` (string): YYYY-MM-DD, defaults to today. Every occurrence on or before this date that has not been invoiced is billed. Idempotent: one invoice per schedule per period, keyed by the occurrence date, so running…
- `dry_run` (boolean): List what would be created without creating anything. Default false. One run creates at most 60 invoices, oldest period first
- `schedule_id` (string): Only this schedule. Free and unlimited on every tier

### `schedule_history` (~63 tokens)

Schedule history

The audit log for one schedule id, oldest first: period, invoice number, issue and due dates, amount, PDF path, and whether that invoice is unpaid, paid, skipped or since deleted. schedule_list finds the id. Pro.

Input parameters:

- `id` (string, required)

### `forecast` (~63 tokens)

Revenue forecast

Expected invoiced revenue per calendar month per currency from active schedules. Invoiced and skipped periods are excluded and paused schedules listed apart. Free: 3 months; Pro: up to 120.

Input parameters:

- `months` (integer): Months ahead including this one, default 12

### `clause_add` (~257 tokens)

Add a clause

Save a reusable contract or proposal clause to the library. Returns the stored clause id, title, category, tags and the variables detected in its body, plus how many clauses of your own the library now holds.

Input parameters:

- `body` (string, required): The clause text. Use {{variable}} placeholders for the facts that change per client, for example {{client}}, {{fee}} or {{late_fee_percent}}; contract_assemble fills them at assembly time. Free tier:…
- `category` (string, required): Grouping. The known ones, in assembly order, are parties, scope, payment, expenses, ip, confidentiality, data, term, liability, warranty, disputes, general -- reuse one of these; any other name is ac…
- `jurisdiction` (string): Where the clause is meant to apply, for example 'PL' or 'England and Wales'
- `language` (string): ISO language code, default en
- `tags` (array)
- `title` (string, required): Clause heading, for example 'Late Payment'
- `variables` (array): Declared variable names. Anything {{...}} in the body is detected anyway

### `clause_get` (~80 tokens)

Read one clause

Return one clause in full by id or title: body, category, tags, jurisdiction, variables and the revision count, which stays 0 on free. A partial title matching several is refused with the candidates.

Input parameters:

- `id` (string): Clause id, for example late-fees
- `title` (string): Clause title; a partial title matches too

### `clause_update` (~106 tokens)

Update a clause

Change the text, category, tags, variables or jurisdiction of a clause. In Pro the previous text is kept as a version; in the free tier the change is applied without history.

Input parameters:

- `body` (string)
- `category` (string)
- `id` (string, required): Clause id or exact title
- `jurisdiction` (string)
- `language` (string)
- `tags` (array)
- `title` (string)
- `variables` (array)

### `clause_delete` (~62 tokens)

Delete a clause

Delete one clause by id or exact title and report how many are left. There is no undo, and a deleted starter clause is not re-seeded. Use clause_update to change one you want to keep.

Input parameters:

- `id` (string, required): Clause id or exact title

### `clause_list` (~56 tokens)

List clauses

List the library as id, title, category, tags and variables, in contract order by category then title, which is the order contract_assemble uses. Narrow with category; use clause_search for words.

Input parameters:

- `category` (string)

### `clause_search` (~112 tokens)

Search clauses

Ranked search over clause titles, tags, categories and bodies. Title and tag matches outrank body matches. Jurisdiction filtering is free; the tag filter is Pro and is skipped rather than refusing the search.

Input parameters:

- `category` (string)
- `jurisdiction` (string): Exact jurisdiction match. Free
- `query` (string, required): Words to look for, for example 'late payment interest'
- `tags` (array): Pro: every tag listed must be present. On free the search still runs, without this filter

### `clause_import` (~145 tokens)

Import clauses

Call this tool to load clauses from a .md or .json file, reporting added, replaced, skipped and capped counts. A duplicate title is skipped unless overwrite. json is Pro. Free: 10 of your own.

Input parameters:

- `overwrite` (boolean): Replace clauses whose title already exists instead of skipping them
- `path` (string, required): Path to a .md or .json file. Markdown form: '## Title', then optional 'category:' / 'tags:' / 'variables:' lines, a blank line, then the body. JSON form: an array of clauses. JSON import is a Pro fea…

### `clause_export` (~119 tokens)

Export clauses

Call this tool to write the whole library to one file and return the path, format and count. It is written in contract order, so it re-imports the same way. markdown is free; json is Pro.

Input parameters:

- `format` (string, required): json (a Pro feature) or markdown (works in the free tier)
- `overwrite` (boolean): Replace the destination if a file is already there. Without it an existing file is never touched
- `path` (string, required): Destination file path. The clauses are written in assembly order, categories first

### `contract_assemble` (~295 tokens)

Assemble a contract

Call this tool to build a contract from library clauses as .docx or markdown. A variable you omit stays as a bracketed prompt, never invented. clause_ids order is document order. Free: 8 clauses.

Input parameters:

- `categories` (array): Instead of ids: every clause in these categories, ordered by category. Free tier: up to 8 clauses per document
- `clause_ids` (array): Clause ids in the order they should appear; this is the document order. Free tier: up to 8 clauses per document
- `client` (string): Client name; also fills the {{client}} variable
- `format` (string): docx (default) or markdown; the document opens with the not-legal-advice line either way
- `out_path` (string): Where to write the file. Default: the server data directory, under a name built from the client and the title
- `overwrite` (boolean): Replace out_path if a file is already there. Without it an existing file is never touched
- `title` (string, required): Document title, for example 'Service Agreement - Beta Corp'
- `values` (object): Values for the {{variables}} in the chosen clauses, for example {"fee":"4500","late_fee_percent":"2"}. Any variable you leave out stays in the document as a bracketed prompt such as [late fee percent…

### `variables_list` (~61 tokens)

List the variables a selection needs

List every {{variable}} the clauses you name use, and which clause uses each, so the facts are gathered before contract_assemble leaves bracketed prompts. Reads only; a title matching several is refused.

Input parameters:

- `clause_ids` (array, required): Clause ids or titles

### `pdf_info` (~83 tokens)

Inspect a PDF

Call this tool to inspect a PDF: page count, page sizes in points/mm, paper name, metadata (title, author, producer, dates) and whether it is encrypted. Read-only, never modifies the file. Free tier: unlimited.

Input parameters:

- `path` (string, required): Path to the PDF file. ~ is expanded; a relative path is resolved against the working directory

### `pdf_count` (~65 tokens)

Count pages in several PDFs

Page count per file plus the total, for any number of PDFs, with how many were readable. A file that cannot be read is reported on its own row and the others still count. Read-only. Free and unlimited.

Input parameters:

- `paths` (array, required): Paths to the PDF files

### `pdf_merge` (~119 tokens)

Merge PDFs into one file

Call this tool to join several PDFs into one, in the order given. Page sizes are kept as-is, so a merged file may have mixed sizes, and the answer says so. Inputs are never modified. Free tier: up to 5 files per merge.

Input parameters:

- `out_path` (string, required): Where to write the merged PDF
- `overwrite` (boolean): Replace out_path if a file is already there. Default false: an existing file is never overwritten
- `paths` (array, required): The PDFs to join, in the order they should appear

### `pdf_split` (~219 tokens)

Split a PDF into several files

Call this tool to write one new PDF per range. Ranges are 1-based and may be open-ended: "1-3,5,7-" gives pages 1-3, page 5, and 7 to the end. The input is never modified. Free tier: files up to 30 pages.

Input parameters:

- `out_path_pattern` (string, required): Output path with a placeholder: {n} is the part number (1, 2, 3...), {range} is the range itself (e.g. 1-3), {name} is the input file name without .pdf. Example: ~/out/{name}-{range}.pdf
- `overwrite` (boolean): Replace existing outputs. Default false: nothing is overwritten and nothing is written at all if any target exists
- `path` (string, required): The PDF to split
- `ranges` (string, required): Comma-separated 1-based page ranges, e.g. "1-3,5,7-". An open-ended range runs to the last page

### `pdf_pages` (~138 tokens)

Extract pages into a new PDF

Call this tool to pull selected pages into one new PDF, in the order written: "2,4-6" gives four pages. Asking for a page twice copies it twice. The input is never modified. Free tier: files up to 30 pages.

Input parameters:

- `out_path` (string, required): Where to write the extracted PDF
- `overwrite` (boolean): Replace out_path if it exists. Default false
- `pages` (string, required): 1-based pages and ranges to keep, in output order, e.g. "2,4-6" or "5,1,1"
- `path` (string, required): The source PDF

### `pdf_rotate` (~157 tokens)

Rotate pages

Call this tool to turn pages by a multiple of 90 degrees, clockwise for positive. Rotation is added to whatever the page already had, for a sideways scan. Writes a new file. Free tier: files up to 30 pages.

Input parameters:

- `degrees` (integer, required): 90, 180, 270 or -90. Positive turns clockwise. Added to the page's existing rotation
- `out_path` (string, required): Where to write the rotated PDF
- `overwrite` (boolean): Replace out_path if it exists. Default false
- `pages` (string): Which pages to turn, e.g. "1" or "2,4-6". Omit for every page
- `path` (string, required): The source PDF

### `pdf_stamp` (~273 tokens)

Stamp text on a PDF

Call this tool to draw a word such as PAID or DRAFT across the pages, in a colour and position you choose. Writes a new file; input untouched. Free tier: the PAID and DRAFT presets in their preset colours.

Input parameters:

- `color` (string): Hex code such as #1b7f3b, or a name: red, green, blue, black, gray, orange, purple. Pro
- `font_size` (number): Point size. By default the stamp is sized to fit the page width
- `opacity` (number): 0 to 1. Default 0.35 in the centre, 0.85 in a corner
- `out_path` (string, required): Where to write the stamped PDF
- `overwrite` (boolean): Replace out_path if it exists. Default false
- `pages` (string): Which pages to stamp, e.g. "1" or "2,4-6". Omit for every page
- `path` (string, required): The source PDF
- `position` (string): Where on the page. Default center, which is drawn on the 45-degree diagonal like a real stamp
- `text` (string, required): What to stamp. PAID and DRAFT are presets with their own colour; any other text is Pro

### `pdf_watermark_business` (~115 tokens)

Stamp your business details in the footer

Call this tool to put your business name and VAT id in the footer of every page, from the shared profile mcp-invoice and mcp-docx write. Use it before sending a document out. Pro.

Input parameters:

- `out_path` (string, required): Where to write the footed PDF
- `overwrite` (boolean): Replace out_path if it exists. Default false
- `pages` (string): Which pages, e.g. "1". Omit for every page
- `path` (string, required): The source PDF

### `pdf_reorder` (~129 tokens)

Reorder the pages of a PDF

Call this tool to write a new PDF with pages in the order you give. The order must name every page exactly once, so nothing drops by accident; use pdf_pages for a subset. Pro.

Input parameters:

- `order` (array, required): The 1-based page numbers in their new order, e.g. [3,1,2] for a three-page file. Every page must appear exactly once
- `out_path` (string, required): Where to write the reordered PDF
- `overwrite` (boolean): Replace out_path if it exists. Default false
- `path` (string, required): The source PDF

### `pdf_text` (~95 tokens)

Read the text of a PDF

Call this tool for best-effort text extraction from standard-font PDFs. Returns nothing for a scan (no OCR), or glyph indices for a custom-encoded font, and says which case applies. Read-only. Free tier: unlimited.

Input parameters:

- `pages` (string): Which pages, e.g. "1" or "2,4-6". Omit for every page
- `path` (string, required): The PDF to read

### `ics_import` (~143 tokens)

Import a calendar (.ics)

Call this tool to read a calendar export and keep it under a name. Give path (.ics file), text (contents), or url (public .ics/webcal feed; Pro). Google, Apple, Outlook exports read. Re-importing a name replaces it.

Input parameters:

- `name` (string, required): What to call this calendar, e.g. "work" or "family"
- `path` (string): Path to a .ics file on this machine
- `text` (string): The .ics file contents, pasted
- `url` (string): Public https:// or webcal:// .ics feed. Fetched once, only because you asked; Pro feature

### `calendars_list` (~54 tokens)

List imported calendars

List imported calendars: name, event-definition count, size, source, import time and free-tier use of the 2 slots. It also names stored .ics files with no calendar row. With none it prints the export steps.

### `events_list` (~115 tokens)

List events in a window

Every event between two dates, recurring expanded to occurrences, sorted by start. Times shown in your zone (profile's, else this machine's) unless zone passed. Each id works for event_export or event_to_time_entry.

Input parameters:

- `calendar` (string): One calendar name; default every imported calendar
- `from` (string, required): First day, YYYY-MM-DD
- `to` (string, required): Last day, YYYY-MM-DD, included
- `zone` (string): Show local times in this zone or city instead of your own

### `events_search` (~94 tokens)

Search events

Find events whose title, description, location or attendees contain a phrase. Searches every imported calendar. Without from/to it looks a year back and a year forward on Pro, and the free window either side of today.

Input parameters:

- `from` (string): First day, YYYY-MM-DD
- `query` (string, required): Words to look for, case-insensitive
- `to` (string): Last day, YYYY-MM-DD, included

### `free_busy` (~159 tokens)

Busy blocks and free windows

Merged busy blocks and the free gaps inside your working hours over a day range. Events marked free do not count; whole-day events block the day. Free: 31 days. Use conflicts for overlapping pairs.

Input parameters:

- `calendars` (array): Calendar names; default all of them
- `from` (string, required): First day, YYYY-MM-DD
- `to` (string, required): Last day, YYYY-MM-DD, included
- `work_end` (string): End of your working day, HH:MM, default 17:00
- `work_start` (string): Start of your working day, HH:MM, default 09:00
- `zone` (string): Zone the working hours and the output are in; default your own

### `conflicts` (~95 tokens)

Find double bookings

Find double bookings: every PAIR of timed events that overlap, with the minutes they collide, across all calendars unless you name one. Whole-day events are reported apart. Free: 31 days. free_busy shows gaps.

Input parameters:

- `calendar` (string): One calendar name; default every imported calendar
- `from` (string, required): First day, YYYY-MM-DD
- `to` (string, required): Last day, YYYY-MM-DD, included

### `next_event` (~67 tokens)

Next event

The first event not yet started, within the next 366 days, across every imported calendar or one you name. Returns title, times in your zone, how long until it starts, location, attendees and its id.

Input parameters:

- `calendar` (string): One calendar name; default every imported calendar

### `event_export` (~136 tokens)

Export events to a .ics file

Call this tool to write chosen events to a new .ics file you can send or import elsewhere. Pass either ids (from events_list) or a from/to window. Times are written in UTC so the file lands correctly in any client.

Input parameters:

- `calendar` (string): With from/to: limit to one calendar
- `from` (string): First day, YYYY-MM-DD (alternative to ids)
- `ids` (array): Event ids from events_list, events_search or next_event
- `out_path` (string, required): Where to write the .ics file
- `to` (string): Last day, YYYY-MM-DD, included

### `event_to_time_entry` (~155 tokens)

Turn a meeting into a time entry

Take one event and return the exact arguments for the time-tracker's entry_add, so a meeting that already happened becomes billable time without retyping it. Writes nothing: pass the JSON straight to entry_add.

Input parameters:

- `currency` (string): Currency of the rate: EUR, USD, GBP, PLN, or the word the user said ('euros'). Without it the time-tracker falls back to USD.
- `event_id` (string, required): Event id from events_list, events_search or next_event
- `project` (string, required): Project or client the meeting is billed to
- `rate`: Hourly rate for this entry; a number (120) or the words the user said ('120 euros an hour')

### `ics_forget` (~64 tokens)

Forget a calendar

Remove one imported calendar by name and delete this server's local copy of its .ics, freeing a slot. Your own calendar and the source file are untouched. ics_import replaces a name in place.

Input parameters:

- `name` (string, required): The calendar name from calendars_list

### `asset_add` (~466 tokens)

Add a fixed asset

Add one fixed asset to the register and return its id with the rate, useful life and convention taken from the bundled tax table. Cost and residual are whole minor units. Free tier holds ten assets.

Input parameters:

- `category` (string, required): A code or name from the bundled table, e.g. "487" or "Computers and computer sets" (pl), "main" (uk), "5-year" (us). Read assets://categories for the list
- `cost_minor` (integer, required): Acquisition cost in MINOR units (integer cents/grosze), e.g. 549900 for 5499.00. Never a decimal
- `currency` (string): ISO code. Defaults to the shared business profile currency, else the scheme table currency
- `declining_coefficient` (number): Declining-balance coefficient. Polish default 2.0, capped by the table; ignored with straight-line
- `in_service_date` (string): ISO date YYYY-MM-DD the asset entered use and the register. Defaults to purchase_date
- `life_years` (number): Override the useful life derived from the table, in years. The answer reports that it was an override
- `method` (string): "straight-line" or "declining-balance". Default: the table row method, straight-line for the Polish annex and the reducing-balance pool rate for the UK
- `name` (string, required): What the asset is, e.g. "MacBook Pro 16" or "Delivery van"
- `note` (string)
- `project` (string): Project, department or cost centre this asset belongs to
- `purchase_date` (string, required): ISO date YYYY-MM-DD the asset was bought
- `rate_pct` (number): Override the annual percentage derived from the table. Use for a lowered Polish rate under art. 16i
- `residual_minor` (integer): Residual or salvage value in MINOR units, default 0. Must be less than cost. MACRS ignores it and the answer says so
- `scheme` (string): "pl" Polish KST annex rates, "uk" HMRC capital allowance pools, "us" IRS MACRS GDS. Omit to derive it from the shared business profile currency

### `asset_list` (~161 tokens)

List the fixed asset register

List the assets in the register with cost, method, rate and net book value at a date, filtered by scheme, category, project, currency or disposal state. Free and unlimited.

Input parameters:

- `as_of` (string): Value the register at this month or date, YYYY-MM or YYYY-MM-DD. Default today
- `category` (string): Only assets in this table category code or name
- `currency` (string): Only assets held in this currency
- `include_disposed` (boolean): Include assets already disposed of. Default false
- `limit` (integer): Maximum rows, default and ceiling 2000
- `project` (string): Only assets on this project or cost centre
- `scheme` (string): Only assets on this tax scheme

### `asset_schedule` (~347 tokens)

Build a depreciation schedule

Build the depreciation schedule for a stored asset, or price one not in the register, per year or per month, to residual or zero, with the table's rate, life and convention. Periods sum exactly to the base.

Input parameters:

- `asset` (string): Asset id such as ASSET-2026-0001, or an exact or partial name. Omit and pass the fields below to price an asset that is not in the register
- `category` (string): Table category code or name, when pricing an asset that is not in the register
- `cost_minor` (integer): Cost in MINOR units, when pricing an asset that is not in the register
- `currency` (string): ISO code, when pricing an asset that is not in the register
- `declining_coefficient` (number): Declining-balance coefficient; ignored with straight-line
- `granularity` (string): "year" for one row per period, "month" for the monthly charge. Default "year"
- `in_service_date` (string): ISO date YYYY-MM-DD the asset entered use. Defaults to purchase_date
- `life_years` (number): Override the useful life from the table, in years
- `method` (string): "straight-line" or "declining-balance"
- `purchase_date` (string): ISO date YYYY-MM-DD, when pricing an asset that is not in the register
- `rate_pct` (number): Override the annual percentage from the table
- `residual_minor` (integer): Residual in MINOR units, default 0
- `scheme` (string): Scheme, when pricing an asset that is not in the register

### `asset_journal` (~157 tokens)

Journal the month's depreciation

Return the depreciation journal for one month: debit depreciation expense and credit accumulated depreciation, per asset and in total, plus an expense_add-ready payload per currency. It writes no expense. Pro.

Input parameters:

- `accumulated_account` (string): Credit account name. Default "Accumulated depreciation"
- `category` (string): Expense category to put on the expense_add payload. Default "depreciation"
- `expense_account` (string): Debit account name. Default "Depreciation expense"
- `month` (string, required): The month to journal, YYYY-MM. A date YYYY-MM-DD is read as its month
- `project` (string): Only assets on this project or cost centre
- `scheme` (string): Only assets on this tax scheme

### `asset_dispose` (~152 tokens)

Dispose of an asset

Record that an asset left the business on a date and return the gain or loss against net book value. proceeds_minor is in MINOR units. Depreciation stops here, and a second disposal is refused.

Input parameters:

- `asset` (string, required): Asset id such as ASSET-2026-0001, or an exact or partial name
- `date` (string, required): ISO date YYYY-MM-DD the asset left the business. It cannot be before the in-service date
- `note` (string): What happened, e.g. sold to a dealer, written off after a fire
- `proceeds_minor` (integer): Sale proceeds in MINOR units, default 0 for a scrapping or a write-off

### `asset_delete` (~77 tokens)

Delete an asset

Remove one asset from the register when nothing depends on it: not disposed of, never journaled. Its free-tier slot is free again. An asset with a dependent is refused and the dependent is named. Free.

Input parameters:

- `asset` (string, required): Asset id such as ASSET-2026-0001, or an exact or partial name

### `asset_report` (~143 tokens)

Report the register

For one year: net book value by category, scheme and currency at as_of, the year's depreciation charge per currency, and every disposal with its gain or loss. Pro; asset_list and asset_schedule are free per-asset views.

Input parameters:

- `as_of` (string): Value the register at this month or date, YYYY-MM or YYYY-MM-DD. Default the last day of the year
- `project` (string): Only assets on this project or cost centre
- `scheme` (string): Only assets on this tax scheme
- `year` (integer): Calendar year to report, e.g. 2026. Default the year of as_of, else this year

### `sale_create` (~606 tokens)

Record a sale

Record a sale and generate the bill of sale: who sold, who bought, what item (with VIN, serial or IMEI where it has one), the price in minor units and the date. Returns the BOS-YYYY-NNNN number of the draft. The draft can still be changed with sale_update; sale_finalize freezes it into the signing copy and sale_render prints it. Free tier: 10 drafts.

Input parameters:

- `as_is` (boolean): Include the as-is clause: sold with all faults, no warranties except any written here. Default true, the norm for second-hand sales; pass false to leave it out
- `buyer_address` (string): The buyer's postal address, one string
- `buyer_email` (string): The buyer's email
- `buyer_name` (string, required): Who is buying, e.g. Jane Kowalska or Northwind Sp. z o.o.
- `buyer_phone` (string): The buyer's phone number
- `condition` (string): The condition at handover, e.g. used, good working order, or new, sealed
- `currency` (string, required): ISO code the price is in
- `date` (string): The date of the sale, YYYY-MM-DD. Default today
- `duplicate_ok` (boolean): Record it even though an identical sale to the same buyer exists, for a genuinely repeated sale. Default false
- `identifier_other` (string): Any other identifying number, e.g. a hull number or an asset tag
- `imei` (string): IMEI, 15 digits, for a phone or tablet
- `item_category` (string): What kind of item, e.g. vehicle, equipment, electronics, stock
- `item_description` (string, required): What was sold, specific enough to identify it, e.g. 2019 Honda Civic 1.5 petrol, grey, or MacBook Pro 14-inch 2021
- `notes` (string): Anything else the document should say, e.g. payment method or what is included in the sale
- `price_minor` (integer, required): The sale price in whole minor units (integer cents). 120000 is USD 1,200.00
- `quantity` (integer): How many units this document covers. Default 1
- `seller_address` (string): The seller's postal address, one string
- `seller_email` (string): The seller's email
- `seller_name` (string): Who is selling. Defaults to the shared business profile's name when the profile has one
- `seller_phone` (string): The seller's phone number
- `serial` (string): Serial number, for equipment and electronics
- `vin` (string): Vehicle Identification Number, 17 characters, for a vehicle sale
- `warranty` (string): A warranty the seller does give, in their own words, e.g. The seller warrants the engine for 30 days from the sale date

### `sale_update` (~274 tokens)

Change a bill of sale before signing

Change anything on a bill of sale that is not finalized yet: price, buyer, seller, item details, identifiers, the as-is clause, warranty or notes. Pass an empty string to clear an optional field. A finalized document cannot be edited.

Input parameters:

- `as_is` (boolean)
- `buyer_address` (string)
- `buyer_email` (string)
- `buyer_name` (string): Who is buying
- `buyer_phone` (string)
- `condition` (string)
- `currency` (string)
- `date` (string): The date of the sale, YYYY-MM-DD
- `identifier_other` (string)
- `imei` (string)
- `item_category` (string)
- `item_description` (string)
- `notes` (string)
- `price_minor` (integer): The sale price in whole minor units (integer cents)
- `quantity` (integer)
- `sale` (string, required): The bill of sale id, e.g. BOS-2026-0001
- `seller_address` (string)
- `seller_email` (string)
- `seller_name` (string): Who is selling
- `seller_phone` (string)
- `serial` (string)
- `vin` (string)
- `warranty` (string)

### `sale_finalize` (~80 tokens)

Finalize the bill of sale for signing

Finalize a bill of sale so it is ready to sign: the document is frozen from this call on, sale_update refuses it, and every later render is the signing copy without the DRAFT watermark. Free tier: 5 finalized documents.

Input parameters:

- `sale` (string, required): The bill of sale id, e.g. BOS-2026-0001

### `sale_list` (~77 tokens)

List bills of sale

List every bill of sale, drafts and finalized, newest first: id, status, date, item, buyer, seller and price. Filter to drafts or finalized documents with status.

Input parameters:

- `limit` (integer): Maximum rows, default 500
- `status` (string): Which documents to list: all (default), draft, or final

### `sale_get` (~69 tokens)

Read one bill of sale

Read one bill of sale in full by its BOS number: both parties, the item and its identifiers, price, terms, and whether it is still a draft or the finalized signing copy.

Input parameters:

- `sale` (string, required): The bill of sale id, e.g. BOS-2026-0001

### `sale_delete` (~111 tokens)

Delete a bill of sale

Delete a bill of sale by its BOS number. A draft goes outright. A finalized document needs confirm_finalized true, because it is the local copy of a record the buyer may hold. The BOS number is never reissued, so a gap in the series is the record that a document was deleted.

Input parameters:

- `confirm_finalized` (boolean): Required true to delete a finalized document. Default false
- `sale` (string, required): The bill of sale id, e.g. BOS-2026-0001

### `sale_render` (~207 tokens)

Print the bill of sale

Render a bill of sale as a clean printable document: Markdown, a self-contained HTML file ready for print-to-PDF, or both, with signature lines for seller and buyer. Drafts render with a DRAFT watermark so a review copy cannot be signed by mistake. Files default to the server's documents folder; pass out_path to choose. Free.

Input parameters:

- `format` (string): What to render: markdown, html, or both (default). The HTML is one self-contained file, no external assets
- `out_path` (string): Where to write. With format both this is a stem and .md / .html are appended. Default: the server's documents folder under the data directory
- `overwrite` (boolean): Replace an existing file at out_path. Default false: an occupied explicit path is refused, a derived one gets -2, -3, ...
- `sale` (string, required): The bill of sale id, e.g. BOS-2026-0001

### `sale_summary` (~43 tokens)

Summarize the sales book

Summarize the book: how many drafts and finalized documents, and the total sold value per currency, drafts separate from finalized. Currencies are never added together. Free.

### `credit_note_create` (~282 tokens)

Issue a credit note

Issue a credit note against an invoice, or standalone: the recipient, the reason (returned goods, overcharge, discount correction, service issue, other), line items with quantity, unit price and tax rate, and the currency. Returns a draft you can still revise; credit_note_finalize burns the final CN number.

Input parameters:

- `currency` (string, required): ISO code the credit is in
- `duplicate_ok` (boolean): Create it even though an identical draft or note exists, for a genuinely repeated credit. Default false
- `invoice_ref` (string): The invoice this credits, e.g. INV-2026-0042. Omit for a standalone credit
- `issue_date` (string): The date the credit is issued, YYYY-MM-DD. Default today; a future date is refused
- `lines` (array, required): What is being credited, line by line
- `notes` (string): Text printed at the foot of the document, e.g. how the credit will be settled
- `reason` (string, required): Why the credit is issued: returned_goods, overcharge, discount_correction, service_issue or other
- `reason_detail` (string): One line of specifics, e.g. Client was billed 10 seats, used 7
- `recipient` (string, required): Who the credit is for, e.g. Acme GmbH

### `credit_note_update` (~194 tokens)

Revise a draft credit note

Change a draft credit note while it is still a draft: recipient, reason, lines, currency, invoice reference, issue date or notes. Pass only what changes; new lines replace all old ones. A finalized note is immutable and is refused by name.

Input parameters:

- `currency` (string): ISO code. Changing it reprices nothing: the stored minor units carry over, so only change it to fix a wrong currency
- `id` (string, required): The draft id, e.g. CN-DRAFT-2026-0001
- `invoice_ref`: New invoice reference, or null to detach the note from its invoice
- `issue_date` (string)
- `lines` (array): The full new line set, replacing every existing line
- `notes`: New notes text, or null to remove the notes
- `reason` (string)
- `reason_detail`
- `recipient` (string)

### `credit_note_finalize` (~90 tokens)

Finalize a credit note

Finalize a draft: burn the final CN-YYYY-NNNN number and freeze the note. From here it cannot be edited or deleted, only rendered and listed, because it is now the document the client sees. Free tier: 10 finalized credit notes; Pro removes the limit.

Input parameters:

- `id` (string, required): The draft id, e.g. CN-DRAFT-2026-0001

### `credit_note_list` (~156 tokens)

List credit notes

List credit notes: number or draft id, recipient, reason, status, issue date and total. Filter by recipient (a part of the name is enough), by reason, by status draft or final, and by issue-date period with from and to.

Input parameters:

- `from` (string): First issue date to include, YYYY-MM-DD
- `limit` (integer): Maximum notes listed, default and ceiling 2000
- `reason` (string): Only notes with this reason
- `recipient` (string): Match the recipient, case-insensitive, a part of the name is enough
- `status` (string): Only drafts or only finalized notes
- `to` (string): Last issue date to include, YYYY-MM-DD

### `credit_note_get` (~90 tokens)

Read one credit note in full

Return one credit note by its CN number or draft id: every line with quantity, unit price and tax, the totals per tax rate, the reason, the invoice it credits, and the notes. Reads only; use credit_note_list for the ids.

Input parameters:

- `id` (string, required): The credit note number CN-2026-0001 or draft id CN-DRAFT-2026-0001

### `credit_note_delete` (~74 tokens)

Delete a draft credit note

Delete a draft credit note entered wrongly, by its draft id. A finalized note is refused by name: it is a document the client may have seen, and deleting it would leave their copy pointing at nothing.

Input parameters:

- `id` (string, required): The draft id, e.g. CN-DRAFT-2026-0001

### `credit_note_render` (~121 tokens)

Render a credit note as Markdown or printable HTML

Turn a credit note into the document: Markdown to paste into an email or ticket, or a self-contained printable HTML page with every style inline. Drafts render with a DRAFT banner so a half-finished credit cannot be sent by mistake. Writes nothing. Free; the free tier stamps a one-line footer.

Input parameters:

- `format` (string): markdown (default) or html, a self-contained printable page
- `id` (string, required): The credit note number CN-2026-0001 or draft id CN-DRAFT-2026-0001

### `credit_note_summary` (~96 tokens)

Total credited by period, currency and reason

How much you have credited: totals per currency over finalized credit notes, broken down by reason and by month, in an optional issue-date period. Drafts count for nothing until they are finalized. Currencies are never added together.

Input parameters:

- `from` (string): First issue date to include, YYYY-MM-DD. Default: the beginning
- `to` (string): Last issue date to include, YYYY-MM-DD. Default: today

### `job_card_create` (~197 tokens)

Open a job card

Open a job card for a job your crew is taking on and return its JC-YYYY-NNNN number: the client, the site, what the job is, the currency and when it is scheduled. Free tier: 10 active cards; archiving a finished job frees its slot.

Input parameters:

- `client` (string, required): Who the job is for, e.g. Kowalski bathroom refit, or Acme Ltd
- `currency` (string, required): ISO code the rates and prices are in
- `description` (string, required): What the job is, e.g. Replace the consumer unit and certify
- `note` (string)
- `scheduled_date` (string): The date the crew is due on site, YYYY-MM-DD. May be in the future; it is a plan, not a log
- `site` (string, required): Where the job is, e.g. 14 Nowa Street, flat 3

### `job_card_log_labor` (~222 tokens)

Log hours on a job card

Log hours worked on a job card: who did the work, the day, the hours and the hourly rate in whole cents, with a note on what was done. The line value is hours times rate, rounded half-up to the cent, fixed the moment it is logged.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it
- `date` (string, required): The day the work was done, YYYY-MM-DD. A future date is refused
- `hours` (number, required): Hours worked, to the hundredth, e.g. 7.5 or 3.25. One entry is one worker's day at most
- `note` (string): What was done, e.g. First fix, kitchen ring main
- `rate_cents` (integer, required): The hourly rate in whole cents. 4500 is 45.00 an hour
- `worker` (string, required): Who did the work, e.g. Anna

### `job_card_log_material` (~200 tokens)

Log materials used on a job card

Log materials used on a job card: the item, the day it went in, the quantity and the unit cost in whole cents. The line value is quantity times unit cost, rounded half-up to the cent, fixed the moment it is logged.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it
- `date` (string, required): The day it went in, YYYY-MM-DD. A future date is refused
- `item` (string, required): What went into the job, e.g. Copper pipe 15mm, or Consumer unit 10-way
- `note` (string)
- `qty` (number, required): How many, to the thousandth, e.g. 2 or 0.5
- `unit_cost_cents` (integer, required): What one costs in whole cents. 1299 is 12.99

### `job_card_update_status` (~156 tokens)

Move a job card one step

Move one job card exactly one step: open, in_progress, done, invoiced, archived, stamping the date and an optional note into its history. A skipped or backwards step is refused and nothing is written. Archiving a finished job frees a free-tier slot.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it
- `date` (string): The date to stamp the step with, YYYY-MM-DD. Default today
- `note` (string)
- `status` (string, required): The next step for this card: open, in_progress, done, invoiced, archived. in-progress is accepted too

### `job_card_list` (~94 tokens)

List job cards

List job cards newest first: client, site, status, hours logged, and the labor, materials and grand totals in cents. Filter by status and by client. Totals are kept per currency, never mixed.

Input parameters:

- `client` (string): Only cards whose client contains this text, case-insensitive
- `status` (string): Only cards at this status: open, in_progress, done, invoiced, archived

### `job_card_get` (~89 tokens)

Read one job card with its running totals

Read one job card in full by JC number or client: every labor entry and every material used, the hours per worker, and the running totals -- labor, materials and grand total -- as integer cents with formatted amounts. Reads only.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it

### `job_card_print` (~112 tokens)

Render the card for the client to sign

Render a job card ready to print and hand to the client: the labor, the materials, the totals and a signature line for client sign-off. Markdown, or self-contained HTML that needs nothing from the network. Writes nothing.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it
- `format` (string): markdown (default) or html. The HTML carries its own styling and references nothing external

### `job_card_delete` (~87 tokens)

Delete an empty job card

Delete a job card entered by mistake. One with labor or materials logged is refused, naming what it holds, because deleting it would lose the record of work done: archive it instead. The JC number is never reissued.

Input parameters:

- `card` (string, required): The job card id, e.g. JC-2026-0003, or the client name when only one card has it

### `job_card_summary` (~105 tokens)

Summarize a day or a week

Hours and value for a day or a week: the job cards touched, the hours per worker, and the labor, materials and total value, kept per currency. A week runs Monday to Sunday. Touched means a labor or material entry dated inside the window.

Input parameters:

- `date` (string): A date inside the window, YYYY-MM-DD. Default today
- `span` (string): day (default) is the date itself; week is the Monday to Sunday containing it

### `invoice_register` (~313 tokens)

Register an unpaid invoice to chase

Start chasing an unpaid invoice: register the client, the invoice reference, the amount in integer cents, the currency and the due date, and get the three-letter escalation schedule (reminder 1, reminder 2, final notice) with the date each falls due. Free tier: 3 unpaid invoices chased at once.

Input parameters:

- `amount_minor` (integer, required): The invoice amount in whole minor units (integer cents). 125000 is USD 1,250.00
- `client` (string, required): Who owes the money, e.g. Acme Ltd
- `currency` (string, required): ISO code the invoice was issued in
- `due` (string, required): The date payment fell due, YYYY-MM-DD. The ladder is anchored to this date
- `duplicate_ok` (boolean): Register even though an identical invoice is already on the register. Default false
- `gaps` (array): Days after the due date at which reminder 1, reminder 2 and the final notice fall due, strictly increasing. Default [7, 14, 21]
- `issued` (string): The date the invoice was issued, YYYY-MM-DD, for the letter's reference line
- `late_fee_percent_per_month` (number): Late payment interest your terms allow, percent per month, simple and pro-rata. When set, reminder 2 and the final notice state the accrued figure
- `note` (string)
- `reference` (string, required): The invoice number being chased, e.g. INV-1042

### `payment_record` (~137 tokens)

Record a payment received

Record money received against a chased invoice, in whole MINOR units: a part payment lowers what is still chased, a payment that covers the balance closes the ladder and frees the free-tier slot. Returns the outstanding amount.

Input parameters:

- `amount_minor` (integer, required): What was received, in whole minor units
- `date` (string): The date the money arrived, YYYY-MM-DD. Default today
- `invoice` (string, required): The chased invoice: its id (DUN-2026-0001) or its invoice reference
- `note` (string): How it was paid, e.g. Bank transfer, or what it was against

### `letter_render` (~209 tokens)

Generate the chase letter for the current stage

Chase an unpaid invoice: generate the letter for the current stage of the ladder -- reminder 1 (polite), reminder 2 (firm, with the late fees note) or the final notice (before-action wording) -- as Markdown or as self-contained printable HTML. Nothing is emailed or sent anywhere: this server produces the letter text, and sending it is your act. Record the sending with letter_sent so the ladder advances.

Input parameters:

- `format` (string): markdown (default) or html: a self-contained printable page with no external anything
- `invoice` (string, required): The chased invoice: its id (DUN-2026-0001) or its invoice reference
- `on` (string): The letter's date, YYYY-MM-DD; late fees accrue to it and the final-notice deadline is 7 days after it. Default today
- `stage` (number): Render a specific stage instead of the one currently due, to preview or to re-issue a letter already sent

### `letter_sent` (~119 tokens)

Record that a letter was sent

Record that a chase letter was actually sent, with its date, so the ladder advances to the next stage. Letters go out in order: reminder 2 cannot be recorded before reminder 1. Returns what is due next and when.

Input parameters:

- `invoice` (string, required): The chased invoice: its id (DUN-2026-0001) or its invoice reference
- `sent` (string): The date it was sent, YYYY-MM-DD. Default today
- `stage` (number): Which letter went out. Default the lowest unsent stage

### `overdue_list` (~85 tokens)

List overdue invoices

Every unpaid invoice past its due date: how many days late, what is still owed, which letters have gone out, and what is due next and when. Sorted by days late, worst first.

Input parameters:

- `limit` (integer): Maximum rows, default and ceiling 2000
- `on` (string): The date to judge lateness against, YYYY-MM-DD. Default today

### `aging_summary` (~87 tokens)

Aging summary of what is owed

The aging summary across every unpaid invoice: current, 1-30, 31-60, 61-90 and 91+ day buckets, with counts and totals in integer cents, per currency. This is the answer to "how much am I owed, and how stale is it".

Input parameters:

- `on` (string): The date to age against, YYYY-MM-DD. Default today

### `chase_today` (~111 tokens)

What to chase today

The day's chase list: which invoices cross an escalation threshold today, which letter to send each and how long it has been waiting, what falls due in the next few days, and which invoices have had all three letters and need a decision outside this server.

Input parameters:

- `on` (string): The date to run the list for, YYYY-MM-DD, e.g. to see what Monday will need. Default today
- `within_days` (integer): How far ahead to list letters coming due, default 7 days

### `invoice_status` (~99 tokens)

The full state of one chased invoice

One chased invoice in full: what was billed, what has been paid and what is still owed, every letter sent with its date, the escalation schedule with each stage's state, and what happens next and when.

Input parameters:

- `invoice` (string, required): The chased invoice: its id (DUN-2026-0001) or its invoice reference
- `on` (string): The date to judge lateness against, YYYY-MM-DD. Default today

### `invoice_delete` (~79 tokens)

Delete an invoice from the register

Delete an invoice entered wrongly, by id or reference: the record, its payments and its letter history. The id is not reissued, so a gap in the DUN series is the record that a deletion happened.

Input parameters:

- `invoice` (string, required): The chased invoice: its id (DUN-2026-0001) or its invoice reference

### `license_status` (~46 tokens)

Report the licence tier of every server connected to this bundle, one row each, plus the single checkout URL that upgrades them all. A server that failed to start is reported with its error.

### `license_activate` (~61 tokens)

Activate one Pro key on every connected server in a single call and return which accepted it. Any rejection fails the whole call, because one server on Pro and another free is invisible to the user.

Input parameters:

- `key` (string, required): License key from the checkout confirmation page

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui#diagnostics

## Score history

- 2026-09-20: 46
- 2026-09-19: 46
- 2026-09-18: 46

## Common questions

### What is the io.github.theluckystrike/office-suite MCP server?

io.github.theluckystrike/office-suite is an MCP server listed in the public MCP registry as io.github.theluckystrike/office-suite. Whole freelancer office in one MCP server: time tracking, price watching, spreadsheets, invoicing. This page covers its MCPB bundle (https://github.com/theluckystrike/mcp-servers/releases/download/v0.22.0/office-suite.mcpb).

### Is the io.github.theluckystrike/office-suite MCP server safe to use?

io.github.theluckystrike/office-suite scores 46 out of 100 on VerifyMCP. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the io.github.theluckystrike/office-suite MCP server expose?

io.github.theluckystrike/office-suite exposes 158 tools: timer_start, timer_stop, timer_status, entry_add, entry_list, and 153 more. Their descriptions and schemas cost roughly 23,736 tokens of context every time the server is loaded.

### What licence is the io.github.theluckystrike/office-suite MCP server under?

io.github.theluckystrike/office-suite declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- Repository: https://github.com/theluckystrike/mcp-servers
- Website: https://mcp.zovo.one/bundle
- Changelog RSS feed: https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui.xml
- Changelog JSON feed: https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui.json
- HTML version of this page: https://verifymcp.io/servers/theluckystrike-office-suite/https-github-com-theluckystrike-mcp-servers-releases-download-v0-22-0-office-sui
