@tacitai/mcp-server 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quarterback AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Tacit — the knowledge backend for Claude Code
2
+
3
+ [![npm](https://img.shields.io/npm/v/@tacit/mcp-server)](https://www.npmjs.com/package/@tacit/mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
6
+ <!-- demo GIF: docs/assets/demo.gif TODO -->
7
+
8
+ ## What is this?
9
+
10
+ `@tacit/mcp-server` is a stdio Model Context Protocol (MCP) server that wraps
11
+ the Tacit `/api/v1` REST and SSE API. It turns a running Tacit instance into a
12
+ knowledge backend for Claude Code: your workspace knowledge, artifacts, and
13
+ synthesis pipeline become first-class tools available inside every Claude Code
14
+ session — no copy-paste, no context switching.
15
+
16
+ ## Quick start
17
+
18
+ Run the interactive setup wizard (requires a running Tacit instance):
19
+
20
+ ```bash
21
+ npx @tacit/mcp-server setup
22
+ ```
23
+
24
+ Pre-publication (from source):
25
+
26
+ ```bash
27
+ node packages/tacit-mcp-server/dist/index.js setup
28
+ ```
29
+
30
+ The wizard writes the `tacit` entry directly to `~/.claude/mcp.json`
31
+ (preserving the `${TACIT_TOKEN}` env reference) and validates connectivity
32
+ before it exits.
33
+
34
+ <!-- TODO: update to published docs URL after MV-1057g merges -->
35
+
36
+ ## Documentation
37
+
38
+ - [Quickstart](../../docs/content/quickstart.md)
39
+ - [API reference](../../docs/content/api-reference.md)
40
+ - [SDK reference](../../docs/content/sdk-reference.md)
41
+
42
+ ## Available MCP tools
43
+
44
+ | Tool | Description |
45
+ | ---- | ----------- |
46
+ | `synthesize` | Stream a grounded synthesis answer from workspace knowledge |
47
+ | `search` | Semantic and keyword search across workspace artifacts |
48
+ | `recall` | Retrieve Chronicle events and timeline context |
49
+ | `artifacts` | List, read, and create workspace artifacts |
50
+ | `ingest` | Ingest documents or URLs into the workspace knowledge base |
51
+
52
+ ## Requirements
53
+
54
+ - Node.js >= 18
55
+ - A running Tacit instance reachable at a configurable base URL
56
+ - A valid Tacit API token
57
+
58
+ ## Repository
59
+
60
+ Source: <https://github.com/scottkmcmillan/tacit>
61
+
62
+ Directory: `packages/tacit-mcp-server`
63
+
64
+ ## Contributing
65
+
66
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for build, test, and pull request
67
+ instructions.
68
+
69
+ ## License
70
+
71
+ [MIT](LICENSE) — Copyright (c) 2026 Quarterback AI.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TacitClient — REST/SSE client over TACIT_API_URL.
3
+ *
4
+ * Security invariants (enforced here and NEVER relaxed):
5
+ * - TACIT_TOKEN is NEVER written to any output stream (stdout, stderr, logs).
6
+ * - The Authorization header value is NEVER logged at any level.
7
+ * - No anthropic/openai SDK import.
8
+ * - Token sourced exclusively from process.env.TACIT_TOKEN.
9
+ */
10
+ export interface TacitClientOptions {
11
+ apiUrl?: string;
12
+ token?: string;
13
+ workspaceId?: string;
14
+ /** Override the root directory used to locate .tacit/sync.json (default: process.cwd()). */
15
+ rootDir?: string;
16
+ }
17
+ export declare class TacitClient {
18
+ private readonly apiUrl;
19
+ private readonly token;
20
+ private readonly _fetch;
21
+ private readonly _rootDir;
22
+ readonly workspaceId: string;
23
+ /**
24
+ * @param fetchOrOpts - Optional: a custom fetch implementation (for testing),
25
+ * or a TacitClientOptions object. If a function is provided it overrides
26
+ * global fetch. Configuration falls back to environment variables.
27
+ * @param opts - Additional options (apiUrl, token, workspaceId, rootDir).
28
+ * Only used when fetchOrOpts is a fetch function.
29
+ */
30
+ constructor(fetchOrOpts?: typeof globalThis.fetch | TacitClientOptions, opts?: TacitClientOptions);
31
+ /**
32
+ * Resolve the workspace ID:
33
+ * 1. Read workspace_id from <rootDir>/.tacit/sync.json if the file exists.
34
+ * 2. Fall back to TACIT_WORKSPACE_ID env var.
35
+ * 3. Return null if neither source is available.
36
+ */
37
+ resolveWorkspaceId(): Promise<string | null>;
38
+ /** Build auth headers. Value is NEVER written to logs. */
39
+ private authHeaders;
40
+ /** GET request returning parsed JSON. */
41
+ get<T = unknown>(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
42
+ /** POST request returning parsed JSON. */
43
+ post<T = unknown>(endpoint: string, body: unknown): Promise<T>;
44
+ /** PATCH request returning parsed JSON. */
45
+ patch<T = unknown>(endpoint: string, body: unknown): Promise<T>;
46
+ /**
47
+ * POST to an SSE endpoint, returning an async generator of text chunks.
48
+ * The Authorization header value is never written to process.stderr or stdout.
49
+ */
50
+ postSSE(endpoint: string, body: unknown): AsyncGenerator<string>;
51
+ /** POST multipart form data (for file ingest). */
52
+ postFormData<T = unknown>(endpoint: string, formData: FormData): Promise<T>;
53
+ }
54
+ export declare class TacitApiError extends Error {
55
+ readonly statusCode: number;
56
+ readonly body: string;
57
+ readonly endpoint: string;
58
+ constructor(statusCode: number, body: string, endpoint: string);
59
+ }
60
+ export declare function getClient(): TacitClient;
package/dist/client.js ADDED
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * TacitClient — REST/SSE client over TACIT_API_URL.
5
+ *
6
+ * Security invariants (enforced here and NEVER relaxed):
7
+ * - TACIT_TOKEN is NEVER written to any output stream (stdout, stderr, logs).
8
+ * - The Authorization header value is NEVER logged at any level.
9
+ * - No anthropic/openai SDK import.
10
+ * - Token sourced exclusively from process.env.TACIT_TOKEN.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.TacitApiError = exports.TacitClient = void 0;
47
+ exports.getClient = getClient;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ class TacitClient {
51
+ apiUrl;
52
+ token;
53
+ _fetch;
54
+ _rootDir;
55
+ workspaceId;
56
+ /**
57
+ * @param fetchOrOpts - Optional: a custom fetch implementation (for testing),
58
+ * or a TacitClientOptions object. If a function is provided it overrides
59
+ * global fetch. Configuration falls back to environment variables.
60
+ * @param opts - Additional options (apiUrl, token, workspaceId, rootDir).
61
+ * Only used when fetchOrOpts is a fetch function.
62
+ */
63
+ constructor(fetchOrOpts, opts = {}) {
64
+ let resolvedOpts = {};
65
+ if (typeof fetchOrOpts === 'function') {
66
+ this._fetch = fetchOrOpts;
67
+ resolvedOpts = opts;
68
+ }
69
+ else {
70
+ if (fetchOrOpts)
71
+ resolvedOpts = fetchOrOpts;
72
+ this._fetch = globalThis.fetch.bind(globalThis);
73
+ }
74
+ this.apiUrl = (resolvedOpts.apiUrl ?? process.env['TACIT_API_URL'] ?? 'http://localhost:7473').replace(/\/$/, '');
75
+ // Token from env only — NEVER log this value.
76
+ this.token = resolvedOpts.token ?? process.env['TACIT_TOKEN'] ?? '';
77
+ this._rootDir = resolvedOpts.rootDir ?? process.cwd();
78
+ // MV-1057e: read workspace_id from .tacit/sync.json if present.
79
+ // Whichever mission (1057c or 1057e) lands second wires the full schema.
80
+ let resolvedWorkspaceId = resolvedOpts.workspaceId ?? process.env['TACIT_WORKSPACE_ID'] ?? '';
81
+ try {
82
+ const syncPath = path.join(this._rootDir, '.tacit', 'sync.json');
83
+ if (fs.existsSync(syncPath)) {
84
+ const sync = JSON.parse(fs.readFileSync(syncPath, 'utf-8'));
85
+ if (typeof sync['workspace_id'] === 'string' && sync['workspace_id']) {
86
+ resolvedWorkspaceId = sync['workspace_id'];
87
+ }
88
+ }
89
+ }
90
+ catch {
91
+ // Soft-coupled: ignore read errors; fall back to env.
92
+ }
93
+ this.workspaceId = resolvedWorkspaceId;
94
+ }
95
+ /**
96
+ * Resolve the workspace ID:
97
+ * 1. Read workspace_id from <rootDir>/.tacit/sync.json if the file exists.
98
+ * 2. Fall back to TACIT_WORKSPACE_ID env var.
99
+ * 3. Return null if neither source is available.
100
+ */
101
+ async resolveWorkspaceId() {
102
+ try {
103
+ const syncPath = path.join(this._rootDir, '.tacit', 'sync.json');
104
+ if (fs.existsSync(syncPath)) {
105
+ const sync = JSON.parse(fs.readFileSync(syncPath, 'utf-8'));
106
+ if (typeof sync['workspace_id'] === 'string' && sync['workspace_id']) {
107
+ return sync['workspace_id'];
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ // Soft-coupled: ignore read errors; fall back to env.
113
+ }
114
+ const envId = process.env['TACIT_WORKSPACE_ID'];
115
+ return (envId && envId.length > 0) ? envId : null;
116
+ }
117
+ /** Build auth headers. Value is NEVER written to logs. */
118
+ authHeaders() {
119
+ const headers = {
120
+ 'Content-Type': 'application/json',
121
+ };
122
+ if (this.token) {
123
+ // Authorization header value intentionally excluded from all logging paths.
124
+ headers['Authorization'] = `Bearer ${this.token}`;
125
+ }
126
+ return headers;
127
+ }
128
+ /** GET request returning parsed JSON. */
129
+ async get(endpoint, params) {
130
+ const url = new URL(`${this.apiUrl}${endpoint}`);
131
+ if (params) {
132
+ for (const [k, v] of Object.entries(params)) {
133
+ if (v !== undefined)
134
+ url.searchParams.set(k, String(v));
135
+ }
136
+ }
137
+ const res = await this._fetch(url.toString(), {
138
+ method: 'GET',
139
+ headers: this.authHeaders(),
140
+ });
141
+ if (!res.ok) {
142
+ const body = await res.text().catch(() => '');
143
+ throw new TacitApiError(res.status, body, endpoint);
144
+ }
145
+ return res.json();
146
+ }
147
+ /** POST request returning parsed JSON. */
148
+ async post(endpoint, body) {
149
+ const res = await this._fetch(`${this.apiUrl}${endpoint}`, {
150
+ method: 'POST',
151
+ headers: this.authHeaders(),
152
+ body: JSON.stringify(body),
153
+ });
154
+ if (!res.ok) {
155
+ const text = await res.text().catch(() => '');
156
+ throw new TacitApiError(res.status, text, endpoint);
157
+ }
158
+ return res.json();
159
+ }
160
+ /** PATCH request returning parsed JSON. */
161
+ async patch(endpoint, body) {
162
+ const res = await this._fetch(`${this.apiUrl}${endpoint}`, {
163
+ method: 'PATCH',
164
+ headers: this.authHeaders(),
165
+ body: JSON.stringify(body),
166
+ });
167
+ if (!res.ok) {
168
+ const text = await res.text().catch(() => '');
169
+ throw new TacitApiError(res.status, text, endpoint);
170
+ }
171
+ return res.json();
172
+ }
173
+ /**
174
+ * POST to an SSE endpoint, returning an async generator of text chunks.
175
+ * The Authorization header value is never written to process.stderr or stdout.
176
+ */
177
+ async *postSSE(endpoint, body) {
178
+ const res = await this._fetch(`${this.apiUrl}${endpoint}`, {
179
+ method: 'POST',
180
+ headers: {
181
+ ...this.authHeaders(),
182
+ 'Accept': 'text/event-stream',
183
+ },
184
+ body: JSON.stringify(body),
185
+ });
186
+ if (!res.ok) {
187
+ const text = await res.text().catch(() => '');
188
+ throw new TacitApiError(res.status, text, endpoint);
189
+ }
190
+ if (!res.body) {
191
+ throw new Error('SSE response has no body');
192
+ }
193
+ const reader = res.body.getReader();
194
+ const decoder = new TextDecoder();
195
+ let buffer = '';
196
+ try {
197
+ while (true) {
198
+ const { done, value } = await reader.read();
199
+ if (done)
200
+ break;
201
+ buffer += decoder.decode(value, { stream: true });
202
+ const lines = buffer.split('\n');
203
+ buffer = lines.pop() ?? '';
204
+ for (const line of lines) {
205
+ if (line.startsWith('data: ')) {
206
+ const data = line.slice(6).trim();
207
+ if (data === '[DONE]')
208
+ return;
209
+ if (data)
210
+ yield data;
211
+ }
212
+ }
213
+ }
214
+ // Flush remaining buffer
215
+ if (buffer.startsWith('data: ')) {
216
+ const data = buffer.slice(6).trim();
217
+ if (data && data !== '[DONE]')
218
+ yield data;
219
+ }
220
+ }
221
+ finally {
222
+ reader.releaseLock();
223
+ }
224
+ }
225
+ /** POST multipart form data (for file ingest). */
226
+ async postFormData(endpoint, formData) {
227
+ // Build headers without Content-Type (let fetch set multipart boundary).
228
+ const headers = {};
229
+ if (this.token) {
230
+ headers['Authorization'] = `Bearer ${this.token}`;
231
+ }
232
+ const res = await this._fetch(`${this.apiUrl}${endpoint}`, {
233
+ method: 'POST',
234
+ headers,
235
+ body: formData,
236
+ });
237
+ if (!res.ok) {
238
+ const text = await res.text().catch(() => '');
239
+ throw new TacitApiError(res.status, text, endpoint);
240
+ }
241
+ return res.json();
242
+ }
243
+ }
244
+ exports.TacitClient = TacitClient;
245
+ class TacitApiError extends Error {
246
+ statusCode;
247
+ body;
248
+ endpoint;
249
+ constructor(statusCode, body, endpoint) {
250
+ super(`Tacit API error ${statusCode} on ${endpoint}`);
251
+ this.statusCode = statusCode;
252
+ this.body = body;
253
+ this.endpoint = endpoint;
254
+ this.name = 'TacitApiError';
255
+ }
256
+ }
257
+ exports.TacitApiError = TacitApiError;
258
+ /** Shared singleton client (initialized lazily on first use). */
259
+ let _defaultClient = null;
260
+ function getClient() {
261
+ if (!_defaultClient) {
262
+ _defaultClient = new TacitClient();
263
+ }
264
+ return _defaultClient;
265
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tacit/mcp-server — stdio MCP server
4
+ *
5
+ * Transport: StdioServerTransport (NOT HTTP).
6
+ * Per-cell invariant: spawned once per IDE session per user; never a daemon.
7
+ * No network listener. All egress goes through TACIT_API_URL only.
8
+ *
9
+ * Security invariants:
10
+ * - No anthropic/openai import.
11
+ * - TACIT_TOKEN never logged.
12
+ * - Token from env only.
13
+ *
14
+ * Compatibility guard (D-7 / KR4):
15
+ * Reads own package.json compatibility.coreArtifact (semver range).
16
+ * If TACIT_CORE_VERSION env is set, checks range; exits non-zero on mismatch.
17
+ * Prints COMPAT_RANGE: <range> on startup for oracle D4.
18
+ *
19
+ * setup subcommand stub (MV-1057h hook point):
20
+ * `tacit-mcp-server setup` is reserved for MV-1057h setup wizard.
21
+ * Currently prints a placeholder message and exits 0.
22
+ */
23
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // SPDX-License-Identifier: MIT
4
+ /**
5
+ * @tacit/mcp-server — stdio MCP server
6
+ *
7
+ * Transport: StdioServerTransport (NOT HTTP).
8
+ * Per-cell invariant: spawned once per IDE session per user; never a daemon.
9
+ * No network listener. All egress goes through TACIT_API_URL only.
10
+ *
11
+ * Security invariants:
12
+ * - No anthropic/openai import.
13
+ * - TACIT_TOKEN never logged.
14
+ * - Token from env only.
15
+ *
16
+ * Compatibility guard (D-7 / KR4):
17
+ * Reads own package.json compatibility.coreArtifact (semver range).
18
+ * If TACIT_CORE_VERSION env is set, checks range; exits non-zero on mismatch.
19
+ * Prints COMPAT_RANGE: <range> on startup for oracle D4.
20
+ *
21
+ * setup subcommand stub (MV-1057h hook point):
22
+ * `tacit-mcp-server setup` is reserved for MV-1057h setup wizard.
23
+ * Currently prints a placeholder message and exits 0.
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
60
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
61
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
62
+ const semver = __importStar(require("semver"));
63
+ const path = __importStar(require("path"));
64
+ const fs = __importStar(require("fs"));
65
+ // Tool handlers
66
+ const search_js_1 = require("./tools/search.js");
67
+ const synthesize_js_1 = require("./tools/synthesize.js");
68
+ const recall_js_1 = require("./tools/recall.js");
69
+ const artifacts_js_1 = require("./tools/artifacts.js");
70
+ const ingest_js_1 = require("./tools/ingest.js");
71
+ // Setup wizard (MV-1057h)
72
+ const setup_js_1 = require("./setup.js");
73
+ // ─── Compatibility guard ──────────────────────────────────────────────────────
74
+ function checkCompatibility() {
75
+ const pkgPath = path.join(__dirname, '..', 'package.json');
76
+ let pkg = {};
77
+ try {
78
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
79
+ }
80
+ catch {
81
+ // Non-fatal if package.json cannot be read at runtime.
82
+ return;
83
+ }
84
+ const compat = pkg['compatibility'];
85
+ const coreArtifactRange = compat?.['coreArtifact'];
86
+ if (typeof coreArtifactRange === 'string') {
87
+ // Print range for oracle D4 grep.
88
+ process.stderr.write(`COMPAT_RANGE: ${coreArtifactRange}\n`);
89
+ const coreVersion = process.env['TACIT_CORE_VERSION'];
90
+ if (coreVersion) {
91
+ const valid = semver.satisfies(coreVersion, coreArtifactRange, { includePrerelease: false });
92
+ if (!valid) {
93
+ process.stderr.write(`ERROR: TACIT_CORE_VERSION=${coreVersion} does not satisfy compatibility.coreArtifact range ${coreArtifactRange}\n`);
94
+ process.stderr.write(`This version of @tacit/mcp-server requires core artifact version in range: ${coreArtifactRange}\n`);
95
+ process.exit(1);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // ─── Tool registry ────────────────────────────────────────────────────────────
101
+ const TOOLS = [
102
+ search_js_1.SEARCH_TOOL_DEF,
103
+ synthesize_js_1.SYNTHESIZE_TOOL_DEF,
104
+ recall_js_1.RECALL_TOOL_DEF,
105
+ artifacts_js_1.ARTIFACT_LIST_TOOL_DEF,
106
+ artifacts_js_1.ARTIFACT_CREATE_TOOL_DEF,
107
+ artifacts_js_1.ARTIFACT_UPDATE_TOOL_DEF,
108
+ artifacts_js_1.ARTIFACT_PUBLISH_TOOL_DEF,
109
+ ingest_js_1.INGEST_FILE_TOOL_DEF,
110
+ ingest_js_1.INGEST_URL_TOOL_DEF,
111
+ ];
112
+ async function dispatchTool(name, args) {
113
+ switch (name) {
114
+ case 'tacit_search':
115
+ return (0, search_js_1.handleSearch)(args);
116
+ case 'tacit_synthesize':
117
+ case 'tacit_ask':
118
+ case 'tacit_compose':
119
+ return (0, synthesize_js_1.handleSynthesize)(args);
120
+ case 'tacit_recall':
121
+ return (0, recall_js_1.handleRecall)(args);
122
+ case 'tacit_artifact_list':
123
+ return (0, artifacts_js_1.handleArtifactList)(args);
124
+ case 'tacit_artifact_create':
125
+ return (0, artifacts_js_1.handleArtifactCreate)(args);
126
+ case 'tacit_artifact_update':
127
+ return (0, artifacts_js_1.handleArtifactUpdate)(args);
128
+ case 'tacit_artifact_publish':
129
+ return (0, artifacts_js_1.handleArtifactPublish)(args);
130
+ case 'tacit_ingest_file':
131
+ return (0, ingest_js_1.handleIngestFile)(args);
132
+ case 'tacit_ingest_url':
133
+ return (0, ingest_js_1.handleIngestUrl)(args);
134
+ default:
135
+ return {
136
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
137
+ isError: true,
138
+ };
139
+ }
140
+ }
141
+ // ─── Main ─────────────────────────────────────────────────────────────────────
142
+ async function run() {
143
+ // Handle setup subcommand before starting stdio transport (MV-1057h).
144
+ if (process.argv[2] === 'setup') {
145
+ await (0, setup_js_1.setup)(process.argv.slice(3));
146
+ return;
147
+ }
148
+ // Compatibility guard — exits non-zero on version mismatch.
149
+ checkCompatibility();
150
+ const server = new index_js_1.Server({ name: '@tacit/mcp-server', version: '0.1.0' }, { capabilities: { tools: {} } });
151
+ // tools/list
152
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
153
+ tools: TOOLS,
154
+ }));
155
+ // tools/call
156
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
157
+ const { name, arguments: args } = request.params;
158
+ const result = await dispatchTool(name, args ?? {});
159
+ return result;
160
+ });
161
+ // Wire stdio transport — NOT an HTTP server (D-4/D-10).
162
+ const transport = new stdio_js_1.StdioServerTransport();
163
+ await server.connect(transport);
164
+ }
165
+ run().catch((err) => {
166
+ process.stderr.write(`tacit-mcp-server fatal: ${err instanceof Error ? err.message : String(err)}\n`);
167
+ process.exit(1);
168
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * setup — wizard subcommand for @tacit/mcp-server
3
+ *
4
+ * Usage:
5
+ * node dist/index.js setup [--dry-run] [--api-url <url>] [--token <token>] [--smoke]
6
+ *
7
+ * Contract (§2.1 of sparc_specification.md, Phase-2 QE resolutions):
8
+ * F1 smoke — calls tacit_synthesize via getClient()/postSSE; exits non-zero on empty/error.
9
+ * F2 env-ref — writes TACIT_TOKEN as the LITERAL placeholder "${TACIT_TOKEN}" (never the real value).
10
+ * Merge — deep-merges ONLY mcpServers.tacit; all other keys preserved.
11
+ * Idempotent — same --api-url → byte-identical mcp.json.
12
+ * Secrets — token NEVER written to any output stream.
13
+ *
14
+ * Security invariants:
15
+ * - Token is NEVER logged or written to disk.
16
+ * - Authorization header value excluded from all logging paths.
17
+ */
18
+ export declare function setup(argv: string[]): Promise<void>;