@crscreditapi/finstack-mcp-server 0.7.0-92ce169 → 0.8.0-8468b06

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
@@ -16,6 +16,7 @@ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Pro
16
16
  | `finstack_get_customer_cohort` | Bulk, paginated list of all customers with portal status, account type, and sales opportunities (type + stage). For cohort/tracker refreshes instead of per-customer summary calls. Optional `status` / `stage` filters. |
17
17
  | `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. |
18
18
  | `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. |
19
+ | `finstack_get_product_catalog` | The CRS product catalog: code, status, vendor, names, description, base cost / default price, inquiry type, FCRA, platforms, add-ons and permissible purposes. Optional exact `code` / `status` / `vendor` filters plus free-text `query`. |
19
20
  | `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. |
20
21
 
21
22
  ## Install
@@ -180,6 +181,14 @@ All parameters optional. `page` defaults to `1`; `per_page` defaults to `500`, c
180
181
 
181
182
  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.
182
183
 
184
+ ### `finstack_get_product_catalog`
185
+
186
+ ```json
187
+ { "vendor": "exp", "status": "active", "query": "credit profile" }
188
+ ```
189
+
190
+ All arguments are optional. `code` (one code or a comma list, e.g. `"EXP1001,TUC2000"`), `status` (`active` / `terminated`) and `vendor` (`exp`, `tuc`, `eqf`, `cic`, `lnr`, `pps`, `mcl`, `kcb`, `cic_tn`, `service_link`, `fico`, `crs`, `ubq`, `at_data`, `accurate_append`, `socure`, `hansa`, `tod`) are exact-match filters applied by the portal; `query` is a case-insensitive AND-match over code, names, description, labels, add-ons and permissible purposes, applied in the MCP server. Returns `{filters, count, products}`, ordered by code. Each product carries `status` / `termination_date`, type / category / vendor (each with a `_label`), `public_name`, `private_name`, `description`, `plan_includes`, `base_cost` and `default_price` (decimal strings, default = base × 2), `inquiry` (`hard` / `soft` / `not_applicable`), `fcra`, `on_path`, `platforms` (`mcl`, `crs`, `b2c`, `liq`, `bureau_direct`), `parent_codes`, `product_codes`, `warning_notice`, `add_ons[]` (code, status, category, name, `bureau_service_code`, `base_cost`, `rolled_up_in_parent`) and `permissible_purposes[]`. Backed by the MCP `product_catalog` chatbase endpoint (mware-portal); subscriber codes and internal notes are not exposed.
191
+
183
192
  All tools accept an optional `environment: "prod" | "dev"` (default `"prod"`).
184
193
 
185
194
  ## Development
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Product catalog calls.
3
+ *
4
+ * Backed by the mware-portal chatbase API:
5
+ * GET /api/v1/chatbase/product_catalog returns the full catalog snapshot (no
6
+ * pagination, ordered by code), authenticated with the shared
7
+ * X-Chatbase-Secret. `code`, `status` and `vendor` are exact-match filters
8
+ * applied server-side; the free-text `query` is matched here in-process.
9
+ */
10
+ import type { ApiClient } from './client.js';
11
+ export declare const PRODUCT_VENDORS: readonly ["exp", "tuc", "eqf", "cic", "lnr", "pps", "mcl", "kcb", "cic_tn", "service_link", "fico", "crs", "ubq", "at_data", "accurate_append", "socure", "hansa", "tod"];
12
+ export type ProductVendor = (typeof PRODUCT_VENDORS)[number];
13
+ export type ProductStatus = 'active' | 'terminated';
14
+ export interface ProductAddOn {
15
+ id: number;
16
+ code: string;
17
+ /** null = active */
18
+ status: 'active' | 'inactive' | null;
19
+ category: string | null;
20
+ category_label: string | null;
21
+ name: string | null;
22
+ private_name: string | null;
23
+ description: string | null;
24
+ bureau_service_code: string | null;
25
+ /** Decimal string */
26
+ base_cost: string | null;
27
+ rolled_up_in_parent: boolean;
28
+ }
29
+ export interface ProductPermissiblePurpose {
30
+ id: number;
31
+ code: string;
32
+ name: string;
33
+ onboarding_path: string | null;
34
+ }
35
+ export interface Product {
36
+ id: number;
37
+ code: string;
38
+ status: ProductStatus;
39
+ termination_date: string | null;
40
+ type: string | null;
41
+ type_label: string | null;
42
+ category: string | null;
43
+ category_label: string | null;
44
+ vendor: string | null;
45
+ vendor_label: string | null;
46
+ public_name: string | null;
47
+ private_name: string | null;
48
+ description: string | null;
49
+ plan_includes: string | null;
50
+ /** Decimal string */
51
+ base_cost: string | null;
52
+ /** Decimal string, base_cost x 2 */
53
+ default_price: string | null;
54
+ inquiry: 'hard' | 'soft' | 'not_applicable' | null;
55
+ inquiry_label: string | null;
56
+ fcra: boolean | null;
57
+ on_path: string | null;
58
+ platforms: Record<string, boolean>;
59
+ parent_codes: string | null;
60
+ product_codes: string | null;
61
+ warning_notice: string | null;
62
+ add_ons: ProductAddOn[];
63
+ permissible_purposes: ProductPermissiblePurpose[];
64
+ created_at: string | null;
65
+ updated_at: string | null;
66
+ }
67
+ export interface ProductCatalogResult {
68
+ filters: {
69
+ code: string | null;
70
+ status: ProductStatus | null;
71
+ vendor: ProductVendor | null;
72
+ query: string | null;
73
+ };
74
+ count: number;
75
+ products: Product[];
76
+ }
77
+ export declare function getProductCatalog(client: ApiClient, args: {
78
+ code?: string;
79
+ status?: ProductStatus;
80
+ vendor?: ProductVendor;
81
+ query?: string;
82
+ }): Promise<ProductCatalogResult>;
@@ -0,0 +1,81 @@
1
+ export const PRODUCT_VENDORS = [
2
+ 'exp',
3
+ 'tuc',
4
+ 'eqf',
5
+ 'cic',
6
+ 'lnr',
7
+ 'pps',
8
+ 'mcl',
9
+ 'kcb',
10
+ 'cic_tn',
11
+ 'service_link',
12
+ 'fico',
13
+ 'crs',
14
+ 'ubq',
15
+ 'at_data',
16
+ 'accurate_append',
17
+ 'socure',
18
+ 'hansa',
19
+ 'tod',
20
+ ];
21
+ function productHaystack(p) {
22
+ return [
23
+ p.code,
24
+ p.public_name,
25
+ p.private_name,
26
+ p.description,
27
+ p.type_label,
28
+ p.category_label,
29
+ p.vendor_label,
30
+ p.product_codes,
31
+ p.parent_codes,
32
+ ...p.add_ons.flatMap((a) => [a.code, a.name, a.private_name, a.bureau_service_code]),
33
+ ...p.permissible_purposes.flatMap((pp) => [pp.code, pp.name]),
34
+ ]
35
+ .filter(Boolean)
36
+ .join(' ')
37
+ .toLowerCase();
38
+ }
39
+ export async function getProductCatalog(client, args) {
40
+ const secret = process.env.FINSTACK_CHATBASE_SECRET;
41
+ if (!secret) {
42
+ throw new Error('FINSTACK_CHATBASE_SECRET must be set');
43
+ }
44
+ const code = args.code
45
+ ?.split(',')
46
+ .map((c) => c.trim().toUpperCase())
47
+ .filter(Boolean)
48
+ .join(',');
49
+ const params = new URLSearchParams();
50
+ if (code)
51
+ params.set('code', code);
52
+ if (args.status)
53
+ params.set('status', args.status);
54
+ if (args.vendor)
55
+ params.set('vendor', args.vendor);
56
+ const qs = params.toString();
57
+ const response = await client.get(`/api/v1/chatbase/product_catalog${qs ? `?${qs}` : ''}`, { 'X-Chatbase-Secret': secret });
58
+ if (response.status !== 'success' || !response.data) {
59
+ throw new Error(response.message || 'Failed to fetch product catalog');
60
+ }
61
+ let products = response.data.products;
62
+ // Every term must match (AND), so "experian soft" narrows rather than widens.
63
+ const q = args.query?.trim().toLowerCase();
64
+ if (q) {
65
+ const terms = q.split(/\s+/).filter(Boolean);
66
+ products = products.filter((p) => {
67
+ const haystack = productHaystack(p);
68
+ return terms.every((t) => haystack.includes(t));
69
+ });
70
+ }
71
+ return {
72
+ filters: {
73
+ code: code || null,
74
+ status: args.status ?? null,
75
+ vendor: args.vendor ?? null,
76
+ query: args.query ?? null,
77
+ },
78
+ count: products.length,
79
+ products,
80
+ };
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { handleGetCustomerCohort, handleGetCustomerInvoices, handleGetCustomerPricingConfig, handleGetCustomerRevenue, handleGetCustomerSummary, handleGetCustomerUsers, handleGetPricingAddendumText, handleSearchCustomer, } from './customer.handler.js';
2
2
  import { handleGetLeadiqAnalytics, handleGetLeadiqCustomer, } from './leadiq.handler.js';
3
+ import { handleGetProductCatalog } from './product.handler.js';
3
4
  import { handleGetVendors } from './vendor.handler.js';
4
5
  const wrap = (fn) => {
5
6
  return async (args) => fn(args);
@@ -15,5 +16,6 @@ export const handlers = {
15
16
  finstack_get_customer_users: wrap(handleGetCustomerUsers),
16
17
  finstack_get_leadiq_analytics: wrap(handleGetLeadiqAnalytics),
17
18
  finstack_get_leadiq_customer: wrap(handleGetLeadiqCustomer),
19
+ finstack_get_product_catalog: wrap(handleGetProductCatalog),
18
20
  finstack_get_vendors: wrap(handleGetVendors),
19
21
  };
@@ -0,0 +1,10 @@
1
+ import { type ProductStatus, type ProductVendor } from '../api/product.api.js';
2
+ interface GetProductCatalogArgs {
3
+ code?: string;
4
+ status?: ProductStatus;
5
+ vendor?: ProductVendor;
6
+ query?: string;
7
+ environment?: string;
8
+ }
9
+ export declare function handleGetProductCatalog(args: GetProductCatalogArgs): Promise<unknown>;
10
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Product catalog handlers
3
+ */
4
+ import { getClient } from '../api/client.js';
5
+ import { getProductCatalog, } from '../api/product.api.js';
6
+ export async function handleGetProductCatalog(args) {
7
+ const client = getClient(args.environment ?? 'prod');
8
+ return getProductCatalog(client, {
9
+ code: args.code,
10
+ status: args.status,
11
+ vendor: args.vendor,
12
+ query: args.query,
13
+ });
14
+ }
@@ -1,8 +1,10 @@
1
1
  import { customerTools } from './customer.tools.js';
2
2
  import { leadiqTools } from './leadiq.tools.js';
3
+ import { productTools } from './product.tools.js';
3
4
  import { vendorTools } from './vendor.tools.js';
4
5
  export const tools = [
5
6
  ...customerTools,
6
7
  ...leadiqTools,
8
+ ...productTools,
7
9
  ...vendorTools,
8
10
  ];
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from '../types/tool.types.js';
2
+ export declare const productTools: ToolDefinition[];
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Product catalog tool definitions
3
+ */
4
+ import { PRODUCT_VENDORS } from '../api/product.api.js';
5
+ export const productTools = [
6
+ {
7
+ name: 'finstack_get_product_catalog',
8
+ description: "The CRS product catalog — every product CRS sells (not a specific customer's configuration; for what one customer has, use finstack_get_customer_pricing_config). Use when asked what a product code (e.g. EXP1001, TUC2000) is, what products exist for a bureau/vendor, whether a product is hard or soft pull / FCRA, its base cost or default list price, which platforms it is on (mcl, crs, b2c, liq, bureau_direct), its add-ons (score models etc. with bureau service codes and base costs), its permissible purposes, or whether it is active or terminated. Each product includes: code, status (active/terminated) and termination_date, type, category, vendor, public_name and private_name, description, plan_includes, base_cost and default_price (decimal strings; default_price = base_cost x 2), inquiry (hard/soft/not_applicable), fcra, on_path, platforms, parent_codes, product_codes, warning_notice, add_ons[], permissible_purposes[]. Filters: `code` (exact, one or comma-separated), `status`, `vendor` are applied server-side; `query` is a free-text AND-match across code, names, description, labels, add-on names/codes and permissible purposes. With no filters the whole catalog is returned (can be large — prefer a filter). Does not include subscriber codes or internal notes. Backed by the mware-portal chatbase product_catalog endpoint.",
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ code: {
13
+ type: 'string',
14
+ description: 'Exact product code, or a comma-separated list — e.g. "EXP1001" or "EXP1001,TUC2000".',
15
+ },
16
+ status: {
17
+ type: 'string',
18
+ enum: ['active', 'terminated'],
19
+ description: 'Only products with this status. Omit for both.',
20
+ },
21
+ vendor: {
22
+ type: 'string',
23
+ enum: [...PRODUCT_VENDORS],
24
+ description: 'Only products from this vendor. exp=Experian, tuc=TransUnion, eqf=Equifax, lnr=LexisNexis, fico=FICO, crs=CRS; others as named.',
25
+ },
26
+ query: {
27
+ type: 'string',
28
+ description: "Free-text keywords, all of which must match (case-insensitive) across code, public/private name, description, type/category/vendor labels, add-on names/codes and permissible purposes. e.g. 'credit profile', 'FICO 9', 'tenant'.",
29
+ },
30
+ environment: {
31
+ type: 'string',
32
+ description: 'Target environment: "prod" (default) or "dev"',
33
+ enum: ['dev', 'prod'],
34
+ },
35
+ },
36
+ },
37
+ },
38
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crscreditapi/finstack-mcp-server",
3
- "version": "0.7.0-92ce169",
3
+ "version": "0.8.0-8468b06",
4
4
  "description": "MCP server for Finstack",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",