@getthesis/mcp-server 0.4.0 → 1.0.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/package.json +1 -1
- package/src/index.js +132 -240
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,240 +1,132 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
server.tool(
|
|
134
|
-
"thesis_check_shifts",
|
|
135
|
-
"Check if any beliefs have shifted since items were dispatched. Use this to verify the strategic ground hasn't moved during execution.",
|
|
136
|
-
{},
|
|
137
|
-
async () => {
|
|
138
|
-
try {
|
|
139
|
-
const data = await api("/api/v1/check");
|
|
140
|
-
if (!data.hasShifts) return ok("All clear — no belief shifts detected.");
|
|
141
|
-
const lines = [];
|
|
142
|
-
if (data.changedBeliefs?.length) {
|
|
143
|
-
lines.push("Changed beliefs:");
|
|
144
|
-
data.changedBeliefs.forEach((b) => lines.push(` - [${b.confidence}] ${b.statement}`));
|
|
145
|
-
}
|
|
146
|
-
if (data.affectedItems?.length) {
|
|
147
|
-
lines.push("Affected dispatched items:");
|
|
148
|
-
data.affectedItems.forEach((i) => lines.push(` - ${i.title} (${i.agentStatus})`));
|
|
149
|
-
}
|
|
150
|
-
return ok(lines.join("\n"));
|
|
151
|
-
} catch (e) { return err(e.message); }
|
|
152
|
-
}
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
server.tool(
|
|
156
|
-
"thesis_get_context",
|
|
157
|
-
"Search context entries in the workspace. Use this to find customer feedback, market signals, or other evidence relevant to your current task.",
|
|
158
|
-
{
|
|
159
|
-
query: z.string().optional().describe("Search query to filter entries"),
|
|
160
|
-
limit: z.number().optional().default(10).describe("Max results"),
|
|
161
|
-
},
|
|
162
|
-
async ({ query, limit }) => {
|
|
163
|
-
try {
|
|
164
|
-
const params = new URLSearchParams();
|
|
165
|
-
if (query) params.set("q", query);
|
|
166
|
-
if (limit) params.set("limit", String(limit));
|
|
167
|
-
const data = await api(`/api/v1/context?${params}`);
|
|
168
|
-
if (!data.entries?.length) return ok("No context entries found.");
|
|
169
|
-
const lines = data.entries.map(
|
|
170
|
-
(e) => `### ${e.title}${e.source ? ` (${e.source})` : ""}\n${e.content}\n`
|
|
171
|
-
);
|
|
172
|
-
return ok(lines.join("\n"));
|
|
173
|
-
} catch (e) { return err(e.message); }
|
|
174
|
-
}
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
server.tool(
|
|
178
|
-
"thesis_get_design_principles",
|
|
179
|
-
"Get the workspace's design principles and constraints. Reference these when making implementation decisions.",
|
|
180
|
-
{},
|
|
181
|
-
async () => {
|
|
182
|
-
try {
|
|
183
|
-
const data = await api("/api/v1/design-principles");
|
|
184
|
-
if (!data.principles?.length) return ok("No design principles set.");
|
|
185
|
-
const lines = data.principles.map(
|
|
186
|
-
(p) => `- ${p.statement}${p.rationale ? ` — ${p.rationale}` : ""}`
|
|
187
|
-
);
|
|
188
|
-
return ok(lines.join("\n"));
|
|
189
|
-
} catch (e) { return err(e.message); }
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
server.tool(
|
|
194
|
-
"thesis_capture_context",
|
|
195
|
-
"Save a piece of strategic context to Thesis. Use this when the conversation surfaces an insight, observation, or signal worth tracking. The user might say 'save that to Thesis', 'capture that', 'that's worth tracking', or similar.",
|
|
196
|
-
{
|
|
197
|
-
title: z.string().describe("Short descriptive title (5-10 words)"),
|
|
198
|
-
content: z.string().describe("The insight, observation, or signal to capture"),
|
|
199
|
-
source: z.string().optional().default("Claude conversation").describe("Where this came from"),
|
|
200
|
-
},
|
|
201
|
-
async ({ title, content, source }) => {
|
|
202
|
-
try {
|
|
203
|
-
const data = await api("/api/v1/context", {
|
|
204
|
-
method: "POST",
|
|
205
|
-
body: JSON.stringify({ title, content, source }),
|
|
206
|
-
});
|
|
207
|
-
let response = `Captured: "${data.title}"`;
|
|
208
|
-
if (data.beliefSuggestions?.length > 0) {
|
|
209
|
-
response += `\n${data.beliefSuggestions.length} belief link${data.beliefSuggestions.length > 1 ? "s" : ""} suggested:`;
|
|
210
|
-
data.beliefSuggestions.forEach((s) => {
|
|
211
|
-
const arrow = s.direction === "supports" ? "↑" : s.direction === "challenges" ? "↓" : "→";
|
|
212
|
-
response += `\n - ${arrow} ${s.beliefStatement} (${s.strength})`;
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
response += `\nReview at: ${API_URL}/context`;
|
|
216
|
-
return ok(response);
|
|
217
|
-
} catch (e) { return err(e.message); }
|
|
218
|
-
}
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
server.tool(
|
|
222
|
-
"thesis_generate_spec",
|
|
223
|
-
"Generate a detailed implementation spec for an item using Opus. Uses the full strategic context — beliefs, theme, design principles, existing items — to produce an agent-ready spec. This is a Pro feature and uses the most capable model.",
|
|
224
|
-
{ itemId: z.string().describe("The item ID to generate a spec for") },
|
|
225
|
-
async ({ itemId }) => {
|
|
226
|
-
try {
|
|
227
|
-
const data = await api(`/api/v1/items/${itemId}/generate-spec`, {
|
|
228
|
-
method: "POST",
|
|
229
|
-
body: JSON.stringify({}),
|
|
230
|
-
});
|
|
231
|
-
return ok(data.spec || "Spec generated but empty.");
|
|
232
|
-
} catch (e) { return err(e.message); }
|
|
233
|
-
}
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
// ─── Start ───────────────────────────────────────────────────────────
|
|
237
|
-
|
|
238
|
-
const transport = new StdioServerTransport();
|
|
239
|
-
await server.connect(transport);
|
|
240
|
-
console.error("Thesis MCP server v0.4.0 running");
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
12
|
+
|
|
13
|
+
const API_URL = process.env.THESIS_API_URL || "https://www.getthesis.io";
|
|
14
|
+
const API_KEY = process.env.THESIS_API_KEY;
|
|
15
|
+
|
|
16
|
+
async function api(path, options = {}) {
|
|
17
|
+
if (!API_KEY) throw new Error("THESIS_API_KEY not set");
|
|
18
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
19
|
+
...options,
|
|
20
|
+
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", ...options.headers },
|
|
21
|
+
});
|
|
22
|
+
const ct = res.headers.get("content-type") || "";
|
|
23
|
+
if (ct.includes("application/json")) {
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ok = (text) => ({ content: [{ type: "text", text }] });
|
|
34
|
+
const err = (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }], isError: true });
|
|
35
|
+
|
|
36
|
+
const server = new McpServer({ name: "thesis", version: pkg.version });
|
|
37
|
+
|
|
38
|
+
server.tool("thesis_get_beliefs", "Get all active strategic beliefs with confidence levels and evidence counts.", {}, async () => {
|
|
39
|
+
try {
|
|
40
|
+
const data = await api("/api/v1/beliefs");
|
|
41
|
+
if (!data.beliefs?.length) return ok("No active beliefs.");
|
|
42
|
+
return ok(data.beliefs.map((b) => `- [${b.confidence}] ${b.statement} (${b.evidenceCount || 0} evidence)`).join("\n"));
|
|
43
|
+
} catch (e) { return err(e.message); }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
server.tool("thesis_check_beliefs", "Check if any beliefs have changed since a given date.", {
|
|
47
|
+
since: z.string().optional().describe("ISO date. Defaults to 24h ago."),
|
|
48
|
+
}, async ({ since }) => {
|
|
49
|
+
const sinceDate = since || new Date(Date.now() - 86400000).toISOString();
|
|
50
|
+
try {
|
|
51
|
+
const data = await api(`/api/v1/beliefs/changes?since=${sinceDate}`);
|
|
52
|
+
if (!data.changes?.length) return ok("No belief changes since " + sinceDate);
|
|
53
|
+
return ok("Belief changes:\n" + data.changes.map((c) => `- [${c.confidence}] ${c.statement}`).join("\n"));
|
|
54
|
+
} catch (e) { return err(e.message); }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
server.tool("thesis_get_context", "Search context entries in the workspace.", {
|
|
58
|
+
query: z.string().optional().describe("Search query"),
|
|
59
|
+
limit: z.number().optional().default(10).describe("Max results"),
|
|
60
|
+
}, async ({ query, limit }) => {
|
|
61
|
+
try {
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
if (query) params.set("q", query);
|
|
64
|
+
if (limit) params.set("limit", String(limit));
|
|
65
|
+
const data = await api(`/api/v1/context?${params}`);
|
|
66
|
+
if (!data.entries?.length) return ok("No context entries found.");
|
|
67
|
+
return ok(data.entries.map((e) => `### ${e.title}${e.source ? ` (${e.source})` : ""}\n${e.content}\n`).join("\n"));
|
|
68
|
+
} catch (e) { return err(e.message); }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
server.tool("thesis_get_design_principles", "Get the workspace's design principles.", {}, async () => {
|
|
72
|
+
try {
|
|
73
|
+
const data = await api("/api/v1/design-principles");
|
|
74
|
+
if (!data.principles?.length) return ok("No design principles set.");
|
|
75
|
+
return ok(data.principles.map((p) => `- ${p.statement}${p.rationale ? ` — ${p.rationale}` : ""}`).join("\n"));
|
|
76
|
+
} catch (e) { return err(e.message); }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
server.tool("thesis_capture_context", "Save a piece of strategic context to Thesis.", {
|
|
80
|
+
title: z.string().describe("Short descriptive title"),
|
|
81
|
+
content: z.string().describe("The insight or signal to capture"),
|
|
82
|
+
source: z.string().optional().default("Claude conversation").describe("Source"),
|
|
83
|
+
}, async ({ title, content, source }) => {
|
|
84
|
+
try {
|
|
85
|
+
const data = await api("/api/v1/context", { method: "POST", body: JSON.stringify({ title, content, source }) });
|
|
86
|
+
let response = `Captured: "${data.title}"`;
|
|
87
|
+
if (data.beliefSuggestions?.length) {
|
|
88
|
+
response += `\n${data.beliefSuggestions.length} belief link${data.beliefSuggestions.length > 1 ? "s" : ""} suggested:`;
|
|
89
|
+
data.beliefSuggestions.forEach((s) => {
|
|
90
|
+
response += `\n - ${s.direction === "supports" ? "↑" : s.direction === "challenges" ? "↓" : "→"} ${s.beliefStatement} (${s.strength})`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return ok(response);
|
|
94
|
+
} catch (e) { return err(e.message); }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
server.tool("thesis_generate_spec", "Generate a detailed implementation spec grounded in strategic context.", {
|
|
98
|
+
decisionId: z.string().optional().describe("Decision ID from the decision log"),
|
|
99
|
+
description: z.string().optional().describe("Free-text description of what to build"),
|
|
100
|
+
}, async ({ decisionId, description }) => {
|
|
101
|
+
if (!decisionId && !description) return err("Provide either decisionId or description.");
|
|
102
|
+
try {
|
|
103
|
+
const data = await api("/api/v1/generate-spec", { method: "POST", body: JSON.stringify({ decisionId, description }) });
|
|
104
|
+
return ok(data.spec || "Spec generated.");
|
|
105
|
+
} catch (e) { return err(e.message); }
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
server.tool("thesis_list_decisions", "List recent decisions from the decision log.", {
|
|
109
|
+
limit: z.number().optional().default(10).describe("Max results"),
|
|
110
|
+
}, async ({ limit }) => {
|
|
111
|
+
try {
|
|
112
|
+
const data = await api(`/api/v1/decisions?limit=${limit}`);
|
|
113
|
+
if (!data.decisions?.length) return ok("No decisions recorded.");
|
|
114
|
+
return ok(data.decisions.map((d) => `- [${d.reflectionStatus}] ${d.title}\n Reasoning: ${(d.reasoning || "").slice(0, 100)}...`).join("\n\n"));
|
|
115
|
+
} catch (e) { return err(e.message); }
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
server.tool("thesis_log_decision", "Log a decision with reasoning.", {
|
|
119
|
+
title: z.string().describe("What was decided"),
|
|
120
|
+
reasoning: z.string().describe("Why"),
|
|
121
|
+
alternatives: z.string().optional().describe("Alternatives considered"),
|
|
122
|
+
assumptions: z.string().optional().describe("Key assumptions"),
|
|
123
|
+
}, async ({ title, reasoning, alternatives, assumptions }) => {
|
|
124
|
+
try {
|
|
125
|
+
await api("/api/v1/decisions", { method: "POST", body: JSON.stringify({ title, reasoning, alternatives, assumptions }) });
|
|
126
|
+
return ok(`Decision logged: "${title}". Reflection due in 30 days.`);
|
|
127
|
+
} catch (e) { return err(e.message); }
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const transport = new StdioServerTransport();
|
|
131
|
+
await server.connect(transport);
|
|
132
|
+
console.error(`Thesis MCP server v${pkg.version} running`);
|