@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/dist/setup.js ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * setup — wizard subcommand for @tacit/mcp-server
5
+ *
6
+ * Usage:
7
+ * node dist/index.js setup [--dry-run] [--api-url <url>] [--token <token>] [--smoke]
8
+ *
9
+ * Contract (§2.1 of sparc_specification.md, Phase-2 QE resolutions):
10
+ * F1 smoke — calls tacit_synthesize via getClient()/postSSE; exits non-zero on empty/error.
11
+ * F2 env-ref — writes TACIT_TOKEN as the LITERAL placeholder "${TACIT_TOKEN}" (never the real value).
12
+ * Merge — deep-merges ONLY mcpServers.tacit; all other keys preserved.
13
+ * Idempotent — same --api-url → byte-identical mcp.json.
14
+ * Secrets — token NEVER written to any output stream.
15
+ *
16
+ * Security invariants:
17
+ * - Token is NEVER logged or written to disk.
18
+ * - Authorization header value excluded from all logging paths.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.setup = setup;
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const os = __importStar(require("os"));
58
+ const client_js_1 = require("./client.js");
59
+ function parseArgs(argv) {
60
+ const opts = {
61
+ dryRun: false,
62
+ apiUrl: null,
63
+ token: null,
64
+ smoke: false,
65
+ };
66
+ for (let i = 0; i < argv.length; i++) {
67
+ const arg = argv[i];
68
+ if (arg === '--dry-run') {
69
+ opts.dryRun = true;
70
+ }
71
+ else if (arg === '--smoke') {
72
+ opts.smoke = true;
73
+ }
74
+ else if (arg === '--api-url') {
75
+ const next = argv[i + 1];
76
+ if (!next || next.startsWith('--')) {
77
+ process.stderr.write('Error: --api-url requires a value\n');
78
+ process.exit(1);
79
+ }
80
+ opts.apiUrl = next;
81
+ i++;
82
+ }
83
+ else if (arg === '--token') {
84
+ const next = argv[i + 1];
85
+ if (!next || next.startsWith('--')) {
86
+ process.stderr.write('Error: --token requires a value\n');
87
+ process.exit(1);
88
+ }
89
+ opts.token = next;
90
+ i++;
91
+ }
92
+ }
93
+ return opts;
94
+ }
95
+ function claudeDir() {
96
+ return path.join(os.homedir(), '.claude');
97
+ }
98
+ function mcpJsonPath() {
99
+ return path.join(claudeDir(), 'mcp.json');
100
+ }
101
+ function readExistingConfig() {
102
+ const p = mcpJsonPath();
103
+ if (!fs.existsSync(p)) {
104
+ return {};
105
+ }
106
+ try {
107
+ const raw = fs.readFileSync(p, 'utf-8');
108
+ return JSON.parse(raw);
109
+ }
110
+ catch {
111
+ // Non-parseable file — start fresh but preserve as-is would corrupt; warn and start fresh.
112
+ process.stderr.write(`Warning: ~/.claude/mcp.json could not be parsed; will overwrite with merged config.\n`);
113
+ return {};
114
+ }
115
+ }
116
+ function buildTacitEntry(apiUrl) {
117
+ return {
118
+ command: 'npx',
119
+ args: ['-y', '@tacit/mcp-server'],
120
+ env: {
121
+ // F2: literal placeholder — Claude Code expands ${VAR} at launch; real token NEVER on disk.
122
+ TACIT_TOKEN: '${TACIT_TOKEN}',
123
+ // TACIT_API_URL is a non-secret URL; written as-is.
124
+ TACIT_API_URL: apiUrl,
125
+ },
126
+ };
127
+ }
128
+ function mergeConfig(existing, tacitEntry) {
129
+ const servers = existing.mcpServers ?? {};
130
+ // Deep-merge ONLY tacit key; all other mcpServers entries and top-level keys preserved.
131
+ const merged = {
132
+ ...existing,
133
+ mcpServers: {
134
+ ...servers,
135
+ tacit: tacitEntry,
136
+ },
137
+ };
138
+ return merged;
139
+ }
140
+ function configToBytes(cfg) {
141
+ return JSON.stringify(cfg, null, 2) + '\n';
142
+ }
143
+ // ─── Smoke synthesis ──────────────────────────────────────────────────────────
144
+ const SMOKE_QUERY = 'What is in this knowledge base?';
145
+ async function runSmoke(apiUrl, token) {
146
+ const client = new client_js_1.TacitClient({
147
+ apiUrl,
148
+ token: token ?? process.env['TACIT_TOKEN'] ?? '',
149
+ });
150
+ let body = '';
151
+ try {
152
+ // F1: use existing postSSE path (NOT a raw fetch).
153
+ for await (const chunk of client.postSSE('/api/v1/mind/synthesize/stream', {
154
+ query: SMOKE_QUERY,
155
+ })) {
156
+ body += chunk;
157
+ }
158
+ }
159
+ catch (err) {
160
+ const msg = err instanceof client_js_1.TacitApiError
161
+ ? `API error ${err.statusCode}: ${err.body}`
162
+ : err instanceof Error ? err.message : String(err);
163
+ process.stderr.write(`SMOKE_SYNTH_FAIL: ${msg}\n`);
164
+ process.exit(1);
165
+ }
166
+ if (!body || body.length === 0) {
167
+ process.stderr.write('SMOKE_SYNTH_FAIL: empty response body\n');
168
+ process.exit(1);
169
+ }
170
+ // Print charcount for oracle D6 verification.
171
+ process.stdout.write(`SMOKE_SYNTH_OK:${body.length}\n`);
172
+ process.exit(0);
173
+ }
174
+ // ─── Main export ──────────────────────────────────────────────────────────────
175
+ async function setup(argv) {
176
+ const opts = parseArgs(argv);
177
+ // Resolve api-url: flag > env. Token: flag > env (never logged).
178
+ const apiUrl = opts.apiUrl ?? process.env['TACIT_API_URL'] ?? null;
179
+ const token = opts.token ?? process.env['TACIT_TOKEN'] ?? null;
180
+ if (!apiUrl) {
181
+ process.stderr.write('Error: no API URL configured.\n' +
182
+ 'Provide --api-url <url> or set TACIT_API_URL environment variable.\n' +
183
+ 'Example: node dist/index.js setup --api-url http://localhost:7473 --token <your-token>\n');
184
+ process.exit(1);
185
+ }
186
+ const tacitEntry = buildTacitEntry(apiUrl);
187
+ const existing = readExistingConfig();
188
+ const merged = mergeConfig(existing, tacitEntry);
189
+ const output = configToBytes(merged);
190
+ // D10: emit confirmation string BEFORE writing (also printed in dry-run).
191
+ process.stdout.write('Writing tacit entry to ~/.claude/mcp.json...\n');
192
+ if (opts.dryRun) {
193
+ process.stdout.write('(dry-run) Would write:\n');
194
+ process.stdout.write(output);
195
+ process.exit(0);
196
+ return;
197
+ }
198
+ // Create ~/.claude with mode 0700 if absent.
199
+ const dir = claudeDir();
200
+ if (!fs.existsSync(dir)) {
201
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
202
+ }
203
+ const dest = mcpJsonPath();
204
+ fs.writeFileSync(dest, output, { encoding: 'utf-8', mode: 0o600 });
205
+ if (opts.smoke) {
206
+ await runSmoke(apiUrl, token);
207
+ // runSmoke calls process.exit internally; return here for type safety.
208
+ return;
209
+ }
210
+ process.exit(0);
211
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Artifact tools:
3
+ * tacit_artifact_list — GET /api/v1/artifacts
4
+ * tacit_artifact_create — POST /api/v1/artifacts (career-scoped handler)
5
+ * tacit_artifact_update — PATCH /api/v1/artifacts/:id
6
+ * tacit_artifact_publish — POST /api/v1/artifacts/:id/publish
7
+ *
8
+ * No public single-GET /api/v1/artifacts/:id (HMAC-internal only; excluded per brief).
9
+ *
10
+ * tacit_artifact_create constraints (D-19/brief KR1):
11
+ * - Routes to handlers::artifacts_career::create_artifact
12
+ * - Backend CreateArtifactRequest accepts ONLY: title, domain, output_format,
13
+ * portfolio_id, structured_metadata. portfolio_id is REQUIRED (403 without it).
14
+ * - artifact_kind is hardcoded server-side (GENERIC) — the client must NOT send it.
15
+ * - The artifact body is populated server-side from structured_metadata
16
+ * (e.g. full_posting_text); the client does NOT send a content field.
17
+ * - This tool always sends "domain":"CAREER" in the request body.
18
+ */
19
+ import { z } from 'zod';
20
+ import { TacitClient } from '../client.js';
21
+ export interface ToolRequest {
22
+ execute(): Promise<{
23
+ content: Array<{
24
+ type: 'text';
25
+ text: string;
26
+ }>;
27
+ isError?: boolean;
28
+ }>;
29
+ }
30
+ export declare const artifactListInputSchema: z.ZodObject<{
31
+ limit: z.ZodOptional<z.ZodNumber>;
32
+ offset: z.ZodOptional<z.ZodNumber>;
33
+ status: z.ZodOptional<z.ZodString>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ limit?: number | undefined;
36
+ status?: string | undefined;
37
+ offset?: number | undefined;
38
+ }, {
39
+ limit?: number | undefined;
40
+ status?: string | undefined;
41
+ offset?: number | undefined;
42
+ }>;
43
+ export type ArtifactListInput = z.infer<typeof artifactListInputSchema>;
44
+ export declare const ARTIFACT_LIST_TOOL_DEF: {
45
+ name: "tacit_artifact_list";
46
+ description: string;
47
+ inputSchema: {
48
+ type: "object";
49
+ properties: {
50
+ limit: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ offset: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ status: {
59
+ type: string;
60
+ description: string;
61
+ };
62
+ };
63
+ required: never[];
64
+ };
65
+ };
66
+ export declare function buildArtifactListRequest(client: TacitClient, input: ArtifactListInput): ToolRequest;
67
+ export declare function handleArtifactList(args: unknown): Promise<{
68
+ content: Array<{
69
+ type: 'text';
70
+ text: string;
71
+ }>;
72
+ isError?: boolean;
73
+ }>;
74
+ export declare const artifactCreateInputSchema: z.ZodObject<{
75
+ title: z.ZodString;
76
+ domain: z.ZodOptional<z.ZodString>;
77
+ portfolio_id: z.ZodString;
78
+ structured_metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
79
+ }, "strip", z.ZodTypeAny, {
80
+ portfolio_id: string;
81
+ title: string;
82
+ domain?: string | undefined;
83
+ structured_metadata?: Record<string, unknown> | undefined;
84
+ }, {
85
+ portfolio_id: string;
86
+ title: string;
87
+ domain?: string | undefined;
88
+ structured_metadata?: Record<string, unknown> | undefined;
89
+ }>;
90
+ export type ArtifactCreateInput = z.infer<typeof artifactCreateInputSchema>;
91
+ export declare const ARTIFACT_CREATE_TOOL_DEF: {
92
+ name: "tacit_artifact_create";
93
+ description: string;
94
+ inputSchema: {
95
+ type: "object";
96
+ properties: {
97
+ title: {
98
+ type: string;
99
+ description: string;
100
+ };
101
+ domain: {
102
+ type: string;
103
+ description: string;
104
+ };
105
+ portfolio_id: {
106
+ type: string;
107
+ description: string;
108
+ };
109
+ structured_metadata: {
110
+ type: string;
111
+ description: string;
112
+ };
113
+ };
114
+ required: string[];
115
+ };
116
+ };
117
+ export declare function buildArtifactCreateRequest(client: TacitClient, input: ArtifactCreateInput): ToolRequest;
118
+ export declare function handleArtifactCreate(args: unknown): Promise<{
119
+ content: Array<{
120
+ type: 'text';
121
+ text: string;
122
+ }>;
123
+ isError?: boolean;
124
+ }>;
125
+ export declare const artifactUpdateInputSchema: z.ZodObject<{
126
+ id: z.ZodString;
127
+ title: z.ZodOptional<z.ZodString>;
128
+ content: z.ZodOptional<z.ZodString>;
129
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ id: string;
132
+ content?: string | undefined;
133
+ title?: string | undefined;
134
+ tags?: string[] | undefined;
135
+ }, {
136
+ id: string;
137
+ content?: string | undefined;
138
+ title?: string | undefined;
139
+ tags?: string[] | undefined;
140
+ }>;
141
+ export type ArtifactUpdateInput = z.infer<typeof artifactUpdateInputSchema>;
142
+ export declare const ARTIFACT_UPDATE_TOOL_DEF: {
143
+ name: "tacit_artifact_update";
144
+ description: string;
145
+ inputSchema: {
146
+ type: "object";
147
+ properties: {
148
+ id: {
149
+ type: string;
150
+ description: string;
151
+ };
152
+ title: {
153
+ type: string;
154
+ description: string;
155
+ };
156
+ content: {
157
+ type: string;
158
+ description: string;
159
+ };
160
+ tags: {
161
+ type: string;
162
+ items: {
163
+ type: string;
164
+ };
165
+ description: string;
166
+ };
167
+ };
168
+ required: string[];
169
+ };
170
+ };
171
+ export declare function buildArtifactUpdateRequest(client: TacitClient, input: ArtifactUpdateInput): ToolRequest;
172
+ export declare function handleArtifactUpdate(args: unknown): Promise<{
173
+ content: Array<{
174
+ type: 'text';
175
+ text: string;
176
+ }>;
177
+ isError?: boolean;
178
+ }>;
179
+ export declare const artifactPublishInputSchema: z.ZodObject<{
180
+ id: z.ZodString;
181
+ }, "strip", z.ZodTypeAny, {
182
+ id: string;
183
+ }, {
184
+ id: string;
185
+ }>;
186
+ export type ArtifactPublishInput = z.infer<typeof artifactPublishInputSchema>;
187
+ export declare const ARTIFACT_PUBLISH_TOOL_DEF: {
188
+ name: "tacit_artifact_publish";
189
+ description: string;
190
+ inputSchema: {
191
+ type: "object";
192
+ properties: {
193
+ id: {
194
+ type: string;
195
+ description: string;
196
+ };
197
+ };
198
+ required: string[];
199
+ };
200
+ };
201
+ export declare function buildArtifactPublishRequest(client: TacitClient, input: ArtifactPublishInput): ToolRequest;
202
+ export declare function handleArtifactPublish(args: unknown): Promise<{
203
+ content: Array<{
204
+ type: 'text';
205
+ text: string;
206
+ }>;
207
+ isError?: boolean;
208
+ }>;
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Artifact tools:
5
+ * tacit_artifact_list — GET /api/v1/artifacts
6
+ * tacit_artifact_create — POST /api/v1/artifacts (career-scoped handler)
7
+ * tacit_artifact_update — PATCH /api/v1/artifacts/:id
8
+ * tacit_artifact_publish — POST /api/v1/artifacts/:id/publish
9
+ *
10
+ * No public single-GET /api/v1/artifacts/:id (HMAC-internal only; excluded per brief).
11
+ *
12
+ * tacit_artifact_create constraints (D-19/brief KR1):
13
+ * - Routes to handlers::artifacts_career::create_artifact
14
+ * - Backend CreateArtifactRequest accepts ONLY: title, domain, output_format,
15
+ * portfolio_id, structured_metadata. portfolio_id is REQUIRED (403 without it).
16
+ * - artifact_kind is hardcoded server-side (GENERIC) — the client must NOT send it.
17
+ * - The artifact body is populated server-side from structured_metadata
18
+ * (e.g. full_posting_text); the client does NOT send a content field.
19
+ * - This tool always sends "domain":"CAREER" in the request body.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.ARTIFACT_PUBLISH_TOOL_DEF = exports.artifactPublishInputSchema = exports.ARTIFACT_UPDATE_TOOL_DEF = exports.artifactUpdateInputSchema = exports.ARTIFACT_CREATE_TOOL_DEF = exports.artifactCreateInputSchema = exports.ARTIFACT_LIST_TOOL_DEF = exports.artifactListInputSchema = void 0;
23
+ exports.buildArtifactListRequest = buildArtifactListRequest;
24
+ exports.handleArtifactList = handleArtifactList;
25
+ exports.buildArtifactCreateRequest = buildArtifactCreateRequest;
26
+ exports.handleArtifactCreate = handleArtifactCreate;
27
+ exports.buildArtifactUpdateRequest = buildArtifactUpdateRequest;
28
+ exports.handleArtifactUpdate = handleArtifactUpdate;
29
+ exports.buildArtifactPublishRequest = buildArtifactPublishRequest;
30
+ exports.handleArtifactPublish = handleArtifactPublish;
31
+ const zod_1 = require("zod");
32
+ const client_js_1 = require("../client.js");
33
+ // ─── List ────────────────────────────────────────────────────────────────────
34
+ exports.artifactListInputSchema = zod_1.z.object({
35
+ limit: zod_1.z.number().int().positive().optional().describe('Maximum artifacts to return'),
36
+ offset: zod_1.z.number().int().nonnegative().optional().describe('Pagination offset'),
37
+ status: zod_1.z.string().optional().describe('Filter by status (e.g. PUBLISHED, DRAFT)'),
38
+ });
39
+ exports.ARTIFACT_LIST_TOOL_DEF = {
40
+ name: 'tacit_artifact_list',
41
+ description: 'List artifacts from the Tacit workspace. Route: GET /api/v1/artifacts.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ limit: { type: 'number', description: 'Maximum artifacts to return' },
46
+ offset: { type: 'number', description: 'Pagination offset' },
47
+ status: { type: 'string', description: 'Filter by status' },
48
+ },
49
+ required: [],
50
+ },
51
+ };
52
+ function buildArtifactListRequest(client, input) {
53
+ return {
54
+ async execute() {
55
+ const { limit, offset, status } = input;
56
+ try {
57
+ const params = {};
58
+ if (limit !== undefined)
59
+ params['limit'] = limit;
60
+ if (offset !== undefined)
61
+ params['offset'] = offset;
62
+ if (status)
63
+ params['status'] = status;
64
+ // GET /api/v1/artifacts
65
+ const result = await client.get('/api/v1/artifacts', params);
66
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
67
+ }
68
+ catch (err) {
69
+ return apiErrorResult(err, '/api/v1/artifacts');
70
+ }
71
+ },
72
+ };
73
+ }
74
+ async function handleArtifactList(args) {
75
+ const parsed = exports.artifactListInputSchema.safeParse(args);
76
+ if (!parsed.success) {
77
+ return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
78
+ }
79
+ return buildArtifactListRequest((0, client_js_1.getClient)(), parsed.data).execute();
80
+ }
81
+ // ─── Create ──────────────────────────────────────────────────────────────────
82
+ exports.artifactCreateInputSchema = zod_1.z.object({
83
+ title: zod_1.z.string().min(1).describe('Artifact title'),
84
+ domain: zod_1.z.string().optional().describe('Domain (always overridden to CAREER by this tool)'),
85
+ portfolio_id: zod_1.z.string().min(1).describe('Portfolio to associate with (REQUIRED by the backend; 403 without it)'),
86
+ structured_metadata: zod_1.z
87
+ .record(zod_1.z.unknown())
88
+ .optional()
89
+ .describe('Structured metadata; the artifact body is populated server-side from this (e.g. full_posting_text)'),
90
+ });
91
+ exports.ARTIFACT_CREATE_TOOL_DEF = {
92
+ name: 'tacit_artifact_create',
93
+ description: 'Create a career-scoped artifact in Tacit (always domain CAREER). ' +
94
+ 'Accepts a title plus an optional structured_metadata object; portfolio_id is required. ' +
95
+ 'The artifact body is populated server-side from structured_metadata (e.g. full_posting_text), ' +
96
+ 'and artifact_kind is hardcoded server-side — neither is sent by this tool. ' +
97
+ 'Route: POST /api/v1/artifacts (handlers::artifacts_career::create_artifact).',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ title: { type: 'string', description: 'Artifact title' },
102
+ domain: { type: 'string', description: 'Domain (always CAREER for this tool)' },
103
+ portfolio_id: { type: 'string', description: 'Portfolio to associate with (REQUIRED; 403 without it)' },
104
+ structured_metadata: {
105
+ type: 'object',
106
+ description: 'Structured metadata; artifact body is populated server-side from this (e.g. full_posting_text)',
107
+ },
108
+ },
109
+ required: ['title', 'portfolio_id'],
110
+ },
111
+ };
112
+ function buildArtifactCreateRequest(client, input) {
113
+ return {
114
+ async execute() {
115
+ const { title, portfolio_id, structured_metadata } = input;
116
+ try {
117
+ // Backend CreateArtifactRequest accepts only title/domain/output_format/portfolio_id/structured_metadata.
118
+ // artifact_kind is hardcoded server-side; the body comes from structured_metadata.full_posting_text.
119
+ const body = {
120
+ title,
121
+ domain: 'CAREER',
122
+ portfolio_id,
123
+ };
124
+ if (structured_metadata !== undefined)
125
+ body['structured_metadata'] = structured_metadata;
126
+ // POST /api/v1/artifacts
127
+ const result = await client.post('/api/v1/artifacts', body);
128
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
129
+ }
130
+ catch (err) {
131
+ return apiErrorResult(err, '/api/v1/artifacts');
132
+ }
133
+ },
134
+ };
135
+ }
136
+ async function handleArtifactCreate(args) {
137
+ const parsed = exports.artifactCreateInputSchema.safeParse(args);
138
+ if (!parsed.success) {
139
+ return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
140
+ }
141
+ return buildArtifactCreateRequest((0, client_js_1.getClient)(), parsed.data).execute();
142
+ }
143
+ // ─── Update ──────────────────────────────────────────────────────────────────
144
+ exports.artifactUpdateInputSchema = zod_1.z.object({
145
+ id: zod_1.z.string().min(1).describe('Artifact UUID to update'),
146
+ title: zod_1.z.string().optional().describe('New title'),
147
+ content: zod_1.z.string().optional().describe('New content body'),
148
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Updated tags'),
149
+ });
150
+ exports.ARTIFACT_UPDATE_TOOL_DEF = {
151
+ name: 'tacit_artifact_update',
152
+ description: 'Update a career artifact by id. Route: PATCH /api/v1/artifacts/:id.',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ id: { type: 'string', description: 'Artifact UUID to update' },
157
+ title: { type: 'string', description: 'New title' },
158
+ content: { type: 'string', description: 'New content body' },
159
+ tags: { type: 'array', items: { type: 'string' }, description: 'Updated tags' },
160
+ },
161
+ required: ['id'],
162
+ },
163
+ };
164
+ function buildArtifactUpdateRequest(client, input) {
165
+ return {
166
+ async execute() {
167
+ const { id, title, content, tags } = input;
168
+ try {
169
+ const body = {};
170
+ if (title !== undefined)
171
+ body['title'] = title;
172
+ // API field is content_body (not content) — map here.
173
+ if (content !== undefined)
174
+ body['content_body'] = content;
175
+ if (tags !== undefined)
176
+ body['tags'] = tags;
177
+ // PATCH /api/v1/artifacts/:id
178
+ const result = await client.patch(`/api/v1/artifacts/${id}`, body);
179
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
180
+ }
181
+ catch (err) {
182
+ return apiErrorResult(err, '/api/v1/artifacts/:id');
183
+ }
184
+ },
185
+ };
186
+ }
187
+ async function handleArtifactUpdate(args) {
188
+ const parsed = exports.artifactUpdateInputSchema.safeParse(args);
189
+ if (!parsed.success) {
190
+ return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
191
+ }
192
+ return buildArtifactUpdateRequest((0, client_js_1.getClient)(), parsed.data).execute();
193
+ }
194
+ // ─── Publish ─────────────────────────────────────────────────────────────────
195
+ exports.artifactPublishInputSchema = zod_1.z.object({
196
+ id: zod_1.z.string().min(1).describe('Artifact UUID to publish'),
197
+ });
198
+ exports.ARTIFACT_PUBLISH_TOOL_DEF = {
199
+ name: 'tacit_artifact_publish',
200
+ description: 'Publish an artifact by id. Route: POST /api/v1/artifacts/:id/publish.',
201
+ inputSchema: {
202
+ type: 'object',
203
+ properties: {
204
+ id: { type: 'string', description: 'Artifact UUID to publish' },
205
+ },
206
+ required: ['id'],
207
+ },
208
+ };
209
+ function buildArtifactPublishRequest(client, input) {
210
+ return {
211
+ async execute() {
212
+ const { id } = input;
213
+ try {
214
+ // POST /api/v1/artifacts/:id/publish
215
+ const result = await client.post(`/api/v1/artifacts/${id}/publish`, {});
216
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
217
+ }
218
+ catch (err) {
219
+ return apiErrorResult(err, '/api/v1/artifacts/:id/publish');
220
+ }
221
+ },
222
+ };
223
+ }
224
+ async function handleArtifactPublish(args) {
225
+ const parsed = exports.artifactPublishInputSchema.safeParse(args);
226
+ if (!parsed.success) {
227
+ return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
228
+ }
229
+ return buildArtifactPublishRequest((0, client_js_1.getClient)(), parsed.data).execute();
230
+ }
231
+ // ─── Shared error helper ──────────────────────────────────────────────────────
232
+ function apiErrorResult(err, endpoint) {
233
+ if (err instanceof client_js_1.TacitApiError) {
234
+ return {
235
+ content: [{ type: 'text', text: `API error ${err.statusCode} on ${endpoint}: ${err.body}` }],
236
+ isError: true,
237
+ };
238
+ }
239
+ const msg = err instanceof Error ? err.message : String(err);
240
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
241
+ }