@getthesis/mcp-server 0.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +132 -212
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getthesis/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "MCP server for Thesis — gives AI agents direct access to strategic context",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -1,212 +1,132 @@
1
- #!/usr/bin/env node
2
-
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { z } from "zod";
6
-
7
- // ─── Config ──────────────────────────────────────────────────────────
8
-
9
- const API_URL = process.env.THESIS_API_URL || "https://www.getthesis.io";
10
- const API_KEY = process.env.THESIS_API_KEY;
11
-
12
- async function api(path, options = {}) {
13
- if (!API_KEY) throw new Error("THESIS_API_KEY not set");
14
-
15
- const res = await fetch(`${API_URL}${path}`, {
16
- ...options,
17
- headers: {
18
- Authorization: `Bearer ${API_KEY}`,
19
- "Content-Type": "application/json",
20
- ...options.headers,
21
- },
22
- });
23
-
24
- const contentType = res.headers.get("content-type") || "";
25
- if (contentType.includes("application/json")) {
26
- const data = await res.json();
27
- if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
28
- return data;
29
- }
30
- const text = await res.text();
31
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
32
- return text;
33
- }
34
-
35
- function ok(text) {
36
- return { content: [{ type: "text", text }] };
37
- }
38
-
39
- function err(msg) {
40
- return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
41
- }
42
-
43
- // ─── Server ──────────────────────────────────────────────────────────
44
-
45
- const server = new McpServer({ name: "thesis", version: "0.2.0" });
46
-
47
- // ─── Tools ───────────────────────────────────────────────────────────
48
-
49
- server.tool(
50
- "thesis_list_items",
51
- "List items in the Thesis workbench. Shows items ready for agent execution, dispatched, or awaiting review.",
52
- {
53
- status: z.enum(["ready", "dispatched", "in_progress", "review", "completed"]).optional().describe("Filter by agent status"),
54
- },
55
- async ({ status }) => {
56
- try {
57
- const query = status ? `?agentStatus=${status}` : "";
58
- const data = await api(`/api/v1/items${query}`);
59
- if (!data.items?.length) return ok("No items found.");
60
- const lines = data.items.map(
61
- (i) => `- [${i.agentStatus || i.status}] ${i.title} (ID: ${i.id})${i.themeName ? ` — Theme: ${i.themeName}` : ""}`
62
- );
63
- return ok(lines.join("\n"));
64
- } catch (e) { return err(e.message); }
65
- }
66
- );
67
-
68
- server.tool(
69
- "thesis_get_brief",
70
- "Get the full strategic brief for a workbench item. Returns the item's strategic context including beliefs, theme, success criteria, constraints, and design principles. Use this before starting work on an item.",
71
- { itemId: z.string().describe("The item ID") },
72
- async ({ itemId }) => {
73
- try {
74
- const data = await api(`/api/v1/items/${itemId}/brief`);
75
- // Return the markdown version for readability
76
- return ok(data.markdown || JSON.stringify(data, null, 2));
77
- } catch (e) { return err(e.message); }
78
- }
79
- );
80
-
81
- server.tool(
82
- "thesis_dispatch",
83
- "Dispatch an item for agent execution. Assembles the strategic brief, marks the item as dispatched, and returns the full brief. Call this when you are about to start working on an item.",
84
- { itemId: z.string().describe("The item ID to dispatch") },
85
- async ({ itemId }) => {
86
- try {
87
- const data = await api(`/api/v1/items/${itemId}/dispatch`, {
88
- method: "POST",
89
- body: JSON.stringify({ provider: "claude_code" }),
90
- });
91
- return ok(data.brief || JSON.stringify(data, null, 2));
92
- } catch (e) { return err(e.message); }
93
- }
94
- );
95
-
96
- server.tool(
97
- "thesis_report_outcome",
98
- "Report the outcome of completed work back to Thesis. Include what you built, key decisions, and anything unexpected. This triggers AI evidence analysis against strategic beliefs.",
99
- {
100
- itemId: z.string().describe("The item ID"),
101
- summary: z.string().describe("What was built"),
102
- decisions: z.string().optional().describe("Key decisions made and why"),
103
- unexpected: z.string().optional().describe("Anything unexpected encountered"),
104
- successCriteriaMet: z.boolean().nullable().optional().describe("true = met, false = not met, null = partially"),
105
- },
106
- async ({ itemId, summary, decisions, unexpected, successCriteriaMet }) => {
107
- try {
108
- const data = await api(`/api/v1/items/${itemId}/outcome`, {
109
- method: "POST",
110
- body: JSON.stringify({ summary, decisions, unexpected, successCriteriaMet }),
111
- });
112
- return ok(`Outcome recorded. Status: ${data.status || "review"}. Evidence analysis running.`);
113
- } catch (e) { return err(e.message); }
114
- }
115
- );
116
-
117
- server.tool(
118
- "thesis_get_beliefs",
119
- "Get all active strategic beliefs in the workspace with confidence levels and evidence counts.",
120
- {},
121
- async () => {
122
- try {
123
- const data = await api("/api/v1/beliefs");
124
- if (!data.beliefs?.length) return ok("No active beliefs.");
125
- const lines = data.beliefs.map(
126
- (b) => `- [${b.confidence}] ${b.statement} (${b.evidenceCount || 0} evidence, ${b.dependentThemeCount || 0} themes)`
127
- );
128
- return ok(lines.join("\n"));
129
- } catch (e) { return err(e.message); }
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_generate_spec",
195
- "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.",
196
- { itemId: z.string().describe("The item ID to generate a spec for") },
197
- async ({ itemId }) => {
198
- try {
199
- const data = await api(`/api/v1/items/${itemId}/generate-spec`, {
200
- method: "POST",
201
- body: JSON.stringify({}),
202
- });
203
- return ok(data.spec || "Spec generated but empty.");
204
- } catch (e) { return err(e.message); }
205
- }
206
- );
207
-
208
- // ─── Start ───────────────────────────────────────────────────────────
209
-
210
- const transport = new StdioServerTransport();
211
- await server.connect(transport);
212
- console.error("Thesis MCP server v0.3.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`);