@tacitai/mcp-server 0.6.2 → 0.6.4
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 +2 -0
- package/dist/prompts.d.ts +87 -0
- package/dist/prompts.js +3 -3
- package/dist/setup.js +36 -18
- package/dist/tools/onboarding.d.ts +3 -0
- package/dist/tools/onboarding.js +84 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ import { registerGmTools } from './tools/gm.js';
|
|
|
59
59
|
import { registerSkillTools } from './tools/skills.js';
|
|
60
60
|
import { registerWikiTools } from './tools/wiki.js';
|
|
61
61
|
import { registerVoiceTools } from './tools/voice.js';
|
|
62
|
+
import { registerOnboardingTools } from './tools/onboarding.js';
|
|
62
63
|
import { registerPrompts } from './prompts.js';
|
|
63
64
|
import { getRequestPrincipal } from './client.js';
|
|
64
65
|
// Setup wizard (MV-1057h / W5)
|
|
@@ -183,6 +184,7 @@ async function run() {
|
|
|
183
184
|
registerSkillTools(server, getClient);
|
|
184
185
|
registerWikiTools(server, getClient);
|
|
185
186
|
registerVoiceTools(server, getClient);
|
|
187
|
+
registerOnboardingTools(server, getClient);
|
|
186
188
|
registerPrompts(server, getClient);
|
|
187
189
|
if (process.env['MCP_TRANSPORT'] === 'http') {
|
|
188
190
|
await startHttpTransport(server);
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,3 +1,90 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { TacitClient } from './client.js';
|
|
3
|
+
export interface WorkspaceState {
|
|
4
|
+
name: string;
|
|
5
|
+
collectionNames: string[];
|
|
6
|
+
collectionCount: number;
|
|
7
|
+
portfolioNames: string[];
|
|
8
|
+
portfolioCount: number;
|
|
9
|
+
sourceCount: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function fetchWorkspaceState(client: TacitClient): Promise<WorkspaceState>;
|
|
12
|
+
export declare const CAPABILITY_DOMAINS: Array<{
|
|
13
|
+
domain: string;
|
|
14
|
+
triggers: string[];
|
|
15
|
+
description: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const EXPERIENCES: readonly [{
|
|
18
|
+
readonly id: "first_light";
|
|
19
|
+
readonly num: 1;
|
|
20
|
+
readonly name: "First Light";
|
|
21
|
+
readonly tier: 1;
|
|
22
|
+
readonly tierName: "Orient & Equip";
|
|
23
|
+
readonly action: "Ask a question about your business — try \"What do we know about [topic]?\"";
|
|
24
|
+
readonly signal: "First grounded, cited answer about your company";
|
|
25
|
+
}, {
|
|
26
|
+
readonly id: "feed_it";
|
|
27
|
+
readonly num: 2;
|
|
28
|
+
readonly name: "Feed It";
|
|
29
|
+
readonly tier: 1;
|
|
30
|
+
readonly tierName: "Orient & Equip";
|
|
31
|
+
readonly action: "Add something to your workspace — share a URL, document, or file.";
|
|
32
|
+
readonly signal: "First successful content ingestion";
|
|
33
|
+
}, {
|
|
34
|
+
readonly id: "build_your_map";
|
|
35
|
+
readonly num: 3;
|
|
36
|
+
readonly name: "Build Your Map";
|
|
37
|
+
readonly tier: 1;
|
|
38
|
+
readonly tierName: "Orient & Equip";
|
|
39
|
+
readonly action: "Organize your work — ask to create a collection, build a portfolio, or structure your business.";
|
|
40
|
+
readonly signal: "First organizational write operation";
|
|
41
|
+
}, {
|
|
42
|
+
readonly id: "connect_your_tools";
|
|
43
|
+
readonly num: 4;
|
|
44
|
+
readonly name: "Connect Your Tools";
|
|
45
|
+
readonly tier: 1;
|
|
46
|
+
readonly tierName: "Orient & Equip";
|
|
47
|
+
readonly action: "Connect Slack and Gmail through claude.ai Integrations — type \"How do I connect my tools?\" for a walkthrough.";
|
|
48
|
+
readonly signal: "Connectors active — email and Slack flowing into your workspace";
|
|
49
|
+
}, {
|
|
50
|
+
readonly id: "morning_brief";
|
|
51
|
+
readonly num: 5;
|
|
52
|
+
readonly name: "Morning Brief";
|
|
53
|
+
readonly tier: 2;
|
|
54
|
+
readonly tierName: "The Operating System";
|
|
55
|
+
readonly action: "Start a session with \"What happened while I was away?\" or \"Morning brief\" — includes email triage, Slack synthesis, and calendar prep.";
|
|
56
|
+
readonly signal: "First daily operating picture with email triage, Slack synthesis, and calendar prep";
|
|
57
|
+
}, {
|
|
58
|
+
readonly id: "run_sales";
|
|
59
|
+
readonly num: 6;
|
|
60
|
+
readonly name: "Run Sales";
|
|
61
|
+
readonly tier: 2;
|
|
62
|
+
readonly tierName: "The Operating System";
|
|
63
|
+
readonly action: "Ask to prepare a proposal, run discovery on a prospect, or build a quote.";
|
|
64
|
+
readonly signal: "First compound business deliverable";
|
|
65
|
+
}, {
|
|
66
|
+
readonly id: "research_publish";
|
|
67
|
+
readonly num: 7;
|
|
68
|
+
readonly name: "Research & Publish";
|
|
69
|
+
readonly tier: 2;
|
|
70
|
+
readonly tierName: "The Operating System";
|
|
71
|
+
readonly action: "Ask to research a topic and publish the findings as institutional knowledge.";
|
|
72
|
+
readonly signal: "First published wiki article or KB entry";
|
|
73
|
+
}, {
|
|
74
|
+
readonly id: "audit_business";
|
|
75
|
+
readonly num: 8;
|
|
76
|
+
readonly name: "Audit Your Business";
|
|
77
|
+
readonly tier: 2;
|
|
78
|
+
readonly tierName: "The Operating System";
|
|
79
|
+
readonly action: "Ask \"How healthy is our business?\" or request a pulse audit.";
|
|
80
|
+
readonly signal: "First scored business health audit";
|
|
81
|
+
}, {
|
|
82
|
+
readonly id: "operating_rhythm";
|
|
83
|
+
readonly num: 9;
|
|
84
|
+
readonly name: "Your Operating Rhythm";
|
|
85
|
+
readonly tier: 3;
|
|
86
|
+
readonly tierName: "Agentic Mastery";
|
|
87
|
+
readonly action: "Set up weekly reviews, delegate recurring work to crews, and build your voice profile.";
|
|
88
|
+
readonly signal: "Return for second session with intent; establish a routine";
|
|
89
|
+
}];
|
|
3
90
|
export declare function registerPrompts(server: McpServer, getClientFn?: () => Promise<TacitClient>): void;
|
package/dist/prompts.js
CHANGED
|
@@ -45,7 +45,7 @@ function buildAssistantSystemPrompt(state) {
|
|
|
45
45
|
lines.push('Prefer a single read tool for direct lookups, synthesis for open questions, and ask before any write or dispatch.');
|
|
46
46
|
return lines.join('\n');
|
|
47
47
|
}
|
|
48
|
-
async function fetchWorkspaceState(client) {
|
|
48
|
+
export async function fetchWorkspaceState(client) {
|
|
49
49
|
await client.initialize();
|
|
50
50
|
const wsName = client.workspaceName || 'your workspace';
|
|
51
51
|
const [collectionsRes, portfoliosRes, sourcesRes] = await Promise.all([
|
|
@@ -71,7 +71,7 @@ async function fetchWorkspaceState(client) {
|
|
|
71
71
|
sourceCount: sources.length,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
const CAPABILITY_DOMAINS = [
|
|
74
|
+
export const CAPABILITY_DOMAINS = [
|
|
75
75
|
{ domain: 'Search & Recall', triggers: ['What do we know about…', 'Find me…', 'Look up…'], description: 'Search workspace knowledge and recall specific items' },
|
|
76
76
|
{ domain: 'Synthesis & Analysis', triggers: ['Summarize…', 'What does our data say about…', 'Compare…'], description: 'Grounded answers with citations from your workspace' },
|
|
77
77
|
{ domain: 'Knowledge Base', triggers: ['Create an article about…', 'Organize our knowledge on…'], description: 'Build and organize your reference library' },
|
|
@@ -91,7 +91,7 @@ const CAPABILITY_DOMAINS = [
|
|
|
91
91
|
{ domain: 'Connectors', triggers: ['Connect my Gmail…', 'Add Slack…'], description: 'Connect external tools via claude.ai Integrations' },
|
|
92
92
|
{ domain: 'Code & GitHub', triggers: ['Show my repo connections…', 'Sync from GitHub…'], description: 'Repository connections and code sync' },
|
|
93
93
|
];
|
|
94
|
-
const EXPERIENCES = [
|
|
94
|
+
export const EXPERIENCES = [
|
|
95
95
|
{ id: 'first_light', num: 1, name: 'First Light', tier: 1, tierName: 'Orient & Equip', action: 'Ask a question about your business — try "What do we know about [topic]?"', signal: 'First grounded, cited answer about your company' },
|
|
96
96
|
{ id: 'feed_it', num: 2, name: 'Feed It', tier: 1, tierName: 'Orient & Equip', action: 'Add something to your workspace — share a URL, document, or file.', signal: 'First successful content ingestion' },
|
|
97
97
|
{ id: 'build_your_map', num: 3, name: 'Build Your Map', tier: 1, tierName: 'Orient & Equip', action: 'Organize your work — ask to create a collection, build a portfolio, or structure your business.', signal: 'First organizational write operation' },
|
package/dist/setup.js
CHANGED
|
@@ -463,42 +463,60 @@ export async function setup(argv) {
|
|
|
463
463
|
// fall back to env-var placeholder when the token came from the environment.
|
|
464
464
|
const resolvedToken = token ?? null;
|
|
465
465
|
const profileEntry = buildProfileEntry(apiUrl, envVarName, resolvedToken);
|
|
466
|
-
const existing = readExistingConfig();
|
|
467
|
-
const merged = mergeConfig(existing, profile, profileEntry);
|
|
468
|
-
const output = configToBytes(merged);
|
|
469
466
|
// D10: emit confirmation string BEFORE writing (also printed in dry-run).
|
|
470
|
-
process.stdout.write(
|
|
467
|
+
process.stdout.write(`\n Registering MCP server "${profile}" with Claude Code...\n`);
|
|
471
468
|
if (opts.dryRun) {
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
: output;
|
|
476
|
-
process.stdout.write('(dry-run) Would write:\n');
|
|
477
|
-
process.stdout.write(redacted);
|
|
469
|
+
process.stdout.write(' (dry-run) Would register:\n');
|
|
470
|
+
const redactedEntry = buildProfileEntry(apiUrl, envVarName, null);
|
|
471
|
+
process.stdout.write(` ${JSON.stringify(redactedEntry, null, 2)}\n`);
|
|
478
472
|
process.exit(0);
|
|
479
473
|
return;
|
|
480
474
|
}
|
|
481
|
-
//
|
|
475
|
+
// Register via `claude mcp add-json` — the only way Claude Code discovers MCP servers.
|
|
476
|
+
// Writing ~/.claude/mcp.json directly does NOT work (Claude Code ignores that file).
|
|
477
|
+
const jsonConfig = JSON.stringify(profileEntry);
|
|
478
|
+
const registered = await new Promise((resolve) => {
|
|
479
|
+
execFile('claude', ['mcp', 'add-json', '--scope', 'user', profile, jsonConfig], {
|
|
480
|
+
timeout: 15_000,
|
|
481
|
+
}, (err, stdout, stderr) => {
|
|
482
|
+
if (err) {
|
|
483
|
+
process.stderr.write(`\n Warning: could not register via "claude mcp add-json".\n`);
|
|
484
|
+
process.stderr.write(` ${stderr || err.message}\n`);
|
|
485
|
+
resolve(false);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
resolve(true);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
// Fallback: also write ~/.claude/mcp.json for environments without `claude` CLI.
|
|
482
493
|
const dir = claudeDir();
|
|
483
494
|
if (!fs.existsSync(dir)) {
|
|
484
495
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
485
496
|
}
|
|
497
|
+
const existing = readExistingConfig();
|
|
498
|
+
const merged = mergeConfig(existing, profile, profileEntry);
|
|
499
|
+
const output = configToBytes(merged);
|
|
486
500
|
const dest = mcpJsonPath();
|
|
487
501
|
fs.writeFileSync(dest, output, { encoding: 'utf-8', mode: 0o600 });
|
|
488
|
-
// Ensure 0600 even when the file pre-existed with looser permissions.
|
|
489
502
|
fs.chmodSync(dest, 0o600);
|
|
490
503
|
// AC-12: write ~/.tacit/profiles/<profile>.json metadata (token NOT included).
|
|
491
504
|
if (expiresAt) {
|
|
492
505
|
writeProfileMetadata(profile, expiresAt, apiUrl);
|
|
493
506
|
}
|
|
494
|
-
if (
|
|
495
|
-
process.stdout.write(`\
|
|
496
|
-
|
|
507
|
+
if (registered) {
|
|
508
|
+
process.stdout.write(`\n Done! Start a new Claude Code session to connect.\n`);
|
|
509
|
+
}
|
|
510
|
+
else if (resolvedToken) {
|
|
511
|
+
process.stdout.write(`\n Config written to ~/.claude/mcp.json (fallback).\n` +
|
|
512
|
+
` If Claude Code doesn't pick it up, run:\n` +
|
|
513
|
+
` claude mcp add-json --scope user ${profile} '${jsonConfig}'\n` +
|
|
514
|
+
` Then start a new Claude Code session.\n`);
|
|
497
515
|
}
|
|
498
516
|
else {
|
|
499
|
-
process.stdout.write(`\
|
|
500
|
-
`
|
|
501
|
-
`\
|
|
517
|
+
process.stdout.write(`\n Setup complete. Add this to your shell profile:\n` +
|
|
518
|
+
` export ${envVarName}=<your-token>\n` +
|
|
519
|
+
`\n Then start a new Claude Code session to connect.\n`);
|
|
502
520
|
}
|
|
503
521
|
if (opts.smoke) {
|
|
504
522
|
await runSmoke(apiUrl, token);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TacitApiError } from '../client.js';
|
|
3
|
+
import { success, error } from './errors.js';
|
|
4
|
+
import { EXPERIENCES, fetchWorkspaceState } from '../prompts.js';
|
|
5
|
+
export function registerOnboardingTools(server, getClient) {
|
|
6
|
+
server.tool('tacit_onboarding', 'Guide the user through the TACIT onboarding journey — the Nine Experiences that take them from first question to operating rhythm. ' +
|
|
7
|
+
'Call this when the user says things like: "onboard me", "take me through onboarding", "show me the way", ' +
|
|
8
|
+
'"get me started", "what should I try next", "how do I get started", "walk me through setup", ' +
|
|
9
|
+
'"what are the experiences", "show me what I can do", or any request for guided onboarding.', {
|
|
10
|
+
completed: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Comma-separated experience IDs the user has already completed (e.g. "first_light,feed_it"). ' +
|
|
14
|
+
'Omit if unknown — the tool will start from the beginning.'),
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
try {
|
|
17
|
+
const client = await getClient();
|
|
18
|
+
const state = await fetchWorkspaceState(client);
|
|
19
|
+
const completedIds = (args.completed || '')
|
|
20
|
+
.split(',')
|
|
21
|
+
.map(s => s.trim().toLowerCase())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
const completedSet = new Set(completedIds);
|
|
24
|
+
const tier1 = EXPERIENCES.filter(e => e.tier === 1);
|
|
25
|
+
const tier2 = EXPERIENCES.filter(e => e.tier === 2);
|
|
26
|
+
const tier3 = EXPERIENCES.filter(e => e.tier === 3);
|
|
27
|
+
const tier1Complete = tier1.every(e => completedSet.has(e.id));
|
|
28
|
+
let next;
|
|
29
|
+
if (!tier1Complete) {
|
|
30
|
+
next = tier1.find(e => !completedSet.has(e.id));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
next = tier2.find(e => !completedSet.has(e.id));
|
|
34
|
+
if (!next)
|
|
35
|
+
next = tier3.find(e => !completedSet.has(e.id));
|
|
36
|
+
}
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(`## Onboarding: ${state.name}`);
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push(`Progress: ${completedIds.length} of ${EXPERIENCES.length} experiences complete.`);
|
|
41
|
+
lines.push('');
|
|
42
|
+
if (!next) {
|
|
43
|
+
lines.push('All nine experiences complete — you are a TACIT power user.');
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push('From here, build your own operating rhythm: set up recurring briefs, delegate complex tasks to crews, and let the system learn your patterns.');
|
|
46
|
+
return success({ onboarding: lines.join('\n'), status: 'complete', completed: completedIds.length, total: EXPERIENCES.length });
|
|
47
|
+
}
|
|
48
|
+
lines.push(`### Next: Experience ${next.num} — ${next.name}`);
|
|
49
|
+
lines.push(`*${next.tierName}*`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
lines.push(next.action);
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push(`**When complete:** ${next.signal}`);
|
|
54
|
+
if (next.tier === 2 && tier1Complete) {
|
|
55
|
+
const otherTier2 = tier2.filter(e => e.id !== next.id && !completedSet.has(e.id));
|
|
56
|
+
if (otherTier2.length > 0) {
|
|
57
|
+
lines.push('');
|
|
58
|
+
lines.push(`Also available: ${otherTier2.map(e => e.name).join(', ')} — Tier 2 experiences work in any order.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
lines.push('');
|
|
62
|
+
lines.push('### All Nine Experiences');
|
|
63
|
+
lines.push('');
|
|
64
|
+
for (const exp of EXPERIENCES) {
|
|
65
|
+
const done = completedSet.has(exp.id);
|
|
66
|
+
const current = exp.id === next.id;
|
|
67
|
+
const prefix = done ? '✓' : current ? '→' : '○';
|
|
68
|
+
lines.push(`${prefix} **${exp.num}. ${exp.name}** (${exp.tierName})${done ? ' — done' : current ? ' — up next' : ''}`);
|
|
69
|
+
}
|
|
70
|
+
return success({
|
|
71
|
+
onboarding: lines.join('\n'),
|
|
72
|
+
status: 'in_progress',
|
|
73
|
+
completed: completedIds.length,
|
|
74
|
+
total: EXPERIENCES.length,
|
|
75
|
+
next_experience: { id: next.id, num: next.num, name: next.name, tier: next.tier, action: next.action },
|
|
76
|
+
});
|
|
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
|
+
}
|