@crscreditapi/finstack-mcp-server 0.3.0-61c51ea → 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
@@ -7,11 +7,13 @@ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Pro
7
7
  | Name | Purpose |
8
8
  |---|---|
9
9
  | `finstack_search_customer` | Fuzzy-match a customer by name and return candidate CIDs. Use when only a name is known. |
10
- | `finstack_get_customer_summary` | Non-PII summary for one customer: onboarding progress, products, API credentials, inspection status, recent billing. |
10
+ | `finstack_get_customer_summary` | Non-PII summary for one customer: portal account status and account type, onboarding progress, products, sales opportunities (type + stage), 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
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
@@ -2,6 +2,12 @@ import type { ApiClient } from './client.js';
2
2
  export interface CustomerSummary {
3
3
  summary: string;
4
4
  account: {
5
+ /**
6
+ * Portal account status as shown on the customer page, pre-formatted:
7
+ * 'active' | 'inactive' | 'blacklisted' | 'locked' | 'obsolete' | 'new_account'.
8
+ */
9
+ status: string | null;
10
+ account_type: 'account' | 'channel_partner' | null;
5
11
  onboarding_path: string | null;
6
12
  onboarding_completion_percent: number;
7
13
  completed_tasks: number;
@@ -19,6 +25,18 @@ export interface CustomerSummary {
19
25
  platform: string | null;
20
26
  status: 'activated' | 'not_activated';
21
27
  }>;
28
+ /**
29
+ * Sales opportunities as shown on the portal's sales opportunity pages
30
+ * (distinct from compliance/vetting-path opportunities).
31
+ */
32
+ sales_opportunities: Array<{
33
+ opportunity_code: string | null;
34
+ opportunity_type: 'full' | 'upsell' | 'expand' | 'land';
35
+ stage: 'closed_won' | 'closed_lost' | 'churned';
36
+ booking_value_mrr: number | null;
37
+ close_date: string | null;
38
+ activation_date: string | null;
39
+ }>;
22
40
  api_credentials: Record<'sandbox' | 'production', {
23
41
  configured: boolean;
24
42
  enabled_count: number;
@@ -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,7 +1,7 @@
1
1
  export const customerTools = [
2
2
  {
3
3
  name: 'finstack_get_customer_summary',
4
- description: 'Fetch a non-PII summary of a B2B customer: onboarding progress, products, API credentials, inspection status, and recent billing. Backed by the mware-portal chatbase customer_context endpoint.',
4
+ description: "Fetch a non-PII summary of a B2B customer: portal account status (account.status: active/inactive/blacklisted/locked/obsolete/new_account) and account type (account/channel_partner), onboarding progress, products, sales opportunities (sales_opportunities[] with opportunity_type full/upsell/expand/land and stage closed_won/closed_lost/churned), API credentials, inspection status, and recent billing. Use this when asked for a customer's status or sales opp stage. Backed by the mware-portal chatbase customer_context endpoint.",
5
5
  inputSchema: {
6
6
  type: 'object',
7
7
  properties: {
@@ -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.3.0-61c51ea",
3
+ "version": "0.5.0-23af60a",
4
4
  "description": "MCP server for Finstack",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",