@clien-ai/mcp 0.1.3 → 0.1.4
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 +19 -0
- package/dist/server.js +26 -143
- package/dist/server.js.map +1 -1
- package/dist/tools/errors.js +23 -0
- package/dist/tools/errors.js.map +1 -0
- package/dist/tools/interview.js +263 -0
- package/dist/tools/interview.js.map +1 -0
- package/dist/tools/personas.js +192 -0
- package/dist/tools/personas.js.map +1 -0
- package/dist/tools/projects.js +195 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/registry.js +147 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/reports.js +210 -0
- package/dist/tools/reports.js.map +1 -0
- package/dist/tools/research.js +45 -11
- package/dist/tools/research.js.map +1 -1
- package/dist/tools/schema.js +25 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/status.js +107 -13
- package/dist/tools/status.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,25 @@ the top 3 threats", etc.
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
+
## Recipes: composing beyond the hero
|
|
72
|
+
|
|
73
|
+
`clien_research` is the hero — the recommended default for validating an idea
|
|
74
|
+
end to end. Alongside it, the server exposes granular primitives (`create_persona`,
|
|
75
|
+
`interview_persona`, `list_projects` / `create_project`, `list_reports` /
|
|
76
|
+
`get_report`, `clien_research_status`) you can sequence for targeted, cheaper
|
|
77
|
+
checks.
|
|
78
|
+
|
|
79
|
+
See [`docs/recipes/`](../docs/recipes/) for the supported compositions:
|
|
80
|
+
|
|
81
|
+
- [Competitor spot-check](../docs/recipes/competitor-spot-check.md) — read `competitors[]` from a report instead of commissioning a new study
|
|
82
|
+
- [Pivot viability](../docs/recipes/pivot-viability.md) — research a pivot and compare it against the original
|
|
83
|
+
- [New-market persona validation](../docs/recipes/new-market-persona-validation.md) — `create_persona` → `interview_persona`, no credits, no full run
|
|
84
|
+
|
|
85
|
+
Rule of thumb: for a **full** validation, call the hero; reach for a primitive
|
|
86
|
+
composition only for a **narrow, cheaper probe**.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
71
90
|
## Inputs
|
|
72
91
|
|
|
73
92
|
| Field | Type | Required | Default | Notes |
|
package/dist/server.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP stdio server entry point.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The tool surface lives in `tools/registry.ts` — both `tools/list` and
|
|
5
|
+
* `tools/call` iterate the `TOOLS` array, so adding a tool needs no edits here.
|
|
6
|
+
* Auth happens lazily on first tool call so the server can announce its tool
|
|
7
|
+
* list without a browser interruption.
|
|
6
8
|
*/
|
|
7
9
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -10,8 +12,8 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
10
12
|
import { loadConfig, PACKAGE_NAME, PACKAGE_VERSION } from './config.js';
|
|
11
13
|
import { ensureSession, } from './auth/oauth.js';
|
|
12
14
|
import { clearTokens, loadRefreshToken, saveRefreshToken, withTokenLock, } from './auth/storage.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
+
import { ToolError } from './tools/errors.js';
|
|
16
|
+
import { TOOLS } from './tools/registry.js';
|
|
15
17
|
function log(...args) {
|
|
16
18
|
// stderr only — stdout is the MCP transport.
|
|
17
19
|
console.error('[clien-mcp]', ...args);
|
|
@@ -29,88 +31,6 @@ async function defaultBrowserOpener() {
|
|
|
29
31
|
await fn(url);
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Input schema for the tool advert. Hand-rolled JSON Schema so we avoid the
|
|
34
|
-
* `zod-to-json-schema` dependency. Must stay in sync with `ResearchInputSchema`
|
|
35
|
-
* in `src/tools/research.ts` — the Zod parser is still the source of truth at
|
|
36
|
-
* runtime; this is only the spec advertised to the caller.
|
|
37
|
-
*
|
|
38
|
-
* Drift surfaces silently: the Zod parser still accepts the input, but strict
|
|
39
|
-
* MCP clients (`additionalProperties: false`) strip undeclared fields before
|
|
40
|
-
* they reach the parser, and LLM callers never learn the field exists.
|
|
41
|
-
* Enforcement: `__tests__/schema-contract.test.ts` walks both schemas and
|
|
42
|
-
* fails if their property keys or required-field sets diverge. Exported here
|
|
43
|
-
* so that test can import it. See
|
|
44
|
-
* `docs/solutions/design-patterns/cross-package-schema-duplication-contract-2026-05-12.md`.
|
|
45
|
-
*/
|
|
46
|
-
export const RESEARCH_INPUT_SCHEMA = {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: {
|
|
49
|
-
idea: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
minLength: 1,
|
|
52
|
-
maxLength: 10_000,
|
|
53
|
-
description: 'The product idea, feature hypothesis, or research question to validate. ' +
|
|
54
|
-
'Can be a one-liner or several paragraphs — more context yields a better report.',
|
|
55
|
-
},
|
|
56
|
-
depth: {
|
|
57
|
-
type: 'string',
|
|
58
|
-
enum: ['quick', 'deep'],
|
|
59
|
-
default: 'deep',
|
|
60
|
-
description: '"deep" (default) runs a 15-25 minute full research pass. ' +
|
|
61
|
-
'"quick" caps cost and runs a tighter pass.',
|
|
62
|
-
},
|
|
63
|
-
project_id: {
|
|
64
|
-
type: 'string',
|
|
65
|
-
format: 'uuid',
|
|
66
|
-
description: 'Optional Clien.ai project UUID to attach this run to. When omitted, ' +
|
|
67
|
-
'the run is standalone and not associated with any project.',
|
|
68
|
-
},
|
|
69
|
-
project_name: {
|
|
70
|
-
type: 'string',
|
|
71
|
-
minLength: 1,
|
|
72
|
-
maxLength: 100,
|
|
73
|
-
description: 'Name for a new Clien.ai project to attach this run to. You SHOULD ' +
|
|
74
|
-
'supply this whenever `project_id` is not set, otherwise the ' +
|
|
75
|
-
'resulting report will not be discoverable in the Clien.ai web UI. ' +
|
|
76
|
-
'Derive a short (2-4 word) human-readable name from the idea — ' +
|
|
77
|
-
'typically the product name. Guidance on when to create a new ' +
|
|
78
|
-
'project vs. reuse an existing one: create a NEW project (supply a ' +
|
|
79
|
-
'fresh `project_name`) for each distinct research idea or product ' +
|
|
80
|
-
'concept; REUSE an existing `project_id` only when the user ' +
|
|
81
|
-
'explicitly asks to add follow-up research to a prior run on the ' +
|
|
82
|
-
'same idea. Ignored when `project_id` is also provided.',
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
required: ['idea'],
|
|
86
|
-
additionalProperties: false,
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Hand-rolled JSON Schema advert for `clien_research_status`. Same
|
|
90
|
-
* Zod-vs-JSON-schema duplication rules as RESEARCH_INPUT_SCHEMA above:
|
|
91
|
-
* the runtime parser in tools/status.ts:StatusInputSchema is the source
|
|
92
|
-
* of truth; this is the advert. Drift is caught by the schema-contract
|
|
93
|
-
* test in __tests__/schema-contract.test.ts.
|
|
94
|
-
*/
|
|
95
|
-
export const RESEARCH_STATUS_INPUT_SCHEMA = {
|
|
96
|
-
type: 'object',
|
|
97
|
-
properties: {
|
|
98
|
-
job_id: {
|
|
99
|
-
type: 'string',
|
|
100
|
-
format: 'uuid',
|
|
101
|
-
description: 'UUID of the research job to query. Returned in the `_meta.job_id` ' +
|
|
102
|
-
'field of a prior `clien_research` call, surfaced in error ' +
|
|
103
|
-
'messages when a run dropped mid-flight (e.g. "MCP error -32000"), ' +
|
|
104
|
-
'and printed to the MCP server\'s stderr log as ' +
|
|
105
|
-
'`[clien-mcp] research started job_id=<uuid>` at run start. Use ' +
|
|
106
|
-
'this tool to RECOVER an in-flight job instead of calling ' +
|
|
107
|
-
'`clien_research` again — calling research again would spawn a ' +
|
|
108
|
-
'new run and debit another 5 credits for the same outcome.',
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
required: ['job_id'],
|
|
112
|
-
additionalProperties: false,
|
|
113
|
-
};
|
|
114
34
|
export function createMcpServer() {
|
|
115
35
|
const config = loadConfig();
|
|
116
36
|
// Per-instance session cache. Scoping this to the closure (rather than the
|
|
@@ -142,31 +62,11 @@ export function createMcpServer() {
|
|
|
142
62
|
}
|
|
143
63
|
const server = new Server({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
144
64
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
145
|
-
tools:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
'15-25 minutes; progress is streamed via notifications/progress. ' +
|
|
151
|
-
'First call opens a browser for one-time OAuth login; subsequent calls reuse the stored refresh token. ' +
|
|
152
|
-
'IMPORTANT: if this call fails mid-flight with "MCP error -32000: Connection closed", ' +
|
|
153
|
-
'do NOT call clien_research again — that spawns a new run and debits another 5 credits for the same idea. ' +
|
|
154
|
-
'Instead call `clien_research_status` with the job_id from the prior call\'s `_meta.job_id` ' +
|
|
155
|
-
'(or from the `[clien-mcp] research started job_id=…` line in the MCP server\'s stderr log).',
|
|
156
|
-
inputSchema: RESEARCH_INPUT_SCHEMA,
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: 'clien_research_status',
|
|
160
|
-
description: 'Fetch the current state of an existing `clien_research` job. Use this to ' +
|
|
161
|
-
'recover a run that disconnected with -32000 mid-flight (the job keeps running ' +
|
|
162
|
-
'on the backend; this tool re-attaches a read view) and to poll for completion. ' +
|
|
163
|
-
'If the job is complete, returns the full markdown report and structured report_data ' +
|
|
164
|
-
'in the same shape `clien_research` would have returned — no need to call research again. ' +
|
|
165
|
-
'If the job is still running, returns a brief status summary. If the job failed or ' +
|
|
166
|
-
'was cancelled, returns the failure reason. Read-only: no credit cost, no side effects.',
|
|
167
|
-
inputSchema: RESEARCH_STATUS_INPUT_SCHEMA,
|
|
168
|
-
},
|
|
169
|
-
],
|
|
65
|
+
tools: TOOLS.map((tool) => ({
|
|
66
|
+
name: tool.name,
|
|
67
|
+
description: tool.description,
|
|
68
|
+
inputSchema: tool.jsonSchema,
|
|
69
|
+
})),
|
|
170
70
|
}));
|
|
171
71
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
172
72
|
const toolName = request.params.name;
|
|
@@ -176,7 +76,8 @@ export function createMcpServer() {
|
|
|
176
76
|
// inside the try-catch would convert it to a tool-level `isError`
|
|
177
77
|
// response — the wrong shape, and a regression caught by
|
|
178
78
|
// server.test.ts > 'unknown tool name surfaces an error'.
|
|
179
|
-
|
|
79
|
+
const tool = TOOLS.find((t) => t.name === toolName);
|
|
80
|
+
if (!tool) {
|
|
180
81
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
181
82
|
}
|
|
182
83
|
// Per MCP spec, `progressToken` may be a string OR a number, and `0` or
|
|
@@ -203,46 +104,28 @@ export function createMcpServer() {
|
|
|
203
104
|
: undefined;
|
|
204
105
|
try {
|
|
205
106
|
const session = await getSession();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
if (toolName === 'clien_research_status') {
|
|
219
|
-
const result = await getResearchStatus(request.params.arguments, {
|
|
220
|
-
config,
|
|
221
|
-
session,
|
|
222
|
-
invalidateSession,
|
|
223
|
-
});
|
|
224
|
-
return {
|
|
225
|
-
content: result.content,
|
|
226
|
-
_meta: result._meta,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
// Unreachable: the pre-try unknown-tool guard above narrows toolName
|
|
230
|
-
// to the two valid names. Kept as a defense-in-depth fallback.
|
|
231
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
107
|
+
const result = await tool.handler(request.params.arguments, {
|
|
108
|
+
config,
|
|
109
|
+
session,
|
|
110
|
+
onProgress,
|
|
111
|
+
invalidateSession,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
content: result.content,
|
|
115
|
+
_meta: result._meta,
|
|
116
|
+
};
|
|
232
117
|
}
|
|
233
118
|
catch (err) {
|
|
234
|
-
const message = err instanceof
|
|
119
|
+
const message = err instanceof ToolError
|
|
235
120
|
? err.userMessage
|
|
236
121
|
: err instanceof Error
|
|
237
122
|
? err.message
|
|
238
123
|
: String(err);
|
|
239
124
|
// Preserve the job_id on error responses so the host AI can pivot to
|
|
240
125
|
// `clien_research_status` (or surface the id to the user) without
|
|
241
|
-
// having to parse it out of the human-readable error text.
|
|
126
|
+
// having to parse it out of the human-readable error text. Tool error
|
|
242
127
|
// classes carry a `jobId` field when one is known.
|
|
243
|
-
const errorJobId = err instanceof
|
|
244
|
-
? err.jobId
|
|
245
|
-
: undefined;
|
|
128
|
+
const errorJobId = err instanceof ToolError ? err.jobId : undefined;
|
|
246
129
|
log('tool error:', message);
|
|
247
130
|
return {
|
|
248
131
|
isError: true,
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared base class for tool-level errors.
|
|
3
|
+
*
|
|
4
|
+
* Every registry tool signals a user-recoverable failure by throwing a
|
|
5
|
+
* subclass of `ToolError`. The server's `CallTool` catch block maps any
|
|
6
|
+
* `ToolError` to a tool-level `{ isError: true, content, _meta.job_id? }`
|
|
7
|
+
* response (as opposed to a JSON-RPC protocol error). Carrying `userMessage`
|
|
8
|
+
* and the optional `jobId` on a single base means the dispatch needs exactly
|
|
9
|
+
* one `instanceof ToolError` check regardless of how many tools the registry
|
|
10
|
+
* holds — see `src/server.ts`.
|
|
11
|
+
*/
|
|
12
|
+
export class ToolError extends Error {
|
|
13
|
+
userMessage;
|
|
14
|
+
jobId;
|
|
15
|
+
constructor(userMessage, opts = {}) {
|
|
16
|
+
super(userMessage);
|
|
17
|
+
this.userMessage = userMessage;
|
|
18
|
+
this.jobId = opts.jobId;
|
|
19
|
+
if (opts.cause !== undefined)
|
|
20
|
+
this.cause = opts.cause;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/tools/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,WAAW,CAAQ;IACnB,KAAK,CAAS;IACvB,YAAY,WAAmB,EAAE,OAA4C,EAAE;QAC7E,KAAK,CAAC,WAAW,CAAC,CAAA;QAClB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACvB,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAG,IAA4B,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IAChF,CAAC;CACF"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `interview_persona` — stateful, full-lifecycle interview tool over the
|
|
3
|
+
* interview REST API (FUL-17 lifecycle + FUL-43 conversational turn). Lets an
|
|
4
|
+
* agent drive a real multi-turn interview with a persona: start it, exchange
|
|
5
|
+
* turns (each returns the persona's FULL reply), read the transcript, and
|
|
6
|
+
* complete/reopen — reusing the same lifecycle as the web app (greeting seed,
|
|
7
|
+
* auto-summary on message thresholds, message-count maintenance), so an
|
|
8
|
+
* MCP-driven interview is indistinguishable from a web one in the DB.
|
|
9
|
+
*
|
|
10
|
+
* Like `personas.ts` / `projects.ts` this is a thin bearer-auth HTTP client: it
|
|
11
|
+
* reuses the shared `apiCall` from `research.ts` (401→refresh→retry, per-endpoint
|
|
12
|
+
* timeouts). No new HTTP machinery.
|
|
13
|
+
*
|
|
14
|
+
* One tool, many actions (an `action` discriminator on a FLAT object schema —
|
|
15
|
+
* the registry type and the schema-contract test both assume a `z.ZodObject`, so
|
|
16
|
+
* a `discriminatedUnion` is off the table). Per-action required fields are
|
|
17
|
+
* enforced in the handler, not via `.superRefine` (which would yield a
|
|
18
|
+
* `ZodEffects`, not a `ZodObject`).
|
|
19
|
+
*
|
|
20
|
+
* Contract (all bearer-authenticated, ownership derived from the parent project):
|
|
21
|
+
* - start → POST /api/interviews { projectId, personaId, withFirstMessage:true, ephemeral } → { interview, personaName, firstMessageContent }
|
|
22
|
+
* - turn → POST /api/interviews/{id}/turn { content } → { reply, message, userMessage, messageCount }
|
|
23
|
+
* - transcript → GET /api/interviews/{id} → { interview }
|
|
24
|
+
* - complete → PATCH /api/interviews/{id} { status:'completed' } → { interview }
|
|
25
|
+
* - reopen → PATCH /api/interviews/{id} { status:'active' } → { interview }
|
|
26
|
+
*
|
|
27
|
+
* The backend stamps `source='mcp'` automatically for bearer callers (FUL-21);
|
|
28
|
+
* `ephemeral: true` on `start` sets `include_in_insights=false`.
|
|
29
|
+
*
|
|
30
|
+
* Scope note: a `turn` generates a real AI reply, metered today only by the
|
|
31
|
+
* monthly usage limit. Per-turn credit debiting is deferred to FUL-33 (the turn
|
|
32
|
+
* endpoint and the generalized per-tool spend RPC / cost table aren't built yet).
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import { ToolError } from './errors.js';
|
|
36
|
+
import { apiCall, categorizeFetchError, truncate, } from './research.js';
|
|
37
|
+
const INTERVIEW_FETCH_TIMEOUT_MS = 60_000;
|
|
38
|
+
/** Upper bound on a turn's question — matches the server's `MESSAGE_CONTENT_MAX`. */
|
|
39
|
+
const CONTENT_MAX = 10_000;
|
|
40
|
+
export const InterviewPersonaInputSchema = z.object({
|
|
41
|
+
action: z
|
|
42
|
+
.enum(['start', 'turn', 'transcript', 'complete', 'reopen'])
|
|
43
|
+
.describe('What to do this call. Drive a full interview as a loop: `start` once to open an ' +
|
|
44
|
+
'interview (seeds the persona\'s greeting), then `turn` repeatedly — send a question, ' +
|
|
45
|
+
'read the persona\'s full reply, decide the next question. `transcript` re-reads the ' +
|
|
46
|
+
'whole conversation. `complete` closes the interview when you\'ve learned enough ' +
|
|
47
|
+
'(5-12 turns is typical); `reopen` resumes a completed one.'),
|
|
48
|
+
projectId: z
|
|
49
|
+
.string()
|
|
50
|
+
.uuid()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('action=start: UUID of the project the interview belongs to. Get it from `list_projects` ' +
|
|
53
|
+
'or `create_project`. You are 404\'d if you do not own it.'),
|
|
54
|
+
personaId: z
|
|
55
|
+
.string()
|
|
56
|
+
.uuid()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('action=start: UUID of the persona to interview (must belong to `projectId`). Get it from ' +
|
|
59
|
+
'`create_persona` (`_meta.persona.id`).'),
|
|
60
|
+
interviewId: z
|
|
61
|
+
.string()
|
|
62
|
+
.uuid()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('action=turn/transcript/complete/reopen: UUID of the interview to act on. Returned by ' +
|
|
65
|
+
'`start` in `_meta.interview.id`.'),
|
|
66
|
+
content: z
|
|
67
|
+
.string()
|
|
68
|
+
.trim()
|
|
69
|
+
.min(1, 'content must be non-empty')
|
|
70
|
+
.max(CONTENT_MAX, `content too long (max ${CONTENT_MAX} chars)`)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('action=turn: the next question to ask the persona. The persona\'s full reply comes back ' +
|
|
73
|
+
'in the tool result text (and `_meta.reply`).'),
|
|
74
|
+
ephemeral: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.default(false)
|
|
78
|
+
.describe('action=start: set true for a scratch/throwaway interview that should NOT count toward the ' +
|
|
79
|
+
'project\'s aggregate insights (sets include_in_insights=false). Leave false (the default) ' +
|
|
80
|
+
'for a real interview you want reflected in project-level analysis.'),
|
|
81
|
+
});
|
|
82
|
+
export class InterviewToolError extends ToolError {
|
|
83
|
+
}
|
|
84
|
+
function formatZodError(error) {
|
|
85
|
+
return error.issues.map((i) => `${i.path.join('.') || 'input'}: ${i.message}`).join('; ');
|
|
86
|
+
}
|
|
87
|
+
function makeCtx(deps) {
|
|
88
|
+
return { config: deps.config, session: deps.session, fetch: deps.fetch ?? fetch };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Shared non-ok handling, mirroring `personas.ts`: 401 drops the cached session
|
|
92
|
+
* (so the next tool call rebuilds from disk) and surfaces a re-auth message;
|
|
93
|
+
* 404 means the interview/project isn't owned by the caller; 400 surfaces the
|
|
94
|
+
* server's validation detail; everything else surfaces a generic HTTP error.
|
|
95
|
+
*/
|
|
96
|
+
async function throwForResponse(response, deps, action) {
|
|
97
|
+
const category = categorizeFetchError(response.status);
|
|
98
|
+
if (category === 'auth') {
|
|
99
|
+
if (response.refreshError?.kind !== 'transient')
|
|
100
|
+
deps.invalidateSession?.();
|
|
101
|
+
throw new InterviewToolError('Session expired. Re-run any Clien.ai tool to re-authenticate, then try again.');
|
|
102
|
+
}
|
|
103
|
+
if (response.status === 404) {
|
|
104
|
+
throw new InterviewToolError(`Could not ${action} — interview or project not found, or you don't own it. ` +
|
|
105
|
+
'Confirm the ids (list_projects / the id from `start`).');
|
|
106
|
+
}
|
|
107
|
+
const text = await response.text().catch(() => '');
|
|
108
|
+
let detail = truncate(text, 200);
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(text);
|
|
111
|
+
if (parsed?.error)
|
|
112
|
+
detail = `${parsed.error}${parsed.code ? ` (${parsed.code})` : ''}`;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// non-JSON body — fall back to the truncated raw text
|
|
116
|
+
}
|
|
117
|
+
if (response.status === 400) {
|
|
118
|
+
throw new InterviewToolError(`Could not ${action} — ${detail}`);
|
|
119
|
+
}
|
|
120
|
+
throw new InterviewToolError(`Failed to ${action} (HTTP ${response.status}): ${detail}`);
|
|
121
|
+
}
|
|
122
|
+
/** Run one `apiCall`, converting timeout/abort into a clear tool error. */
|
|
123
|
+
async function call(ctx, method, path, action, body) {
|
|
124
|
+
try {
|
|
125
|
+
return await apiCall(ctx, method, path, {
|
|
126
|
+
...(body ? { body } : {}),
|
|
127
|
+
timeoutMs: INTERVIEW_FETCH_TIMEOUT_MS,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const e = err;
|
|
132
|
+
if (e.name === 'AbortError' || e.name === 'TimeoutError') {
|
|
133
|
+
throw new InterviewToolError(`Request to ${action} timed out after ${INTERVIEW_FETCH_TIMEOUT_MS / 1000}s. ` +
|
|
134
|
+
'It may or may not have been applied — fetch the transcript before retrying.');
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function readJson(response) {
|
|
140
|
+
return (await response.json().catch(() => null));
|
|
141
|
+
}
|
|
142
|
+
function formatTranscript(interview) {
|
|
143
|
+
const messages = interview.messages ?? [];
|
|
144
|
+
const lines = messages.map((m) => {
|
|
145
|
+
const speaker = m.role === 'assistant' ? 'Persona' : 'Interviewer';
|
|
146
|
+
return `${speaker}: ${m.content ?? ''}`;
|
|
147
|
+
});
|
|
148
|
+
const header = `Interview ${interview.id ?? '?'} — status: ${interview.status ?? 'unknown'}, ` +
|
|
149
|
+
`${messages.length} message${messages.length === 1 ? '' : 's'}.`;
|
|
150
|
+
return lines.length > 0 ? `${header}\n\n${lines.join('\n\n')}` : header;
|
|
151
|
+
}
|
|
152
|
+
async function startInterview(input, ctx, deps) {
|
|
153
|
+
if (!input.projectId || !input.personaId) {
|
|
154
|
+
throw new InterviewToolError("action 'start' requires projectId and personaId.");
|
|
155
|
+
}
|
|
156
|
+
const response = await call(ctx, 'POST', '/api/interviews', 'start interview', {
|
|
157
|
+
projectId: input.projectId,
|
|
158
|
+
personaId: input.personaId,
|
|
159
|
+
withFirstMessage: true,
|
|
160
|
+
ephemeral: input.ephemeral,
|
|
161
|
+
});
|
|
162
|
+
if (!response.ok)
|
|
163
|
+
await throwForResponse(response, deps, 'start interview');
|
|
164
|
+
const json = await readJson(response);
|
|
165
|
+
const interview = json?.interview;
|
|
166
|
+
if (!interview?.id) {
|
|
167
|
+
throw new InterviewToolError('Interview creation returned no interview id; the operation may not have succeeded.');
|
|
168
|
+
}
|
|
169
|
+
const who = json?.personaName ? ` with ${json.personaName}` : '';
|
|
170
|
+
const greeting = json?.firstMessageContent ? `\nGreeting: ${json.firstMessageContent}` : '';
|
|
171
|
+
const insightsNote = input.ephemeral
|
|
172
|
+
? ' Marked ephemeral — excluded from project insights.'
|
|
173
|
+
: '';
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `Started interview ${interview.id}${who}.${insightsNote}${greeting}\n` +
|
|
179
|
+
"Ask questions with action='turn'; close it with action='complete' when done.",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
_meta: { interview, personaName: json?.personaName, firstMessageContent: json?.firstMessageContent },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function sendTurn(input, ctx, deps) {
|
|
186
|
+
if (!input.interviewId || !input.content) {
|
|
187
|
+
throw new InterviewToolError("action 'turn' requires interviewId and content.");
|
|
188
|
+
}
|
|
189
|
+
const response = await call(ctx, 'POST', `/api/interviews/${input.interviewId}/turn`, 'send interview turn', { content: input.content });
|
|
190
|
+
if (!response.ok)
|
|
191
|
+
await throwForResponse(response, deps, 'send interview turn');
|
|
192
|
+
const json = await readJson(response);
|
|
193
|
+
const reply = json?.reply;
|
|
194
|
+
if (typeof reply !== 'string' || reply.length === 0) {
|
|
195
|
+
throw new InterviewToolError('Turn returned no persona reply; the turn may not have succeeded.');
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: 'text', text: reply }],
|
|
199
|
+
_meta: {
|
|
200
|
+
reply,
|
|
201
|
+
messageCount: json?.messageCount,
|
|
202
|
+
message: json?.message,
|
|
203
|
+
userMessage: json?.userMessage,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function fetchTranscript(input, ctx, deps) {
|
|
208
|
+
if (!input.interviewId) {
|
|
209
|
+
throw new InterviewToolError("action 'transcript' requires interviewId.");
|
|
210
|
+
}
|
|
211
|
+
const response = await call(ctx, 'GET', `/api/interviews/${input.interviewId}`, 'fetch transcript');
|
|
212
|
+
if (!response.ok)
|
|
213
|
+
await throwForResponse(response, deps, 'fetch transcript');
|
|
214
|
+
const json = await readJson(response);
|
|
215
|
+
const interview = json?.interview;
|
|
216
|
+
if (!interview?.id) {
|
|
217
|
+
throw new InterviewToolError('Transcript fetch returned no interview.');
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: formatTranscript(interview) }],
|
|
221
|
+
_meta: { interview },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async function setStatus(input, ctx, deps, status) {
|
|
225
|
+
const action = status === 'completed' ? 'complete' : 'reopen';
|
|
226
|
+
if (!input.interviewId) {
|
|
227
|
+
throw new InterviewToolError(`action '${action}' requires interviewId.`);
|
|
228
|
+
}
|
|
229
|
+
const response = await call(ctx, 'PATCH', `/api/interviews/${input.interviewId}`, `${action} interview`, { status });
|
|
230
|
+
if (!response.ok)
|
|
231
|
+
await throwForResponse(response, deps, `${action} interview`);
|
|
232
|
+
const json = await readJson(response);
|
|
233
|
+
const interview = json?.interview;
|
|
234
|
+
if (!interview?.id) {
|
|
235
|
+
throw new InterviewToolError(`Could not ${action} interview — no interview returned.`);
|
|
236
|
+
}
|
|
237
|
+
const verb = status === 'completed' ? 'Completed' : 'Reopened';
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: 'text', text: `${verb} interview ${interview.id} (status: ${interview.status ?? status}).` }],
|
|
240
|
+
_meta: { interview },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export async function interviewPersona(input, deps) {
|
|
244
|
+
const parsed = InterviewPersonaInputSchema.safeParse(input);
|
|
245
|
+
if (!parsed.success) {
|
|
246
|
+
throw new InterviewToolError(`Invalid input — ${formatZodError(parsed.error)}`);
|
|
247
|
+
}
|
|
248
|
+
const ctx = makeCtx(deps);
|
|
249
|
+
const data = parsed.data;
|
|
250
|
+
switch (data.action) {
|
|
251
|
+
case 'start':
|
|
252
|
+
return startInterview(data, ctx, deps);
|
|
253
|
+
case 'turn':
|
|
254
|
+
return sendTurn(data, ctx, deps);
|
|
255
|
+
case 'transcript':
|
|
256
|
+
return fetchTranscript(data, ctx, deps);
|
|
257
|
+
case 'complete':
|
|
258
|
+
return setStatus(data, ctx, deps, 'completed');
|
|
259
|
+
case 'reopen':
|
|
260
|
+
return setStatus(data, ctx, deps, 'active');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=interview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/tools/interview.ts"],"names":[],"mappings":"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"}
|