@tacitai/mcp-server 0.1.1 → 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.
Files changed (70) hide show
  1. package/dist/auth.d.ts +32 -0
  2. package/dist/auth.js +64 -0
  3. package/dist/client.d.ts +60 -5
  4. package/dist/client.js +234 -53
  5. package/dist/index.js +130 -106
  6. package/dist/oauth.d.ts +115 -0
  7. package/dist/oauth.js +619 -0
  8. package/dist/prompts.d.ts +2 -0
  9. package/dist/prompts.js +12 -0
  10. package/dist/setup.d.ts +28 -5
  11. package/dist/setup.js +266 -59
  12. package/dist/tools/agents.d.ts +277 -0
  13. package/dist/tools/agents.js +335 -0
  14. package/dist/tools/annotations.d.ts +117 -0
  15. package/dist/tools/annotations.js +130 -0
  16. package/dist/tools/artifacts.d.ts +178 -8
  17. package/dist/tools/artifacts.js +372 -81
  18. package/dist/tools/chronicle.d.ts +3 -0
  19. package/dist/tools/chronicle.js +165 -0
  20. package/dist/tools/connector_write_back.d.ts +3 -0
  21. package/dist/tools/connector_write_back.js +27 -0
  22. package/dist/tools/converse.d.ts +10 -0
  23. package/dist/tools/converse.js +88 -0
  24. package/dist/tools/crews.d.ts +3 -0
  25. package/dist/tools/crews.js +115 -0
  26. package/dist/tools/drafts.d.ts +83 -0
  27. package/dist/tools/drafts.js +86 -0
  28. package/dist/tools/errors.d.ts +15 -0
  29. package/dist/tools/errors.js +21 -0
  30. package/dist/tools/export.d.ts +54 -0
  31. package/dist/tools/export.js +55 -0
  32. package/dist/tools/gm.d.ts +214 -0
  33. package/dist/tools/gm.js +198 -0
  34. package/dist/tools/ingest.d.ts +58 -4
  35. package/dist/tools/ingest.js +109 -76
  36. package/dist/tools/kb.d.ts +544 -0
  37. package/dist/tools/kb.js +687 -0
  38. package/dist/tools/lake-structure.d.ts +3 -0
  39. package/dist/tools/lake-structure.js +126 -0
  40. package/dist/tools/lake.d.ts +932 -0
  41. package/dist/tools/lake.js +1017 -0
  42. package/dist/tools/move.d.ts +200 -0
  43. package/dist/tools/move.js +96 -0
  44. package/dist/tools/outbox.d.ts +274 -0
  45. package/dist/tools/outbox.js +223 -0
  46. package/dist/tools/people.d.ts +3 -0
  47. package/dist/tools/people.js +59 -0
  48. package/dist/tools/pyramid.d.ts +554 -0
  49. package/dist/tools/pyramid.js +758 -0
  50. package/dist/tools/recall.d.ts +2 -0
  51. package/dist/tools/recall.js +28 -17
  52. package/dist/tools/registry.d.ts +17 -0
  53. package/dist/tools/registry.js +199 -0
  54. package/dist/tools/research.d.ts +47 -0
  55. package/dist/tools/research.js +575 -0
  56. package/dist/tools/search.d.ts +9 -0
  57. package/dist/tools/search.js +86 -25
  58. package/dist/tools/sharing.d.ts +63 -0
  59. package/dist/tools/sharing.js +157 -0
  60. package/dist/tools/skills.d.ts +9 -0
  61. package/dist/tools/skills.js +98 -0
  62. package/dist/tools/synthesize.d.ts +54 -2
  63. package/dist/tools/synthesize.js +80 -23
  64. package/dist/tools/web-url.d.ts +57 -0
  65. package/dist/tools/web-url.js +74 -0
  66. package/dist/tools/workspaces.d.ts +3 -0
  67. package/dist/tools/workspaces.js +47 -0
  68. package/dist/transport.d.ts +9 -0
  69. package/dist/transport.js +209 -0
  70. package/package.json +5 -4
@@ -0,0 +1,165 @@
1
+ import { z } from 'zod';
2
+ import { TacitApiError } from '../client.js';
3
+ import { success, error } from './errors.js';
4
+ export function registerChronicleTools(server, getClient) {
5
+ server.tool('tacit_chronicle_timeline', 'Query the Chronicle timeline with date range, scope, and text filters.', {
6
+ from: z.string().optional().describe('RFC 3339 lower bound (inclusive)'),
7
+ to: z.string().optional().describe('RFC 3339 upper bound (inclusive)'),
8
+ offset: z.number().int().optional().describe('Pagination offset'),
9
+ limit: z.number().int().optional().describe('Max results (cap 200)'),
10
+ scope: z.enum(['all', 'project', 'chapter']).optional().describe('Display scope (default: all)'),
11
+ chapter_id: z.string().uuid().optional().describe('Required when scope=chapter'),
12
+ project_id: z.string().uuid().optional().describe('Required when scope=project'),
13
+ created_by: z.string().optional().describe("Filter by creator ('me' for current user)"),
14
+ q: z.string().optional().describe('Full-text search filter'),
15
+ }, async (args) => {
16
+ try {
17
+ const client = await getClient();
18
+ const params = new URLSearchParams();
19
+ if (args.from)
20
+ params.set('from', args.from);
21
+ if (args.to)
22
+ params.set('to', args.to);
23
+ if (args.offset !== undefined)
24
+ params.set('offset', String(args.offset));
25
+ if (args.limit !== undefined)
26
+ params.set('limit', String(args.limit));
27
+ if (args.scope)
28
+ params.set('scope', args.scope);
29
+ if (args.chapter_id)
30
+ params.set('chapter_id', args.chapter_id);
31
+ if (args.project_id)
32
+ params.set('project_id', args.project_id);
33
+ if (args.created_by)
34
+ params.set('created_by', args.created_by);
35
+ if (args.q)
36
+ params.set('q', args.q);
37
+ const qs = params.toString();
38
+ const data = await client.request('GET', `/api/v1/chronicle/timeline${qs ? `?${qs}` : ''}`);
39
+ return success(data);
40
+ }
41
+ catch (e) {
42
+ if (e instanceof TacitApiError)
43
+ return error(e.message);
44
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
45
+ }
46
+ });
47
+ server.tool('tacit_chronicle_events', 'List recent Chronicle events with optional filters.', {
48
+ q: z.string().optional().describe('Prefix search filter'),
49
+ limit: z.number().int().optional().describe('Max results (default 20, cap 100)'),
50
+ offset: z.number().int().optional().describe('Pagination offset'),
51
+ created_by: z.string().optional().describe("Filter: 'me' for current user"),
52
+ full_content: z.boolean().optional().describe('Return full metadata (default: 100-char snippet)'),
53
+ event_types: z.string().optional().describe('Comma-separated event types to filter'),
54
+ }, async (args) => {
55
+ try {
56
+ const client = await getClient();
57
+ const params = new URLSearchParams();
58
+ if (args.q)
59
+ params.set('q', args.q);
60
+ if (args.limit !== undefined)
61
+ params.set('limit', String(args.limit));
62
+ if (args.offset !== undefined)
63
+ params.set('offset', String(args.offset));
64
+ if (args.created_by)
65
+ params.set('created_by', args.created_by);
66
+ if (args.full_content !== undefined)
67
+ params.set('full_content', String(args.full_content));
68
+ if (args.event_types)
69
+ params.set('event_types', args.event_types);
70
+ const qs = params.toString();
71
+ const data = await client.request('GET', `/api/v1/chronicle/events/recent${qs ? `?${qs}` : ''}`);
72
+ return success(data);
73
+ }
74
+ catch (e) {
75
+ if (e instanceof TacitApiError)
76
+ return error(e.message);
77
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
78
+ }
79
+ });
80
+ server.tool('tacit_chronicle_search', 'Search Chronicle events using hybrid BM25+ANN retrieval.', {
81
+ q: z.string().describe('Search query text'),
82
+ limit: z.number().int().optional().describe('Max results'),
83
+ domain: z.string().optional().describe('Domain context for pre-ANN GIN filtering'),
84
+ hybrid: z.boolean().optional().describe('Use hybrid BM25+ANN search path'),
85
+ }, async (args) => {
86
+ try {
87
+ const client = await getClient();
88
+ const params = new URLSearchParams();
89
+ params.set('q', args.q);
90
+ params.set('source_type', 'CHRONICLE_EVENT');
91
+ if (args.limit !== undefined)
92
+ params.set('limit', String(args.limit));
93
+ if (args.domain)
94
+ params.set('domain', args.domain);
95
+ if (args.hybrid !== undefined)
96
+ params.set('hybrid', String(args.hybrid));
97
+ const data = await client.request('GET', `/api/v1/search?${params.toString()}`);
98
+ return success(data);
99
+ }
100
+ catch (e) {
101
+ if (e instanceof TacitApiError)
102
+ return error(e.message);
103
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
104
+ }
105
+ });
106
+ server.tool('tacit_chronicle_graph', 'Get the Chronicle causal + semantic DAG graph.', {
107
+ scope: z.string().describe('Graph scope (required)'),
108
+ limit: z.number().int().optional().describe('Max nodes'),
109
+ created_by: z.string().optional().describe('Filter by creator'),
110
+ }, async (args) => {
111
+ try {
112
+ const client = await getClient();
113
+ const params = new URLSearchParams();
114
+ params.set('scope', args.scope);
115
+ if (args.limit !== undefined)
116
+ params.set('limit', String(args.limit));
117
+ if (args.created_by)
118
+ params.set('created_by', args.created_by);
119
+ const data = await client.request('GET', `/api/v1/chronicle/graph?${params.toString()}`);
120
+ return success(data);
121
+ }
122
+ catch (e) {
123
+ if (e instanceof TacitApiError)
124
+ return error(e.message);
125
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
126
+ }
127
+ });
128
+ server.tool('tacit_create_event', 'Create a new Chronicle event.', {
129
+ event_type: z.string().describe('Event type identifier'),
130
+ raw_payload: z.string().describe('Event content/payload'),
131
+ project_id: z.string().uuid().optional().describe('Portfolio context'),
132
+ chapter_id: z.string().uuid().optional().describe('Program context'),
133
+ modality: z.string().optional().describe('Event modality'),
134
+ parent_event_ids: z.array(z.string()).optional().describe('Parent event IDs (ULID or UUID)'),
135
+ supersedes_event_id: z.string().optional().describe('Event this supersedes'),
136
+ occurred_at: z.string().datetime().optional().describe('ISO 8601 timestamp'),
137
+ title: z.string().optional().describe('Human-readable event title'),
138
+ metadata: z.record(z.unknown()).optional().describe('Structured metadata JSON'),
139
+ }, async (args) => {
140
+ try {
141
+ const client = await getClient();
142
+ const data = await client.request('POST', '/api/v1/events', args);
143
+ return success(data);
144
+ }
145
+ catch (e) {
146
+ if (e instanceof TacitApiError)
147
+ return error(e.message);
148
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
149
+ }
150
+ });
151
+ server.tool('tacit_get_event', 'Get a single event by ID.', {
152
+ event_id: z.string().describe('Event ID (ULID or UUID)'),
153
+ }, async (args) => {
154
+ try {
155
+ const client = await getClient();
156
+ const data = await client.request('GET', `/api/v1/events/${args.event_id}`);
157
+ return success(data);
158
+ }
159
+ catch (e) {
160
+ if (e instanceof TacitApiError)
161
+ return error(e.message);
162
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
163
+ }
164
+ });
165
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { TacitClient } from '../client.js';
3
+ export declare function registerConnectorWriteBackTools(server: McpServer, getClientFn: () => Promise<TacitClient>): void;
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // MV-1531b W3: Connector write-back MCP tool
3
+ import { z } from 'zod';
4
+ export function registerConnectorWriteBackTools(server, getClientFn) {
5
+ server.tool('tacit_connector_write_back', 'Propose structured field mutations to be written back to a connector source. Returns proposal_id, status, and mutations_count.', {
6
+ connector_id: z.string().min(1).describe('Connector UUID'),
7
+ mutations: z.array(z.object({
8
+ table_name: z.string().min(1),
9
+ record_id: z.string().min(1),
10
+ field_name: z.string().min(1),
11
+ new_value: z.unknown(),
12
+ })).min(1).describe('Field mutations to apply'),
13
+ }, async (args) => {
14
+ const client = await getClientFn();
15
+ try {
16
+ const result = await client.post('/api/v1/connectors/write-back', {
17
+ connector_id: args.connector_id,
18
+ mutations: args.mutations,
19
+ });
20
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
21
+ }
22
+ catch (err) {
23
+ const msg = err instanceof Error ? err.message : String(err);
24
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * tacit_converse — conversational AI tool (MV-1523 W5)
3
+ *
4
+ * POST /api/v1/converse — SSE stream
5
+ * GET /api/v1/converse/sessions — list sessions
6
+ * GET /api/v1/converse/sessions/:id/messages — get messages
7
+ */
8
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { TacitClient } from '../client.js';
10
+ export declare function registerConverseTools(server: McpServer, getClientFn: () => Promise<TacitClient>): void;
@@ -0,0 +1,88 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * tacit_converse — conversational AI tool (MV-1523 W5)
4
+ *
5
+ * POST /api/v1/converse — SSE stream
6
+ * GET /api/v1/converse/sessions — list sessions
7
+ * GET /api/v1/converse/sessions/:id/messages — get messages
8
+ */
9
+ import { z } from 'zod';
10
+ export function registerConverseTools(server, getClientFn) {
11
+ server.tool('tacit_converse', 'Send a message in a conversational AI session. Returns the assistant response. Creates a new session if session_id is omitted.', {
12
+ message: z.string().min(1).describe('The message to send'),
13
+ session_id: z.string().optional().describe('Existing session UUID to continue; omit for a new session'),
14
+ }, async (args) => {
15
+ const client = await getClientFn();
16
+ const body = { message: args.message, channel: 'mcp' };
17
+ if (args.session_id)
18
+ body['session_id'] = args.session_id;
19
+ try {
20
+ const chunks = [];
21
+ let sessionId = '';
22
+ let structuredAnswer = null;
23
+ for await (const chunk of client.postSSE('/api/v1/converse', body)) {
24
+ try {
25
+ const parsed = JSON.parse(chunk);
26
+ if (parsed.type === 'session_start')
27
+ sessionId = parsed.session_id;
28
+ else if (parsed.type === 'token')
29
+ chunks.push(parsed.delta);
30
+ else if (parsed.type === 'structured_answer')
31
+ structuredAnswer = parsed.data;
32
+ }
33
+ catch {
34
+ chunks.push(chunk);
35
+ }
36
+ }
37
+ const text = chunks.join('');
38
+ const result = { session_id: sessionId, response: text };
39
+ if (structuredAnswer)
40
+ result['structured_answer'] = structuredAnswer;
41
+ return {
42
+ content: [{
43
+ type: 'text',
44
+ text: JSON.stringify(result),
45
+ }],
46
+ };
47
+ }
48
+ catch (err) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
51
+ }
52
+ });
53
+ server.tool('tacit_converse_sessions', 'List the user\'s conversational AI sessions.', {
54
+ limit: z.number().optional().describe('Max sessions to return (default 20)'),
55
+ }, async (args) => {
56
+ const client = await getClientFn();
57
+ try {
58
+ const query = {};
59
+ if (args.limit)
60
+ query['limit'] = String(args.limit);
61
+ const qs = new URLSearchParams(query).toString();
62
+ const result = await client.get(`/api/v1/converse/sessions${qs ? `?${qs}` : ''}`);
63
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
64
+ }
65
+ catch (err) {
66
+ const msg = err instanceof Error ? err.message : String(err);
67
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
68
+ }
69
+ });
70
+ server.tool('tacit_converse_messages', 'Get messages from a conversational AI session.', {
71
+ session_id: z.string().describe('Session UUID'),
72
+ limit: z.number().optional().describe('Max messages to return (default 50)'),
73
+ }, async (args) => {
74
+ const client = await getClientFn();
75
+ try {
76
+ const query = {};
77
+ if (args.limit)
78
+ query['limit'] = String(args.limit);
79
+ const qs = new URLSearchParams(query).toString();
80
+ const result = await client.get(`/api/v1/converse/sessions/${args.session_id}/messages${qs ? `?${qs}` : ''}`);
81
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
82
+ }
83
+ catch (err) {
84
+ const msg = err instanceof Error ? err.message : String(err);
85
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
86
+ }
87
+ });
88
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { TacitClient } from '../client.js';
3
+ export declare function registerCrewTools(server: McpServer, getClient: () => Promise<TacitClient>): void;
@@ -0,0 +1,115 @@
1
+ import { z } from 'zod';
2
+ import { TacitApiError } from '../client.js';
3
+ import { success, error } from './errors.js';
4
+ export function registerCrewTools(server, getClient) {
5
+ server.tool('tacit_dispatch_crew', 'Dispatch a crew for autonomous task execution.', {
6
+ composition_type: z.string().describe('Crew composition type to dispatch'),
7
+ }, async (args) => {
8
+ try {
9
+ const client = await getClient();
10
+ const data = await client.request('POST', '/api/v1/bha/crews/dispatch', args);
11
+ return success(data);
12
+ }
13
+ catch (e) {
14
+ if (e instanceof TacitApiError)
15
+ return error(e.message);
16
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
17
+ }
18
+ });
19
+ server.tool('tacit_crew_status', 'Get the status and details of a crew run.', {
20
+ run_id: z.string().uuid().describe('Crew run UUID'),
21
+ }, async (args) => {
22
+ try {
23
+ const client = await getClient();
24
+ const data = await client.request('GET', `/api/v1/bha/crews/${args.run_id}`);
25
+ return success(data);
26
+ }
27
+ catch (e) {
28
+ if (e instanceof TacitApiError)
29
+ return error(e.message);
30
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
31
+ }
32
+ });
33
+ server.tool('tacit_crew_history', 'Get crew run history for the current workspace.', {}, async () => {
34
+ try {
35
+ const client = await getClient();
36
+ const wsId = client.workspaceId;
37
+ if (!wsId)
38
+ return error('No active workspace — call tacit_list_workspaces first');
39
+ const data = await client.request('GET', `/api/v1/bha/crews/history/${wsId}`);
40
+ return success(data);
41
+ }
42
+ catch (e) {
43
+ if (e instanceof TacitApiError)
44
+ return error(e.message);
45
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
46
+ }
47
+ });
48
+ server.tool('tacit_approve_gate', 'Approve a crew gate checkpoint.', {
49
+ run_id: z.string().uuid().describe('Crew run UUID'),
50
+ gate_id: z.string().uuid().describe('Gate UUID'),
51
+ notes: z.string().optional().describe('Approval notes'),
52
+ redirect_instructions: z.string().optional().describe('Redirect instructions for the crew'),
53
+ }, async (args) => {
54
+ try {
55
+ const client = await getClient();
56
+ const { run_id, gate_id, ...body } = args;
57
+ const data = await client.request('POST', `/api/v1/bha/crews/${run_id}/gates/${gate_id}/approve`, body);
58
+ return success(data);
59
+ }
60
+ catch (e) {
61
+ if (e instanceof TacitApiError)
62
+ return error(e.message);
63
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
64
+ }
65
+ });
66
+ server.tool('tacit_reject_gate', 'Reject a crew gate checkpoint.', {
67
+ run_id: z.string().uuid().describe('Crew run UUID'),
68
+ gate_id: z.string().uuid().describe('Gate UUID'),
69
+ notes: z.string().optional().describe('Rejection notes'),
70
+ redirect_instructions: z.string().optional().describe('Redirect instructions for the crew'),
71
+ }, async (args) => {
72
+ try {
73
+ const client = await getClient();
74
+ const { run_id, gate_id, ...body } = args;
75
+ const data = await client.request('POST', `/api/v1/bha/crews/${run_id}/gates/${gate_id}/reject`, body);
76
+ return success(data);
77
+ }
78
+ catch (e) {
79
+ if (e instanceof TacitApiError)
80
+ return error(e.message);
81
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
82
+ }
83
+ });
84
+ server.tool('tacit_list_deliverables', 'List deliverables for a dossier.', {
85
+ dossier_id: z.string().uuid().describe('Dossier UUID'),
86
+ }, async (args) => {
87
+ try {
88
+ const client = await getClient();
89
+ const data = await client.request('GET', `/api/v1/bha/dossiers/${args.dossier_id}/deliverables`);
90
+ return success(data);
91
+ }
92
+ catch (e) {
93
+ if (e instanceof TacitApiError)
94
+ return error(e.message);
95
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
96
+ }
97
+ });
98
+ server.tool('tacit_edit_deliverable', "Edit a deliverable's content.", {
99
+ deliverable_id: z.string().uuid().describe('Deliverable UUID'),
100
+ content_body: z.string().describe('New content body'),
101
+ edit_type: z.string().describe('Edit type identifier'),
102
+ }, async (args) => {
103
+ try {
104
+ const client = await getClient();
105
+ const { deliverable_id, ...body } = args;
106
+ const data = await client.request('PUT', `/api/v1/bha/deliverables/${deliverable_id}/content`, body);
107
+ return success(data);
108
+ }
109
+ catch (e) {
110
+ if (e instanceof TacitApiError)
111
+ return error(e.message);
112
+ return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
113
+ }
114
+ });
115
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Draft tools (MV-1515n W5 / FEAT-142):
3
+ * tacit_draft_search — GET /api/v1/artifacts/drafts/search
4
+ *
5
+ * Drafts only (non-published) — never used by synthesis (§10 synthesis wall,
6
+ * ADR-182). Semantic mode sees title + first 4,000 chars. Returns 503
7
+ * embedding_unavailable when the embed server is down — there is no silent
8
+ * keyword fallback (C5).
9
+ *
10
+ * This is a separate tool from tacit_search / tacit_synthesize / tacit_recall,
11
+ * which are unaffected and never read draft_projections.
12
+ */
13
+ import { z } from 'zod';
14
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { TacitClient } from '../client.js';
16
+ export interface ToolRequest {
17
+ execute(): Promise<{
18
+ content: Array<{
19
+ type: 'text';
20
+ text: string;
21
+ }>;
22
+ isError?: boolean;
23
+ }>;
24
+ }
25
+ export declare const draftSearchInputSchema: z.ZodObject<{
26
+ q: z.ZodString;
27
+ mode: z.ZodOptional<z.ZodEnum<["keyword", "semantic", "hybrid"]>>;
28
+ origin: z.ZodOptional<z.ZodEnum<["IMPORTED", "SYNTHESIZED", "WIKI_CONVERGENCE"]>>;
29
+ portfolio_id: z.ZodOptional<z.ZodString>;
30
+ limit: z.ZodOptional<z.ZodNumber>;
31
+ }, "strip", z.ZodTypeAny, {
32
+ q: string;
33
+ limit?: number | undefined;
34
+ origin?: "IMPORTED" | "SYNTHESIZED" | "WIKI_CONVERGENCE" | undefined;
35
+ portfolio_id?: string | undefined;
36
+ mode?: "hybrid" | "keyword" | "semantic" | undefined;
37
+ }, {
38
+ q: string;
39
+ limit?: number | undefined;
40
+ origin?: "IMPORTED" | "SYNTHESIZED" | "WIKI_CONVERGENCE" | undefined;
41
+ portfolio_id?: string | undefined;
42
+ mode?: "hybrid" | "keyword" | "semantic" | undefined;
43
+ }>;
44
+ export type DraftSearchInput = z.infer<typeof draftSearchInputSchema>;
45
+ export declare const DRAFT_SEARCH_TOOL_DEF: {
46
+ name: "tacit_draft_search";
47
+ description: string;
48
+ inputSchema: {
49
+ type: "object";
50
+ properties: {
51
+ q: {
52
+ type: string;
53
+ description: string;
54
+ };
55
+ mode: {
56
+ type: string;
57
+ enum: string[];
58
+ default: string;
59
+ };
60
+ origin: {
61
+ type: string;
62
+ enum: string[];
63
+ };
64
+ portfolio_id: {
65
+ type: string;
66
+ };
67
+ limit: {
68
+ type: string;
69
+ maximum: number;
70
+ };
71
+ };
72
+ required: string[];
73
+ };
74
+ };
75
+ export declare function buildDraftSearchRequest(client: TacitClient, input: DraftSearchInput): ToolRequest;
76
+ export declare function handleDraftSearch(args: unknown): Promise<{
77
+ content: Array<{
78
+ type: 'text';
79
+ text: string;
80
+ }>;
81
+ isError?: boolean;
82
+ }>;
83
+ export declare function registerDraftTools(server: McpServer, getClientFn: () => Promise<TacitClient>): void;
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * Draft tools (MV-1515n W5 / FEAT-142):
4
+ * tacit_draft_search — GET /api/v1/artifacts/drafts/search
5
+ *
6
+ * Drafts only (non-published) — never used by synthesis (§10 synthesis wall,
7
+ * ADR-182). Semantic mode sees title + first 4,000 chars. Returns 503
8
+ * embedding_unavailable when the embed server is down — there is no silent
9
+ * keyword fallback (C5).
10
+ *
11
+ * This is a separate tool from tacit_search / tacit_synthesize / tacit_recall,
12
+ * which are unaffected and never read draft_projections.
13
+ */
14
+ import { z } from 'zod';
15
+ import { getClient } from '../client.js';
16
+ import { apiErrorResult } from './errors.js';
17
+ export const draftSearchInputSchema = z.object({
18
+ q: z.string().min(1).describe('Search text (required)'),
19
+ mode: z.enum(['keyword', 'semantic', 'hybrid']).optional().describe('Search mode (default hybrid)'),
20
+ origin: z.enum(['IMPORTED', 'SYNTHESIZED', 'WIKI_CONVERGENCE']).optional().describe('Filter by artifact origin'),
21
+ portfolio_id: z.string().optional().describe('Filter by portfolio UUID'),
22
+ limit: z.number().int().positive().max(50).optional().describe('Maximum results to return (max 50)'),
23
+ });
24
+ export const DRAFT_SEARCH_TOOL_DEF = {
25
+ name: 'tacit_draft_search',
26
+ description: 'Search DRAFT (non-published) artifacts by keyword, semantic, or hybrid mode. ' +
27
+ 'Drafts only — this store is never read by synthesis or tacit_search/tacit_synthesize/tacit_recall (synthesis wall). ' +
28
+ 'Semantic mode indexes title + the first 4,000 chars of body. ' +
29
+ 'Returns HTTP 503 with error code embedding_unavailable when the embed server is down — there is no silent ' +
30
+ 'fallback to keyword search in that case; retry with mode=keyword explicitly instead. ' +
31
+ 'Route: GET /api/v1/artifacts/drafts/search.',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ q: { type: 'string', description: 'Search text (required)' },
36
+ mode: { type: 'string', enum: ['keyword', 'semantic', 'hybrid'], default: 'hybrid' },
37
+ origin: { type: 'string', enum: ['IMPORTED', 'SYNTHESIZED', 'WIKI_CONVERGENCE'] },
38
+ portfolio_id: { type: 'string' },
39
+ limit: { type: 'number', maximum: 50 },
40
+ },
41
+ required: ['q'],
42
+ },
43
+ };
44
+ export function buildDraftSearchRequest(client, input) {
45
+ return {
46
+ async execute() {
47
+ const { q, mode, origin, portfolio_id, limit } = input;
48
+ try {
49
+ const params = { q };
50
+ if (mode)
51
+ params['mode'] = mode;
52
+ if (origin)
53
+ params['origin'] = origin;
54
+ if (portfolio_id)
55
+ params['portfolio_id'] = portfolio_id;
56
+ if (limit !== undefined)
57
+ params['limit'] = limit;
58
+ // GET /api/v1/artifacts/drafts/search
59
+ const result = await client.get('/api/v1/artifacts/drafts/search', params);
60
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
61
+ }
62
+ catch (err) {
63
+ return apiErrorResult(err, '/api/v1/artifacts/drafts/search');
64
+ }
65
+ },
66
+ };
67
+ }
68
+ export async function handleDraftSearch(args) {
69
+ const parsed = draftSearchInputSchema.safeParse(args);
70
+ if (!parsed.success) {
71
+ return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
72
+ }
73
+ return buildDraftSearchRequest(getClient(), parsed.data).execute();
74
+ }
75
+ export function registerDraftTools(server, getClientFn) {
76
+ server.tool('tacit_draft_search', 'Search DRAFT (non-published) artifacts by keyword, semantic, or hybrid mode. Returns HTTP 503 when embed server is down.', {
77
+ q: z.string().min(1).describe('Search text (required)'),
78
+ mode: z.enum(['keyword', 'semantic', 'hybrid']).optional().describe('Search mode (default hybrid)'),
79
+ origin: z.enum(['IMPORTED', 'SYNTHESIZED', 'WIKI_CONVERGENCE']).optional().describe('Filter by artifact origin'),
80
+ portfolio_id: z.string().optional().describe('Filter by portfolio UUID'),
81
+ limit: z.number().int().positive().max(50).optional().describe('Maximum results to return (max 50)'),
82
+ }, async (args) => {
83
+ const client = await getClientFn();
84
+ return buildDraftSearchRequest(client, args).execute();
85
+ });
86
+ }
@@ -0,0 +1,15 @@
1
+ type ToolResult = {
2
+ content: Array<{
3
+ type: 'text';
4
+ text: string;
5
+ }>;
6
+ isError?: boolean;
7
+ };
8
+ export declare function success(data: unknown): ToolResult;
9
+ export declare function error(message: string): ToolResult & {
10
+ isError: true;
11
+ };
12
+ export declare function apiErrorResult(err: unknown, endpoint: string): ToolResult & {
13
+ isError: boolean;
14
+ };
15
+ export {};
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import { TacitApiError, TokenExpiredError } from '../client.js';
3
+ export function success(data) {
4
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
5
+ }
6
+ export function error(message) {
7
+ return { isError: true, content: [{ type: 'text', text: message }] };
8
+ }
9
+ export function apiErrorResult(err, endpoint) {
10
+ if (err instanceof TokenExpiredError) {
11
+ return {
12
+ content: [{ type: 'text', text: err.message }],
13
+ isError: true,
14
+ };
15
+ }
16
+ if (err instanceof TacitApiError) {
17
+ return { content: [{ type: 'text', text: `API error ${err.statusCode} on ${endpoint}: ${err.body}` }], isError: true };
18
+ }
19
+ const msg = err instanceof Error ? err.message : String(err);
20
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
21
+ }