@crscreditapi/finstack-mcp-server 0.1.0-eff2af8 → 0.2.0-cbc96e9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.js +12 -0
- package/dist/api/customer.api.d.ts +29 -3
- package/dist/api/customer.api.js +37 -0
- package/dist/handlers/customer.handler.d.ts +12 -1
- package/dist/handlers/customer.handler.js +12 -1
- package/dist/handlers/index.js +3 -1
- package/dist/tools/customer.tools.js +43 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Pro
|
|
|
10
10
|
| `finstack_get_customer_summary` | Non-PII summary for one customer: onboarding progress, products, API credentials, inspection status, recent billing. |
|
|
11
11
|
| `finstack_get_customer_revenue` | Revenue for one customer over a date range, bucketed by `day` / `week` / `month` / `quarter` / `year`. |
|
|
12
12
|
| `finstack_get_customer_invoices` | Paginated list of finalized invoices for one customer with full line-item breakdown. |
|
|
13
|
+
| `finstack_get_customer_pricing_config` | Full configured pricing state for one customer: products, pricing addendums, and environment-user productPricing. Use to cross-check invoiced rates against contracted pricing. |
|
|
14
|
+
| `finstack_get_pricing_addendum_text` | Extracted text from a pricing addendum PDF (with or without disclaimer). Server-side parses the PDF and returns plain text. |
|
|
13
15
|
|
|
14
16
|
## Install
|
|
15
17
|
|
|
@@ -125,6 +127,22 @@ Range caps depend on `type`: `day` ≤ 92 days, `week` ≤ 2 years, `month`/`qua
|
|
|
125
127
|
|
|
126
128
|
Identify the customer with either `customer_code` or `user_id`. `page` defaults to `1`, `per_page` defaults to `50` and is silently capped at `100`. Returns `{customer_code, page, per_page, total_count, total_pages, invoices: [...]}`, where each invoice carries its full `items[]` line-item breakdown.
|
|
127
129
|
|
|
130
|
+
### `finstack_get_customer_pricing_config`
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{ "customer_code": "CID18068" }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Returns `{products, pricing_addendums, environment_users}`. `pricing_addendums[]` entries carry `id`, `name`, `status`, `quote_id`, timestamps, `has_pdf`, `has_pdf_without_disclaimer`, and `pdf_url` / `pdf_without_disclaimer_url`. The PDF URLs require the chatbase secret to hit directly — use `finstack_get_pricing_addendum_text` to read the contents instead.
|
|
137
|
+
|
|
138
|
+
### `finstack_get_pricing_addendum_text`
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{ "id": 1234, "variant": "with_disclaimer" }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`variant` is optional and defaults to `"with_disclaimer"`. Returns `{addendum_id, variant, page_count, text}`. The server fetches the PDF over the chatbase API and extracts text with `pdf-parse`; complex table layouts may run columns together but remain readable for LLM reasoning.
|
|
145
|
+
|
|
128
146
|
All tools accept an optional `environment: "prod" | "dev"` (default `"prod"`).
|
|
129
147
|
|
|
130
148
|
## Development
|
package/dist/api/client.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare class ApiClient {
|
|
|
18
18
|
post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
19
19
|
put<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
20
20
|
del<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
21
|
+
getBuffer(path: string, headers?: Record<string, string>): Promise<{
|
|
22
|
+
buffer: Buffer;
|
|
23
|
+
contentType: string;
|
|
24
|
+
}>;
|
|
21
25
|
}
|
|
22
26
|
export declare function getClient(env?: Environment): ApiClient;
|
|
23
27
|
export {};
|
package/dist/api/client.js
CHANGED
|
@@ -43,6 +43,18 @@ export class ApiClient {
|
|
|
43
43
|
async del(path, headers) {
|
|
44
44
|
return this.request('DELETE', path, undefined, headers);
|
|
45
45
|
}
|
|
46
|
+
async getBuffer(path, headers) {
|
|
47
|
+
const response = await this.fetchWithTimeout(`${this.config.baseUrl}${path}`, { method: 'GET', headers: headers ?? {} });
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const error = await response.text();
|
|
50
|
+
throw new Error(`GET ${path} failed: ${response.status} - ${error}`);
|
|
51
|
+
}
|
|
52
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
53
|
+
return {
|
|
54
|
+
buffer: Buffer.from(arrayBuffer),
|
|
55
|
+
contentType: response.headers.get('content-type') ?? 'application/octet-stream',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
function loadConfigs() {
|
|
48
60
|
const timeoutMs = process.env.FINSTACK_TIMEOUT_MS
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Customer-related calls against the mware-portal API.
|
|
3
|
-
*/
|
|
4
1
|
import type { ApiClient } from './client.js';
|
|
5
2
|
export interface CustomerSummary {
|
|
6
3
|
summary: string;
|
|
@@ -117,3 +114,32 @@ export declare function getCustomerInvoices(client: ApiClient, args: {
|
|
|
117
114
|
page?: number;
|
|
118
115
|
perPage?: number;
|
|
119
116
|
}): Promise<CustomerInvoicesPage>;
|
|
117
|
+
export interface PricingAddendum {
|
|
118
|
+
id: number;
|
|
119
|
+
name: string;
|
|
120
|
+
status: string;
|
|
121
|
+
quote_id: string | number | null;
|
|
122
|
+
created_at: string;
|
|
123
|
+
updated_at: string;
|
|
124
|
+
has_pdf: boolean;
|
|
125
|
+
has_pdf_without_disclaimer: boolean;
|
|
126
|
+
pdf_url: string | null;
|
|
127
|
+
pdf_without_disclaimer_url: string | null;
|
|
128
|
+
}
|
|
129
|
+
export interface CustomerPricingConfig {
|
|
130
|
+
products: Array<Record<string, unknown>>;
|
|
131
|
+
pricing_addendums: PricingAddendum[];
|
|
132
|
+
environment_users: Array<Record<string, unknown>>;
|
|
133
|
+
}
|
|
134
|
+
export declare function getCustomerPricingConfig(client: ApiClient, customerCode: string): Promise<CustomerPricingConfig>;
|
|
135
|
+
export type PricingAddendumVariant = 'with_disclaimer' | 'without_disclaimer';
|
|
136
|
+
export interface PricingAddendumText {
|
|
137
|
+
addendum_id: number;
|
|
138
|
+
variant: PricingAddendumVariant;
|
|
139
|
+
page_count: number;
|
|
140
|
+
text: string;
|
|
141
|
+
}
|
|
142
|
+
export declare function getPricingAddendumText(client: ApiClient, args: {
|
|
143
|
+
id: number;
|
|
144
|
+
variant: PricingAddendumVariant;
|
|
145
|
+
}): Promise<PricingAddendumText>;
|
package/dist/api/customer.api.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer-related calls against the mware-portal API.
|
|
3
|
+
*/
|
|
4
|
+
import { PDFParse } from 'pdf-parse';
|
|
1
5
|
export async function getCustomerSummary(client, customerCode) {
|
|
2
6
|
const secret = process.env.FINSTACK_CHATBASE_SECRET;
|
|
3
7
|
if (!secret) {
|
|
@@ -69,3 +73,36 @@ export async function getCustomerInvoices(client, args) {
|
|
|
69
73
|
}
|
|
70
74
|
return response.data;
|
|
71
75
|
}
|
|
76
|
+
export async function getCustomerPricingConfig(client, customerCode) {
|
|
77
|
+
const secret = process.env.FINSTACK_CHATBASE_SECRET;
|
|
78
|
+
if (!secret) {
|
|
79
|
+
throw new Error('FINSTACK_CHATBASE_SECRET must be set');
|
|
80
|
+
}
|
|
81
|
+
const params = new URLSearchParams({ customer_code: customerCode });
|
|
82
|
+
const response = await client.get(`/api/v1/chatbase/customer_pricing_config?${params}`, { 'X-Chatbase-Secret': secret });
|
|
83
|
+
if (response.status !== 'success' || !response.data) {
|
|
84
|
+
throw new Error(response.message || 'Failed to fetch customer pricing config');
|
|
85
|
+
}
|
|
86
|
+
return response.data;
|
|
87
|
+
}
|
|
88
|
+
export async function getPricingAddendumText(client, args) {
|
|
89
|
+
const secret = process.env.FINSTACK_CHATBASE_SECRET;
|
|
90
|
+
if (!secret) {
|
|
91
|
+
throw new Error('FINSTACK_CHATBASE_SECRET must be set');
|
|
92
|
+
}
|
|
93
|
+
const params = new URLSearchParams({ variant: args.variant });
|
|
94
|
+
const { buffer } = await client.getBuffer(`/api/v1/chatbase/pricing_addendum_pdf/${args.id}?${params}`, { 'X-Chatbase-Secret': secret });
|
|
95
|
+
const parser = new PDFParse({ data: new Uint8Array(buffer) });
|
|
96
|
+
try {
|
|
97
|
+
const result = await parser.getText();
|
|
98
|
+
return {
|
|
99
|
+
addendum_id: args.id,
|
|
100
|
+
variant: args.variant,
|
|
101
|
+
page_count: result.total,
|
|
102
|
+
text: result.text,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await parser.destroy();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RevenuePeriodType } from '../api/customer.api.js';
|
|
1
|
+
import { type PricingAddendumVariant, type RevenuePeriodType } from '../api/customer.api.js';
|
|
2
2
|
interface GetCustomerSummaryArgs {
|
|
3
3
|
customer_code: string;
|
|
4
4
|
environment?: string;
|
|
@@ -26,4 +26,15 @@ interface GetCustomerInvoicesArgs {
|
|
|
26
26
|
environment?: string;
|
|
27
27
|
}
|
|
28
28
|
export declare function handleGetCustomerInvoices(args: GetCustomerInvoicesArgs): Promise<unknown>;
|
|
29
|
+
interface GetCustomerPricingConfigArgs {
|
|
30
|
+
customer_code: string;
|
|
31
|
+
environment?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function handleGetCustomerPricingConfig(args: GetCustomerPricingConfigArgs): Promise<unknown>;
|
|
34
|
+
interface GetPricingAddendumTextArgs {
|
|
35
|
+
id: number;
|
|
36
|
+
variant?: PricingAddendumVariant;
|
|
37
|
+
environment?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function handleGetPricingAddendumText(args: GetPricingAddendumTextArgs): Promise<unknown>;
|
|
29
40
|
export {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Customer tool handlers
|
|
3
3
|
*/
|
|
4
4
|
import { getClient } from '../api/client.js';
|
|
5
|
-
import { getCustomerInvoices, getCustomerRevenue, getCustomerSummary, searchCustomers, } from '../api/customer.api.js';
|
|
5
|
+
import { getCustomerInvoices, getCustomerPricingConfig, getCustomerRevenue, getCustomerSummary, getPricingAddendumText, searchCustomers, } from '../api/customer.api.js';
|
|
6
6
|
export async function handleGetCustomerSummary(args) {
|
|
7
7
|
const client = getClient(args.environment ?? 'prod');
|
|
8
8
|
return getCustomerSummary(client, args.customer_code);
|
|
@@ -29,3 +29,14 @@ export async function handleGetCustomerInvoices(args) {
|
|
|
29
29
|
perPage: args.per_page,
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
export async function handleGetCustomerPricingConfig(args) {
|
|
33
|
+
const client = getClient(args.environment ?? 'prod');
|
|
34
|
+
return getCustomerPricingConfig(client, args.customer_code);
|
|
35
|
+
}
|
|
36
|
+
export async function handleGetPricingAddendumText(args) {
|
|
37
|
+
const client = getClient(args.environment ?? 'prod');
|
|
38
|
+
return getPricingAddendumText(client, {
|
|
39
|
+
id: args.id,
|
|
40
|
+
variant: args.variant ?? 'with_disclaimer',
|
|
41
|
+
});
|
|
42
|
+
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleGetCustomerInvoices, handleGetCustomerRevenue, handleGetCustomerSummary, handleSearchCustomer, } from './customer.handler.js';
|
|
1
|
+
import { handleGetCustomerInvoices, handleGetCustomerPricingConfig, handleGetCustomerRevenue, handleGetCustomerSummary, handleGetPricingAddendumText, handleSearchCustomer, } from './customer.handler.js';
|
|
2
2
|
const wrap = (fn) => {
|
|
3
3
|
return async (args) => fn(args);
|
|
4
4
|
};
|
|
@@ -7,4 +7,6 @@ export const handlers = {
|
|
|
7
7
|
finstack_search_customer: wrap(handleSearchCustomer),
|
|
8
8
|
finstack_get_customer_revenue: wrap(handleGetCustomerRevenue),
|
|
9
9
|
finstack_get_customer_invoices: wrap(handleGetCustomerInvoices),
|
|
10
|
+
finstack_get_customer_pricing_config: wrap(handleGetCustomerPricingConfig),
|
|
11
|
+
finstack_get_pricing_addendum_text: wrap(handleGetPricingAddendumText),
|
|
10
12
|
};
|
|
@@ -77,6 +77,49 @@ export const customerTools = [
|
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
name: 'finstack_get_customer_pricing_config',
|
|
82
|
+
description: "Full configured pricing state for a B2B customer: per-product BillingProductCustomer records (tiers, fees, add-ons, override_price), pricing_addendums (id, name, status, quote_id, timestamps, has_pdf flags, pdf URLs), and environment_users with their productPricing config (billingAmount, effectiveDate, pricingTiers, tiered/volume flags; credentials excluded). Use when the user asks what a customer is contracted to pay, to cross-check invoice line-item rates against contracted pricing, or to inspect tier/volume pricing setup. Each pricing addendum's PDF text can be fetched with finstack_get_pricing_addendum_text using the returned id.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
customer_code: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Customer code (CID) — e.g. "CID18068"',
|
|
89
|
+
},
|
|
90
|
+
environment: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'Target environment: "prod" (default) or "dev"',
|
|
93
|
+
enum: ['dev', 'prod'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ['customer_code'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'finstack_get_pricing_addendum_text',
|
|
101
|
+
description: "Extracted text contents of a pricing addendum PDF. Use after finstack_get_customer_pricing_config to read the full body of an addendum (terms, line items, footnotes) when the structured pricing config doesn't answer the question. Pass the addendum's id from pricing_addendums[]. Returns page_count and the concatenated text. Table layouts may come through with run-together columns — readable for LLM reasoning but not pixel-perfect.",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
id: {
|
|
106
|
+
type: 'number',
|
|
107
|
+
description: 'Pricing addendum id, from pricing_addendums[].id in finstack_get_customer_pricing_config',
|
|
108
|
+
},
|
|
109
|
+
variant: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
enum: ['with_disclaimer', 'without_disclaimer'],
|
|
112
|
+
description: 'Which attachment to read. Default "with_disclaimer". Use "without_disclaimer" only if pricing_addendums[].has_pdf_without_disclaimer is true.',
|
|
113
|
+
},
|
|
114
|
+
environment: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Target environment: "prod" (default) or "dev"',
|
|
117
|
+
enum: ['dev', 'prod'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['id'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
80
123
|
{
|
|
81
124
|
name: 'finstack_get_customer_revenue',
|
|
82
125
|
description: 'Fetch revenue for a B2B customer over a date range, bucketed by period (day/week/month/quarter/year). Use when the user asks how much a customer paid, revenue trends, MRR/ARR-style figures, or billing over time. Returns periods[{period,revenue}] plus a total. Max range: day=92d, week=2y, month/quarter=10y, year=50y; pick the coarsest type that covers the range.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crscreditapi/finstack-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-cbc96e9",
|
|
4
4
|
"description": "MCP server for Finstack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,11 +31,13 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
33
|
"dotenv": "^16.3.0",
|
|
34
|
-
"express": "^5.2.1"
|
|
34
|
+
"express": "^5.2.1",
|
|
35
|
+
"pdf-parse": "^2.4.5"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/express": "^5.0.6",
|
|
38
39
|
"@types/node": "^20.11.0",
|
|
40
|
+
"@types/pdf-parse": "^1.1.5",
|
|
39
41
|
"typescript": "^5.3.0"
|
|
40
42
|
}
|
|
41
43
|
}
|