@clien-ai/mcp 0.2.0 → 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 +96 -11
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/collections.js +433 -0
- package/dist/tools/collections.js.map +1 -0
- package/dist/tools/interview.js +190 -25
- package/dist/tools/interview.js.map +1 -1
- package/dist/tools/param-aliases.js +144 -0
- package/dist/tools/param-aliases.js.map +1 -0
- package/dist/tools/personas.js +449 -40
- package/dist/tools/personas.js.map +1 -1
- package/dist/tools/projects.js +545 -6
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/registry.js +237 -18
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/scoped-research.js +19 -3
- package/dist/tools/scoped-research.js.map +1 -1
- package/dist/types/report.js +182 -3
- package/dist/types/report.js.map +1 -1
- package/package.json +1 -1
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.
|
|
4
|
-
`clien_research` —
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
##
|
|
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.
|
package/dist/server.js.map
CHANGED
|
@@ -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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"}
|