@clien-ai/mcp 0.2.1 → 0.3.0

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 CHANGED
@@ -1,12 +1,16 @@
1
1
  # @clien-ai/mcp
2
2
 
3
- Run a full Clien.ai product-research pass from Claude Code. One tool —
4
- `clien_research` — that takes an idea, runs a 15-25 minute deep research pass
3
+ Run a full Clien.ai product-research pass from Claude Code. The hero tool —
4
+ `clien_research` — takes an idea, runs a 15-25 minute deep research pass
5
5
  on our sandbox, and hands you back a markdown report plus structured
6
6
  `report_data` (hypotheses, competitors, key findings) with a typed
7
7
  [trust layer](#trust-signals) — per-claim GROUNDED/SPECULATION/NO_RECEIPT
8
8
  classifications, source receipts, and robustness survival counts.
9
9
 
10
+ Alongside it, [19 more tools](#tools) let an agent read and edit what a project
11
+ already holds — projects, personas, interviews, reports, competitors, and
12
+ community signals — most of them free.
13
+
10
14
  ---
11
15
 
12
16
  ## Quick start
@@ -98,13 +102,92 @@ simply omits them. The full Zod schema lives in
98
102
 
99
103
  ---
100
104
 
105
+ ## Tools
106
+
107
+ Twenty tools. `clien_research` is the hero — the recommended default for
108
+ validating an idea end to end. Everything else is either a **free read** for
109
+ orienting on what a project already holds, or a **targeted primitive** for a
110
+ narrow, cheaper probe.
111
+
112
+ Descriptions below are condensed from each tool's own registered description in
113
+ [`src/tools/registry.ts`](src/tools/registry.ts) — that file is the source of
114
+ truth, and the server advertises the full text over `tools/list`.
115
+
116
+ **Params are `snake_case`.** Five tools (`create_persona`, `generate_persona`,
117
+ `enrich_persona`, `update_persona`, `interview_persona`) also still accept their
118
+ pre-1.0 camelCase spellings (`projectId`, `companySize`, …) as deprecated
119
+ aliases. They resolve to the same request and the same result; passing both
120
+ spellings of one field with *different* values is rejected rather than silently
121
+ resolved.
122
+
123
+ An alias-only call is valid against the advertised schema too, not just honoured
124
+ at call time — including on a client that pre-validates outgoing arguments. To
125
+ make that true, the three ids that have an alias (`project_id` on
126
+ `create_persona` / `generate_persona`, `persona_id` on `enrich_persona`) are no
127
+ longer in their schema's `required` list; they are still mandatory, enforced
128
+ when the call is handled, and omitting one fails with an error naming the param
129
+ before anything is written. The **Required** columns below list what each tool
130
+ actually needs, `required` list or not. New code should send the snake_case
131
+ name — the aliases exist for callers written before the rename.
132
+
133
+ ### Research
134
+
135
+ | Tool | Required | Cost | What it does |
136
+ |------|----------|------|--------------|
137
+ | `clien_research` | `idea` | debits credits | The full pass: markdown report + structured `report_data` with the typed [trust layer](#trust-signals). Typical wall time 15–25 min, progress streamed. Attach it to a project with `project_id` or `project_name` — a run with neither is refused. |
138
+ | `clien_research_status` | — | free | Re-attach to an existing job by `job_id` — recovers a run that dropped with `-32000` and polls for completion. Called with no arguments it discovers your recent runs. |
139
+ | `scan_competitors` | `topic` | debits credits | One web researcher over the COMPETITOR dimension only (2–10 min). Returns competitor profiles in `_meta.competitors`. Check the free `list_competitors` first. |
140
+ | `search_forums` | `topic` | debits credits | One forum researcher over the COMMUNITY dimension only (2–10 min). Returns threads in `_meta.threads`. Check the free `list_community_signals` first. Run sequentially with `scan_competitors` on the same project — a parallel second call gets a 409. |
141
+
142
+ ### Projects
143
+
144
+ | Tool | Required | Cost | What it does |
145
+ |------|----------|------|--------------|
146
+ | `get_project` | `project_id` | free | **Start here on an existing project.** Metadata plus counts, ids, and one-line summaries of its personas, interviews, competitors, community signals, and reports — replacing five `list_*` calls. Pass `include` to inline full records. A failed collection returns `{ error: "unavailable" }` under its key, which is *not* the same as empty. |
147
+ | `list_projects` | — | free | Your projects, newest first, paginated. |
148
+ | `create_project` | `name` | free | Create a project to group runs, personas, and reports. |
149
+
150
+ ### Personas
151
+
152
+ | Tool | Required | Cost | What it does |
153
+ |------|----------|------|--------------|
154
+ | `list_personas` | `project_id` | free | The project's personas, newest first, paginated — including ones created in the web app. The entity list, not the grounding surface. |
155
+ | `get_persona` | `persona_id` | free | One persona entity in full. Grounding (`sources[]`, `identityPriors`) lives on `report_data`, via `get_report`. |
156
+ | `create_persona` | `project_id`, `name`, `role` | free | Create a persona from fields you already know. No AI, no credit. |
157
+ | `update_persona` | `persona_id` | free | Correct explicit scalar fields on an existing persona. No AI, no credit. |
158
+ | `generate_persona` | `project_id`, `name`, `role` | 1 credit | AI-generate a persona from a short brief, grounded by default in real user-voice evidence. A failed generation is never charged. |
159
+ | `enrich_persona` | `persona_id` | 1 credit | AI-fill only the fields that are currently BLANK on an existing persona (or the ones named in `refresh`). A no-op charges nothing; a failure is refunded. |
160
+
161
+ ### Interviews
162
+
163
+ | Tool | Required | Cost | What it does |
164
+ |------|----------|------|--------------|
165
+ | `interview_persona` | `action` | see note | Stateful multi-turn interview. `action:start` → `turn` (repeatedly) → `complete`, plus `transcript` and `reopen`. Each `turn` generates a real AI reply, metered by your monthly usage; per-turn credit pricing is not yet applied. |
166
+ | `list_interviews` | `project_id` | free | The project's interviews, newest first, paginated — recovers ids for interviews started in the web app. |
167
+ | `get_interview` | `interview_id` | free | One interview's full transcript, from just an id. |
168
+
169
+ ### Reports & collections
170
+
171
+ | Tool | Required | Cost | What it does |
172
+ |------|----------|------|--------------|
173
+ | `list_reports` | — | free | Past `clien_research` runs, newest first. Optionally scoped to one `project_id`. |
174
+ | `get_report` | `job_id` | free | A past run's full report + `report_data`, same shape `clien_research` returns. Use this instead of re-running. |
175
+ | `list_competitors` | `project_id` | free | The project's deduped, accumulated competitor set, most-recently-seen first. Each of the four company facts carries a sibling `<fact>Grounding` — key on its `state`. |
176
+ | `list_community_signals` | `project_id` | free | The project's deduped, accumulated thread set, newest-**discovered** first (a stable order — a thread seen again does not move back to page one). Carries the verbatim `relevantQuotes[]` cached at collection. |
177
+
178
+ "free" above means what each tool's own description states: no credit cost and
179
+ no side effects.
180
+
181
+ **Not covered:** there is no delete tool. Projects and personas can be deleted
182
+ in the web app, but not from here — an agent can create, read, and correct them,
183
+ not remove them.
184
+
185
+ ---
186
+
101
187
  ## Recipes: composing beyond the hero
102
188
 
103
- `clien_research` is the hero — the recommended default for validating an idea
104
- end to end. Alongside it, the server exposes granular primitives (`create_persona`,
105
- `generate_persona`, `enrich_persona`, `interview_persona`, `scan_competitors`, `search_forums`,
106
- `list_projects` / `create_project`, `list_reports` / `get_report`,
107
- `clien_research_status`) you can sequence for targeted, cheaper checks.
189
+ For a **full** validation, call the hero. Reach for a primitive composition only
190
+ for a **narrow, cheaper probe**.
108
191
 
109
192
  See [`docs/recipes/`](../docs/recipes/) for the supported compositions:
110
193
 
@@ -112,12 +195,9 @@ See [`docs/recipes/`](../docs/recipes/) for the supported compositions:
112
195
  - [Pivot viability](../docs/recipes/pivot-viability.md) — research a pivot and compare it against the original
113
196
  - [New-market persona validation](../docs/recipes/new-market-persona-validation.md) — `create_persona` → `interview_persona`, no credits, no full run
114
197
 
115
- Rule of thumb: for a **full** validation, call the hero; reach for a primitive
116
- composition only for a **narrow, cheaper probe**.
117
-
118
198
  ---
119
199
 
120
- ## Inputs
200
+ ## `clien_research` inputs
121
201
 
122
202
  | Field | Type | Required | Default | Notes |
123
203
  |----------------|--------------------------|----------|---------|--------------------------------------------------------------------------------------------------|
@@ -125,6 +205,11 @@ composition only for a **narrow, cheaper probe**.
125
205
  | `depth` | `"quick"` \| `"deep"` | no | `deep` | `quick` caps cost; `deep` is the full 15-25 min pass |
126
206
  | `project_id` | UUID | no | | Existing Clien.ai project to attach the run to (returned in `_meta.project_id` from prior runs) |
127
207
  | `project_name` | string (1-100 chars) | no | | Name for a new project. Supply when `project_id` is not set so the report is discoverable in the web UI. Ignored when `project_id` is also provided. |
208
+ | `confirm_spend` | boolean | no | | Leave unset on a first attempt. Set true only to confirm a run after a prior call returned the soft daily-spend-guard prompt. |
209
+ | `run_anyway` | boolean | no | | Leave unset on a first attempt. Set true only to override the soft task guard that refuses off-script inputs. |
210
+
211
+ A run with neither `project_id` nor `project_name` is refused, so every report
212
+ stays discoverable in the web UI.
128
213
 
129
214
  ## Outputs
130
215
 
package/dist/server.js CHANGED
@@ -18,6 +18,13 @@ function log(...args) {
18
18
  // stderr only — stdout is the MCP transport.
19
19
  console.error('[clien-mcp]', ...args);
20
20
  }
21
+ function recordToolUsage(toolName) {
22
+ // Minimal dogfooding telemetry (FUL-212): which tools agents actually call.
23
+ // stderr only — stdout is the MCP transport. Tool name + timestamp only;
24
+ // no args, no PII, no payloads. Distinct prefix keeps usage records
25
+ // greppable and parseable, separate from operational `[clien-mcp]` logs.
26
+ console.error('[clien-mcp:usage]', JSON.stringify({ tool: toolName, ts: new Date().toISOString() }));
27
+ }
21
28
  /**
22
29
  * Default browser opener — lazily imports `open` so the package still loads
23
30
  * on systems that can't open a browser (we handle the error in OAuth).
@@ -80,6 +87,9 @@ export function createMcpServer() {
80
87
  if (!tool) {
81
88
  throw new Error(`Unknown tool: ${toolName}`);
82
89
  }
90
+ // Record usage once per valid tool call, before dispatch — a call that
91
+ // later errors is still usage (the agent reached for the tool).
92
+ recordToolUsage(toolName);
83
93
  // Per MCP spec, `progressToken` may be a string OR a number, and `0` or
84
94
  // `''` are valid token values. A truthy check (`progressToken ?`) silently
85
95
  // drops progress for those callers. Compare against `undefined` instead.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,433 @@
1
+ /**
2
+ * `list_competitors` + `list_community_signals` (FUL-215 / U7) — read-only
3
+ * collection primitives over the project's persisted Research collections.
4
+ *
5
+ * Before these tools, a competitor or a community thread was only reachable
6
+ * NESTED inside a research report (`get_report` → `report_data.webResearch` /
7
+ * `forumResearch`), which meant an agent could not answer "what competitors does
8
+ * this project know about?" without picking a run first — and never saw rows
9
+ * merged from OTHER runs or from the web app. These read the deduped, append-only
10
+ * collections directly, so they surface the project's whole accumulated set.
11
+ *
12
+ * The MCP package is a thin bearer-auth HTTP client: both tools reuse the shared
13
+ * `apiCall` client from `research.ts` (same 401→refresh→retry behavior,
14
+ * per-endpoint timeouts) exactly as `personas.ts` / `reports.ts` do. No new HTTP
15
+ * machinery.
16
+ *
17
+ * Contract:
18
+ * - list_competitors → GET /api/competitors?projectId&limit&offset →
19
+ * { competitors: <raw row>[], pagination: { limit, offset, total } }
20
+ * - list_community_signals → GET /api/community?projectId&limit&offset →
21
+ * { communitySignals: <raw row>[], pagination: { limit, offset, total } }
22
+ *
23
+ * `offset` is an ABSOLUTE row offset honoured exactly by the endpoints (FUL-214),
24
+ * not rounded to a page boundary — identical semantics to `/api/personas`, so an
25
+ * agent can page with any offset it likes.
26
+ *
27
+ * ⚠️ The two collections do NOT share a sort order, and the difference is
28
+ * user-visible, so each tool must advertise its own. `listCompetitors` orders by
29
+ * `last_seen_at DESC` — a competitor re-sighted by a later run moves back to the
30
+ * top. `listCommunitySignals` orders by `created_at DESC`, the FIRST-sighting
31
+ * timestamp, precisely so the feed "never reshuffles when a thread is merged
32
+ * again" (app/lib/db/community-db.ts). Copying the competitor wording onto the
33
+ * community tool would tell an agent the newest ACTIVITY is on page one when what
34
+ * it actually gets is the newest DISCOVERY.
35
+ *
36
+ * ⚠️ VOCABULARY BOUNDARY (FUL-215 decision). The U6 endpoints return RAW
37
+ * snake_case database rows (`sentiment_summary`, `comment_count`, `hq_location`).
38
+ * The rest of the MCP surface speaks camelCase for the very same concepts — see
39
+ * `CompetitorProfileSchema` / `ForumThreadSchema` in `../types/report.js`, which
40
+ * back `scan_competitors` and `search_forums`. Shipping both spellings would mean
41
+ * an agent that ran `scan_competitors` and then `list_competitors` sees
42
+ * `commentCount` and `comment_count` for one number and cannot tell they are the
43
+ * same field. So this module maps at the tool boundary, via the two EXPLICIT
44
+ * named mappers below — deliberately not a generic snake→camel helper: the field
45
+ * list is part of the public MCP contract, so it must be readable in review and
46
+ * pinned by a test (`mcp/__tests__/collections.test.ts`), not derived by string
47
+ * surgery that silently admits whatever column is added next.
48
+ *
49
+ * Error mapping mirrors `personas.ts` / `reports.ts`: 401 → invalidate the cached
50
+ * session and surface a re-auth message; 404 → the project isn't owned by the
51
+ * caller (the endpoints deliberately make missing and not-owned indistinguishable);
52
+ * other non-ok → a generic "HTTP <n>" error. Everything here is read-only, so a
53
+ * timeout or network error is always safe to retry — no work is lost, no credit
54
+ * is spent.
55
+ */
56
+ import { z } from 'zod';
57
+ import { ToolError } from './errors.js';
58
+ import { apiCall, categorizeFetchError, truncate, } from './research.js';
59
+ const COLLECTIONS_FETCH_TIMEOUT_MS = 30_000;
60
+ // Mirrors `CollectionListQuerySchema` in app/lib/api/collection-list-query.ts.
61
+ // The MCP package is published separately and cannot import from the Next.js
62
+ // app; the server re-validates regardless — this is the advertised spec.
63
+ const LIST_LIMIT_MAX = 100;
64
+ const LIST_LIMIT_DEFAULT = 20;
65
+ export const ListCompetitorsInputSchema = z.object({
66
+ project_id: z
67
+ .string()
68
+ .uuid()
69
+ .describe('UUID of the project whose competitor set to list. Get it from ' +
70
+ '`list_projects` (or `create_project`). Rejected with a 404 if you do not ' +
71
+ 'own the project. Lists the project\'s whole accumulated competitor set — ' +
72
+ 'including competitors found by web-app runs and by earlier ' +
73
+ '`scan_competitors` / `clien_research` calls, deduped across runs.'),
74
+ limit: z
75
+ .number()
76
+ .int()
77
+ .min(1)
78
+ .max(LIST_LIMIT_MAX)
79
+ .optional()
80
+ .default(LIST_LIMIT_DEFAULT)
81
+ .describe(`Maximum number of competitors to return (1-${LIST_LIMIT_MAX}, default ${LIST_LIMIT_DEFAULT}). ` +
82
+ 'Most-recently-seen competitors come first.'),
83
+ offset: z
84
+ .number()
85
+ .int()
86
+ .min(0)
87
+ .optional()
88
+ .default(0)
89
+ .describe('Number of competitors to skip before returning results (default 0). ' +
90
+ 'An absolute row offset, honoured exactly — combine with `limit` to page ' +
91
+ 'through a large set; the total count is in `_meta.pagination.total`.'),
92
+ });
93
+ export const ListCommunitySignalsInputSchema = z.object({
94
+ project_id: z
95
+ .string()
96
+ .uuid()
97
+ .describe('UUID of the project whose community signals to list. Get it from ' +
98
+ '`list_projects` (or `create_project`). Rejected with a 404 if you do not ' +
99
+ 'own the project. Lists the project\'s whole accumulated thread set — ' +
100
+ 'including threads found by web-app runs and by earlier `search_forums` / ' +
101
+ '`clien_research` calls, deduped by url across runs.'),
102
+ limit: z
103
+ .number()
104
+ .int()
105
+ .min(1)
106
+ .max(LIST_LIMIT_MAX)
107
+ .optional()
108
+ .default(LIST_LIMIT_DEFAULT)
109
+ .describe(`Maximum number of community signals to return (1-${LIST_LIMIT_MAX}, default ${LIST_LIMIT_DEFAULT}). ` +
110
+ 'Newest-discovered first. This feed is ordered by when the project FIRST saw a ' +
111
+ 'thread, and that order is stable — a thread seen again by a later run does not ' +
112
+ 'jump back to the top (unlike `list_competitors`, which is ordered by latest sighting).'),
113
+ offset: z
114
+ .number()
115
+ .int()
116
+ .min(0)
117
+ .optional()
118
+ .default(0)
119
+ .describe('Number of community signals to skip before returning results (default 0). ' +
120
+ 'An absolute row offset, honoured exactly — combine with `limit` to page ' +
121
+ 'through a large feed; the total count is in `_meta.pagination.total`.'),
122
+ });
123
+ function nullableString(value) {
124
+ return typeof value === 'string' ? value : null;
125
+ }
126
+ function nullableNumber(value) {
127
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
128
+ }
129
+ function stringArray(value) {
130
+ return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
131
+ }
132
+ function factGrounding(value) {
133
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
134
+ ? value
135
+ : null;
136
+ }
137
+ /**
138
+ * `competitors` row → the camelCase competitor exposed to agents.
139
+ *
140
+ * The field list IS the public contract (FUL-215 decision), so it is written out
141
+ * rather than derived. Every mapped key exists because an agent can act on it or
142
+ * reason with it; the columns NOT mapped are listed here so the omissions are as
143
+ * reviewable as the inclusions:
144
+ *
145
+ * - `dedup_key` — pure write-path machinery (how the merge RPC recognises the
146
+ * same competitor across runs). Meaningless to a reader, and exposing it
147
+ * invites an agent to construct one.
148
+ * - `funding_asserted_at`, `employee_count_asserted_at`, `founded_asserted_at`,
149
+ * `hq_location_asserted_at` — FUL-178 merge-arbitration timestamps: they exist
150
+ * to decide WHICH RUN'S value wins a conflict, not to tell a reader how fresh
151
+ * a fact is. The reader-facing recency signals are already here:
152
+ * `<fact>Grounding.sourceRetrievedAt` (when the source was fetched) and the
153
+ * row-level `lastSeenAt`.
154
+ *
155
+ * Naming follows `CompetitorProfileSchema` (../types/report.ts), the shape
156
+ * `scan_competitors` already returns — hence `employeeCount` (not `employees`)
157
+ * and `hqLocation`. `relevantHypotheses` has no column here: it is a per-report
158
+ * association, not a property of the deduped competitor.
159
+ */
160
+ export function toCompetitorSummary(row) {
161
+ return {
162
+ id: nullableString(row.id),
163
+ projectId: nullableString(row.project_id),
164
+ name: nullableString(row.name),
165
+ url: nullableString(row.url),
166
+ description: nullableString(row.description),
167
+ pricing: nullableString(row.pricing),
168
+ strengths: stringArray(row.strengths),
169
+ weaknesses: stringArray(row.weaknesses),
170
+ // FUL-145 (M1b) company metadata + its FUL-201 per-fact grounding. The value
171
+ // and its grounding are advanced in lockstep at merge time, so a grounding
172
+ // always describes the value shown beside it — which is exactly why both must
173
+ // travel together: a funding figure without its state is an un-auditable claim.
174
+ funding: nullableString(row.funding),
175
+ employeeCount: nullableString(row.employee_count),
176
+ founded: nullableString(row.founded),
177
+ hqLocation: nullableString(row.hq_location),
178
+ fundingGrounding: factGrounding(row.funding_grounding),
179
+ employeeCountGrounding: factGrounding(row.employee_count_grounding),
180
+ foundedGrounding: factGrounding(row.founded_grounding),
181
+ hqLocationGrounding: factGrounding(row.hq_location_grounding),
182
+ // Provenance: `source` is the FUL-21 origin ('validation' | 'mcp' | 'web'),
183
+ // `firstSeenRunId` the job that first surfaced this competitor — an agent can
184
+ // pass it straight to `get_report`.
185
+ source: nullableString(row.source),
186
+ firstSeenRunId: nullableString(row.first_seen_run_id),
187
+ lastSeenAt: nullableString(row.last_seen_at),
188
+ createdAt: nullableString(row.created_at),
189
+ };
190
+ }
191
+ /**
192
+ * `community_signals` row → the camelCase community signal exposed to agents.
193
+ *
194
+ * Two renames worth flagging, both required by the FUL-215 "one spelling per
195
+ * concept" rule rather than by casing:
196
+ * - `sentiment_summary` → `sentimentSummary` (matches `ForumThreadSchema`).
197
+ * - `quotes` → `relevantQuotes`. The column is POPULATED from
198
+ * `report_data.forumResearch.threads[].relevantQuotes` (see the FUL-60 merge
199
+ * RPC), so these are the same concept that lost its adjective on the way into
200
+ * Postgres. `search_forums` returns `relevantQuotes`; so does this.
201
+ *
202
+ * Every column on the table is mapped — there is no bookkeeping-only column here
203
+ * apart from the dedup the RPC performs on `url`, which is itself user-facing.
204
+ */
205
+ export function toCommunitySignalSummary(row) {
206
+ return {
207
+ id: nullableString(row.id),
208
+ projectId: nullableString(row.project_id),
209
+ title: nullableString(row.title),
210
+ url: nullableString(row.url),
211
+ platform: nullableString(row.platform),
212
+ sentimentSummary: nullableString(row.sentiment_summary),
213
+ relevantQuotes: stringArray(row.quotes),
214
+ upvotes: nullableNumber(row.upvotes),
215
+ commentCount: nullableNumber(row.comment_count),
216
+ source: nullableString(row.source),
217
+ firstSeenRunId: nullableString(row.first_seen_run_id),
218
+ lastSeenAt: nullableString(row.last_seen_at),
219
+ createdAt: nullableString(row.created_at),
220
+ };
221
+ }
222
+ export class CollectionToolError extends ToolError {
223
+ }
224
+ function formatZodError(error) {
225
+ return error.issues.map((i) => `${i.path.join('.') || 'input'}: ${i.message}`).join('; ');
226
+ }
227
+ function makeCtx(deps) {
228
+ return { config: deps.config, session: deps.session, fetch: deps.fetch ?? fetch };
229
+ }
230
+ /**
231
+ * Shared non-ok handling, mirroring `personas.ts`: 401 drops the cached session
232
+ * and surfaces a re-auth message; 404 means the project is missing OR not the
233
+ * caller's (U6 makes the two indistinguishable on purpose, so a caller cannot
234
+ * probe for other users' project ids); 400 surfaces the server's validation
235
+ * detail; everything else surfaces a generic HTTP error.
236
+ */
237
+ async function throwForResponse(response, deps, action, projectId) {
238
+ const category = categorizeFetchError(response.status);
239
+ if (category === 'auth') {
240
+ if (response.refreshError?.kind !== 'transient')
241
+ deps.invalidateSession?.();
242
+ throw new CollectionToolError('Session expired. Re-run any Clien.ai tool to re-authenticate, then try again.');
243
+ }
244
+ if (response.status === 404) {
245
+ throw new CollectionToolError(`Project ${projectId} not found. It may not exist or may belong to a different ` +
246
+ 'account — call list_projects to confirm the id.');
247
+ }
248
+ const text = await response.text().catch(() => '');
249
+ let detail = truncate(text, 200);
250
+ try {
251
+ const parsed = JSON.parse(text);
252
+ if (parsed?.error)
253
+ detail = `${parsed.error}${parsed.code ? ` (${parsed.code})` : ''}`;
254
+ }
255
+ catch {
256
+ // non-JSON body — fall back to the truncated raw text
257
+ }
258
+ if (response.status === 400) {
259
+ throw new CollectionToolError(`Could not ${action} — ${detail}`);
260
+ }
261
+ throw new CollectionToolError(`Failed to ${action} (HTTP ${response.status}): ${detail}`);
262
+ }
263
+ /**
264
+ * Shared GET + parse for both collections. Keeps the two tools to their
265
+ * differences (endpoint, collection key, rendering) instead of duplicating the
266
+ * timeout/auth/error dance.
267
+ *
268
+ * `collectionKey` is the array property the endpoint returns (`competitors` /
269
+ * `communitySignals`). It is checked here rather than defaulted at the call site
270
+ * ON PURPOSE: a 200 whose body is unreadable — an HTML error page from a proxy, a
271
+ * truncated response, a reshaped envelope — would otherwise fall through to an
272
+ * empty array, and an empty array is not neutral in these tools. Both of them
273
+ * answer an empty collection with "run scan_competitors / search_forums", which
274
+ * DEBITS CREDITS. So a transport failure that reads as "this project has nothing"
275
+ * can cost the user real money and put a false "no competitors" claim in front of
276
+ * an agent. "I could not read the answer" and "the answer is none" must not look
277
+ * the same.
278
+ */
279
+ async function fetchCollection(deps, path, collectionKey, projectId, limit, offset, action) {
280
+ const ctx = makeCtx(deps);
281
+ const query = new URLSearchParams({
282
+ projectId,
283
+ limit: String(limit),
284
+ offset: String(offset),
285
+ });
286
+ let response;
287
+ try {
288
+ response = await apiCall(ctx, 'GET', `${path}?${query.toString()}`, {
289
+ timeoutMs: COLLECTIONS_FETCH_TIMEOUT_MS,
290
+ });
291
+ }
292
+ catch (err) {
293
+ const e = err;
294
+ if (e.name === 'AbortError' || e.name === 'TimeoutError') {
295
+ throw new CollectionToolError(`Timed out after ${COLLECTIONS_FETCH_TIMEOUT_MS / 1000}s trying to ${action}. ` +
296
+ 'Try again in a moment — this is a read, so nothing was spent and nothing was lost.');
297
+ }
298
+ throw err;
299
+ }
300
+ if (!response.ok)
301
+ await throwForResponse(response, deps, action, projectId);
302
+ const json = (await response.json().catch(() => null));
303
+ const rows = json?.[collectionKey];
304
+ if (!Array.isArray(rows)) {
305
+ throw new CollectionToolError(`Could not ${action} — the server returned a 200 with an unreadable body ` +
306
+ `(no \`${collectionKey}\` array). This is NOT an empty collection: the request ` +
307
+ 'failed and the project may well have data. Retry — this is a read, so nothing ' +
308
+ 'was spent and nothing was lost. Do not run a paid scan on the strength of this.');
309
+ }
310
+ const pagination = json?.pagination ?? {};
311
+ return { rows, pagination };
312
+ }
313
+ /**
314
+ * The pagination echoed back in `_meta`. Prefers what the server reported (it is
315
+ * authoritative about `total`) but always yields three defined numbers, so an
316
+ * agent paging on `_meta.pagination` never has to handle a missing field on a
317
+ * response the server reshaped.
318
+ */
319
+ function resolvePagination(serverPagination, limit, offset, total) {
320
+ return {
321
+ limit: serverPagination.limit ?? limit,
322
+ offset: serverPagination.offset ?? offset,
323
+ total,
324
+ };
325
+ }
326
+ /** Shared "N more not shown" tail so both lists page identically. */
327
+ function moreLine(offset, shown, total) {
328
+ const seen = offset + shown;
329
+ return seen < total ? `\n\n${total - seen} more not shown — page with offset=${seen}.` : '';
330
+ }
331
+ export async function listCompetitors(input, deps) {
332
+ const parsed = ListCompetitorsInputSchema.safeParse(input);
333
+ if (!parsed.success) {
334
+ throw new CollectionToolError(`Invalid input — ${formatZodError(parsed.error)}`);
335
+ }
336
+ const { project_id: projectId, limit, offset } = parsed.data;
337
+ const { rows, pagination } = await fetchCollection(deps, '/api/competitors', 'competitors', projectId, limit, offset, 'list competitors');
338
+ const competitors = rows.map(toCompetitorSummary);
339
+ const total = pagination.total ?? competitors.length;
340
+ let text;
341
+ if (competitors.length === 0) {
342
+ text =
343
+ offset > 0
344
+ ? `No competitors at offset ${offset} in project ${projectId} (total: ${total}).`
345
+ : `No competitors recorded for project ${projectId}. Run scan_competitors (or ` +
346
+ 'clien_research) to populate the project\'s competitor set.';
347
+ }
348
+ else {
349
+ // Numbering is offset-aware so a second page reads as 21., 22., … rather
350
+ // than restarting at 1 and looking like a different result set.
351
+ const lines = competitors.map((c, i) => {
352
+ const header = `${offset + i + 1}. **${c.name ?? 'Unnamed competitor'}**${c.pricing ? ` — ${c.pricing}` : ''}`;
353
+ const desc = c.description ? `\n ${truncate(c.description, 240)}` : '';
354
+ const url = c.url ? `\n ${c.url}` : '';
355
+ const meta = [
356
+ c.funding && `Funding: ${truncate(c.funding, 80)}`,
357
+ c.employeeCount && `Employees: ${truncate(c.employeeCount, 40)}`,
358
+ c.founded && `Founded: ${truncate(c.founded, 40)}`,
359
+ c.hqLocation && `HQ: ${truncate(c.hqLocation, 80)}`,
360
+ ].filter(Boolean);
361
+ const metaLine = meta.length ? `\n ${meta.join(' · ')}` : '';
362
+ return `${header}${desc}${url}${metaLine}\n id: ${c.id ?? 'unknown'}`;
363
+ });
364
+ // The trust caveat is only worth its length when there is actually a company
365
+ // fact on this page to be careful about — a set with no M1b facts would just
366
+ // get a warning about nothing.
367
+ // `||`, not `??`: this must agree with the `c.funding && …` truthiness guards
368
+ // above that decide whether each fact RENDERS. With `??`, a competitor whose
369
+ // funding is '' short-circuits on the empty string and the caveat disappears
370
+ // even though a later fact is populated and on screen.
371
+ const hasCompanyFacts = competitors.some((c) => Boolean(c.funding || c.employeeCount || c.founded || c.hqLocation));
372
+ const trustLine = hasCompanyFacts
373
+ ? ' The company facts (funding/employees/founded/HQ) are only as trustworthy as their ' +
374
+ 'sibling `<fact>Grounding.state`: treat a missing or non-GROUNDED grounding as ' +
375
+ 'unverified rather than as fact.'
376
+ : '';
377
+ text =
378
+ `Found ${total} competitor${total === 1 ? '' : 's'} in project ${projectId}:\n\n` +
379
+ `${lines.join('\n\n')}${moreLine(offset, competitors.length, total)}\n\n` +
380
+ `Full records are in \`_meta.competitors\`.${trustLine}`;
381
+ }
382
+ return {
383
+ content: [{ type: 'text', text }],
384
+ _meta: { competitors, pagination: resolvePagination(pagination, limit, offset, total) },
385
+ };
386
+ }
387
+ export async function listCommunitySignals(input, deps) {
388
+ const parsed = ListCommunitySignalsInputSchema.safeParse(input);
389
+ if (!parsed.success) {
390
+ throw new CollectionToolError(`Invalid input — ${formatZodError(parsed.error)}`);
391
+ }
392
+ const { project_id: projectId, limit, offset } = parsed.data;
393
+ const { rows, pagination } = await fetchCollection(deps, '/api/community', 'communitySignals', projectId, limit, offset, 'list community signals');
394
+ const signals = rows.map(toCommunitySignalSummary);
395
+ const total = pagination.total ?? signals.length;
396
+ let text;
397
+ if (signals.length === 0) {
398
+ text =
399
+ offset > 0
400
+ ? `No community signals at offset ${offset} in project ${projectId} (total: ${total}).`
401
+ : `No community signals recorded for project ${projectId}. Run search_forums (or ` +
402
+ 'clien_research) to populate the project\'s community feed.';
403
+ }
404
+ else {
405
+ const lines = signals.map((s, i) => {
406
+ const platform = s.platform ? ` [${s.platform}]` : '';
407
+ const header = `${offset + i + 1}. **${truncate(s.title ?? 'Untitled thread', 160)}**${platform}`;
408
+ const sentiment = s.sentimentSummary ? `\n ${truncate(s.sentimentSummary, 240)}` : '';
409
+ const url = s.url ? `\n ${s.url}` : '';
410
+ const quote = s.relevantQuotes.length
411
+ ? `\n "${truncate(s.relevantQuotes[0] ?? '', 200)}"`
412
+ : '';
413
+ const engagement = [
414
+ s.upvotes !== null && `${s.upvotes} upvotes`,
415
+ s.commentCount !== null && `${s.commentCount} comments`,
416
+ ].filter(Boolean);
417
+ const engagementLine = engagement.length ? `\n ${engagement.join(' · ')}` : '';
418
+ return `${header}${sentiment}${url}${quote}${engagementLine}\n id: ${s.id ?? 'unknown'}`;
419
+ });
420
+ text =
421
+ `Found ${total} community signal${total === 1 ? '' : 's'} in project ${projectId}:\n\n` +
422
+ `${lines.join('\n\n')}${moreLine(offset, signals.length, total)}\n\n` +
423
+ 'Full records — including every cached quote — are in `_meta.communitySignals`.';
424
+ }
425
+ return {
426
+ content: [{ type: 'text', text }],
427
+ _meta: {
428
+ communitySignals: signals,
429
+ pagination: resolvePagination(pagination, limit, offset, total),
430
+ },
431
+ };
432
+ }
433
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/tools/collections.ts"],"names":[],"mappings":"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"}