@clien-ai/mcp 0.1.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/dist/config.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Runtime configuration for @clien-ai/mcp.
3
+ *
4
+ * All values are environment-overridable so the package can be pointed at a
5
+ * local dev server or the test Supabase project during integration tests.
6
+ * Defaults are production.
7
+ *
8
+ * Client IDs are PUBLIC PKCE clients — no secret, safe to embed.
9
+ */
10
+ const PROD_SUPABASE_URL = 'https://pnjwddgqxiohaziscoye.supabase.co';
11
+ const TEST_SUPABASE_URL = 'https://fudinhtlghtipznalrgy.supabase.co';
12
+ const PROD_CLIENT_ID = '9ab91ab2-844a-4b55-a54c-7f9765aa4aaa';
13
+ const TEST_CLIENT_ID = '7e40f4b2-c10d-4f51-a755-c05e513d3b96';
14
+ const DEFAULT_PORTS = [7873, 7874, 7875];
15
+ const DEFAULT_POLL_INTERVAL_MS = 5000;
16
+ const DEFAULT_OAUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes to complete browser consent
17
+ function readEnv(name) {
18
+ const val = process.env[name];
19
+ return val && val.length > 0 ? val : undefined;
20
+ }
21
+ /**
22
+ * Build runtime config. Prefers env vars (for dev/test), falls back to prod
23
+ * defaults. Switching to test mode: set CLIEN_MCP_ENV=test.
24
+ */
25
+ export function loadConfig() {
26
+ const env = (readEnv('CLIEN_MCP_ENV') ?? 'prod').toLowerCase();
27
+ const isTest = env === 'test';
28
+ const serverUrl = readEnv('CLIEN_MCP_SERVER_URL') ??
29
+ (isTest ? 'https://clien.ai' : 'https://clien.ai');
30
+ const supabaseUrl = readEnv('CLIEN_MCP_SUPABASE_URL') ??
31
+ (isTest ? TEST_SUPABASE_URL : PROD_SUPABASE_URL);
32
+ const clientId = readEnv('CLIEN_MCP_CLIENT_ID') ??
33
+ (isTest ? TEST_CLIENT_ID : PROD_CLIENT_ID);
34
+ const pollEnv = readEnv('CLIEN_MCP_POLL_INTERVAL_MS');
35
+ const pollIntervalMs = pollEnv ? Math.max(500, Number(pollEnv)) : DEFAULT_POLL_INTERVAL_MS;
36
+ return {
37
+ serverUrl: serverUrl.replace(/\/$/, ''),
38
+ supabaseUrl: supabaseUrl.replace(/\/$/, ''),
39
+ clientId,
40
+ scope: readEnv('CLIEN_MCP_SCOPE') ?? 'openid',
41
+ oauthPorts: DEFAULT_PORTS,
42
+ pollIntervalMs,
43
+ oauthTimeoutMs: DEFAULT_OAUTH_TIMEOUT_MS,
44
+ };
45
+ }
46
+ export const PACKAGE_NAME = '@clien-ai/mcp';
47
+ export const PACKAGE_VERSION = '0.1.0';
48
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmBH,MAAM,iBAAiB,GAAG,0CAA0C,CAAA;AACpE,MAAM,iBAAiB,GAAG,0CAA0C,CAAA;AAEpE,MAAM,cAAc,GAAG,sCAAsC,CAAA;AAC7D,MAAM,cAAc,GAAG,sCAAsC,CAAA;AAE7D,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAA;AACjD,MAAM,wBAAwB,GAAG,IAAI,CAAA;AACrC,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,wCAAwC;AAEvF,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC7B,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAA;IAC9D,MAAM,MAAM,GAAG,GAAG,KAAK,MAAM,CAAA;IAE7B,MAAM,SAAS,GACb,OAAO,CAAC,sBAAsB,CAAC;QAC/B,CAAC,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAA;IACpD,MAAM,WAAW,GACf,OAAO,CAAC,wBAAwB,CAAC;QACjC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAA;IAClD,MAAM,QAAQ,GACZ,OAAO,CAAC,qBAAqB,CAAC;QAC9B,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;IAE5C,MAAM,OAAO,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAA;IACrD,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAA;IAE1F,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACvC,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3C,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAC,IAAI,QAAQ;QAC7C,UAAU,EAAE,aAAa;QACzB,cAAc;QACd,cAAc,EAAE,wBAAwB;KACzC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAA;AAC3C,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAA"}
package/dist/server.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * MCP stdio server entry point.
3
+ *
4
+ * Registers the `clien_research` tool. Auth happens lazily on first tool call
5
+ * so the server can announce its tool list without a browser interruption.
6
+ */
7
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
10
+ import { loadConfig, PACKAGE_NAME, PACKAGE_VERSION } from './config.js';
11
+ import { ensureSession, } from './auth/oauth.js';
12
+ import { clearTokens, loadRefreshToken, saveRefreshToken, withTokenLock, } from './auth/storage.js';
13
+ import { ResearchToolError, runResearch, } from './tools/research.js';
14
+ function log(...args) {
15
+ // stderr only — stdout is the MCP transport.
16
+ console.error('[clien-mcp]', ...args);
17
+ }
18
+ /**
19
+ * Default browser opener — lazily imports `open` so the package still loads
20
+ * on systems that can't open a browser (we handle the error in OAuth).
21
+ */
22
+ async function defaultBrowserOpener() {
23
+ const mod = (await import('open'));
24
+ const fn = typeof mod === 'function'
25
+ ? mod
26
+ : mod.default;
27
+ return async (url) => {
28
+ await fn(url);
29
+ };
30
+ }
31
+ /**
32
+ * Input schema for the tool advert. Hand-rolled JSON Schema so we avoid the
33
+ * `zod-to-json-schema` dependency. Must stay in sync with `ResearchInputSchema`
34
+ * in `src/tools/research.ts` — the Zod parser is still the source of truth at
35
+ * runtime; this is only the spec advertised to the caller.
36
+ *
37
+ * Drift surfaces silently: the Zod parser still accepts the input, but strict
38
+ * MCP clients (`additionalProperties: false`) strip undeclared fields before
39
+ * they reach the parser, and LLM callers never learn the field exists.
40
+ * Enforcement: `__tests__/schema-contract.test.ts` walks both schemas and
41
+ * fails if their property keys or required-field sets diverge. Exported here
42
+ * so that test can import it. See
43
+ * `docs/solutions/design-patterns/cross-package-schema-duplication-contract-2026-05-12.md`.
44
+ */
45
+ export const RESEARCH_INPUT_SCHEMA = {
46
+ type: 'object',
47
+ properties: {
48
+ idea: {
49
+ type: 'string',
50
+ minLength: 1,
51
+ maxLength: 10_000,
52
+ description: 'The product idea, feature hypothesis, or research question to validate. ' +
53
+ 'Can be a one-liner or several paragraphs — more context yields a better report.',
54
+ },
55
+ depth: {
56
+ type: 'string',
57
+ enum: ['quick', 'deep'],
58
+ default: 'deep',
59
+ description: '"deep" (default) runs a 15-25 minute full research pass. ' +
60
+ '"quick" caps cost and runs a tighter pass.',
61
+ },
62
+ project_id: {
63
+ type: 'string',
64
+ format: 'uuid',
65
+ description: 'Optional Clien.ai project UUID to attach this run to. When omitted, ' +
66
+ 'the run is standalone and not associated with any project.',
67
+ },
68
+ project_name: {
69
+ type: 'string',
70
+ minLength: 1,
71
+ maxLength: 100,
72
+ description: 'Name for a new Clien.ai project to attach this run to. You SHOULD ' +
73
+ 'supply this whenever `project_id` is not set, otherwise the ' +
74
+ 'resulting report will not be discoverable in the Clien.ai web UI. ' +
75
+ 'Derive a short (2-4 word) human-readable name from the idea — ' +
76
+ 'typically the product name. Guidance on when to create a new ' +
77
+ 'project vs. reuse an existing one: create a NEW project (supply a ' +
78
+ 'fresh `project_name`) for each distinct research idea or product ' +
79
+ 'concept; REUSE an existing `project_id` only when the user ' +
80
+ 'explicitly asks to add follow-up research to a prior run on the ' +
81
+ 'same idea. Ignored when `project_id` is also provided.',
82
+ },
83
+ },
84
+ required: ['idea'],
85
+ additionalProperties: false,
86
+ };
87
+ export function createMcpServer() {
88
+ const config = loadConfig();
89
+ // Per-instance session cache. Scoping this to the closure (rather than the
90
+ // module) means each `createMcpServer()` call gets its own cache, so tests
91
+ // that build a fresh server per case start cold without any shared-state
92
+ // gymnastics. In production we only ever construct one server per process
93
+ // (`startStdioServer`), so behavior is identical.
94
+ let cachedSession = null;
95
+ async function getSession() {
96
+ if (cachedSession)
97
+ return cachedSession;
98
+ const opener = await defaultBrowserOpener();
99
+ cachedSession = await ensureSession({
100
+ config,
101
+ loadRefreshToken,
102
+ saveRefreshToken,
103
+ clearTokens,
104
+ openBrowser: opener,
105
+ withTokenLock,
106
+ });
107
+ return cachedSession;
108
+ }
109
+ // Invalidate the in-memory session so the next tool call rebuilds it from
110
+ // disk. Called from the polling loop when a 401 indicates server-side
111
+ // revocation — without this, a long-lived MCP process would keep using the
112
+ // cached (now-invalid) access token until process restart. See issue #168.
113
+ function invalidateSession() {
114
+ cachedSession = null;
115
+ }
116
+ const server = new Server({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
117
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
118
+ tools: [
119
+ {
120
+ name: 'clien_research',
121
+ description: 'Run a full product-research pass on an idea. Returns a markdown report plus ' +
122
+ 'structured report_data (hypotheses, competitors[], key findings). Typical wall time ' +
123
+ '15-25 minutes; progress is streamed via notifications/progress. ' +
124
+ 'First call opens a browser for one-time OAuth login; subsequent calls reuse the stored refresh token.',
125
+ inputSchema: RESEARCH_INPUT_SCHEMA,
126
+ },
127
+ ],
128
+ }));
129
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
+ if (request.params.name !== 'clien_research') {
131
+ throw new Error(`Unknown tool: ${request.params.name}`);
132
+ }
133
+ // Per MCP spec, `progressToken` may be a string OR a number, and `0` or
134
+ // `''` are valid token values. A truthy check (`progressToken ?`) silently
135
+ // drops progress for those callers. Compare against `undefined` instead.
136
+ const progressToken = request.params._meta?.progressToken;
137
+ const onProgress = progressToken !== undefined
138
+ ? async (progress) => {
139
+ try {
140
+ await server.notification({
141
+ method: 'notifications/progress',
142
+ params: {
143
+ progressToken,
144
+ progress: progress.progress,
145
+ total: progress.total,
146
+ message: progress.message,
147
+ },
148
+ });
149
+ }
150
+ catch (err) {
151
+ log('progress notification failed:', err);
152
+ }
153
+ }
154
+ : undefined;
155
+ try {
156
+ const session = await getSession();
157
+ const result = await runResearch(request.params.arguments, {
158
+ config,
159
+ session,
160
+ onProgress,
161
+ invalidateSession,
162
+ });
163
+ return {
164
+ content: result.content,
165
+ _meta: result._meta,
166
+ };
167
+ }
168
+ catch (err) {
169
+ const message = err instanceof ResearchToolError
170
+ ? err.userMessage
171
+ : err instanceof Error
172
+ ? err.message
173
+ : String(err);
174
+ log('tool error:', message);
175
+ return {
176
+ isError: true,
177
+ content: [{ type: 'text', text: message }],
178
+ };
179
+ }
180
+ });
181
+ return server;
182
+ }
183
+ export async function startStdioServer() {
184
+ const server = createMcpServer();
185
+ const transport = new StdioServerTransport();
186
+ await server.connect(transport);
187
+ log(`connected on stdio — ${PACKAGE_NAME}@${PACKAGE_VERSION}`);
188
+ }
189
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}