@tacitai/mcp-server 0.2.0 → 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.
- package/dist/index.js +3 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.js +5 -3
- package/dist/tools/skills.d.ts +9 -0
- package/dist/tools/skills.js +98 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ import { registerPeopleTools } from './tools/people.js';
|
|
|
56
56
|
import { registerLakeStructureTools } from './tools/lake-structure.js';
|
|
57
57
|
import { registerResearchTools } from './tools/research.js';
|
|
58
58
|
import { registerGmTools } from './tools/gm.js';
|
|
59
|
+
import { registerSkillTools } from './tools/skills.js';
|
|
59
60
|
import { registerPrompts } from './prompts.js';
|
|
60
61
|
import { getRequestPrincipal } from './client.js';
|
|
61
62
|
// Setup wizard (MV-1057h / W5)
|
|
@@ -136,7 +137,7 @@ async function run() {
|
|
|
136
137
|
}
|
|
137
138
|
checkProfileExpiry(profile);
|
|
138
139
|
checkCompatibility();
|
|
139
|
-
const server = new McpServer({ name: '@tacitai/mcp-server', version: '0.
|
|
140
|
+
const server = new McpServer({ name: '@tacitai/mcp-server', version: '0.3.0' }, { capabilities: { tools: {}, prompts: {} } });
|
|
140
141
|
const isHttpMode = process.env['MCP_TRANSPORT'] === 'http';
|
|
141
142
|
let clientInstance = null;
|
|
142
143
|
const getClient = async () => {
|
|
@@ -176,6 +177,7 @@ async function run() {
|
|
|
176
177
|
registerLakeStructureTools(server, getClient);
|
|
177
178
|
registerGmTools(server, getClient);
|
|
178
179
|
registerResearchTools(server, getClient, getRequestPrincipal);
|
|
180
|
+
registerSkillTools(server, getClient);
|
|
179
181
|
registerPrompts(server);
|
|
180
182
|
if (process.env['MCP_TRANSPORT'] === 'http') {
|
|
181
183
|
await startHttpTransport(server);
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compile-time tool classification. Governs which tools may be called over HTTPS.
|
|
3
|
-
* Partition: 6 stdio_only + 15 admin_only + 2 gate_only +
|
|
3
|
+
* Partition: 6 stdio_only + 15 admin_only + 2 gate_only + 110 https = 133.
|
|
4
4
|
*/
|
|
5
5
|
export type ToolTransport = 'https' | 'stdio_only' | 'admin_only' | 'gate_only';
|
|
6
6
|
export type ToolClass = 'read' | 'write' | 'admin' | 'dispatch';
|
package/dist/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
/**
|
|
3
3
|
* Compile-time tool classification. Governs which tools may be called over HTTPS.
|
|
4
|
-
* Partition: 6 stdio_only + 15 admin_only + 2 gate_only +
|
|
4
|
+
* Partition: 6 stdio_only + 15 admin_only + 2 gate_only + 110 https = 133.
|
|
5
5
|
*/
|
|
6
6
|
const STDIO_ONLY = [
|
|
7
7
|
'tacit_ingest_file',
|
|
@@ -72,6 +72,7 @@ const READ_TOOLS = [
|
|
|
72
72
|
'tacit_lake_taxonomy_browse',
|
|
73
73
|
'tacit_list_annotations',
|
|
74
74
|
'tacit_list_deliverables',
|
|
75
|
+
'tacit_list_skills',
|
|
75
76
|
'tacit_list_people',
|
|
76
77
|
'tacit_list_workspaces',
|
|
77
78
|
'tacit_outbox_status',
|
|
@@ -97,6 +98,7 @@ const DISPATCH_TOOLS = [
|
|
|
97
98
|
'tacit_kb_pull_source',
|
|
98
99
|
'tacit_lake_trigger_sync',
|
|
99
100
|
'tacit_research_launch',
|
|
101
|
+
'tacit_run_skill',
|
|
100
102
|
];
|
|
101
103
|
const WRITE_TOOLS = [
|
|
102
104
|
'tacit_agent_decide',
|
|
@@ -176,8 +178,8 @@ function build() {
|
|
|
176
178
|
if (!exclusive.has(n))
|
|
177
179
|
add(n, classOf(n), 'https', false);
|
|
178
180
|
}
|
|
179
|
-
if (map.size !==
|
|
180
|
-
throw new Error(`tool registry expected
|
|
181
|
+
if (map.size !== 133)
|
|
182
|
+
throw new Error(`tool registry expected 133 tools, got ${map.size}`);
|
|
181
183
|
return map;
|
|
182
184
|
}
|
|
183
185
|
export const TOOL_REGISTRY = build();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tacit_list_skills / tacit_run_skill — MCP skill orchestration (MV-1533 W6)
|
|
3
|
+
*
|
|
4
|
+
* GET /api/v1/internal/skills — list available exec skills
|
|
5
|
+
* POST /api/v1/converse — invoke a skill by hint (SSE stream)
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { TacitClient } from '../client.js';
|
|
9
|
+
export declare function registerSkillTools(server: McpServer, getClientFn: () => Promise<TacitClient>): void;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* tacit_list_skills / tacit_run_skill — MCP skill orchestration (MV-1533 W6)
|
|
4
|
+
*
|
|
5
|
+
* GET /api/v1/internal/skills — list available exec skills
|
|
6
|
+
* POST /api/v1/converse — invoke a skill by hint (SSE stream)
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
export function registerSkillTools(server, getClientFn) {
|
|
10
|
+
// --- tacit_list_skills ---
|
|
11
|
+
server.tool('tacit_list_skills', 'List available TACIT exec skills with their domains, descriptions, and access levels.', {}, async () => {
|
|
12
|
+
const client = await getClientFn();
|
|
13
|
+
try {
|
|
14
|
+
const data = await client.get('/api/v1/internal/skills');
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21
|
+
return { content: [{ type: 'text', text: `Error listing skills: ${msg}` }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
// --- tacit_run_skill ---
|
|
25
|
+
server.tool('tacit_run_skill', 'Run a TACIT exec skill by name. Sends the message through the converse pipeline with skill_hint routing. Returns the skill response, which may include tool calls, structured answers, or approval requests.', {
|
|
26
|
+
skill_id: z.string().min(1).describe('The skill domain ID (e.g., "pyr-exec", "lake-exec", "kb-exec", "sales-exec", "gm-exec")'),
|
|
27
|
+
message: z.string().min(1).describe('The natural-language instruction for the skill'),
|
|
28
|
+
session_id: z.string().optional().describe('Existing converse session UUID to continue; omit for a new session'),
|
|
29
|
+
}, async (args) => {
|
|
30
|
+
const client = await getClientFn();
|
|
31
|
+
// Validate skill_id against the catalog
|
|
32
|
+
try {
|
|
33
|
+
const catalog = await client.get('/api/v1/internal/skills');
|
|
34
|
+
const validDomains = catalog.skills.map((s) => s.domain);
|
|
35
|
+
if (!validDomains.includes(args.skill_id)) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text', text: `Unknown skill_id "${args.skill_id}". Valid skills: ${validDomains.join(', ')}` }],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Catalog unreachable — proceed anyway, backend will validate
|
|
44
|
+
}
|
|
45
|
+
const body = {
|
|
46
|
+
message: args.message,
|
|
47
|
+
skill_hint: args.skill_id,
|
|
48
|
+
channel: 'mcp',
|
|
49
|
+
};
|
|
50
|
+
if (args.session_id)
|
|
51
|
+
body['session_id'] = args.session_id;
|
|
52
|
+
try {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let sessionId = '';
|
|
55
|
+
let structuredAnswer = null;
|
|
56
|
+
let pendingApproval = null;
|
|
57
|
+
for await (const chunk of client.postSSE('/api/v1/converse', body)) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(chunk);
|
|
60
|
+
if (parsed.type === 'session_start')
|
|
61
|
+
sessionId = parsed.session_id;
|
|
62
|
+
else if (parsed.type === 'token')
|
|
63
|
+
chunks.push(parsed.delta);
|
|
64
|
+
else if (parsed.type === 'structured_answer')
|
|
65
|
+
structuredAnswer = parsed.data;
|
|
66
|
+
else if (parsed.type === 'pending_approval')
|
|
67
|
+
pendingApproval = parsed;
|
|
68
|
+
else if (parsed.type === 'tool_call_end') {
|
|
69
|
+
if (parsed.result)
|
|
70
|
+
chunks.push(typeof parsed.result === 'string' ? parsed.result : JSON.stringify(parsed.result));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
chunks.push(chunk);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const text = chunks.join('');
|
|
78
|
+
const result = {
|
|
79
|
+
skill_id: args.skill_id,
|
|
80
|
+
session_id: sessionId,
|
|
81
|
+
response: text,
|
|
82
|
+
};
|
|
83
|
+
if (structuredAnswer)
|
|
84
|
+
result['structured_answer'] = structuredAnswer;
|
|
85
|
+
if (pendingApproval) {
|
|
86
|
+
result['pending_approval'] = pendingApproval;
|
|
87
|
+
result['instructions'] = 'This operation requires approval. Use tacit_converse with the session_id and confirm_token to approve or reject.';
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
95
|
+
return { content: [{ type: 'text', text: `Error running skill ${args.skill_id}: ${msg}` }], isError: true };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|