@crscreditapi/finstack-mcp-server 0.10.0-80b4ec3 → 0.10.0-a6ee499
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/.env.example +5 -0
- package/README.md +2 -2
- package/dist/api/leadiq.api.d.ts +10 -0
- package/dist/toolLog.d.ts +1 -0
- package/dist/toolLog.js +59 -21
- package/dist/tools/leadiq.tools.js +1 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -15,4 +15,9 @@ FINSTACK_CHATBASE_SECRET=
|
|
|
15
15
|
# Support Autopsy observability: insert one row per tool call into the shared tool_calls table
|
|
16
16
|
# (n8n Postgres). Leave unset to emit a JSON log line instead. TOOL_LOG=off disables both.
|
|
17
17
|
TOOL_CALLS_DATABASE_URL=
|
|
18
|
+
# Preferred over the URL (no percent-encoding needed):
|
|
19
|
+
# TOOL_CALLS_PGHOST=
|
|
20
|
+
# TOOL_CALLS_PGUSER=n8n
|
|
21
|
+
# TOOL_CALLS_PGPASSWORD=
|
|
22
|
+
# TOOL_CALLS_PGDATABASE=n8n
|
|
18
23
|
# SSL is on by default (no sslmode needed in the URL). TOOL_CALLS_PGSSL=off for a local DB.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Pro
|
|
|
18
18
|
| `finstack_get_add_on_customers` | Which customers have an add-on (by add-on code, e.g. `CRS1002`) attached to at least one product, and on which products. Paged. |
|
|
19
19
|
| `finstack_get_customer_products` | The products one customer holds with subscription status, platform, activation date and attached add-ons. No prices. |
|
|
20
20
|
| `finstack_get_customer_add_ons` | The add-ons one customer has, each with the products it is attached to. |
|
|
21
|
-
| `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. |
|
|
21
|
+
| `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. Revenue is split into the two products under the LeadsIQ umbrella (`leadsiq_revenue` export vs `data_append_revenue`). |
|
|
22
22
|
| `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. |
|
|
23
23
|
| `finstack_get_cop_ai_analytics` | Credit Order Pro AI summary & chat usage for a trailing window: totals, per-customer activity (reports, summaries, opens, questions, ratings, Bedrock cost) and who currently has the feature enabled. |
|
|
24
24
|
| `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`. |
|
|
@@ -208,7 +208,7 @@ Identify the customer with either `customer_code` or `user_id`. Returns `{custom
|
|
|
208
208
|
{ "days": 30 }
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
`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.
|
|
211
|
+
`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. `totals`, `prev_period_totals` and every `customers[]` row also split that revenue into the two products under the LeadsIQ umbrella — `leadsiq_revenue` (LeadsIQ export) and `data_append_revenue` (Data Append) — with `revenue` always equal to their sum.
|
|
212
212
|
|
|
213
213
|
### `finstack_get_leadiq_customer`
|
|
214
214
|
|
package/dist/api/leadiq.api.d.ts
CHANGED
|
@@ -10,7 +10,12 @@ import type { ApiClient } from './client.js';
|
|
|
10
10
|
export interface LeadiqPeriodTotals {
|
|
11
11
|
pulls: number;
|
|
12
12
|
records: number;
|
|
13
|
+
/** Total revenue; always equals leadsiq_revenue + data_append_revenue. */
|
|
13
14
|
revenue: number;
|
|
15
|
+
/** LeadsIQ export (lead generation) product revenue. */
|
|
16
|
+
leadsiq_revenue: number;
|
|
17
|
+
/** Data Append product revenue. */
|
|
18
|
+
data_append_revenue: number;
|
|
14
19
|
active_customers: number;
|
|
15
20
|
}
|
|
16
21
|
export interface LeadiqActiveCustomer {
|
|
@@ -18,7 +23,12 @@ export interface LeadiqActiveCustomer {
|
|
|
18
23
|
customer_code: string | null;
|
|
19
24
|
pulls: number;
|
|
20
25
|
records: number;
|
|
26
|
+
/** Total revenue; always equals leadsiq_revenue + data_append_revenue. */
|
|
21
27
|
revenue: number;
|
|
28
|
+
/** LeadsIQ export (lead generation) product revenue. */
|
|
29
|
+
leadsiq_revenue: number;
|
|
30
|
+
/** Data Append product revenue. */
|
|
31
|
+
data_append_revenue: number;
|
|
22
32
|
last_pull_at: string | null;
|
|
23
33
|
}
|
|
24
34
|
export interface LeadiqNeverPulledCustomer {
|
package/dist/toolLog.d.ts
CHANGED
package/dist/toolLog.js
CHANGED
|
@@ -18,21 +18,46 @@ const ENABLED = (process.env.TOOL_LOG || 'on').toLowerCase() !== 'off';
|
|
|
18
18
|
// TOOL_CALLS_PGSSL=off disables it for a local database.
|
|
19
19
|
const DB_URL = (process.env.TOOL_CALLS_DATABASE_URL || '').replace(/[?&]sslmode=[^&]+/, '').replace(/\?$/, '');
|
|
20
20
|
const DB_SSL = (process.env.TOOL_CALLS_PGSSL || 'on').toLowerCase() === 'off' ? false : { rejectUnauthorized: false };
|
|
21
|
+
// Either a URL (password must be percent-encoded) or discrete variables, which need no
|
|
22
|
+
// encoding and are therefore preferred: TOOL_CALLS_PGHOST, TOOL_CALLS_PGUSER,
|
|
23
|
+
// TOOL_CALLS_PGPASSWORD, TOOL_CALLS_PGDATABASE (default n8n), TOOL_CALLS_PGPORT (default 5432).
|
|
24
|
+
function poolConfig() {
|
|
25
|
+
const base = { max: 2, idleTimeoutMillis: 30_000, connectionTimeoutMillis: 3_000, ssl: DB_SSL };
|
|
26
|
+
const host = process.env.TOOL_CALLS_PGHOST;
|
|
27
|
+
if (host) {
|
|
28
|
+
return {
|
|
29
|
+
...base,
|
|
30
|
+
host,
|
|
31
|
+
port: Number(process.env.TOOL_CALLS_PGPORT || 5432),
|
|
32
|
+
user: process.env.TOOL_CALLS_PGUSER || 'n8n',
|
|
33
|
+
password: process.env.TOOL_CALLS_PGPASSWORD || '',
|
|
34
|
+
database: process.env.TOOL_CALLS_PGDATABASE || 'n8n',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (DB_URL)
|
|
38
|
+
return { ...base, connectionString: DB_URL };
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
21
41
|
const STREAM = (process.env.MCP_TRANSPORT || 'stdio') === 'http' ? process.stdout : process.stderr;
|
|
22
42
|
let pool = null;
|
|
23
43
|
let lastDbErrorAt = 0;
|
|
44
|
+
let poolBroken = false;
|
|
24
45
|
function getPool() {
|
|
25
|
-
if (
|
|
46
|
+
if (poolBroken)
|
|
26
47
|
return null;
|
|
27
48
|
if (!pool) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
49
|
+
const cfg = poolConfig();
|
|
50
|
+
if (!cfg)
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
pool = new pg.Pool(cfg);
|
|
54
|
+
pool.on('error', (err) => warnDb(err));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
poolBroken = true; // bad config (e.g. unencoded password in the URL): fall back to log lines
|
|
58
|
+
warnDb(err);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
36
61
|
}
|
|
37
62
|
return pool;
|
|
38
63
|
}
|
|
@@ -79,18 +104,31 @@ const INSERT = `INSERT INTO tool_calls
|
|
|
79
104
|
(trace_id, channel, agent, server, tool, args_hash, args_summary, started_at,
|
|
80
105
|
duration_ms, ok, error_class, error_message, result_bytes, truncated, upstream_status)
|
|
81
106
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`;
|
|
107
|
+
/** Never throws: a logging problem must never fail a tool call. */
|
|
82
108
|
export function logToolCall(e) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
try {
|
|
110
|
+
if (!ENABLED)
|
|
111
|
+
return;
|
|
112
|
+
const r = toRecord(e);
|
|
113
|
+
const p = getPool();
|
|
114
|
+
if (!p) {
|
|
115
|
+
STREAM.write(JSON.stringify({ ts: new Date().toISOString(), kind: 'tool_call', ...r }) + '\n');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
p.query(INSERT, [
|
|
120
|
+
r.trace_id, r.channel, r.agent, r.server, r.tool, r.args_hash, JSON.stringify(r.args_summary),
|
|
121
|
+
r.started_at, r.duration_ms, r.ok, r.error_class, r.error_message, r.result_bytes,
|
|
122
|
+
r.truncated, r.upstream_status,
|
|
123
|
+
]).catch(warnDb);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
// pg can throw synchronously on an unparseable connection string
|
|
127
|
+
poolBroken = true;
|
|
128
|
+
warnDb(err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
warnDb(err);
|
|
90
133
|
}
|
|
91
|
-
p.query(INSERT, [
|
|
92
|
-
r.trace_id, r.channel, r.agent, r.server, r.tool, r.args_hash, JSON.stringify(r.args_summary),
|
|
93
|
-
r.started_at, r.duration_ms, r.ok, r.error_class, r.error_message, r.result_bytes,
|
|
94
|
-
r.truncated, r.upstream_status,
|
|
95
|
-
]).catch(warnDb);
|
|
96
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const leadiqTools = [
|
|
2
2
|
{
|
|
3
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.',
|
|
4
|
+
description: 'System-wide LeadsIQ (lead generation / batch append) analytics for a trailing window. Returns totals and previous-period totals (pulls, records, revenue, leadsiq_revenue, data_append_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. Revenue is split into the two products under the LeadsIQ umbrella: leadsiq_revenue (LeadsIQ export) and data_append_revenue (Data Append); revenue is always their sum, so report both when asked about LeadsIQ revenue. 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]), "how much was LeadsIQ vs Data Append revenue" (leadsiq_revenue / data_append_revenue), or "who is enrolled in LeadsIQ but never pulled". For one customer\'s detail use finstack_get_leadiq_customer.',
|
|
5
5
|
inputSchema: {
|
|
6
6
|
type: 'object',
|
|
7
7
|
properties: {
|