@coldiq/mcp 0.3.11 → 0.3.12

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,OAAO,CAAA;IACb,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAMD,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBjE;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,WAAW,CAAC,CA0DtB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,OAAO,CAAA;IACb,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAMD,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBjE;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,WAAW,CAAC,CA6DtB"}
package/dist/client.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Agent, setGlobalDispatcher } from 'undici';
2
+ import { recordChargeFromHeaders } from './session-usage.js';
2
3
  let _apiUrl;
3
4
  let _apiKey;
4
5
  let _httpTimeoutMs;
@@ -54,6 +55,9 @@ export async function callApi(method, path, body, queryParams) {
54
55
  res.headers.forEach((value, key) => {
55
56
  headers[key.toLowerCase()] = value;
56
57
  });
58
+ // Tally this call's credit charge for the per-session usage report. No-op
59
+ // unless the response carries a charge header, so polls/errors are skipped.
60
+ recordChargeFromHeaders(path, headers);
57
61
  return { ok: res.ok, status: res.status, data, headers };
58
62
  }
59
63
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAU5D,IAAI,OAAe,CAAA;AACnB,IAAI,OAAe,CAAA;AACnB,IAAI,cAAsB,CAAA;AAE1B,MAAM,UAAU,UAAU,CAAC,MAAe,EAAE,MAAe;IACzD,OAAO,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,wBAAwB,CAAA;IAC1E,OAAO,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAA;IACpD,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,OAAO,EAAE,EAAE,CAAC,CAAA;IAC5E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAA;IACvG,CAAC;IACD,uBAAuB;IACvB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAErC,mEAAmE;IACnE,uFAAuF;IACvF,qFAAqF;IACrF,mBAAmB,CACjB,IAAI,KAAK,CAAC;QACR,gBAAgB,EAAE,MAAM;QACxB,mBAAmB,EAAE,MAAM;QAC3B,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,CAAC;KACd,CAAC,CACH,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAsB,EACtB,IAAY,EACZ,IAAc,EACd,WAAoC;IAEpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,OAAO,MAAM,IAAI,EAAE,CAAC,CAAA;IAC3C,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;gBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,OAAO,EAAE;KACnC,CAAA;IAED,MAAM,IAAI,GAAgB;QACxB,MAAM;QACN,OAAO;QACP,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC;KAC5C,CAAA;IAED,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;QAC5C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAa,CAAA;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;QAChD,CAAC;QACD,MAAM,OAAO,GAA2B,EAAE,CAAA;QAC1C,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACjC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAA;QACpC,CAAC,CAAC,CAAA;QACF,0EAA0E;QAC1E,4EAA4E;QAC5E,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACtC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,SAAS,GACb,GAAG,YAAY,KAAK;YACpB,CAAC,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,CAAA;QAC5D,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,CAAC;gBACT,IAAI,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE;gBACpC,OAAO,EAAE,EAAE;aACZ,CAAA;QACH,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACpG,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,CAAC;YACT,IAAI,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE;YAChC,OAAO,EAAE,EAAE;SACZ,CAAA;IACH,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { searchSeoName, searchSeoDescription, searchSeoSchema, searchSeoHandler,
20
20
  import { findSignalsName, findSignalsDescription, findSignalsSchema, findSignalsHandler, } from './tools/find-signals.js';
21
21
  import { fetchPageContentName, fetchPageContentDescription, fetchPageContentSchema, fetchPageContentHandler, } from './tools/fetch-page-content.js';
22
22
  import { getCreditBalanceName, getCreditBalanceDescription, getCreditBalanceSchema, getCreditBalanceHandler, } from './tools/get-credit-balance.js';
23
+ import { getSessionUsageName, getSessionUsageDescription, getSessionUsageSchema, getSessionUsageHandler, } from './tools/get-session-usage.js';
23
24
  import { extractPostEngagementName, extractPostEngagementDescription, extractPostEngagementSchema, extractPostEngagementHandler, } from './tools/extract-post-engagement.js';
24
25
  import { getPlaceReviewsName, getPlaceReviewsDescription, getPlaceReviewsSchema, getPlaceReviewsHandler, } from './tools/get-place-reviews.js';
25
26
  import { listDataSourcesName, listDataSourcesDescription, listDataSourcesSchema, listDataSourcesHandler, } from './tools/list-data-sources.js';
@@ -59,6 +60,7 @@ server.tool(searchSeoName, searchSeoDescription, searchSeoSchema, searchSeoHandl
59
60
  server.tool(findSignalsName, findSignalsDescription, findSignalsSchema, findSignalsHandler);
60
61
  server.tool(fetchPageContentName, fetchPageContentDescription, fetchPageContentSchema, fetchPageContentHandler);
61
62
  server.tool(getCreditBalanceName, getCreditBalanceDescription, getCreditBalanceSchema, getCreditBalanceHandler);
63
+ server.tool(getSessionUsageName, getSessionUsageDescription, getSessionUsageSchema, getSessionUsageHandler);
62
64
  server.tool(listDataSourcesName, listDataSourcesDescription, listDataSourcesSchema, listDataSourcesHandler);
63
65
  server.tool(extractPostEngagementName, extractPostEngagementDescription, extractPostEngagementSchema, extractPostEngagementHandler);
64
66
  server.tool(getPlaceReviewsName, getPlaceReviewsDescription, getPlaceReviewsSchema, getPlaceReviewsHandler);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * In-process ColdIQ credit accumulator for one MCP session ("campaign").
3
+ *
4
+ * The ColdIQ MCP server runs as a stdio subprocess spawned per client
5
+ * conversation, so a module-level singleton here is naturally scoped to a
6
+ * single campaign: it starts empty when the process boots and is discarded
7
+ * when the conversation's server exits. We tally the per-call charges the API
8
+ * already reports via the `X-ColdIQ-Credits-Charged` / `-Remaining` response
9
+ * headers (the same signal the executor surfaces in `_meta`), so reporting
10
+ * session spend never issues an extra billable call.
11
+ *
12
+ * Caveats this module deliberately accepts (and the tool surfaces to the user):
13
+ * - It only counts calls made during the current process lifetime; resuming or
14
+ * restarting the conversation starts a fresh tally.
15
+ * - `charged` reflects what the API billed at call time. For async jobs that is
16
+ * the submit-time reservation; later webhook settlement is not visible here.
17
+ */
18
+ export interface EndpointUsage {
19
+ /** Number of billed calls to this endpoint this session. */
20
+ calls: number;
21
+ /** Total net ColdIQ credits charged across those calls. */
22
+ credits: number;
23
+ }
24
+ export interface SessionUsageSnapshot {
25
+ /** ISO 8601 timestamp of when this MCP session (process) started. */
26
+ startedAt: string;
27
+ /** Milliseconds elapsed since the session started. */
28
+ durationMs: number;
29
+ /** Count of calls that charged credits this session. */
30
+ billedCalls: number;
31
+ /** Sum of net credits charged across all billed calls this session. */
32
+ totalCreditsCharged: number;
33
+ /** Balance the API reported after the most recent billed call, if any. */
34
+ balanceAfterLastCharge?: number;
35
+ /** Per-endpoint breakdown, highest spend first. */
36
+ byEndpoint: Array<{
37
+ endpoint: string;
38
+ calls: number;
39
+ credits: number;
40
+ }>;
41
+ }
42
+ /**
43
+ * Record a single billed call. `charged` is the net credits for this call;
44
+ * `remaining`, when present, is the authoritative balance afterwards.
45
+ * Non-finite or negative charges are ignored so a malformed header can't
46
+ * corrupt the running total.
47
+ */
48
+ export declare function recordCharge(endpoint: string, charged: number, remaining?: number): void;
49
+ /**
50
+ * Record a charge from a response's (lowercased) header map. A no-op when the
51
+ * response carries no charge header — polls, errors, and unbilled routes are
52
+ * silently skipped, so this is safe to call after every request.
53
+ */
54
+ export declare function recordChargeFromHeaders(endpoint: string, headers: Record<string, string>): void;
55
+ /** Build a snapshot of session spend so far. `now` is injectable for tests. */
56
+ export declare function getSessionUsage(now?: number): SessionUsageSnapshot;
57
+ /** Test-only: reset the accumulator to a known clean state. */
58
+ export declare function resetSessionUsage(startedAt?: number): void;
59
+ //# sourceMappingURL=session-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-usage.d.ts","sourceRoot":"","sources":["../src/session-usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,0EAA0E;IAC1E,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,mDAAmD;IACnD,UAAU,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACxE;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAcxF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAI/F;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAmB,GAAG,oBAAoB,CAgB9E;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,MAAmB,GAAG,IAAI,CAMtE"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * In-process ColdIQ credit accumulator for one MCP session ("campaign").
3
+ *
4
+ * The ColdIQ MCP server runs as a stdio subprocess spawned per client
5
+ * conversation, so a module-level singleton here is naturally scoped to a
6
+ * single campaign: it starts empty when the process boots and is discarded
7
+ * when the conversation's server exits. We tally the per-call charges the API
8
+ * already reports via the `X-ColdIQ-Credits-Charged` / `-Remaining` response
9
+ * headers (the same signal the executor surfaces in `_meta`), so reporting
10
+ * session spend never issues an extra billable call.
11
+ *
12
+ * Caveats this module deliberately accepts (and the tool surfaces to the user):
13
+ * - It only counts calls made during the current process lifetime; resuming or
14
+ * restarting the conversation starts a fresh tally.
15
+ * - `charged` reflects what the API billed at call time. For async jobs that is
16
+ * the submit-time reservation; later webhook settlement is not visible here.
17
+ */
18
+ const CHARGED_HEADER = 'x-coldiq-credits-charged';
19
+ const REMAINING_HEADER = 'x-coldiq-credits-remaining';
20
+ const state = {
21
+ startedAt: Date.now(),
22
+ billedCalls: 0,
23
+ totalCreditsCharged: 0,
24
+ byEndpoint: new Map(),
25
+ };
26
+ function parseHeaderNumber(raw) {
27
+ if (raw === undefined)
28
+ return undefined;
29
+ const n = Number(raw);
30
+ return Number.isFinite(n) ? n : undefined;
31
+ }
32
+ // Credits can be fractional (settlement refunds). Round to 4 dp so summed
33
+ // floating-point noise never leaks into the reported totals.
34
+ function round(n) {
35
+ return Math.round(n * 10000) / 10000;
36
+ }
37
+ /**
38
+ * Record a single billed call. `charged` is the net credits for this call;
39
+ * `remaining`, when present, is the authoritative balance afterwards.
40
+ * Non-finite or negative charges are ignored so a malformed header can't
41
+ * corrupt the running total.
42
+ */
43
+ export function recordCharge(endpoint, charged, remaining) {
44
+ if (!Number.isFinite(charged) || charged < 0)
45
+ return;
46
+ state.billedCalls += 1;
47
+ state.totalCreditsCharged += charged;
48
+ if (remaining !== undefined && Number.isFinite(remaining)) {
49
+ state.balanceAfterLastCharge = remaining;
50
+ }
51
+ const existing = state.byEndpoint.get(endpoint);
52
+ if (existing) {
53
+ existing.calls += 1;
54
+ existing.credits += charged;
55
+ }
56
+ else {
57
+ state.byEndpoint.set(endpoint, { calls: 1, credits: charged });
58
+ }
59
+ }
60
+ /**
61
+ * Record a charge from a response's (lowercased) header map. A no-op when the
62
+ * response carries no charge header — polls, errors, and unbilled routes are
63
+ * silently skipped, so this is safe to call after every request.
64
+ */
65
+ export function recordChargeFromHeaders(endpoint, headers) {
66
+ const charged = parseHeaderNumber(headers[CHARGED_HEADER]);
67
+ if (charged === undefined)
68
+ return;
69
+ recordCharge(endpoint, charged, parseHeaderNumber(headers[REMAINING_HEADER]));
70
+ }
71
+ /** Build a snapshot of session spend so far. `now` is injectable for tests. */
72
+ export function getSessionUsage(now = Date.now()) {
73
+ const byEndpoint = Array.from(state.byEndpoint.entries())
74
+ .map(([endpoint, u]) => ({ endpoint, calls: u.calls, credits: round(u.credits) }))
75
+ .sort((a, b) => b.credits - a.credits);
76
+ const snap = {
77
+ startedAt: new Date(state.startedAt).toISOString(),
78
+ durationMs: now - state.startedAt,
79
+ billedCalls: state.billedCalls,
80
+ totalCreditsCharged: round(state.totalCreditsCharged),
81
+ byEndpoint,
82
+ };
83
+ if (state.balanceAfterLastCharge !== undefined) {
84
+ snap.balanceAfterLastCharge = state.balanceAfterLastCharge;
85
+ }
86
+ return snap;
87
+ }
88
+ /** Test-only: reset the accumulator to a known clean state. */
89
+ export function resetSessionUsage(startedAt = Date.now()) {
90
+ state.startedAt = startedAt;
91
+ state.billedCalls = 0;
92
+ state.totalCreditsCharged = 0;
93
+ state.balanceAfterLastCharge = undefined;
94
+ state.byEndpoint.clear();
95
+ }
96
+ //# sourceMappingURL=session-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-usage.js","sourceRoot":"","sources":["../src/session-usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare const getSessionUsageName = "get_session_usage";
2
+ export declare const getSessionUsageDescription: string;
3
+ export declare const getSessionUsageSchema: {};
4
+ export declare function getSessionUsageHandler(_input: Record<string, unknown>): Promise<{
5
+ content: {
6
+ type: "text";
7
+ text: string;
8
+ }[];
9
+ }>;
10
+ //# sourceMappingURL=get-session-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-session-usage.d.ts","sourceRoot":"","sources":["../../src/tools/get-session-usage.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,sBAAsB,CAAA;AAEtD,eAAO,MAAM,0BAA0B,QAMuB,CAAA;AAE9D,eAAO,MAAM,qBAAqB,IAAK,CAAA;AAEvC,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;GAG3E"}
@@ -0,0 +1,14 @@
1
+ import { getSessionUsage } from '../session-usage.js';
2
+ export const getSessionUsageName = 'get_session_usage';
3
+ export const getSessionUsageDescription = 'Report the ColdIQ credits spent so far in THIS conversation (the current MCP session). ' +
4
+ 'Returns the total credits charged, the number of billed calls, a per-endpoint breakdown of where ' +
5
+ 'credits went (highest spend first), and the account balance reported after the most recent billed call. ' +
6
+ 'Use this to answer "how many credits has this chat used" or "what did I spend credits on here". ' +
7
+ 'Note: it counts only calls made during the current session and resets if the conversation restarts; ' +
8
+ 'for the authoritative live balance use get_credit_balance.';
9
+ export const getSessionUsageSchema = {};
10
+ export async function getSessionUsageHandler(_input) {
11
+ const usage = getSessionUsage();
12
+ return { content: [{ type: 'text', text: JSON.stringify({ data: usage }) }] };
13
+ }
14
+ //# sourceMappingURL=get-session-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-session-usage.js","sourceRoot":"","sources":["../../src/tools/get-session-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,CAAA;AAEtD,MAAM,CAAC,MAAM,0BAA0B,GACrC,yFAAyF;IACzF,mGAAmG;IACnG,0GAA0G;IAC1G,kGAAkG;IAClG,sGAAsG;IACtG,4DAA4D,CAAA;AAE9D,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAEvC,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAA+B;IAC1E,MAAM,KAAK,GAAG,eAAe,EAAE,CAAA;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AACxF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coldiq/mcp",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/client.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Agent, setGlobalDispatcher } from 'undici'
2
+ import { recordChargeFromHeaders } from './session-usage.js'
2
3
 
3
4
  export type ApiResponse = {
4
5
  ok: boolean
@@ -75,6 +76,9 @@ export async function callApi(
75
76
  res.headers.forEach((value, key) => {
76
77
  headers[key.toLowerCase()] = value
77
78
  })
79
+ // Tally this call's credit charge for the per-session usage report. No-op
80
+ // unless the response carries a charge header, so polls/errors are skipped.
81
+ recordChargeFromHeaders(path, headers)
78
82
  return { ok: res.ok, status: res.status, data, headers }
79
83
  } catch (err) {
80
84
  const isTimeout =
package/src/index.ts CHANGED
@@ -130,6 +130,13 @@ import {
130
130
  getCreditBalanceHandler,
131
131
  } from './tools/get-credit-balance.js'
132
132
 
133
+ import {
134
+ getSessionUsageName,
135
+ getSessionUsageDescription,
136
+ getSessionUsageSchema,
137
+ getSessionUsageHandler,
138
+ } from './tools/get-session-usage.js'
139
+
133
140
  import {
134
141
  extractPostEngagementName,
135
142
  extractPostEngagementDescription,
@@ -192,6 +199,7 @@ server.tool(searchSeoName, searchSeoDescription, searchSeoSchema, searchSeoHandl
192
199
  server.tool(findSignalsName, findSignalsDescription, findSignalsSchema, findSignalsHandler)
193
200
  server.tool(fetchPageContentName, fetchPageContentDescription, fetchPageContentSchema, fetchPageContentHandler)
194
201
  server.tool(getCreditBalanceName, getCreditBalanceDescription, getCreditBalanceSchema, getCreditBalanceHandler)
202
+ server.tool(getSessionUsageName, getSessionUsageDescription, getSessionUsageSchema, getSessionUsageHandler)
195
203
  server.tool(listDataSourcesName, listDataSourcesDescription, listDataSourcesSchema, listDataSourcesHandler)
196
204
  server.tool(extractPostEngagementName, extractPostEngagementDescription, extractPostEngagementSchema, extractPostEngagementHandler)
197
205
  server.tool(getPlaceReviewsName, getPlaceReviewsDescription, getPlaceReviewsSchema, getPlaceReviewsHandler)
@@ -0,0 +1,130 @@
1
+ /**
2
+ * In-process ColdIQ credit accumulator for one MCP session ("campaign").
3
+ *
4
+ * The ColdIQ MCP server runs as a stdio subprocess spawned per client
5
+ * conversation, so a module-level singleton here is naturally scoped to a
6
+ * single campaign: it starts empty when the process boots and is discarded
7
+ * when the conversation's server exits. We tally the per-call charges the API
8
+ * already reports via the `X-ColdIQ-Credits-Charged` / `-Remaining` response
9
+ * headers (the same signal the executor surfaces in `_meta`), so reporting
10
+ * session spend never issues an extra billable call.
11
+ *
12
+ * Caveats this module deliberately accepts (and the tool surfaces to the user):
13
+ * - It only counts calls made during the current process lifetime; resuming or
14
+ * restarting the conversation starts a fresh tally.
15
+ * - `charged` reflects what the API billed at call time. For async jobs that is
16
+ * the submit-time reservation; later webhook settlement is not visible here.
17
+ */
18
+
19
+ const CHARGED_HEADER = 'x-coldiq-credits-charged'
20
+ const REMAINING_HEADER = 'x-coldiq-credits-remaining'
21
+
22
+ export interface EndpointUsage {
23
+ /** Number of billed calls to this endpoint this session. */
24
+ calls: number
25
+ /** Total net ColdIQ credits charged across those calls. */
26
+ credits: number
27
+ }
28
+
29
+ export interface SessionUsageSnapshot {
30
+ /** ISO 8601 timestamp of when this MCP session (process) started. */
31
+ startedAt: string
32
+ /** Milliseconds elapsed since the session started. */
33
+ durationMs: number
34
+ /** Count of calls that charged credits this session. */
35
+ billedCalls: number
36
+ /** Sum of net credits charged across all billed calls this session. */
37
+ totalCreditsCharged: number
38
+ /** Balance the API reported after the most recent billed call, if any. */
39
+ balanceAfterLastCharge?: number
40
+ /** Per-endpoint breakdown, highest spend first. */
41
+ byEndpoint: Array<{ endpoint: string; calls: number; credits: number }>
42
+ }
43
+
44
+ interface MutableState {
45
+ startedAt: number
46
+ billedCalls: number
47
+ totalCreditsCharged: number
48
+ balanceAfterLastCharge?: number
49
+ byEndpoint: Map<string, EndpointUsage>
50
+ }
51
+
52
+ const state: MutableState = {
53
+ startedAt: Date.now(),
54
+ billedCalls: 0,
55
+ totalCreditsCharged: 0,
56
+ byEndpoint: new Map(),
57
+ }
58
+
59
+ function parseHeaderNumber(raw: string | undefined): number | undefined {
60
+ if (raw === undefined) return undefined
61
+ const n = Number(raw)
62
+ return Number.isFinite(n) ? n : undefined
63
+ }
64
+
65
+ // Credits can be fractional (settlement refunds). Round to 4 dp so summed
66
+ // floating-point noise never leaks into the reported totals.
67
+ function round(n: number): number {
68
+ return Math.round(n * 10000) / 10000
69
+ }
70
+
71
+ /**
72
+ * Record a single billed call. `charged` is the net credits for this call;
73
+ * `remaining`, when present, is the authoritative balance afterwards.
74
+ * Non-finite or negative charges are ignored so a malformed header can't
75
+ * corrupt the running total.
76
+ */
77
+ export function recordCharge(endpoint: string, charged: number, remaining?: number): void {
78
+ if (!Number.isFinite(charged) || charged < 0) return
79
+ state.billedCalls += 1
80
+ state.totalCreditsCharged += charged
81
+ if (remaining !== undefined && Number.isFinite(remaining)) {
82
+ state.balanceAfterLastCharge = remaining
83
+ }
84
+ const existing = state.byEndpoint.get(endpoint)
85
+ if (existing) {
86
+ existing.calls += 1
87
+ existing.credits += charged
88
+ } else {
89
+ state.byEndpoint.set(endpoint, { calls: 1, credits: charged })
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Record a charge from a response's (lowercased) header map. A no-op when the
95
+ * response carries no charge header — polls, errors, and unbilled routes are
96
+ * silently skipped, so this is safe to call after every request.
97
+ */
98
+ export function recordChargeFromHeaders(endpoint: string, headers: Record<string, string>): void {
99
+ const charged = parseHeaderNumber(headers[CHARGED_HEADER])
100
+ if (charged === undefined) return
101
+ recordCharge(endpoint, charged, parseHeaderNumber(headers[REMAINING_HEADER]))
102
+ }
103
+
104
+ /** Build a snapshot of session spend so far. `now` is injectable for tests. */
105
+ export function getSessionUsage(now: number = Date.now()): SessionUsageSnapshot {
106
+ const byEndpoint = Array.from(state.byEndpoint.entries())
107
+ .map(([endpoint, u]) => ({ endpoint, calls: u.calls, credits: round(u.credits) }))
108
+ .sort((a, b) => b.credits - a.credits)
109
+
110
+ const snap: SessionUsageSnapshot = {
111
+ startedAt: new Date(state.startedAt).toISOString(),
112
+ durationMs: now - state.startedAt,
113
+ billedCalls: state.billedCalls,
114
+ totalCreditsCharged: round(state.totalCreditsCharged),
115
+ byEndpoint,
116
+ }
117
+ if (state.balanceAfterLastCharge !== undefined) {
118
+ snap.balanceAfterLastCharge = state.balanceAfterLastCharge
119
+ }
120
+ return snap
121
+ }
122
+
123
+ /** Test-only: reset the accumulator to a known clean state. */
124
+ export function resetSessionUsage(startedAt: number = Date.now()): void {
125
+ state.startedAt = startedAt
126
+ state.billedCalls = 0
127
+ state.totalCreditsCharged = 0
128
+ state.balanceAfterLastCharge = undefined
129
+ state.byEndpoint.clear()
130
+ }
@@ -0,0 +1,18 @@
1
+ import { getSessionUsage } from '../session-usage.js'
2
+
3
+ export const getSessionUsageName = 'get_session_usage'
4
+
5
+ export const getSessionUsageDescription =
6
+ 'Report the ColdIQ credits spent so far in THIS conversation (the current MCP session). ' +
7
+ 'Returns the total credits charged, the number of billed calls, a per-endpoint breakdown of where ' +
8
+ 'credits went (highest spend first), and the account balance reported after the most recent billed call. ' +
9
+ 'Use this to answer "how many credits has this chat used" or "what did I spend credits on here". ' +
10
+ 'Note: it counts only calls made during the current session and resets if the conversation restarts; ' +
11
+ 'for the authoritative live balance use get_credit_balance.'
12
+
13
+ export const getSessionUsageSchema = {}
14
+
15
+ export async function getSessionUsageHandler(_input: Record<string, unknown>) {
16
+ const usage = getSessionUsage()
17
+ return { content: [{ type: 'text' as const, text: JSON.stringify({ data: usage }) }] }
18
+ }
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
2
  import { initClient } from '../src/client.js'
3
3
  import { executeWithFallback } from '../src/executor.js'
4
+ import { getSessionUsage, resetSessionUsage } from '../src/session-usage.js'
4
5
  import * as registry from '../src/registry.js'
5
6
  import type { ProviderEntry } from '../src/registry.js'
6
7
 
@@ -225,6 +226,74 @@ describe('executor exception safety', () => {
225
226
  })
226
227
  })
227
228
 
229
+ // ---------------------------------------------------------------------------
230
+ // Session-usage integration — the async create/poll path must record a charge
231
+ // exactly once. Locks in the "no double count" guarantee against future
232
+ // refactors of executeAsync's header merging.
233
+ // ---------------------------------------------------------------------------
234
+
235
+ describe('executor records session credit usage exactly once on the async path', () => {
236
+ const originalFetch = globalThis.fetch
237
+
238
+ beforeEach(() => {
239
+ initClient('http://test-api.local', 'test-key-123')
240
+ resetSessionUsage(0)
241
+ })
242
+
243
+ afterEach(() => {
244
+ globalThis.fetch = originalFetch
245
+ vi.restoreAllMocks()
246
+ })
247
+
248
+ it('charge header on the create response is counted once; polls add nothing', async () => {
249
+ stubProviders([
250
+ makeProvider({
251
+ id: 'async-charged',
252
+ endpoint: '/async-charged',
253
+ hasResult: (data) => (data as Record<string, unknown>).done === true,
254
+ async: {
255
+ extractId: () => 'job-1',
256
+ pollEndpoint: (id) => `/poll/${id}`,
257
+ pollIntervalMs: 5,
258
+ timeoutMs: 500,
259
+ // Complete only once the poll body says so, forcing ≥1 poll round.
260
+ isComplete: (data) => (data as Record<string, unknown>).done === true,
261
+ },
262
+ }),
263
+ ])
264
+
265
+ let pollCount = 0
266
+ globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
267
+ const u = url.toString()
268
+ if (u.includes('/poll/')) {
269
+ pollCount++
270
+ // First poll still processing (no charge header), second completes.
271
+ const body = pollCount >= 2 ? { done: true } : { status: 'processing' }
272
+ return new Response(JSON.stringify(body), { status: 200 })
273
+ }
274
+ // Create response — carries the billing headers, like the real API.
275
+ return new Response(JSON.stringify({ job_id: 'job-1' }), {
276
+ status: 200,
277
+ headers: {
278
+ 'X-ColdIQ-Credits-Charged': '5',
279
+ 'X-ColdIQ-Credits-Remaining': '120',
280
+ },
281
+ })
282
+ }) as typeof fetch
283
+
284
+ const result = await executeWithFallback('find_people', { domain: 'coldiq.com' })
285
+
286
+ expect('data' in result).toBe(true)
287
+ expect(pollCount).toBeGreaterThanOrEqual(2)
288
+
289
+ const usage = getSessionUsage()
290
+ expect(usage.billedCalls).toBe(1)
291
+ expect(usage.totalCreditsCharged).toBe(5)
292
+ expect(usage.balanceAfterLastCharge).toBe(120)
293
+ expect(usage.byEndpoint).toEqual([{ endpoint: '/async-charged', calls: 1, credits: 5 }])
294
+ })
295
+ })
296
+
228
297
  // ---------------------------------------------------------------------------
229
298
  // postFilter integration (existing tests)
230
299
  // ---------------------------------------------------------------------------
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
2
+ import {
3
+ recordCharge,
4
+ recordChargeFromHeaders,
5
+ getSessionUsage,
6
+ resetSessionUsage,
7
+ } from '../src/session-usage.js'
8
+ import { initClient, callApi } from '../src/client.js'
9
+
10
+ describe('session-usage accumulator', () => {
11
+ beforeEach(() => {
12
+ resetSessionUsage(1_000)
13
+ })
14
+
15
+ it('starts empty', () => {
16
+ const usage = getSessionUsage(1_000)
17
+ expect(usage.billedCalls).toBe(0)
18
+ expect(usage.totalCreditsCharged).toBe(0)
19
+ expect(usage.byEndpoint).toEqual([])
20
+ expect(usage.balanceAfterLastCharge).toBeUndefined()
21
+ expect(usage.startedAt).toBe(new Date(1_000).toISOString())
22
+ })
23
+
24
+ it('accumulates charges and tracks the last reported balance', () => {
25
+ recordCharge('/prospeo/find-email', 1, 99)
26
+ recordCharge('/prospeo/find-email', 1, 98)
27
+ recordCharge('/companyenrich/companies/search', 3, 95)
28
+
29
+ const usage = getSessionUsage(2_000)
30
+ expect(usage.billedCalls).toBe(3)
31
+ expect(usage.totalCreditsCharged).toBe(5)
32
+ expect(usage.balanceAfterLastCharge).toBe(95)
33
+ expect(usage.durationMs).toBe(1_000)
34
+ })
35
+
36
+ it('breaks down per endpoint sorted by credits descending', () => {
37
+ recordCharge('/cheap', 1)
38
+ recordCharge('/expensive', 10)
39
+ recordCharge('/cheap', 1)
40
+
41
+ const { byEndpoint } = getSessionUsage()
42
+ expect(byEndpoint).toEqual([
43
+ { endpoint: '/expensive', calls: 1, credits: 10 },
44
+ { endpoint: '/cheap', calls: 2, credits: 2 },
45
+ ])
46
+ })
47
+
48
+ it('rounds fractional credit sums to avoid float noise', () => {
49
+ recordCharge('/x', 0.1)
50
+ recordCharge('/x', 0.2)
51
+ expect(getSessionUsage().totalCreditsCharged).toBe(0.3)
52
+ })
53
+
54
+ it('ignores malformed and negative charges', () => {
55
+ recordCharge('/x', Number.NaN)
56
+ recordCharge('/x', -5)
57
+ recordCharge('/x', Infinity)
58
+ const usage = getSessionUsage()
59
+ expect(usage.billedCalls).toBe(0)
60
+ expect(usage.totalCreditsCharged).toBe(0)
61
+ })
62
+
63
+ describe('recordChargeFromHeaders', () => {
64
+ it('records when the charge header is present', () => {
65
+ recordChargeFromHeaders('/find-email', {
66
+ 'x-coldiq-credits-charged': '2',
67
+ 'x-coldiq-credits-remaining': '198',
68
+ })
69
+ const usage = getSessionUsage()
70
+ expect(usage.billedCalls).toBe(1)
71
+ expect(usage.totalCreditsCharged).toBe(2)
72
+ expect(usage.balanceAfterLastCharge).toBe(198)
73
+ })
74
+
75
+ it('is a no-op when no charge header is present (e.g. a poll response)', () => {
76
+ recordChargeFromHeaders('/poll', { 'content-type': 'application/json' })
77
+ expect(getSessionUsage().billedCalls).toBe(0)
78
+ })
79
+
80
+ it('records the charge even when the remaining header is absent', () => {
81
+ recordChargeFromHeaders('/x', { 'x-coldiq-credits-charged': '4' })
82
+ const usage = getSessionUsage()
83
+ expect(usage.totalCreditsCharged).toBe(4)
84
+ expect(usage.balanceAfterLastCharge).toBeUndefined()
85
+ })
86
+ })
87
+ })
88
+
89
+ describe('callApi records credit charges into session usage', () => {
90
+ const originalFetch = globalThis.fetch
91
+
92
+ beforeEach(() => {
93
+ initClient('http://test-api.local', 'test-key')
94
+ resetSessionUsage(0)
95
+ })
96
+
97
+ afterEach(() => {
98
+ globalThis.fetch = originalFetch
99
+ })
100
+
101
+ it('tallies a charge surfaced in response headers', async () => {
102
+ globalThis.fetch = vi.fn(async () =>
103
+ new Response(JSON.stringify({ email: 'michel@coldiq.com' }), {
104
+ status: 200,
105
+ headers: {
106
+ 'X-ColdIQ-Credits-Charged': '1',
107
+ 'X-ColdIQ-Credits-Remaining': '249',
108
+ },
109
+ }),
110
+ ) as typeof fetch
111
+
112
+ await callApi('POST', '/prospeo/find-email', { domain: 'coldiq.com' })
113
+
114
+ const usage = getSessionUsage()
115
+ expect(usage.billedCalls).toBe(1)
116
+ expect(usage.totalCreditsCharged).toBe(1)
117
+ expect(usage.balanceAfterLastCharge).toBe(249)
118
+ expect(usage.byEndpoint).toEqual([{ endpoint: '/prospeo/find-email', calls: 1, credits: 1 }])
119
+ })
120
+
121
+ it('does not tally calls that return no charge header', async () => {
122
+ globalThis.fetch = vi.fn(async () =>
123
+ new Response(JSON.stringify({ status: 'processing' }), { status: 200 }),
124
+ ) as typeof fetch
125
+
126
+ await callApi('GET', '/jobs/abc123')
127
+
128
+ expect(getSessionUsage().billedCalls).toBe(0)
129
+ })
130
+ })
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import {
3
+ getSessionUsageHandler,
4
+ getSessionUsageName,
5
+ getSessionUsageSchema,
6
+ } from '../../src/tools/get-session-usage.js'
7
+ import { recordCharge, resetSessionUsage } from '../../src/session-usage.js'
8
+
9
+ describe('get_session_usage handler', () => {
10
+ beforeEach(() => {
11
+ resetSessionUsage()
12
+ })
13
+
14
+ it('is registered as a zero-arg tool', () => {
15
+ expect(getSessionUsageName).toBe('get_session_usage')
16
+ expect(getSessionUsageSchema).toEqual({})
17
+ })
18
+
19
+ it('returns an empty snapshot before any billed call', async () => {
20
+ const result = await getSessionUsageHandler({})
21
+ expect(result.isError).toBeFalsy()
22
+ const parsed = JSON.parse(result.content[0].text)
23
+ expect(parsed.data.billedCalls).toBe(0)
24
+ expect(parsed.data.totalCreditsCharged).toBe(0)
25
+ expect(parsed.data.byEndpoint).toEqual([])
26
+ })
27
+
28
+ it('reflects recorded charges with a per-endpoint breakdown', async () => {
29
+ recordCharge('/prospeo/find-email', 1, 99)
30
+ recordCharge('/companyenrich/companies/search', 3, 96)
31
+
32
+ const result = await getSessionUsageHandler({})
33
+ const parsed = JSON.parse(result.content[0].text)
34
+
35
+ expect(parsed.data.billedCalls).toBe(2)
36
+ expect(parsed.data.totalCreditsCharged).toBe(4)
37
+ expect(parsed.data.balanceAfterLastCharge).toBe(96)
38
+ expect(parsed.data.byEndpoint[0]).toEqual({
39
+ endpoint: '/companyenrich/companies/search',
40
+ calls: 1,
41
+ credits: 3,
42
+ })
43
+ })
44
+ })