@crscreditapi/finstack-mcp-server 0.2.0-cbc96e9 → 0.3.0-61c51ea
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 +1 -0
- package/dist/api/vendor.api.d.ts +44 -0
- package/dist/api/vendor.api.js +56 -0
- package/dist/handlers/index.js +2 -0
- package/dist/handlers/vendor.handler.d.ts +9 -0
- package/dist/handlers/vendor.handler.js +13 -0
- package/dist/tools/index.js +2 -1
- package/dist/tools/vendor.tools.d.ts +5 -0
- package/dist/tools/vendor.tools.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ 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_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. |
|
|
15
16
|
|
|
16
17
|
## Install
|
|
17
18
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor contact directory calls.
|
|
3
|
+
*
|
|
4
|
+
* Backed by the mware-portal chatbase API, same as the customer tools:
|
|
5
|
+
* GET /api/v1/chatbase/vendor_contacts returns the whole vendor contact list
|
|
6
|
+
* in one shot (authenticated with the shared X-Chatbase-Secret). We fetch the
|
|
7
|
+
* full list and filter in-process so callers can match on specialty tags and
|
|
8
|
+
* connection degree too.
|
|
9
|
+
*/
|
|
10
|
+
import type { ApiClient } from './client.js';
|
|
11
|
+
export interface VendorContact {
|
|
12
|
+
id: number;
|
|
13
|
+
vendor: string;
|
|
14
|
+
name: string;
|
|
15
|
+
email: string;
|
|
16
|
+
phone: string;
|
|
17
|
+
role: string;
|
|
18
|
+
location: string;
|
|
19
|
+
linkedin: string | null;
|
|
20
|
+
specialties: string[];
|
|
21
|
+
degree: string;
|
|
22
|
+
notes: string;
|
|
23
|
+
}
|
|
24
|
+
export type VendorConnection = '1st Degree' | '2nd Degree' | 'all';
|
|
25
|
+
export interface VendorSearchResult {
|
|
26
|
+
query: string | null;
|
|
27
|
+
connection: VendorConnection;
|
|
28
|
+
total_matched: number;
|
|
29
|
+
returned: number;
|
|
30
|
+
contacts: VendorContact[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Search the vendor contact directory.
|
|
34
|
+
*
|
|
35
|
+
* The whole list is fetched from the chatbase endpoint, then filtered here:
|
|
36
|
+
* the connection filter narrows by degree, and the keyword query is OR-matched
|
|
37
|
+
* across every contact field plus specialty tags so that a question like
|
|
38
|
+
* "who handles ECM pricing" matches on role, notes, AND the `pricing` specialty.
|
|
39
|
+
*/
|
|
40
|
+
export declare function searchVendorContacts(client: ApiClient, args: {
|
|
41
|
+
query?: string;
|
|
42
|
+
connection?: VendorConnection;
|
|
43
|
+
limit?: number;
|
|
44
|
+
}): Promise<VendorSearchResult>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const CONNECTION_TO_DEGREE = {
|
|
2
|
+
'1st Degree': 'first_degree',
|
|
3
|
+
'2nd Degree': 'second_degree',
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Search the vendor contact directory.
|
|
7
|
+
*
|
|
8
|
+
* The whole list is fetched from the chatbase endpoint, then filtered here:
|
|
9
|
+
* the connection filter narrows by degree, and the keyword query is OR-matched
|
|
10
|
+
* across every contact field plus specialty tags so that a question like
|
|
11
|
+
* "who handles ECM pricing" matches on role, notes, AND the `pricing` specialty.
|
|
12
|
+
*/
|
|
13
|
+
export async function searchVendorContacts(client, args) {
|
|
14
|
+
const secret = process.env.FINSTACK_CHATBASE_SECRET;
|
|
15
|
+
if (!secret) {
|
|
16
|
+
throw new Error('FINSTACK_CHATBASE_SECRET must be set');
|
|
17
|
+
}
|
|
18
|
+
const response = await client.get('/api/v1/chatbase/vendor_contacts', { 'X-Chatbase-Secret': secret });
|
|
19
|
+
if (response.status !== 'success' || !response.data) {
|
|
20
|
+
throw new Error(response.message || 'Failed to fetch vendor contacts');
|
|
21
|
+
}
|
|
22
|
+
let matched = response.data.vendor_contacts;
|
|
23
|
+
if (args.connection && args.connection !== 'all') {
|
|
24
|
+
const degree = CONNECTION_TO_DEGREE[args.connection];
|
|
25
|
+
matched = matched.filter((v) => v.degree === degree);
|
|
26
|
+
}
|
|
27
|
+
const q = args.query?.trim().toLowerCase();
|
|
28
|
+
if (q) {
|
|
29
|
+
const terms = q.split(/\s+/).filter((t) => t.length > 1);
|
|
30
|
+
if (terms.length > 0) {
|
|
31
|
+
matched = matched.filter((v) => {
|
|
32
|
+
const haystack = [
|
|
33
|
+
v.vendor,
|
|
34
|
+
v.name,
|
|
35
|
+
v.email,
|
|
36
|
+
v.role,
|
|
37
|
+
v.notes,
|
|
38
|
+
v.location,
|
|
39
|
+
...(v.specialties ?? []),
|
|
40
|
+
]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join(' ')
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
return terms.some((t) => haystack.includes(t));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const limit = args.limit && args.limit > 0 ? Math.min(args.limit, 100) : 25;
|
|
49
|
+
return {
|
|
50
|
+
query: args.query ?? null,
|
|
51
|
+
connection: args.connection ?? 'all',
|
|
52
|
+
total_matched: matched.length,
|
|
53
|
+
returned: Math.min(matched.length, limit),
|
|
54
|
+
contacts: matched.slice(0, limit),
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { handleGetCustomerInvoices, handleGetCustomerPricingConfig, handleGetCustomerRevenue, handleGetCustomerSummary, handleGetPricingAddendumText, handleSearchCustomer, } from './customer.handler.js';
|
|
2
|
+
import { handleGetVendors } from './vendor.handler.js';
|
|
2
3
|
const wrap = (fn) => {
|
|
3
4
|
return async (args) => fn(args);
|
|
4
5
|
};
|
|
@@ -9,4 +10,5 @@ export const handlers = {
|
|
|
9
10
|
finstack_get_customer_invoices: wrap(handleGetCustomerInvoices),
|
|
10
11
|
finstack_get_customer_pricing_config: wrap(handleGetCustomerPricingConfig),
|
|
11
12
|
finstack_get_pricing_addendum_text: wrap(handleGetPricingAddendumText),
|
|
13
|
+
finstack_get_vendors: wrap(handleGetVendors),
|
|
12
14
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type VendorConnection } from '../api/vendor.api.js';
|
|
2
|
+
interface GetVendorsArgs {
|
|
3
|
+
query?: string;
|
|
4
|
+
connection?: VendorConnection;
|
|
5
|
+
limit?: number;
|
|
6
|
+
environment?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function handleGetVendors(args: GetVendorsArgs): Promise<unknown>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor contact directory handlers
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from '../api/client.js';
|
|
5
|
+
import { searchVendorContacts, } from '../api/vendor.api.js';
|
|
6
|
+
export async function handleGetVendors(args) {
|
|
7
|
+
const client = getClient(args.environment ?? 'prod');
|
|
8
|
+
return searchVendorContacts(client, {
|
|
9
|
+
query: args.query,
|
|
10
|
+
connection: args.connection,
|
|
11
|
+
limit: args.limit,
|
|
12
|
+
});
|
|
13
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const vendorTools = [
|
|
2
|
+
{
|
|
3
|
+
name: 'finstack_get_vendors',
|
|
4
|
+
description: "Look up WHO to contact at an external vendor or partner — the person to email, call, or get introduced to. This is about CRS's OWN vendors/partners (credit bureaus and data vendors like Equifax, TransUnion, Experian, CIC, LexisNexis, FICO, SBFE), NOT CRS customers (use the other finstack_* tools for customer accounts, billing, and pricing). Use whenever the user asks who to speak to / talk to / contact / reach out to / get an intro to, or who the point of contact or relationship owner is — for a vendor, a product or topic area (e.g. ECM), a role, or a specialty. Examples: 'who should I speak to for new ECM products', 'who is our pricing contact at Equifax', 'who handles compliance at Experian', 'who owns the TransUnion relationship'. Pass the key terms (vendor name, product/topic like 'ECM', role, or specialty) as `query` — it OR-matches across company, contact name, role, notes, location, and specialty tags (pricing, technical, legal, paperwork, champion, compliance, onboarding, sales, support, executive). Each result includes name, vendor, role, email, phone, location, LinkedIn, connection degree, and specialties. Omit `query` to list the whole directory. Read-only — it cannot add, edit, or delete contacts.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
query: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: "Keywords to match — company, person, role, product area, or specialty. e.g. 'ECM', 'Equifax pricing', 'compliance'. Tokenized and OR-matched across all contact fields and specialty tags. Omit to return the full directory.",
|
|
11
|
+
},
|
|
12
|
+
connection: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['1st Degree', '2nd Degree', 'all'],
|
|
15
|
+
description: "Filter by connection strength. '1st Degree' = direct contacts, '2nd Degree' = warm intros. Default 'all'.",
|
|
16
|
+
},
|
|
17
|
+
limit: {
|
|
18
|
+
type: 'number',
|
|
19
|
+
description: 'Max contacts to return. Default 25, capped at 100.',
|
|
20
|
+
minimum: 1,
|
|
21
|
+
maximum: 100,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
];
|