Legacy Billing MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Legacy Billing MCP Servercalculate an invoice for customer with ID 9876 for March 2024"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
legacy-mcp-bridge
A small, complete example of making a legacy system agent-operable:
an untyped 2014-era billing module (
legacy/billing.js) is wrapped, not rewrittena typed anti-corruption layer and hexagonal ports isolate it
a modern replacement is introduced with the Strangler Fig pattern (
legacy→shadow→modern)characterization tests pin the legacy behaviour, quirks included, so refactoring can't change invoices
a Model Context Protocol (MCP) server exposes the system to AI agents as typed, validated, read-only tools
an agent context layer (
AGENTS.md,.claude/commands) tells agents the rules of the codebase
Architecture
AI agent (Claude, Cursor, ...)
│ MCP (stdio, JSON-RPC)
┌────────▼─────────┐
│ src/mcp/server │ zod input/output contracts, tool errors
└────────┬─────────┘
│ InvoiceCalculator port
┌────────────▼─────────────┐
│ StranglerInvoiceCalculator│ MIGRATION_MODE = legacy | shadow | modern
└──────┬────────────┬──────┘
│ │
LegacyInvoiceCalculator ModernInvoiceCalculator
(anti-corruption layer) (pure, typed, injected)
│
legacy/billing.js ← frozen, never editedLayer | File | Responsibility |
Contracts |
| zod schemas = single source of truth for types and MCP tool schemas |
Ports |
| interfaces the core depends on |
Legacy adapter |
| translates untyped legacy data and error codes into domain types |
Modern adapter |
| clean reimplementation, named constants, no globals |
Strangler facade |
| routes traffic; in |
Composition root |
| the only place that wires concrete classes (DI) |
MCP adapter |
| tools for agents |
Related MCP server: Ops MCP Server (Finamatik)
MCP tools
Tool | What it does |
| all customers and their pricing tier |
| one customer by id |
| subtotal, tier discount, 18% VAT, shipping, total, through the strangler facade |
| runs legacy and modern on the same order and reports whether they match |
Every tool has an input and output schema and readOnlyHint. Unknown customers return a tool error that tells the agent how to recover (Call list_customers to see valid ids) instead of crashing.
Pinned legacy quirks
Characterization tests document behaviour that is surprising but that customers' invoices depend on. They are kept until the business decides otherwise:
tier codes are case-sensitive: customer 4 has tier
g, so gets no gold discountnegative quantities are silently treated as 0
the bulk discount (2% at qty ≥ 10) is applied per line, before rounding
the subtotal is a raw float sum and is not rounded
shipping is not taxed; it is free above 1000 after discount, and 0 for empty orders
A seeded property test also checks that the modern calculator matches legacy exactly on 5,000 random orders.
Run it
npm install
npm test # characterization + MCP contract tests
npm run buildAdd it to an MCP client (for example Claude Code, Claude Desktop or Cursor):
{
"mcpServers": {
"legacy-billing": {
"command": "node",
"args": ["/absolute/path/to/legacy-mcp-bridge/dist/src/index.js"],
"env": { "MIGRATION_MODE": "shadow" }
}
}
}Or with Claude Code:
claude mcp add legacy-billing --env MIGRATION_MODE=shadow -- node dist/src/index.jsMigration playbook
legacy: wrap the old module behind a port; write characterization tests against it.
shadow: build the modern implementation; run both on every call, serve legacy, log mismatches.
modern: when shadow logs are clean and the tests pass, switch the flag.
Delete the legacy adapter and module once nothing routes to them.
License
MIT
Available Tools
4 toolscalculate_invoiceCalculate invoiceBRead-onlyIdempotent
Calculate subtotal, tier discount, 18% VAT, shipping and total for an order. Routed through the strangler facade (legacy, shadow or modern implementation).
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes | Order lines | |
| customerId | Yes | Customer id from list_customers |
Output Schema
| Name | Required | Description |
|---|---|---|
| tax | Yes | |
| total | Yes | |
| discount | Yes | |
| shipping | Yes | |
| subtotal | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint true, so the safety profile is covered. The description adds genuine value by disclosing the calculation rules (18% VAT, tier discount, shipping), but the routing sentence leaves the agent unable to tell how the legacy/shadow/modern implementation is selected or whether the result could vary by route. Rounding and currency handling are also unstated.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, with the computed components front-loaded so the agent learns the output shape immediately. The trailing parenthetical about the strangler facade is slightly cryptic and could have been clearer or cut, but nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full parameter coverage, the description only needs to explain the domain logic, which it largely does via the VAT/discount/shipping list. The gap is the unexplained routing across legacy, shadow and modern implementations: an agent cannot know which one it hit or whether results should be compared, which is material for a money-calculation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema itself documents customerId, items, sku, price in EUR, and the legacy negative-quantity behaviour. The description adds nothing about parameter meaning beyond that, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb (calculate) and an explicit output set: subtotal, tier discount, 18% VAT, shipping, total for an order. That is far more than a restatement of the name. It does not, however, differentiate itself from siblings such as compare_implementations, which the mention of legacy/shadow/modern facades implicitly gestures at.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to call this rather than compare_implementations, nor any prerequisite or exclusion. The 'strangler facade' sentence hints that an implementation is chosen somewhere, but it never tells the agent whether this call is the right one for comparing behaviour across implementations or is merely the production path.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compare_implementationsCompare legacy vs modernARead-onlyIdempotent
Run the order through BOTH the legacy and the modern calculator and report whether they agree. Use this to verify a refactor before switching MIGRATION_MODE to modern.
| Name | Required | Description | Default |
|---|---|---|---|
| items | Yes | Order lines | |
| customerId | Yes | Customer id from list_customers |
Output Schema
| Name | Required | Description |
|---|---|---|
| match | Yes | |
| legacy | Yes | |
| modern | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, so safety is covered. The description adds genuinely useful context beyond that: the tool executes two independent calculation paths and returns an agreement verdict, which explains cost and result shape. It stops short of saying what happens on mismatch (error vs. report body), but the output schema covers the response.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core behavior and followed by the operational trigger. No redundant restatement of the name or schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter read-only comparison tool with a fully documented schema, annotations, and an output schema, nothing essential is missing. The description supplies the purpose, the dual-execution semantics, and the migration-workflow context an agent needs to choose it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including the legacy negative-quantity behavior and the customerId source (list_customers). The description adds no parameter-level meaning, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (compare/run) and resource (order through legacy and modern calculators) and the output form (whether they agree). It is clearly distinguishable from the sibling calculate_invoice, which computes a single result rather than diffing two implementations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it: verifying a refactor before flipping MIGRATION_MODE to modern. It does not state when not to use it (e.g., for production invoicing use calculate_invoice), so it is a clear context rather than a full routing rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_customerGet customerBRead-only
Get one customer by id.
| Name | Required | Description | Default |
|---|---|---|---|
| customerId | Yes | Customer id from list_customers |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | |
| name | Yes | |
| tier | Yes | |
| country | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. Beyond that the description adds nothing: it does not say what happens when the id is unknown (null vs error), whether the lookup is cached, or any permission requirement. For a tool whose annotations already carry the read-only signal, the description contributes essentially no extra behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short, front-loaded sentence with a verb, a resource, and the lookup qualifier. There is no filler and nothing that would need to be moved earlier.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a simple single-parameter read with an output schema, so much of the burden is already carried by structured fields. Still, a complete definition would say how a missing or invalid id is surfaced, which matters for an agent deciding whether to call this or list_customers.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%: the single customerId parameter is documented in the schema with its origin ("Customer id from list_customers"), including type and bounds. The description adds no format, range, or sourcing detail beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Get one customer") and the lookup key ("by id"), which implicitly distinguishes it from the sibling list_customers. It stops short of explicitly naming the sibling the way a 5 would.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase "by id" implies you must already have a customer id, and the sibling name list_customers is only hinted at via the schema's "Customer id from list_customers". There is no explicit when-to-use-this-vs-alternatives guidance or mention of what to do without an id.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_customersList customersARead-only
List all customers with their pricing tier.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| customers | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes that this is a safe read, so the description only needs to add context. It discloses that pricing tier is included in the results, but says nothing about ordering, pagination, or volume limits for a full customer list.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no redundant or filler wording. Every element, the listing action and the included pricing tier, earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no input parameters, a read-only annotation, and an output schema covering return values, the description is close to sufficient. The only gap is operational context such as pagination or ordering behavior for a potentially large customer list.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4 and there is no parameter meaning the description could add. Nothing in the schema is left unexplained.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('List all customers') and adds the returned attribute ('their pricing tier'), so the operation is unambiguous. It does not distinguish itself from the sibling get_customer, which is the only missing piece for a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The word 'all' implies a bulk listing as opposed to fetching one customer, which hints at when to use it over get_customer. However, no alternative is named and no condition or exclusion is stated, leaving usage to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
calculate_invoice - First observed
compare_implementations - First observed
get_customer - First observed
list_customers
TDQS
Scored across 4 tools
The two customer tools (list vs get-by-id) are clearly separated, and calculate_invoice vs compare_implementations have distinct purposes spelled out in their descriptions. There is minor conceptual overlap since compare_implementations implicitly runs both calculators, so an agent might pick calculate_invoice when it really wanted verification, but the descriptions mitigate this.
All four tools follow a clean snake_case verb_noun pattern (list_customers, get_customer, calculate_invoice, compare_implementations). The verbs are conventional and predictable for their actions.
Four tools is thin for a server labeled 'Billing'; the surface is essentially read-customers plus invoice math plus a migration check. The count is defensible for a narrow strangler-facade migration service, but feels borderline minimal for the stated domain.
Customers are read-only (no create/update) and there is no invoice persistence or retrieval — only calculation — plus no order or payment operations. The calculation and migration-verification path is well covered, but the broader billing lifecycle has notable gaps.
Maintenance
Related MCP Connectors
Invoicing tools for AI assistants: clients, draft invoices with computed tax, and email sending.
Time tracking and invoicing for AI agents and their humans: track, log and bill work by agent.
Quote-to-cash tools: parse invoices and POs, match POs to invoices, collection emails, billing math.
Create invoices, clients, and Stripe payment links from any agent. 7 tools. No email or deletes.
Related MCP Servers
- AlicenseAqualityDmaintenanceExposes a ledger system (invoice queue, duplicate control, VAT register, contractor history, decision journal) as MCP tools for AI agents, enabling accurate invoice processing with deterministic validation.7MIT
- AlicenseAqualityAmaintenanceTen typed tools that let Claude Desktop, Claude Code or any MCP client work with a company operations dataset: customers, orders, invoices, payments and stock. Every call is validated against a schema and recorded in a log. Python, official mcp SDK with FastMCP, MIT licence.1010 PyPIMIT
- AlicenseAqualityCmaintenanceLets an AI agent list reports, start and poll exports, and look up insurance claims from a legacy system that has no API, with each server process serving only the tools and report types its role permits. Every tool call is written to an append-only, field-masked audit log, and the backend's login credentials stay in an encrypted vault the model never sees.3MIT
- AlicenseAqualityBmaintenanceEnables MCP clients such as Claude and Cursor to manage customers, create and update draft invoices, list or download invoices as PDF and XML e-invoices, and record and list payments in a BillingEngine account. It runs locally with your API key and never deletes data.10338 npmMIT