Skip to content

feat: add finance, procurement and agreement recap tools - #81

Open
asachs01 wants to merge 1 commit into
mainfrom
feat/finance-procurement-tools
Open

feat: add finance, procurement and agreement recap tools#81
asachs01 wants to merge 1 commit into
mainfrom
feat/finance-procurement-tools

Conversation

@asachs01

@asachs01 asachs01 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What

Adds 10 read-only finance and procurement tools. Finance was the largest remaining gap in this server's coverage: an MSP finance team could read agreements and invoices, but nothing about the buy side, the sell side, or agreement burn-down.

Procurementsrc/tools/finance-procurement.ts

Tool Endpoint
cw_search_purchase_orders GET /procurement/purchaseorders
cw_get_purchase_order GET /procurement/purchaseorders/{id}
cw_get_purchase_order_items GET /procurement/purchaseorders/{id}/lineitems
cw_search_procurement_products GET /procurement/products
cw_get_procurement_product GET /procurement/products/{id}

Sales orderssrc/tools/finance-procurement.ts

Tool Endpoint
cw_search_sales_orders GET /sales/orders
cw_get_sales_order GET /sales/orders/{id}
cw_get_sales_order_products GET /procurement/products, scoped to the order

Agreement recapssrc/tools/agreements.ts

Tool Endpoint
cw_search_agreement_recaps GET /finance/agreementrecap/
cw_get_agreement_recap GET /finance/agreementrecap/{id}

The recap is the billing rollup ConnectWise computes per agreement — starting, used, remaining, available and overrun amounts, unbilled periods, last and next invoice. It answers burn-down and overage questions without summing additions by hand.

Notes

Procurement product items are catalog SKUs instantiated onto a ticket, project, order or opportunity, carrying their own price, cost and margin. They are not the catalog SKU definitions, which stay under cw_search_catalog_items. Both tool descriptions point at each other so the calling model picks the right one.

cw_get_sales_order_products is always scoped to its order. Caller-supplied conditions are ANDed with that scope rather than replacing it, so the tool cannot be talked out of its own filter — there's a test for exactly that.

Every tool is read-only. No write paths were added.

Verification against the vendor contract

Paths were checked against ConnectWise's published OpenAPI contract (Connectwise Manage Public Endpoints, 2025.16, openapi 3.0.1) rather than trusted from the upstream forks. Two findings are pinned by tests and code comments because they look like typos and are not:

  • /finance/agreementrecap/ is documented only with a trailing slash. The slash-less form appears nowhere in the contract, and the by-id sibling /finance/agreementrecap/{id} has no slash. Its neighbour /finance/agreements also has no slash. Corroborated by three independently generated clients (Ruby, Go, TypeScript) that all emit the trailing slash. Don't "tidy" it.
  • ProductItem has a nested salesOrder reference and no flat salesOrderId. So the sales-order filter is salesOrder/id=N. CW does ship flat *Id companions elsewhere on that same model (warehouseId, locationId, …), so the absence here is meaningful. salesOrderId=N would have been the silently-failing form.

Also confirmed: lineitems is all-lowercase with no trailing slash on the purchase-order path (lineItems appears nowhere in the contract), and — noted in a comment for whoever adds it next — /sales/orders/{id}/lineitems/ does carry a trailing slash. CW is not symmetric here.

src/__tests__/finance-procurement.test.ts drives the Worker entrypoint over real MCP JSON-RPC with fetch stubbed, and asserts the ConnectWise URL each tool actually builds. A tool can be registered, typecheck, and still call the wrong path; this is the thing worth pinning.

Test run

$ npm run typecheck && npm run lint && npm run build && npm test

> tsc --noEmit          # clean
> eslint src            # clean
> tsc                   # clean

 RUN  v4.1.11
 Test Files  6 passed (6)
      Tests  69 passed (69)

Attribution

Derived from two community forks, both Apache-2.0, as is this project:

  • The procurement, purchase-order and sales-order tools are derived from work by @Tekscape-finance in Tekscape-finance/connectwise-manage-mcp — a finance team at an MSP who built what they needed.
  • The agreement-recap tools are derived from work by @Cloudwize-Technologies in Cloudwize-Technologies/connectwise-manage-mcp.

Thank you to both.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Finance was the largest remaining gap in this server's coverage: an MSP
finance team could read agreements and invoices, but nothing about the
buy side, the sell side, or agreement burn-down. Adds 10 read-only tools.

Procurement (src/tools/finance-procurement.ts):
  cw_search_purchase_orders       GET /procurement/purchaseorders
  cw_get_purchase_order           GET /procurement/purchaseorders/{id}
  cw_get_purchase_order_items     GET /procurement/purchaseorders/{id}/lineitems
  cw_search_procurement_products  GET /procurement/products
  cw_get_procurement_product      GET /procurement/products/{id}

Sales orders (src/tools/finance-procurement.ts):
  cw_search_sales_orders          GET /sales/orders
  cw_get_sales_order              GET /sales/orders/{id}
  cw_get_sales_order_products     GET /procurement/products, scoped to the order

Agreement recaps (src/tools/agreements.ts):
  cw_search_agreement_recaps      GET /finance/agreementrecap/
  cw_get_agreement_recap          GET /finance/agreementrecap/{id}

Procurement product *items* are catalog SKUs instantiated onto a ticket,
project, order or opportunity, carrying their own price, cost and margin.
They are not the catalog SKU definitions, which stay under
cw_search_catalog_items; both descriptions point at each other so the
calling model picks the right one.

cw_get_sales_order_products is always scoped to its order. Caller-supplied
conditions are ANDed with that scope rather than replacing it, so the tool
cannot be talked out of its own filter. There is a test for that.

Every path was verified against ConnectWise's published OpenAPI contract
("Connectwise Manage Public Endpoints", 2025.16) rather than trusted from
the forks. Two findings are pinned by tests and comments because they look
like typos and are not: /finance/agreementrecap/ is documented only WITH a
trailing slash while its by-id sibling has none, and ProductItem carries a
nested salesOrder reference with no flat salesOrderId companion, so the
sales-order filter is salesOrder/id=N.

Tests drive the Worker entrypoint over real MCP JSON-RPC with fetch stubbed
and assert the ConnectWise URL each tool actually builds -- a tool can be
registered, typecheck, and still call the wrong path.

Attribution (Apache-2.0, as is this project):
  The procurement, purchase-order and sales-order tools are derived from
  work by @Tekscape-finance in Tekscape-finance/connectwise-manage-mcp.
  The agreement-recap tools are derived from work by @Cloudwize-Technologies
  in Cloudwize-Technologies/connectwise-manage-mcp.
  Thank you to both.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 47 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: fa276c21-9db5-4510-8b42-3c4e1c8027e5

📥 Commits

Reviewing files that changed from the base of the PR and between c97f568 and 0399d2c.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • src/__tests__/finance-procurement.test.ts
  • src/mcp-server.ts
  • src/tools/agreements.ts
  • src/tools/finance-procurement.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant