@crscreditapi/finstack-mcp-server 0.4.0-0c53dac → 0.5.0-23af60a

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 CHANGED
@@ -12,6 +12,8 @@ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Pro
12
12
  | `finstack_get_customer_invoices` | Paginated list of finalized invoices for one customer with full line-item breakdown. |
13
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
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. |
15
+ | `finstack_get_leadiq_analytics` | System-wide LeadsIQ analytics for a trailing window: totals vs previous period, the complete list of customers that pulled (revenue desc), entitled/active/invited split, and enrolled-but-never-pulled accounts. |
16
+ | `finstack_get_leadiq_customer` | One customer's LeadsIQ detail: enrollment state, window/YTD/lifetime stats, match rate and append hit rates, most commonly used pull filters, recent pulls. |
15
17
  | `finstack_get_vendors` | Look up who to contact at an external vendor/partner (credit bureaus and data vendors like Equifax, TransUnion, Experian, CIC). OR-matches `query` across company, name, role, notes, location, and specialty tags; optional `connection` degree filter. |
16
18
 
17
19
  ## Install
@@ -144,6 +146,22 @@ Returns `{products, pricing_addendums, environment_users}`. `pricing_addendums[]
144
146
 
145
147
  `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.
146
148
 
149
+ ### `finstack_get_leadiq_analytics`
150
+
151
+ ```json
152
+ { "days": 30 }
153
+ ```
154
+
155
+ `days` is optional (default `7`, clamped server-side to 1–366). Returns `{period, totals, prev_period_totals, customers, customers_truncated, entitlement, never_pulled}`. `customers[]` is the complete list of accounts with ≥ 1 pull in the window, highest revenue first (`customers_truncated` is only `true` if the 50-account safety cap trimmed it). Definitions match the portal's admin LeadsIQ customer dashboard: a pull is one top-level LeadsIQ job, records count the export leg only, and revenue is the job's cost.
156
+
157
+ ### `finstack_get_leadiq_customer`
158
+
159
+ ```json
160
+ { "customer_code": "CID18068", "days": 30 }
161
+ ```
162
+
163
+ Identify the customer with either `customer_code` or `user_id`; `days` is optional (default `30`) and only scopes the `period` block. Returns `{customer, entitlement, contacts, period, year_to_date, lifetime, top_filters, recent_pulls}`. `top_filters[]` aggregates each pull's submitted form filters (dashboard formatting) with per-filter usage share and most common values.
164
+
147
165
  All tools accept an optional `environment: "prod" | "dev"` (default `"prod"`).
148
166
 
149
167
  ## Development
@@ -0,0 +1,104 @@
1
+ /**
2
+ * LeadsIQ analytics calls against the mware-portal API.
3
+ *
4
+ * Both endpoints share the admin LeadsIQ customer dashboard's definitions:
5
+ * a pull is one top-level LeadiqJob, records count the export leg only,
6
+ * revenue is the pull's display cost, and entitlement means the customer has
7
+ * a LeadsIQ environment user in any environment.
8
+ */
9
+ import type { ApiClient } from './client.js';
10
+ export interface LeadiqPeriodTotals {
11
+ pulls: number;
12
+ records: number;
13
+ revenue: number;
14
+ active_customers: number;
15
+ }
16
+ export interface LeadiqActiveCustomer {
17
+ name: string | null;
18
+ customer_code: string | null;
19
+ pulls: number;
20
+ records: number;
21
+ revenue: number;
22
+ last_pull_at: string | null;
23
+ }
24
+ export interface LeadiqNeverPulledCustomer {
25
+ name: string;
26
+ customer_code: string | null;
27
+ sales: string | null;
28
+ }
29
+ export interface LeadiqAnalyticsDigest {
30
+ period: {
31
+ start_date: string;
32
+ end_date: string;
33
+ days: number;
34
+ };
35
+ totals: LeadiqPeriodTotals;
36
+ prev_period_totals: LeadiqPeriodTotals;
37
+ /** Every customer with >= 1 pull in the window, highest revenue first. */
38
+ customers: LeadiqActiveCustomer[];
39
+ /** True only if the 50-account safety cap trimmed `customers`. */
40
+ customers_truncated: boolean;
41
+ entitlement: {
42
+ entitled: number;
43
+ active: number;
44
+ invited: number;
45
+ };
46
+ never_pulled: LeadiqNeverPulledCustomer[];
47
+ }
48
+ export declare function getLeadiqAnalytics(client: ApiClient, days?: number): Promise<LeadiqAnalyticsDigest>;
49
+ export interface LeadiqStats {
50
+ pulls: number;
51
+ records: number;
52
+ revenue: number;
53
+ }
54
+ export interface LeadiqTopFilter {
55
+ filter: string;
56
+ used_in_pulls: number;
57
+ share_of_pulls_pct: number | null;
58
+ top_values: Array<{
59
+ value: string;
60
+ pulls: number;
61
+ }>;
62
+ }
63
+ export interface LeadiqRecentPull {
64
+ submitted_at: string | null;
65
+ job_type: string;
66
+ status: string;
67
+ records: number;
68
+ revenue: number;
69
+ append_products: string[] | null;
70
+ }
71
+ export interface LeadiqCustomerDetail {
72
+ customer: {
73
+ name: string;
74
+ customer_code: string | null;
75
+ };
76
+ entitlement: {
77
+ entitled: boolean;
78
+ production_access: boolean;
79
+ status: 'active' | 'invited';
80
+ };
81
+ contacts: {
82
+ admin: string | null;
83
+ sales: string | null;
84
+ };
85
+ period: {
86
+ start_date: string;
87
+ end_date: string;
88
+ days: number;
89
+ } & LeadiqStats;
90
+ year_to_date: LeadiqStats;
91
+ lifetime: LeadiqStats & {
92
+ first_pull_at: string | null;
93
+ last_pull_at: string | null;
94
+ match_rate_pct: number | null;
95
+ append_hit_rates_pct: Record<string, number | null>;
96
+ };
97
+ top_filters: LeadiqTopFilter[];
98
+ recent_pulls: LeadiqRecentPull[];
99
+ }
100
+ export declare function getLeadiqCustomer(client: ApiClient, args: {
101
+ customerCode?: string;
102
+ userId?: string;
103
+ days?: number;
104
+ }): Promise<LeadiqCustomerDetail>;
@@ -0,0 +1,36 @@
1
+ export async function getLeadiqAnalytics(client, days) {
2
+ const secret = process.env.FINSTACK_CHATBASE_SECRET;
3
+ if (!secret) {
4
+ throw new Error('FINSTACK_CHATBASE_SECRET must be set');
5
+ }
6
+ const params = new URLSearchParams();
7
+ if (days !== undefined) {
8
+ params.set('days', String(days));
9
+ }
10
+ const response = await client.get(`/api/v1/chatbase/leadiq_analytics?${params}`, { 'X-Chatbase-Secret': secret });
11
+ if (response.status !== 'success' || !response.data) {
12
+ throw new Error(response.message || 'Failed to fetch LeadsIQ analytics');
13
+ }
14
+ return response.data;
15
+ }
16
+ export async function getLeadiqCustomer(client, args) {
17
+ const secret = process.env.FINSTACK_CHATBASE_SECRET;
18
+ if (!secret) {
19
+ throw new Error('FINSTACK_CHATBASE_SECRET must be set');
20
+ }
21
+ if (!args.customerCode && !args.userId) {
22
+ throw new Error('Provide either customer_code or user_id');
23
+ }
24
+ const params = new URLSearchParams();
25
+ if (args.customerCode)
26
+ params.set('customer_code', args.customerCode);
27
+ if (args.userId)
28
+ params.set('user_id', args.userId);
29
+ if (args.days !== undefined)
30
+ params.set('days', String(args.days));
31
+ const response = await client.get(`/api/v1/chatbase/leadiq_customer?${params}`, { 'X-Chatbase-Secret': secret });
32
+ if (response.status !== 'success' || !response.data) {
33
+ throw new Error(response.message || 'Failed to fetch LeadsIQ customer detail');
34
+ }
35
+ return response.data;
36
+ }
@@ -1,4 +1,5 @@
1
1
  import { handleGetCustomerInvoices, handleGetCustomerPricingConfig, handleGetCustomerRevenue, handleGetCustomerSummary, handleGetPricingAddendumText, handleSearchCustomer, } from './customer.handler.js';
2
+ import { handleGetLeadiqAnalytics, handleGetLeadiqCustomer, } from './leadiq.handler.js';
2
3
  import { handleGetVendors } from './vendor.handler.js';
3
4
  const wrap = (fn) => {
4
5
  return async (args) => fn(args);
@@ -10,5 +11,7 @@ export const handlers = {
10
11
  finstack_get_customer_invoices: wrap(handleGetCustomerInvoices),
11
12
  finstack_get_customer_pricing_config: wrap(handleGetCustomerPricingConfig),
12
13
  finstack_get_pricing_addendum_text: wrap(handleGetPricingAddendumText),
14
+ finstack_get_leadiq_analytics: wrap(handleGetLeadiqAnalytics),
15
+ finstack_get_leadiq_customer: wrap(handleGetLeadiqCustomer),
13
16
  finstack_get_vendors: wrap(handleGetVendors),
14
17
  };
@@ -0,0 +1,13 @@
1
+ interface GetLeadiqAnalyticsArgs {
2
+ days?: number;
3
+ environment?: string;
4
+ }
5
+ export declare function handleGetLeadiqAnalytics(args: GetLeadiqAnalyticsArgs): Promise<unknown>;
6
+ interface GetLeadiqCustomerArgs {
7
+ customer_code?: string;
8
+ user_id?: string;
9
+ days?: number;
10
+ environment?: string;
11
+ }
12
+ export declare function handleGetLeadiqCustomer(args: GetLeadiqCustomerArgs): Promise<unknown>;
13
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * LeadsIQ tool handlers
3
+ */
4
+ import { getClient } from '../api/client.js';
5
+ import { getLeadiqAnalytics, getLeadiqCustomer } from '../api/leadiq.api.js';
6
+ export async function handleGetLeadiqAnalytics(args) {
7
+ const client = getClient(args.environment ?? 'prod');
8
+ return getLeadiqAnalytics(client, args.days);
9
+ }
10
+ export async function handleGetLeadiqCustomer(args) {
11
+ const client = getClient(args.environment ?? 'prod');
12
+ return getLeadiqCustomer(client, {
13
+ customerCode: args.customer_code,
14
+ userId: args.user_id,
15
+ days: args.days,
16
+ });
17
+ }
@@ -1,3 +1,8 @@
1
1
  import { customerTools } from './customer.tools.js';
2
+ import { leadiqTools } from './leadiq.tools.js';
2
3
  import { vendorTools } from './vendor.tools.js';
3
- export const tools = [...customerTools, ...vendorTools];
4
+ export const tools = [
5
+ ...customerTools,
6
+ ...leadiqTools,
7
+ ...vendorTools,
8
+ ];
@@ -0,0 +1,5 @@
1
+ /**
2
+ * LeadsIQ tool definitions
3
+ */
4
+ import type { ToolDefinition } from '../types/tool.types.js';
5
+ export declare const leadiqTools: ToolDefinition[];
@@ -0,0 +1,50 @@
1
+ export const leadiqTools = [
2
+ {
3
+ name: 'finstack_get_leadiq_analytics',
4
+ description: 'System-wide LeadsIQ (lead generation / batch append) analytics for a trailing window. Returns totals and previous-period totals (pulls, records, revenue, active_customers), customers — the COMPLETE list of customers with at least one pull in the window (highest revenue first; customers_truncated is true only if the 50-account safety cap trimmed it), the entitled/active/invited account split, and never_pulled — entitled accounts that have never run a pull, with their sales owner. Use for questions like "which customers pulled last month" (set days=30 and read customers[]), "who is the most active LeadsIQ customer by revenue" (customers[0]), or "who is enrolled in LeadsIQ but never pulled". For one customer\'s detail use finstack_get_leadiq_customer.',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: {
8
+ days: {
9
+ type: 'number',
10
+ description: 'Trailing window in days, 1-366 (default 7; values outside the range are clamped server-side). Use 30 for "last month", 365 for "this past year".',
11
+ minimum: 1,
12
+ maximum: 366,
13
+ },
14
+ environment: {
15
+ type: 'string',
16
+ description: 'Target environment: "prod" (default) or "dev"',
17
+ enum: ['dev', 'prod'],
18
+ },
19
+ },
20
+ },
21
+ },
22
+ {
23
+ name: 'finstack_get_leadiq_customer',
24
+ description: "One B2B customer's LeadsIQ usage detail: enrollment state (entitled, production_access, active/invited status), admin and sales contacts, pulls/records/revenue for a trailing window plus year_to_date and lifetime, lifetime match rate and per-field append hit rates (mobile/email/landline/demographic, as percentages), top_filters — the form filters the customer most commonly runs pulls with, each with usage share and most common values — and the 10 most recent pulls. Use for questions like \"what LeadsIQ revenue did customer X have this year\" (read year_to_date.revenue) or \"which filters does customer X commonly use\". Revenue here is LeadsIQ job cost only; for a customer's total billed revenue across all products use finstack_get_customer_revenue. Identify the customer with EITHER customer_code OR user_id.",
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ customer_code: {
29
+ type: 'string',
30
+ description: 'Customer code (CID) — e.g. "CID18068". Provide this OR user_id.',
31
+ },
32
+ user_id: {
33
+ type: 'string',
34
+ description: 'mware-portal user ID. Provide this OR customer_code.',
35
+ },
36
+ days: {
37
+ type: 'number',
38
+ description: 'Trailing window in days for the period block, 1-366 (default 30; values outside the range are clamped server-side).',
39
+ minimum: 1,
40
+ maximum: 366,
41
+ },
42
+ environment: {
43
+ type: 'string',
44
+ description: 'Target environment: "prod" (default) or "dev"',
45
+ enum: ['dev', 'prod'],
46
+ },
47
+ },
48
+ },
49
+ },
50
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crscreditapi/finstack-mcp-server",
3
- "version": "0.4.0-0c53dac",
3
+ "version": "0.5.0-23af60a",
4
4
  "description": "MCP server for Finstack",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",