@crscreditapi/finstack-mcp-server 0.2.0-fac1a77 → 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/.env.example +0 -5
- package/README.md +1 -0
- package/dist/api/vendor.api.d.ts +19 -9
- package/dist/api/vendor.api.js +21 -41
- package/dist/handlers/index.js +2 -2
- package/dist/handlers/vendor.handler.d.ts +3 -5
- package/dist/handlers/vendor.handler.js +4 -2
- package/dist/tools/vendor.tools.js +2 -2
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -11,8 +11,3 @@ FINSTACK_PROD_BASE_URL=https://portal.example.com
|
|
|
11
11
|
|
|
12
12
|
# Shared secret for the mware-portal chatbase endpoints (X-Chatbase-Secret header)
|
|
13
13
|
FINSTACK_CHATBASE_SECRET=
|
|
14
|
-
|
|
15
|
-
# === Vendor contact directory (crs-vendor-contacts) ===
|
|
16
|
-
# Base URL of the already-deployed vendor contacts app. Read-only, no auth.
|
|
17
|
-
# Defaults to the production Vercel URL if unset.
|
|
18
|
-
# VENDOR_CONTACTS_BASE_URL=https://crs-vendor-contacts.vercel.app
|
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
|
|
package/dist/api/vendor.api.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
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';
|
|
1
11
|
export interface VendorContact {
|
|
2
|
-
id:
|
|
12
|
+
id: number;
|
|
3
13
|
vendor: string;
|
|
4
14
|
name: string;
|
|
5
15
|
email: string;
|
|
6
16
|
phone: string;
|
|
7
17
|
role: string;
|
|
8
|
-
notes: string;
|
|
9
|
-
connection: string;
|
|
10
18
|
location: string;
|
|
11
|
-
|
|
19
|
+
linkedin: string | null;
|
|
12
20
|
specialties: string[];
|
|
21
|
+
degree: string;
|
|
22
|
+
notes: string;
|
|
13
23
|
}
|
|
14
24
|
export type VendorConnection = '1st Degree' | '2nd Degree' | 'all';
|
|
15
25
|
export interface VendorSearchResult {
|
|
@@ -22,12 +32,12 @@ export interface VendorSearchResult {
|
|
|
22
32
|
/**
|
|
23
33
|
* Search the vendor contact directory.
|
|
24
34
|
*
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `pricing` specialty.
|
|
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.
|
|
29
39
|
*/
|
|
30
|
-
export declare function searchVendorContacts(args: {
|
|
40
|
+
export declare function searchVendorContacts(client: ApiClient, args: {
|
|
31
41
|
query?: string;
|
|
32
42
|
connection?: VendorConnection;
|
|
33
43
|
limit?: number;
|
package/dist/api/vendor.api.js
CHANGED
|
@@ -1,53 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Slack-powered vendor Rolodex). There is no database — the service stores
|
|
6
|
-
* contacts in a JSON blob and exposes them over a read-only REST endpoint
|
|
7
|
-
* (GET /api/vendors). We fetch the list and filter in-process so callers can
|
|
8
|
-
* match on specialty tags too (the upstream ?q= filter only covers a subset
|
|
9
|
-
* of fields).
|
|
10
|
-
*
|
|
11
|
-
* This calls the vendor app's already-deployed public API, so it needs no
|
|
12
|
-
* redeploy of that project — only the env var VENDOR_CONTACTS_BASE_URL
|
|
13
|
-
* (defaults to the production Vercel URL).
|
|
14
|
-
*/
|
|
15
|
-
import { ApiClient } from './client.js';
|
|
16
|
-
const DEFAULT_BASE_URL = 'https://crs-vendor-contacts.vercel.app';
|
|
17
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
18
|
-
let _vendorClient = null;
|
|
19
|
-
function getVendorClient() {
|
|
20
|
-
if (_vendorClient)
|
|
21
|
-
return _vendorClient;
|
|
22
|
-
const baseUrl = process.env.VENDOR_CONTACTS_BASE_URL ?? DEFAULT_BASE_URL;
|
|
23
|
-
const timeoutMs = process.env.FINSTACK_TIMEOUT_MS
|
|
24
|
-
? parseInt(process.env.FINSTACK_TIMEOUT_MS, 10)
|
|
25
|
-
: DEFAULT_TIMEOUT_MS;
|
|
26
|
-
_vendorClient = new ApiClient({ baseUrl, timeoutMs });
|
|
27
|
-
return _vendorClient;
|
|
28
|
-
}
|
|
1
|
+
const CONNECTION_TO_DEGREE = {
|
|
2
|
+
'1st Degree': 'first_degree',
|
|
3
|
+
'2nd Degree': 'second_degree',
|
|
4
|
+
};
|
|
29
5
|
/**
|
|
30
6
|
* Search the vendor contact directory.
|
|
31
7
|
*
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `pricing` specialty.
|
|
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.
|
|
36
12
|
*/
|
|
37
|
-
export async function searchVendorContacts(args) {
|
|
38
|
-
const
|
|
39
|
-
|
|
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;
|
|
40
23
|
if (args.connection && args.connection !== 'all') {
|
|
41
|
-
|
|
24
|
+
const degree = CONNECTION_TO_DEGREE[args.connection];
|
|
25
|
+
matched = matched.filter((v) => v.degree === degree);
|
|
42
26
|
}
|
|
43
|
-
const qs = params.toString();
|
|
44
|
-
const all = await client.get(qs ? `/api/vendors?${qs}` : '/api/vendors');
|
|
45
|
-
let matched = all;
|
|
46
27
|
const q = args.query?.trim().toLowerCase();
|
|
47
28
|
if (q) {
|
|
48
29
|
const terms = q.split(/\s+/).filter((t) => t.length > 1);
|
|
49
30
|
if (terms.length > 0) {
|
|
50
|
-
matched =
|
|
31
|
+
matched = matched.filter((v) => {
|
|
51
32
|
const haystack = [
|
|
52
33
|
v.vendor,
|
|
53
34
|
v.name,
|
|
@@ -55,7 +36,6 @@ export async function searchVendorContacts(args) {
|
|
|
55
36
|
v.role,
|
|
56
37
|
v.notes,
|
|
57
38
|
v.location,
|
|
58
|
-
v.bestFor,
|
|
59
39
|
...(v.specialties ?? []),
|
|
60
40
|
]
|
|
61
41
|
.filter(Boolean)
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { handleGetCustomerInvoices, handleGetCustomerPricingConfig, handleGetCustomerRevenue, handleGetCustomerSummary, handleGetPricingAddendumText, handleSearchCustomer, } from './customer.handler.js';
|
|
2
|
-
import {
|
|
2
|
+
import { handleGetVendors } from './vendor.handler.js';
|
|
3
3
|
const wrap = (fn) => {
|
|
4
4
|
return async (args) => fn(args);
|
|
5
5
|
};
|
|
@@ -10,5 +10,5 @@ export const handlers = {
|
|
|
10
10
|
finstack_get_customer_invoices: wrap(handleGetCustomerInvoices),
|
|
11
11
|
finstack_get_customer_pricing_config: wrap(handleGetCustomerPricingConfig),
|
|
12
12
|
finstack_get_pricing_addendum_text: wrap(handleGetPricingAddendumText),
|
|
13
|
-
|
|
13
|
+
finstack_get_vendors: wrap(handleGetVendors),
|
|
14
14
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vendor contact directory handlers
|
|
3
|
-
*/
|
|
4
1
|
import { type VendorConnection } from '../api/vendor.api.js';
|
|
5
|
-
interface
|
|
2
|
+
interface GetVendorsArgs {
|
|
6
3
|
query?: string;
|
|
7
4
|
connection?: VendorConnection;
|
|
8
5
|
limit?: number;
|
|
6
|
+
environment?: string;
|
|
9
7
|
}
|
|
10
|
-
export declare function
|
|
8
|
+
export declare function handleGetVendors(args: GetVendorsArgs): Promise<unknown>;
|
|
11
9
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vendor contact directory handlers
|
|
3
3
|
*/
|
|
4
|
+
import { getClient } from '../api/client.js';
|
|
4
5
|
import { searchVendorContacts, } from '../api/vendor.api.js';
|
|
5
|
-
export async function
|
|
6
|
-
|
|
6
|
+
export async function handleGetVendors(args) {
|
|
7
|
+
const client = getClient(args.environment ?? 'prod');
|
|
8
|
+
return searchVendorContacts(client, {
|
|
7
9
|
query: args.query,
|
|
8
10
|
connection: args.connection,
|
|
9
11
|
limit: args.limit,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const vendorTools = [
|
|
2
2
|
{
|
|
3
|
-
name: '
|
|
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
|
|
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
5
|
inputSchema: {
|
|
6
6
|
type: 'object',
|
|
7
7
|
properties: {
|