@crscreditapi/finstack-mcp-server 0.1.0-2bad4fe → 0.1.0-76ce272

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 ADDED
@@ -0,0 +1,136 @@
1
+ # finstack-mcp-server
2
+
3
+ MCP server exposing CRS B2B (Finstack) customer data over the [Model Context Protocol](https://modelcontextprotocol.io). Backed by the mware-portal chatbase API.
4
+
5
+ ## Tools
6
+
7
+ | Name | Purpose |
8
+ |---|---|
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. |
11
+ | `finstack_get_customer_revenue` | Revenue for one customer over a date range, bucketed by `day` / `week` / `month` / `quarter` / `year`. |
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install -g @crscreditapi/finstack-mcp-server
17
+ ```
18
+
19
+ After install, the `finstack-mcp-server` binary is on your `PATH`.
20
+
21
+ ## Configuration
22
+
23
+ The server reads configuration from environment variables (also loaded from a local `.env` file via `dotenv`).
24
+
25
+ | Variable | Required | Default | Description |
26
+ |---|---|---|---|
27
+ | `FINSTACK_PROD_BASE_URL` | yes | — | Base URL of the mware-portal API (production). |
28
+ | `FINSTACK_CHATBASE_SECRET` | yes | — | Shared secret sent as `X-Chatbase-Secret` on every request. |
29
+ | `FINSTACK_DEV_BASE_URL` | no | falls back to prod | Base URL for the dev environment. Used when a tool call passes `environment: "dev"`. |
30
+ | `FINSTACK_TIMEOUT_MS` | no | `30000` | Per-request timeout in milliseconds. |
31
+ | `MCP_TRANSPORT` | no | `stdio` | Transport mode: `stdio` (for Claude Code and other CLI clients) or `http` (for n8n and remote clients). |
32
+ | `MCP_PORT` | no | `3002` | Port for the HTTP transport. Ignored when `MCP_TRANSPORT=stdio`. |
33
+
34
+ A starter file is included as `.env.example`.
35
+
36
+ ## Usage
37
+
38
+ ### Stdio (Claude Code, Claude Desktop, other CLI clients)
39
+
40
+ Add an entry to your MCP config (`.mcp.json` for Claude Code, `claude_desktop_config.json` for Claude Desktop):
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "finstack": {
46
+ "command": "finstack-mcp-server",
47
+ "env": {
48
+ "MCP_TRANSPORT": "stdio",
49
+ "FINSTACK_PROD_BASE_URL": "https://portal.example.com",
50
+ "FINSTACK_CHATBASE_SECRET": "<your-secret>"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ The client launches the process; tool calls travel over stdio. No port to open.
58
+
59
+ ### HTTP (n8n, remote MCP clients)
60
+
61
+ Run it as a long-lived service:
62
+
63
+ ```bash
64
+ MCP_TRANSPORT=http \
65
+ MCP_PORT=3002 \
66
+ FINSTACK_PROD_BASE_URL=https://portal.example.com \
67
+ FINSTACK_CHATBASE_SECRET=<your-secret> \
68
+ finstack-mcp-server
69
+ ```
70
+
71
+ Endpoints:
72
+
73
+ - `POST /` and `POST /mcp` — MCP requests (streamable HTTP transport)
74
+ - `GET /health` — health check, returns `{"status":"ok","uptime":<seconds>}`
75
+
76
+ Point your client at `http://<host>:3002/mcp`.
77
+
78
+ ### Docker
79
+
80
+ ```dockerfile
81
+ FROM node:20-alpine
82
+ RUN npm install -g @crscreditapi/finstack-mcp-server
83
+ ENV MCP_TRANSPORT=http MCP_PORT=3002
84
+ EXPOSE 3002
85
+ CMD ["finstack-mcp-server"]
86
+ ```
87
+
88
+ Pass the `FINSTACK_*` variables at runtime (e.g. via `--env-file` or an orchestrator's secret store).
89
+
90
+ ## Tool call reference
91
+
92
+ ### `finstack_search_customer`
93
+
94
+ ```json
95
+ { "q": "rentvine", "threshold": 0.8 }
96
+ ```
97
+
98
+ `q` (required, min 2 chars) is matched case-insensitively. `threshold` (optional, 0.0–1.0, default `0.9`) controls fuzziness — lower it if nothing comes back.
99
+
100
+ ### `finstack_get_customer_summary`
101
+
102
+ ```json
103
+ { "customer_code": "CID18068" }
104
+ ```
105
+
106
+ ### `finstack_get_customer_revenue`
107
+
108
+ ```json
109
+ {
110
+ "customer_code": "CID18068",
111
+ "type": "month",
112
+ "start_date": "2025-05-08",
113
+ "end_date": "2026-05-08"
114
+ }
115
+ ```
116
+
117
+ Range caps depend on `type`: `day` ≤ 92 days, `week` ≤ 2 years, `month`/`quarter` ≤ 10 years, `year` ≤ 50 years.
118
+
119
+ All three tools accept an optional `environment: "prod" | "dev"` (default `"prod"`).
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ git clone https://github.com/StitchCredit/finstack-mcp-server.git
125
+ cd finstack-mcp-server
126
+ npm install
127
+ cp .env.example .env # fill in FINSTACK_PROD_BASE_URL and FINSTACK_CHATBASE_SECRET
128
+ npm run build
129
+ npm start
130
+ ```
131
+
132
+ For stdio mode while iterating, set `MCP_TRANSPORT=stdio` in `.env` and wire the local `dist/index.js` into your MCP client's config.
133
+
134
+ ## License
135
+
136
+ Private — internal use within CRS Credit API.
@@ -44,3 +44,33 @@ export interface CustomerSummary {
44
44
  };
45
45
  }
46
46
  export declare function getCustomerSummary(client: ApiClient, customerCode: string): Promise<CustomerSummary>;
47
+ export interface CustomerSearchHit {
48
+ name: string;
49
+ customer_code: string;
50
+ score: number;
51
+ }
52
+ export interface CustomerSearchResult {
53
+ results: CustomerSearchHit[];
54
+ threshold: number;
55
+ message?: string;
56
+ }
57
+ export declare function searchCustomers(client: ApiClient, query: string, threshold?: number): Promise<CustomerSearchResult>;
58
+ export type RevenuePeriodType = 'day' | 'week' | 'month' | 'quarter' | 'year';
59
+ export interface RevenuePeriod {
60
+ period: string;
61
+ revenue: number;
62
+ }
63
+ export interface CustomerRevenue {
64
+ customer_code: string;
65
+ type: RevenuePeriodType;
66
+ start_date: string;
67
+ end_date: string;
68
+ periods: RevenuePeriod[];
69
+ total: number;
70
+ }
71
+ export declare function getCustomerRevenue(client: ApiClient, args: {
72
+ customerCode: string;
73
+ type: RevenuePeriodType;
74
+ startDate: string;
75
+ endDate: string;
76
+ }): Promise<CustomerRevenue>;
@@ -10,3 +10,39 @@ export async function getCustomerSummary(client, customerCode) {
10
10
  }
11
11
  return response.data;
12
12
  }
13
+ export async function searchCustomers(client, query, threshold) {
14
+ const secret = process.env.FINSTACK_CHATBASE_SECRET;
15
+ if (!secret) {
16
+ throw new Error('FINSTACK_CHATBASE_SECRET must be set');
17
+ }
18
+ const params = new URLSearchParams({ q: query });
19
+ if (threshold !== undefined) {
20
+ params.set('threshold', String(threshold));
21
+ }
22
+ const response = await client.get(`/api/v1/chatbase/customer_search?${params}`, { 'X-Chatbase-Secret': secret });
23
+ if (response.status !== 'success') {
24
+ throw new Error(response.message || 'Customer search failed');
25
+ }
26
+ return {
27
+ results: response.data ?? [],
28
+ threshold: response.threshold ?? 0.9,
29
+ message: response.message,
30
+ };
31
+ }
32
+ export async function getCustomerRevenue(client, args) {
33
+ const secret = process.env.FINSTACK_CHATBASE_SECRET;
34
+ if (!secret) {
35
+ throw new Error('FINSTACK_CHATBASE_SECRET must be set');
36
+ }
37
+ const params = new URLSearchParams({
38
+ customer_code: args.customerCode,
39
+ type: args.type,
40
+ start_date: args.startDate,
41
+ end_date: args.endDate,
42
+ });
43
+ const response = await client.get(`/api/v1/chatbase/customer_revenue?${params}`, { 'X-Chatbase-Secret': secret });
44
+ if (response.status !== 'success' || !response.data) {
45
+ throw new Error(response.message || 'Failed to fetch customer revenue');
46
+ }
47
+ return response.data;
48
+ }
@@ -1,6 +1,21 @@
1
+ import { type RevenuePeriodType } from '../api/customer.api.js';
1
2
  interface GetCustomerSummaryArgs {
2
3
  customer_code: string;
3
4
  environment?: string;
4
5
  }
5
6
  export declare function handleGetCustomerSummary(args: GetCustomerSummaryArgs): Promise<unknown>;
7
+ interface SearchCustomerArgs {
8
+ q: string;
9
+ threshold?: number;
10
+ environment?: string;
11
+ }
12
+ export declare function handleSearchCustomer(args: SearchCustomerArgs): Promise<unknown>;
13
+ interface GetCustomerRevenueArgs {
14
+ customer_code: string;
15
+ type: RevenuePeriodType;
16
+ start_date: string;
17
+ end_date: string;
18
+ environment?: string;
19
+ }
20
+ export declare function handleGetCustomerRevenue(args: GetCustomerRevenueArgs): Promise<unknown>;
6
21
  export {};
@@ -2,8 +2,21 @@
2
2
  * Customer tool handlers
3
3
  */
4
4
  import { getClient } from '../api/client.js';
5
- import { getCustomerSummary } from '../api/customer.api.js';
5
+ import { getCustomerRevenue, getCustomerSummary, searchCustomers, } from '../api/customer.api.js';
6
6
  export async function handleGetCustomerSummary(args) {
7
7
  const client = getClient(args.environment ?? 'prod');
8
8
  return getCustomerSummary(client, args.customer_code);
9
9
  }
10
+ export async function handleSearchCustomer(args) {
11
+ const client = getClient(args.environment ?? 'prod');
12
+ return searchCustomers(client, args.q, args.threshold);
13
+ }
14
+ export async function handleGetCustomerRevenue(args) {
15
+ const client = getClient(args.environment ?? 'prod');
16
+ return getCustomerRevenue(client, {
17
+ customerCode: args.customer_code,
18
+ type: args.type,
19
+ startDate: args.start_date,
20
+ endDate: args.end_date,
21
+ });
22
+ }
@@ -1,7 +1,9 @@
1
- import { handleGetCustomerSummary } from './customer.handler.js';
1
+ import { handleGetCustomerRevenue, handleGetCustomerSummary, handleSearchCustomer, } from './customer.handler.js';
2
2
  const wrap = (fn) => {
3
3
  return async (args) => fn(args);
4
4
  };
5
5
  export const handlers = {
6
- get_customer_summary: wrap(handleGetCustomerSummary),
6
+ finstack_get_customer_summary: wrap(handleGetCustomerSummary),
7
+ finstack_search_customer: wrap(handleSearchCustomer),
8
+ finstack_get_customer_revenue: wrap(handleGetCustomerRevenue),
7
9
  };
@@ -1,6 +1,6 @@
1
1
  export const customerTools = [
2
2
  {
3
- name: 'get_customer_summary',
3
+ name: 'finstack_get_customer_summary',
4
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.',
5
5
  inputSchema: {
6
6
  type: 'object',
@@ -18,4 +18,62 @@ export const customerTools = [
18
18
  required: ['customer_code'],
19
19
  },
20
20
  },
21
+ {
22
+ name: 'finstack_search_customer',
23
+ description: "Find a B2B customer's code (CID) by fuzzy-matching their name. Use this when the user references a customer by name but hasn't supplied a CID — pass the returned customer_code into finstack_get_customer_summary. If no matches at the default threshold, lower it (e.g. 0.5) to broaden the search.",
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ q: {
28
+ type: 'string',
29
+ description: 'Search string — customer name or fragment, min 2 chars, case-insensitive',
30
+ minLength: 2,
31
+ },
32
+ threshold: {
33
+ type: 'number',
34
+ description: 'Similarity threshold 0.0-1.0 (default 0.9). Lower for fuzzier matches.',
35
+ minimum: 0,
36
+ maximum: 1,
37
+ },
38
+ environment: {
39
+ type: 'string',
40
+ description: 'Target environment: "prod" (default) or "dev"',
41
+ enum: ['dev', 'prod'],
42
+ },
43
+ },
44
+ required: ['q'],
45
+ },
46
+ },
47
+ {
48
+ name: 'finstack_get_customer_revenue',
49
+ description: 'Fetch revenue for a B2B customer over a date range, bucketed by period (day/week/month/quarter/year). Use when the user asks how much a customer paid, revenue trends, MRR/ARR-style figures, or billing over time. Returns periods[{period,revenue}] plus a total. Max range: day=92d, week=2y, month/quarter=10y, year=50y; pick the coarsest type that covers the range.',
50
+ inputSchema: {
51
+ type: 'object',
52
+ properties: {
53
+ customer_code: {
54
+ type: 'string',
55
+ description: 'Customer code (CID) — e.g. "CID18068"',
56
+ },
57
+ type: {
58
+ type: 'string',
59
+ enum: ['day', 'week', 'month', 'quarter', 'year'],
60
+ description: 'Bucketing granularity for the returned periods',
61
+ },
62
+ start_date: {
63
+ type: 'string',
64
+ description: 'Start date (inclusive), format YYYY-MM-DD',
65
+ },
66
+ end_date: {
67
+ type: 'string',
68
+ description: 'End date (inclusive), format YYYY-MM-DD. Must be >= start_date.',
69
+ },
70
+ environment: {
71
+ type: 'string',
72
+ description: 'Target environment: "prod" (default) or "dev"',
73
+ enum: ['dev', 'prod'],
74
+ },
75
+ },
76
+ required: ['customer_code', 'type', 'start_date', 'end_date'],
77
+ },
78
+ },
21
79
  ];
@@ -10,6 +10,10 @@ export interface JsonSchemaProperty {
10
10
  required?: string[];
11
11
  minItems?: number;
12
12
  maxItems?: number;
13
+ minLength?: number;
14
+ maxLength?: number;
15
+ minimum?: number;
16
+ maximum?: number;
13
17
  }
14
18
  export interface ToolInputSchema {
15
19
  type: 'object';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crscreditapi/finstack-mcp-server",
3
- "version": "0.1.0-2bad4fe",
3
+ "version": "0.1.0-76ce272",
4
4
  "description": "MCP server for Finstack",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",