@episoda/mcp 0.1.11 → 0.1.12

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.
@@ -20,7 +20,8 @@ var readEnvConfig = () => ({
20
20
  apiUrl: normalizeEnv(process.env.EPISODA_API_URL),
21
21
  sessionToken: normalizeEnv(process.env.EPISODA_SESSION_TOKEN),
22
22
  projectId: normalizeEnv(process.env.EPISODA_PROJECT_ID),
23
- workspaceId: normalizeEnv(process.env.EPISODA_WORKSPACE_ID)
23
+ workspaceId: normalizeEnv(process.env.EPISODA_WORKSPACE_ID),
24
+ machineUuid: normalizeEnv(process.env.EPISODA_MACHINE_UUID)
24
25
  });
25
26
  var buildMissingMessage = (missing, apiUrl) => {
26
27
  return [
@@ -52,14 +53,15 @@ var loadLocalConfig = () => {
52
53
  async function resolveRuntimeConfig() {
53
54
  const envConfig = readEnvConfig();
54
55
  let fileConfig = null;
55
- if (!envConfig.sessionToken || !envConfig.projectId || !envConfig.workspaceId || !envConfig.apiUrl) {
56
+ if (!envConfig.sessionToken || !envConfig.projectId || !envConfig.workspaceId || !envConfig.apiUrl || !envConfig.machineUuid) {
56
57
  fileConfig = loadLocalConfig();
57
58
  }
58
59
  const resolved = {
59
60
  apiUrl: envConfig.apiUrl || fileConfig?.api_url || DEFAULT_API_URL,
60
61
  sessionToken: envConfig.sessionToken || fileConfig?.access_token || "",
61
62
  projectId: envConfig.projectId || fileConfig?.project_id || "",
62
- workspaceId: envConfig.workspaceId || fileConfig?.workspace_id || ""
63
+ workspaceId: envConfig.workspaceId || fileConfig?.workspace_id || "",
64
+ machineUuid: envConfig.machineUuid || fileConfig?.machine_uuid || fileConfig?.device_id || void 0
63
65
  };
64
66
  const missing = [];
65
67
  if (!resolved.sessionToken) missing.push("EPISODA_SESSION_TOKEN");
@@ -84,39 +86,84 @@ async function hydrateRuntimeConfig() {
84
86
  if (!normalizeEnv(process.env.EPISODA_WORKSPACE_ID)) {
85
87
  process.env.EPISODA_WORKSPACE_ID = resolved.workspaceId;
86
88
  }
89
+ if (resolved.machineUuid && !normalizeEnv(process.env.EPISODA_MACHINE_UUID)) {
90
+ process.env.EPISODA_MACHINE_UUID = resolved.machineUuid;
91
+ }
87
92
  return resolved;
88
93
  }
89
94
 
90
- // src/workflow-server.ts
91
- var EPISODA_API_URL = process.env.EPISODA_API_URL || "https://episoda.dev";
92
- var EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || "";
93
- var EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || "";
94
- var EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || "";
95
- async function apiRequest(method, path2, body) {
96
- const url = `${EPISODA_API_URL}${path2}`;
95
+ // src/request-executors.ts
96
+ function buildMcpHeaders(runtime) {
97
+ const headers = {
98
+ "Content-Type": "application/json",
99
+ "Authorization": `Bearer ${runtime.sessionToken}`
100
+ };
101
+ if (runtime.projectId) {
102
+ headers["x-project-id"] = runtime.projectId;
103
+ }
104
+ if (runtime.workspaceId) {
105
+ headers["x-workspace-id"] = runtime.workspaceId;
106
+ }
107
+ if (runtime.machineUuid) {
108
+ headers["x-machine-uuid"] = runtime.machineUuid;
109
+ }
110
+ return headers;
111
+ }
112
+ async function apiRequest(runtime, method, path2, body, fetchImpl = fetch) {
113
+ const url = `${runtime.apiUrl}${path2}`;
97
114
  const options = {
98
115
  method,
99
- headers: {
100
- "Content-Type": "application/json",
101
- "Authorization": `Bearer ${EPISODA_SESSION_TOKEN}`
102
- }
116
+ headers: buildMcpHeaders(runtime)
103
117
  };
104
118
  if (body && method !== "GET") {
105
119
  options.body = JSON.stringify(body);
106
120
  }
107
- if (EPISODA_PROJECT_ID) {
108
- options.headers["x-project-id"] = EPISODA_PROJECT_ID;
109
- }
110
- if (EPISODA_WORKSPACE_ID) {
111
- options.headers["x-workspace-id"] = EPISODA_WORKSPACE_ID;
112
- }
113
- const response = await fetch(url, options);
121
+ const response = await fetchImpl(url, options);
114
122
  if (!response.ok) {
115
123
  const text = await response.text();
116
124
  throw new Error(`API error ${response.status}: ${text}`);
117
125
  }
118
126
  return response.json();
119
127
  }
128
+ async function executeTransitionModule(runtime, args, fetchImpl = fetch) {
129
+ const result = await apiRequest(
130
+ runtime,
131
+ "POST",
132
+ `/api/modules/${args.module_uid}/transition`,
133
+ { targetState: args.target_state },
134
+ fetchImpl
135
+ );
136
+ if (!result.success) {
137
+ return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
138
+ }
139
+ return {
140
+ content: [{
141
+ type: "text",
142
+ text: `Module ${args.module_uid} transitioned to ${args.target_state}`
143
+ }]
144
+ };
145
+ }
146
+
147
+ // src/workflow-server.ts
148
+ var EPISODA_API_URL = process.env.EPISODA_API_URL || "https://episoda.dev";
149
+ var EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || "";
150
+ var EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || "";
151
+ var EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || "";
152
+ var EPISODA_MACHINE_UUID = process.env.EPISODA_MACHINE_UUID || "";
153
+ async function apiRequest2(method, path2, body) {
154
+ return apiRequest(
155
+ {
156
+ apiUrl: EPISODA_API_URL,
157
+ sessionToken: EPISODA_SESSION_TOKEN,
158
+ projectId: EPISODA_PROJECT_ID,
159
+ workspaceId: EPISODA_WORKSPACE_ID,
160
+ machineUuid: EPISODA_MACHINE_UUID
161
+ },
162
+ method,
163
+ path2,
164
+ body
165
+ );
166
+ }
120
167
  var server = new McpServer({
121
168
  name: "episoda-workflow",
122
169
  version: "1.0.0"
@@ -147,7 +194,7 @@ server.registerTool(
147
194
  }
148
195
  },
149
196
  async (args) => {
150
- const result = await apiRequest("POST", "/api/tasks", {
197
+ const result = await apiRequest2("POST", "/api/tasks", {
151
198
  module_id: args.module_uid,
152
199
  // API expects module_id (accepts both UUID and UID)
153
200
  title: args.title,
@@ -175,7 +222,7 @@ server.registerTool(
175
222
  }
176
223
  },
177
224
  async (args) => {
178
- const result = await apiRequest("PATCH", `/api/tasks/${args.task_uid}`, {
225
+ const result = await apiRequest2("PATCH", `/api/tasks/${args.task_uid}`, {
179
226
  state: args.state
180
227
  });
181
228
  if (!result.success) {
@@ -203,7 +250,7 @@ server.registerTool(
203
250
  const updates = {};
204
251
  if (args.title) updates.title = args.title;
205
252
  if (args.description) updates.description_md = args.description;
206
- const result = await apiRequest("PATCH", `/api/tasks/${args.task_uid}`, updates);
253
+ const result = await apiRequest2("PATCH", `/api/tasks/${args.task_uid}`, updates);
207
254
  if (!result.success) {
208
255
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
209
256
  }
@@ -224,7 +271,7 @@ server.registerTool(
224
271
  }
225
272
  },
226
273
  async (args) => {
227
- const result = await apiRequest("GET", `/api/tasks/${args.task_uid}`);
274
+ const result = await apiRequest2("GET", `/api/tasks/${args.task_uid}`);
228
275
  if (!result.success || !result.task) {
229
276
  return { content: [{ type: "text", text: `Error: ${result.error || "Task not found"}` }], isError: true };
230
277
  }
@@ -252,7 +299,7 @@ server.registerTool(
252
299
  }
253
300
  },
254
301
  async (args) => {
255
- const result = await apiRequest("GET", `/api/tasks?module_id=${args.module_uid}`);
302
+ const result = await apiRequest2("GET", `/api/tasks?module_id=${args.module_uid}`);
256
303
  if (!result.success || !result.tasks) {
257
304
  return { content: [{ type: "text", text: `Error: ${result.error || "Failed to fetch tasks"}` }], isError: true };
258
305
  }
@@ -283,7 +330,7 @@ server.registerTool(
283
330
  }
284
331
  },
285
332
  async (args) => {
286
- const result = await apiRequest("DELETE", `/api/tasks/${args.task_uid}`);
333
+ const result = await apiRequest2("DELETE", `/api/tasks/${args.task_uid}`);
287
334
  if (!result.success) {
288
335
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
289
336
  }
@@ -315,7 +362,7 @@ Reduces latency from ~1.5s (6 calls) to ~300ms (1 call).`,
315
362
  async (args) => {
316
363
  try {
317
364
  const lookupPromises = args.task_uids.map(
318
- (uid) => apiRequest("GET", `/api/tasks/${uid}`).then((r) => r.success && r.task ? { uid, id: r.task.id } : null).catch(() => null)
365
+ (uid) => apiRequest2("GET", `/api/tasks/${uid}`).then((r) => r.success && r.task ? { uid, id: r.task.id } : null).catch(() => null)
319
366
  );
320
367
  const lookupResults = await Promise.all(lookupPromises);
321
368
  const validTasks = lookupResults.filter((t) => t !== null);
@@ -334,7 +381,7 @@ Not found: ${args.task_uids.join(", ")}`
334
381
  id: t.id,
335
382
  state: args.state
336
383
  }));
337
- const result = await apiRequest("PATCH", "/api/tasks/batch", {
384
+ const result = await apiRequest2("PATCH", "/api/tasks/batch", {
338
385
  updates
339
386
  });
340
387
  if (!result.success) {
@@ -394,7 +441,7 @@ EFFICIENCY: Single call instead of multiple create_task calls.`,
394
441
  },
395
442
  async (args) => {
396
443
  try {
397
- const result = await apiRequest("POST", "/api/tasks/bulk", {
444
+ const result = await apiRequest2("POST", "/api/tasks/bulk", {
398
445
  module_id: args.module_uid,
399
446
  tasks: args.tasks.map((t) => ({
400
447
  title: t.title,
@@ -451,7 +498,7 @@ EFFICIENCY: Single MCP call instead of multiple get_task_details calls.`,
451
498
  async (args) => {
452
499
  try {
453
500
  const fetchPromises = args.task_uids.map(
454
- (uid) => apiRequest("GET", `/api/tasks/${uid}`).then((r) => r.success && r.task ? { found: true, task: r.task } : { found: false, uid }).catch(() => ({ found: false, uid }))
501
+ (uid) => apiRequest2("GET", `/api/tasks/${uid}`).then((r) => r.success && r.task ? { found: true, task: r.task } : { found: false, uid }).catch(() => ({ found: false, uid }))
455
502
  );
456
503
  const results = await Promise.all(fetchPromises);
457
504
  const foundTasks = [];
@@ -510,7 +557,7 @@ server.registerTool(
510
557
  }
511
558
  },
512
559
  async (args) => {
513
- const result = await apiRequest("POST", "/api/modules", {
560
+ const result = await apiRequest2("POST", "/api/modules", {
514
561
  title: args.title,
515
562
  description_md: args.description
516
563
  });
@@ -539,7 +586,7 @@ server.registerTool(
539
586
  const updates = {};
540
587
  if (args.title) updates.title = args.title;
541
588
  if (args.description) updates.description_md = args.description;
542
- const result = await apiRequest("PATCH", `/api/modules/${args.module_uid}`, updates);
589
+ const result = await apiRequest2("PATCH", `/api/modules/${args.module_uid}`, updates);
543
590
  if (!result.success) {
544
591
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
545
592
  }
@@ -560,7 +607,7 @@ server.registerTool(
560
607
  }
561
608
  },
562
609
  async (args) => {
563
- const result = await apiRequest("PATCH", `/api/modules/${args.module_uid}`, {
610
+ const result = await apiRequest2("PATCH", `/api/modules/${args.module_uid}`, {
564
611
  state: "review"
565
612
  });
566
613
  if (!result.success) {
@@ -583,7 +630,7 @@ server.registerTool(
583
630
  }
584
631
  },
585
632
  async (args) => {
586
- const result = await apiRequest("PATCH", `/api/modules/${args.module_uid}`, {
633
+ const result = await apiRequest2("PATCH", `/api/modules/${args.module_uid}`, {
587
634
  state: "done"
588
635
  });
589
636
  if (!result.success) {
@@ -606,7 +653,7 @@ server.registerTool(
606
653
  }
607
654
  },
608
655
  async (args) => {
609
- const result = await apiRequest("GET", `/api/modules/${args.module_uid}`);
656
+ const result = await apiRequest2("GET", `/api/modules/${args.module_uid}`);
610
657
  if (!result.success || !result.module) {
611
658
  return { content: [{ type: "text", text: `Error: ${result.error || "Module not found"}` }], isError: true };
612
659
  }
@@ -633,7 +680,7 @@ server.registerTool(
633
680
  }
634
681
  },
635
682
  async (args) => {
636
- const result = await apiRequest("DELETE", `/api/modules/${args.module_uid}`);
683
+ const result = await apiRequest2("DELETE", `/api/modules/${args.module_uid}`);
637
684
  if (!result.success) {
638
685
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
639
686
  }
@@ -666,7 +713,7 @@ USE THIS WHEN:
666
713
  if (args.limit) params.set("limit", String(args.limit));
667
714
  if (args.include_task_counts) params.set("includeTaskCounts", "true");
668
715
  const query = params.toString();
669
- const result = await apiRequest("GET", `/api/modules${query ? `?${query}` : ""}`);
716
+ const result = await apiRequest2("GET", `/api/modules${query ? `?${query}` : ""}`);
670
717
  if (!result.success || !result.modules) {
671
718
  return { content: [{ type: "text", text: `Error: ${result.error || "Failed to fetch modules"}` }], isError: true };
672
719
  }
@@ -699,22 +746,7 @@ Use this instead of request_review or mark_done for full control.`,
699
746
  target_state: z.enum(["ready", "doing", "review", "done"]).describe("Target state for the module")
700
747
  }
701
748
  },
702
- async (args) => {
703
- const result = await apiRequest(
704
- "POST",
705
- `/api/modules/${args.module_uid}/transition`,
706
- { targetState: args.target_state }
707
- );
708
- if (!result.success) {
709
- return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
710
- }
711
- return {
712
- content: [{
713
- type: "text",
714
- text: `Module ${args.module_uid} transitioned to ${args.target_state}`
715
- }]
716
- };
717
- }
749
+ handleTransitionModule
718
750
  );
719
751
  server.registerTool(
720
752
  "archive_module",
@@ -725,7 +757,7 @@ server.registerTool(
725
757
  }
726
758
  },
727
759
  async (args) => {
728
- const result = await apiRequest(
760
+ const result = await apiRequest2(
729
761
  "POST",
730
762
  `/api/modules/${args.module_uid}/archive`,
731
763
  {}
@@ -750,7 +782,7 @@ server.registerTool(
750
782
  }
751
783
  },
752
784
  async (args) => {
753
- const result = await apiRequest("GET", `/api/knowledge/${args.knowledge_id}`);
785
+ const result = await apiRequest2("GET", `/api/knowledge/${args.knowledge_id}`);
754
786
  if (!result.success || !result.knowledge) {
755
787
  return { content: [{ type: "text", text: `Error: ${result.error || "Knowledge not found"}` }], isError: true };
756
788
  }
@@ -793,7 +825,7 @@ DO NOT USE WHEN:
793
825
  }
794
826
  },
795
827
  async (args) => {
796
- const result = await apiRequest(
828
+ const result = await apiRequest2(
797
829
  "POST",
798
830
  "/api/search/semantic",
799
831
  {
@@ -854,7 +886,7 @@ DO NOT USE WHEN:
854
886
  }
855
887
  },
856
888
  async (args) => {
857
- const result = await apiRequest(
889
+ const result = await apiRequest2(
858
890
  "POST",
859
891
  "/api/search/text",
860
892
  {
@@ -914,13 +946,13 @@ This is the recommended default search for most queries.`,
914
946
  },
915
947
  async (args) => {
916
948
  const [semanticResult, textResult] = await Promise.all([
917
- apiRequest("POST", "/api/search/semantic", {
949
+ apiRequest2("POST", "/api/search/semantic", {
918
950
  query: args.query,
919
951
  types: ["knowledge"],
920
952
  limit: 50,
921
953
  threshold: 0.4
922
954
  }),
923
- apiRequest("POST", "/api/search/text", {
955
+ apiRequest2("POST", "/api/search/text", {
924
956
  query: args.query,
925
957
  types: ["knowledge"],
926
958
  limit: 50
@@ -993,7 +1025,7 @@ USE THIS WHEN:
993
1025
  }
994
1026
  },
995
1027
  async (args) => {
996
- const result = await apiRequest(
1028
+ const result = await apiRequest2(
997
1029
  "POST",
998
1030
  "/api/search/conversations",
999
1031
  {
@@ -1038,7 +1070,7 @@ server.registerTool(
1038
1070
  }
1039
1071
  },
1040
1072
  async (args) => {
1041
- const result = await apiRequest(
1073
+ const result = await apiRequest2(
1042
1074
  "GET",
1043
1075
  `/api/modules/${args.module_uid}/knowledge`
1044
1076
  );
@@ -1069,7 +1101,7 @@ server.registerTool(
1069
1101
  }
1070
1102
  },
1071
1103
  async (args) => {
1072
- const result = await apiRequest(
1104
+ const result = await apiRequest2(
1073
1105
  "POST",
1074
1106
  `/api/modules/${args.module_uid}/knowledge/link`,
1075
1107
  { knowledge_id: args.knowledge_id }
@@ -1105,7 +1137,7 @@ All documentation MUST be created in the knowledge base, not as markdown files.`
1105
1137
  }
1106
1138
  },
1107
1139
  async (args) => {
1108
- const result = await apiRequest("POST", "/api/knowledge", {
1140
+ const result = await apiRequest2("POST", "/api/knowledge", {
1109
1141
  title: args.title,
1110
1142
  domain: args.domain,
1111
1143
  doc_type: args.doc_type,
@@ -1144,7 +1176,7 @@ server.registerTool(
1144
1176
  if (args.domain) updates.domain = args.domain;
1145
1177
  if (args.doc_type) updates.doc_type = args.doc_type;
1146
1178
  if (args.significance !== void 0) updates.significance = args.significance;
1147
- const result = await apiRequest("PATCH", `/api/knowledge/${args.knowledge_id}`, updates);
1179
+ const result = await apiRequest2("PATCH", `/api/knowledge/${args.knowledge_id}`, updates);
1148
1180
  if (!result.success) {
1149
1181
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
1150
1182
  }
@@ -1165,7 +1197,7 @@ server.registerTool(
1165
1197
  }
1166
1198
  },
1167
1199
  async (args) => {
1168
- const result = await apiRequest("DELETE", `/api/knowledge/${args.knowledge_id}`);
1200
+ const result = await apiRequest2("DELETE", `/api/knowledge/${args.knowledge_id}`);
1169
1201
  if (!result.success) {
1170
1202
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
1171
1203
  }
@@ -1186,7 +1218,7 @@ server.registerTool(
1186
1218
  }
1187
1219
  },
1188
1220
  async (args) => {
1189
- const result = await apiRequest(
1221
+ const result = await apiRequest2(
1190
1222
  "GET",
1191
1223
  `/api/agent/context/module/${args.module_uid}`
1192
1224
  );
@@ -1208,7 +1240,7 @@ server.registerTool(
1208
1240
  inputSchema: {}
1209
1241
  },
1210
1242
  async () => {
1211
- const result = await apiRequest(
1243
+ const result = await apiRequest2(
1212
1244
  "GET",
1213
1245
  "/api/agent/context/system"
1214
1246
  );
@@ -1223,20 +1255,39 @@ server.registerTool(
1223
1255
  };
1224
1256
  }
1225
1257
  );
1226
- async function main() {
1258
+ async function handleTransitionModule(args) {
1259
+ return executeTransitionModule(
1260
+ {
1261
+ apiUrl: EPISODA_API_URL,
1262
+ sessionToken: EPISODA_SESSION_TOKEN,
1263
+ projectId: EPISODA_PROJECT_ID,
1264
+ workspaceId: EPISODA_WORKSPACE_ID,
1265
+ machineUuid: EPISODA_MACHINE_UUID
1266
+ },
1267
+ args
1268
+ );
1269
+ }
1270
+ async function startServer() {
1227
1271
  const runtimeConfig = await hydrateRuntimeConfig();
1228
1272
  EPISODA_API_URL = runtimeConfig.apiUrl;
1229
1273
  EPISODA_SESSION_TOKEN = runtimeConfig.sessionToken;
1230
1274
  EPISODA_PROJECT_ID = runtimeConfig.projectId;
1231
1275
  EPISODA_WORKSPACE_ID = runtimeConfig.workspaceId;
1276
+ EPISODA_MACHINE_UUID = runtimeConfig.machineUuid || "";
1232
1277
  const transport = new StdioServerTransport();
1233
1278
  await server.connect(transport);
1234
1279
  console.error("[episoda-workflow] MCP server started");
1235
1280
  console.error(`[episoda-workflow] API URL: ${EPISODA_API_URL}`);
1236
1281
  console.error(`[episoda-workflow] Token: ${EPISODA_SESSION_TOKEN ? "****" : "NOT SET"}`);
1237
1282
  }
1238
- main().catch((error) => {
1239
- console.error("[episoda-workflow] Fatal error:", error);
1240
- process.exit(1);
1241
- });
1283
+ if (process.env.EPISODA_MCP_NO_AUTOSTART !== "1") {
1284
+ startServer().catch((error) => {
1285
+ console.error("[episoda-workflow] Fatal error:", error);
1286
+ process.exit(1);
1287
+ });
1288
+ }
1289
+ export {
1290
+ handleTransitionModule,
1291
+ startServer
1292
+ };
1242
1293
  //# sourceMappingURL=workflow-server.js.map