@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,44 +86,72 @@ 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/dev-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 DEV_ENVIRONMENT_ID = process.env.DEV_ENVIRONMENT_ID || "";
94
- var MODULE_UID = process.env.MODULE_UID || "";
95
- var EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || "";
96
- var EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || "";
97
- var targetSchema = {
98
- moduleUid: z.string().optional().describe("Module UID to target (overrides server default)")
99
- };
100
- async function apiRequest(method, path2, body) {
101
- const url = `${EPISODA_API_URL}${path2}`;
102
- const options = {
103
- method,
104
- headers: {
105
- "Content-Type": "application/json",
106
- "Authorization": `Bearer ${EPISODA_SESSION_TOKEN}`
107
- }
95
+ // src/request-executors.ts
96
+ function buildMcpHeaders(runtime) {
97
+ const headers = {
98
+ "Content-Type": "application/json",
99
+ "Authorization": `Bearer ${runtime.sessionToken}`
108
100
  };
109
- if (EPISODA_PROJECT_ID) {
110
- options.headers["x-project-id"] = EPISODA_PROJECT_ID;
101
+ if (runtime.projectId) {
102
+ headers["x-project-id"] = runtime.projectId;
111
103
  }
112
- if (EPISODA_WORKSPACE_ID) {
113
- options.headers["x-workspace-id"] = EPISODA_WORKSPACE_ID;
104
+ if (runtime.workspaceId) {
105
+ headers["x-workspace-id"] = runtime.workspaceId;
114
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}`;
114
+ const options = {
115
+ method,
116
+ headers: buildMcpHeaders(runtime)
117
+ };
115
118
  if (body && method !== "GET") {
116
119
  options.body = JSON.stringify(body);
117
120
  }
118
- const response = await fetch(url, options);
121
+ const response = await fetchImpl(url, options);
119
122
  if (!response.ok) {
120
123
  const text = await response.text();
121
124
  throw new Error(`API error ${response.status}: ${text}`);
122
125
  }
123
126
  return response.json();
124
127
  }
128
+
129
+ // src/dev-server.ts
130
+ var EPISODA_API_URL = process.env.EPISODA_API_URL || "https://episoda.dev";
131
+ var EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || "";
132
+ var DEV_ENVIRONMENT_ID = process.env.DEV_ENVIRONMENT_ID || "";
133
+ var MODULE_UID = process.env.MODULE_UID || "";
134
+ var EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || "";
135
+ var EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || "";
136
+ var EPISODA_MACHINE_UUID = process.env.EPISODA_MACHINE_UUID || "";
137
+ var targetSchema = {
138
+ moduleUid: z.string().optional().describe("Module UID to target (overrides server default)")
139
+ };
140
+ async function apiRequest2(method, path2, body) {
141
+ return apiRequest(
142
+ {
143
+ apiUrl: EPISODA_API_URL,
144
+ sessionToken: EPISODA_SESSION_TOKEN,
145
+ projectId: EPISODA_PROJECT_ID,
146
+ workspaceId: EPISODA_WORKSPACE_ID,
147
+ // EP1376: Intentional parity with workflow/git MCP servers.
148
+ machineUuid: EPISODA_MACHINE_UUID
149
+ },
150
+ method,
151
+ path2,
152
+ body
153
+ );
154
+ }
125
155
  function devPath(endpoint, overrideTarget) {
126
156
  const target = overrideTarget || MODULE_UID || DEV_ENVIRONMENT_ID;
127
157
  if (!target) {
@@ -166,7 +196,7 @@ server.registerTool(
166
196
  },
167
197
  async (args) => {
168
198
  try {
169
- const result = await apiRequest(
199
+ const result = await apiRequest2(
170
200
  "POST",
171
201
  devPath("/read-file", args.moduleUid),
172
202
  {
@@ -205,7 +235,7 @@ server.registerTool(
205
235
  },
206
236
  async (args) => {
207
237
  try {
208
- const result = await apiRequest(
238
+ const result = await apiRequest2(
209
239
  "POST",
210
240
  devPath("/write-file", args.moduleUid),
211
241
  {
@@ -249,7 +279,7 @@ server.registerTool(
249
279
  },
250
280
  async (args) => {
251
281
  try {
252
- const result = await apiRequest(
282
+ const result = await apiRequest2(
253
283
  "POST",
254
284
  devPath("/edit-file", args.moduleUid),
255
285
  {
@@ -297,7 +327,7 @@ server.registerTool(
297
327
  },
298
328
  async (args) => {
299
329
  try {
300
- const result = await apiRequest(
330
+ const result = await apiRequest2(
301
331
  "DELETE",
302
332
  `${devPath("/delete-file", args.moduleUid)}?path=${encodeURIComponent(args.path)}&recursive=${args.recursive || false}`
303
333
  );
@@ -331,7 +361,7 @@ server.registerTool(
331
361
  },
332
362
  async (args) => {
333
363
  try {
334
- const result = await apiRequest(
364
+ const result = await apiRequest2(
335
365
  "POST",
336
366
  devPath("/list-dir", args.moduleUid),
337
367
  {
@@ -378,7 +408,7 @@ server.registerTool(
378
408
  },
379
409
  async (args) => {
380
410
  try {
381
- const result = await apiRequest(
411
+ const result = await apiRequest2(
382
412
  "POST",
383
413
  devPath("/mkdir", args.moduleUid),
384
414
  { path: args.path }
@@ -413,7 +443,7 @@ server.registerTool(
413
443
  },
414
444
  async (args) => {
415
445
  try {
416
- const result = await apiRequest(
446
+ const result = await apiRequest2(
417
447
  "POST",
418
448
  devPath("/search-files", args.moduleUid),
419
449
  {
@@ -464,7 +494,7 @@ server.registerTool(
464
494
  async (args) => {
465
495
  try {
466
496
  const flags = args.caseSensitive === false ? "-rni" : "-rn";
467
- const result = await apiRequest(
497
+ const result = await apiRequest2(
468
498
  "POST",
469
499
  devPath("/grep", args.moduleUid),
470
500
  {
@@ -515,7 +545,7 @@ server.registerTool(
515
545
  },
516
546
  async (args) => {
517
547
  try {
518
- const result = await apiRequest(
548
+ const result = await apiRequest2(
519
549
  "POST",
520
550
  devPath("/exec", args.moduleUid),
521
551
  {
@@ -579,7 +609,7 @@ server.registerTool(
579
609
  },
580
610
  async (args) => {
581
611
  try {
582
- const result = await apiRequest(
612
+ const result = await apiRequest2(
583
613
  "POST",
584
614
  devPath("/batch", args.moduleUid),
585
615
  {
@@ -622,12 +652,13 @@ function formatSize(bytes) {
622
652
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
623
653
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
624
654
  }
625
- async function main() {
655
+ async function startServer() {
626
656
  const runtimeConfig = await hydrateRuntimeConfig();
627
657
  EPISODA_API_URL = runtimeConfig.apiUrl;
628
658
  EPISODA_SESSION_TOKEN = runtimeConfig.sessionToken;
629
659
  EPISODA_PROJECT_ID = runtimeConfig.projectId;
630
660
  EPISODA_WORKSPACE_ID = runtimeConfig.workspaceId;
661
+ EPISODA_MACHINE_UUID = runtimeConfig.machineUuid || "";
631
662
  if (!MODULE_UID && !DEV_ENVIRONMENT_ID) {
632
663
  console.warn("[episoda-dev] Warning: MODULE_UID/DEV_ENVIRONMENT_ID not set. Provide moduleUid per tool call.");
633
664
  }
@@ -639,8 +670,13 @@ async function main() {
639
670
  console.error(`[episoda-dev] Dev Target: ${devTarget}`);
640
671
  console.error(`[episoda-dev] Token: ${EPISODA_SESSION_TOKEN ? "****" : "NOT SET"}`);
641
672
  }
642
- main().catch((error) => {
643
- console.error("[episoda-dev] Fatal error:", error);
644
- process.exit(1);
645
- });
673
+ if (process.env.EPISODA_MCP_NO_AUTOSTART !== "1") {
674
+ startServer().catch((error) => {
675
+ console.error("[episoda-dev] Fatal error:", error);
676
+ process.exit(1);
677
+ });
678
+ }
679
+ export {
680
+ startServer
681
+ };
646
682
  //# sourceMappingURL=dev-server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev-server.ts","../src/runtime-config.ts"],"sourcesContent":["/**\n * EP908: Episoda Dev MCP Server\n *\n * MCP server for Claude Code agents to perform file and environment operations\n * on dev environments (cloud VMs or local machines).\n *\n * Provides tools for:\n * - File operations (read, write, edit, delete, mkdir)\n * - Directory operations (list, search)\n * - Code search (grep)\n * - Command execution\n * - Batch operations\n *\n * Usage:\n * npx -y @episoda/mcp dev\n *\n * Required environment variables:\n * EPISODA_API_URL - Base URL for Episoda API (e.g., https://episoda.dev)\n * EPISODA_SESSION_TOKEN - Bearer token for API authentication\n *\n * Optional environment variables:\n * MODULE_UID - Module UID (preferred, e.g., EP123) or provide moduleUid per tool call\n * DEV_ENVIRONMENT_ID - UUID of the dev environment (legacy fallback)\n * EPISODA_PROJECT_ID - Project ID (for scoped requests)\n * EPISODA_WORKSPACE_ID - Workspace ID (for scoped requests)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport { hydrateRuntimeConfig } from './runtime-config.js'\n\n// Environment configuration\nlet EPISODA_API_URL = process.env.EPISODA_API_URL || 'https://episoda.dev'\nlet EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || ''\nconst DEV_ENVIRONMENT_ID = process.env.DEV_ENVIRONMENT_ID || ''\nconst MODULE_UID = process.env.MODULE_UID || ''\nlet EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || ''\nlet EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || ''\n\nconst targetSchema = {\n moduleUid: z.string().optional().describe('Module UID to target (overrides server default)')\n}\n\n/**\n * Make an authenticated API request\n */\nasync function apiRequest<T>(\n method: 'GET' | 'POST' | 'DELETE',\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\n const url = `${EPISODA_API_URL}${path}`\n const options: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${EPISODA_SESSION_TOKEN}`\n }\n }\n\n if (EPISODA_PROJECT_ID) {\n (options.headers as Record<string, string>)['x-project-id'] = EPISODA_PROJECT_ID\n }\n if (EPISODA_WORKSPACE_ID) {\n (options.headers as Record<string, string>)['x-workspace-id'] = EPISODA_WORKSPACE_ID\n }\n\n if (body && method !== 'GET') {\n options.body = JSON.stringify(body)\n }\n\n const response = await fetch(url, options)\n\n if (!response.ok) {\n const text = await response.text()\n throw new Error(`API error ${response.status}: ${text}`)\n }\n\n return response.json() as Promise<T>\n}\n\n/**\n * Helper to build API path with dev environment ID\n */\nfunction devPath(endpoint: string, overrideTarget?: string): string {\n const target = overrideTarget || MODULE_UID || DEV_ENVIRONMENT_ID\n if (!target) {\n throw new Error('Module target missing. Provide moduleUid in the tool call or set MODULE_UID/DEV_ENVIRONMENT_ID.')\n }\n return `/api/dev/${target}${endpoint}`\n}\n\n// Create MCP server\nconst server = new McpServer({\n name: 'episoda-dev',\n version: '1.0.0'\n}, {\n capabilities: {\n tools: {}\n },\n instructions: `\n Episoda Dev MCP Server\n\n This server provides file and environment operations for dev environments\n (cloud VMs or local machines). All operations execute in the context of\n the configured dev environment.\n You may pass moduleUid per call to target a specific module.\n\n Categories:\n - File Operations: read_file, write_file, edit_file, delete_file\n - Directory Operations: list_directory, mkdir, search_files\n - Code Search: grep\n - Execution: exec_command\n - Batch: batch_operations\n\n All paths should be absolute paths within the dev environment.\n `\n})\n\n// ============================================\n// File Operations\n// ============================================\n\nserver.registerTool(\n 'read_file',\n {\n description: 'Read contents of a file',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n encoding: z.enum(['utf8', 'base64']).optional().describe('File encoding (default: utf8)')\n }\n },\n async (args) => {\n interface ReadResponse {\n success: boolean\n data?: { content: string }\n error?: string\n }\n\n try {\n const result = await apiRequest<ReadResponse>(\n 'POST',\n devPath('/read-file', args.moduleUid),\n {\n path: args.path,\n encoding: args.encoding || 'utf8'\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to read file'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: result.data.content }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error reading file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'write_file',\n {\n description: 'Write content to a file (creates or overwrites)',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n content: z.string().describe('Content to write'),\n encoding: z.enum(['utf8', 'base64']).optional().describe('Content encoding (default: utf8)'),\n createDirs: z.boolean().optional().describe('Create parent directories if missing (default: true)')\n }\n },\n async (args) => {\n interface WriteResponse {\n success: boolean\n data?: { size: number }\n error?: string\n }\n\n try {\n const result = await apiRequest<WriteResponse>(\n 'POST',\n devPath('/write-file', args.moduleUid),\n {\n path: args.path,\n content: args.content,\n encoding: args.encoding || 'utf8',\n createDirs: args.createDirs !== false\n }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to write file'}` }],\n isError: true\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Wrote ${result.data?.size || 0} bytes to ${args.path}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error writing file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'edit_file',\n {\n description: 'Edit a file by replacing a specific string with another',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n old_string: z.string().describe('The exact string to find and replace'),\n new_string: z.string().describe('The replacement string'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces first only)')\n }\n },\n async (args) => {\n interface EditResponse {\n success: boolean\n data?: { replacements: number; newSize: number }\n error?: string\n }\n\n try {\n const result = await apiRequest<EditResponse>(\n 'POST',\n devPath('/edit-file', args.moduleUid),\n {\n path: args.path,\n old_string: args.old_string,\n new_string: args.new_string,\n replace_all: args.replace_all || false\n }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to edit file'}` }],\n isError: true\n }\n }\n\n const replacements = result.data?.replacements || 0\n if (replacements === 0) {\n return {\n content: [{ type: 'text', text: 'Warning: No replacements made (string not found)' }]\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Made ${replacements} replacement(s) in ${args.path}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error editing file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'delete_file',\n {\n description: 'Delete a file or directory',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to delete'),\n recursive: z.boolean().optional().describe('Delete directories recursively (default: false)')\n }\n },\n async (args) => {\n interface DeleteResponse {\n success: boolean\n error?: string\n }\n\n try {\n const result = await apiRequest<DeleteResponse>(\n 'DELETE',\n `${devPath('/delete-file', args.moduleUid)}?path=${encodeURIComponent(args.path)}&recursive=${args.recursive || false}`\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to delete'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: `Deleted ${args.path}` }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error deleting: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Directory Operations\n// ============================================\n\nserver.registerTool(\n 'list_directory',\n {\n description: 'List contents of a directory',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to directory'),\n recursive: z.boolean().optional().describe('List recursively (default: false)'),\n includeHidden: z.boolean().optional().describe('Include hidden files (default: false)')\n }\n },\n async (args) => {\n interface ListResponse {\n success: boolean\n data?: {\n entries: Array<{\n name: string\n type: 'file' | 'directory'\n size: number\n }>\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<ListResponse>(\n 'POST',\n devPath('/list-dir', args.moduleUid),\n {\n path: args.path,\n recursive: args.recursive || false,\n includeHidden: args.includeHidden || false\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to list directory'}` }],\n isError: true\n }\n }\n\n if (result.data.entries.length === 0) {\n return {\n content: [{ type: 'text', text: `Directory ${args.path} is empty` }]\n }\n }\n\n const listing = result.data.entries.map(e => {\n const icon = e.type === 'directory' ? '/' : ''\n const size = e.type === 'file' ? ` (${formatSize(e.size)})` : ''\n return `${e.name}${icon}${size}`\n }).join('\\n')\n\n return {\n content: [{ type: 'text', text: listing }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error listing directory: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'mkdir',\n {\n description: 'Create a directory (including parent directories)',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path of directory to create')\n }\n },\n async (args) => {\n interface MkdirResponse {\n success: boolean\n error?: string\n }\n\n try {\n const result = await apiRequest<MkdirResponse>(\n 'POST',\n devPath('/mkdir', args.moduleUid),\n { path: args.path }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to create directory'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: `Created directory ${args.path}` }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error creating directory: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'search_files',\n {\n description: 'Search for files by glob pattern',\n inputSchema: {\n ...targetSchema,\n pattern: z.string().describe('Glob pattern (e.g., \"*.ts\", \"**/*.tsx\")'),\n basePath: z.string().describe('Base directory to search from'),\n maxResults: z.number().optional().describe('Maximum results (default: 100)')\n }\n },\n async (args) => {\n interface SearchResponse {\n success: boolean\n data?: { files: string[] }\n error?: string\n }\n\n try {\n const result = await apiRequest<SearchResponse>(\n 'POST',\n devPath('/search-files', args.moduleUid),\n {\n pattern: args.pattern,\n basePath: args.basePath,\n maxResults: args.maxResults || 100\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to search'}` }],\n isError: true\n }\n }\n\n if (result.data.files.length === 0) {\n return {\n content: [{ type: 'text', text: `No files matching \"${args.pattern}\" found` }]\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Found ${result.data.files.length} file(s):\\n${result.data.files.join('\\n')}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error searching: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Code Search\n// ============================================\n\nserver.registerTool(\n 'grep',\n {\n description: 'Search for pattern in file contents',\n inputSchema: {\n ...targetSchema,\n pattern: z.string().describe('Search pattern (regex supported)'),\n path: z.string().describe('File or directory to search'),\n filePattern: z.string().optional().describe('Filter by file pattern (e.g., \"*.ts\")'),\n caseSensitive: z.boolean().optional().describe('Case sensitive search (default: true)'),\n maxResults: z.number().optional().describe('Maximum results (default: 100)')\n }\n },\n async (args) => {\n interface GrepResponse {\n success: boolean\n data?: {\n matches: Array<{\n file: string\n line: number\n content: string\n }>\n }\n error?: string\n }\n\n try {\n const flags = args.caseSensitive === false ? '-rni' : '-rn'\n const result = await apiRequest<GrepResponse>(\n 'POST',\n devPath('/grep', args.moduleUid),\n {\n pattern: args.pattern,\n path: args.path,\n flags\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to search'}` }],\n isError: true\n }\n }\n\n if (result.data.matches.length === 0) {\n return {\n content: [{ type: 'text', text: `No matches found for \"${args.pattern}\"` }]\n }\n }\n\n const matches = result.data.matches.map(m =>\n `${m.file}:${m.line}: ${m.content}`\n ).join('\\n')\n\n return {\n content: [{\n type: 'text',\n text: `Found ${result.data.matches.length} match(es):\\n${matches}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error searching: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Command Execution\n// ============================================\n\nserver.registerTool(\n 'exec_command',\n {\n description: 'Execute a shell command',\n inputSchema: {\n ...targetSchema,\n command: z.string().describe('Command to execute'),\n cwd: z.string().optional().describe('Working directory'),\n timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)')\n }\n },\n async (args) => {\n interface ExecResponse {\n success: boolean\n data?: {\n stdout: string\n stderr: string\n exitCode: number\n timedOut?: boolean\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<ExecResponse>(\n 'POST',\n devPath('/exec', args.moduleUid),\n {\n command: args.command,\n cwd: args.cwd,\n timeout: args.timeout || 30000\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Command failed'}` }],\n isError: true\n }\n }\n\n const { stdout, stderr, exitCode, timedOut } = result.data\n\n if (timedOut) {\n return {\n content: [{ type: 'text', text: `Command timed out\\n\\nOutput so far:\\n${stdout}` }],\n isError: true\n }\n }\n\n let output = ''\n if (stdout) output += stdout\n if (stderr) output += `\\n\\nSTDERR:\\n${stderr}`\n if (exitCode !== 0) output += `\\n\\nExit code: ${exitCode}`\n\n return {\n content: [{ type: 'text', text: output || '(no output)' }],\n isError: exitCode !== 0\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error executing command: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Batch Operations\n// ============================================\n\nserver.registerTool(\n 'batch_operations',\n {\n description: 'Execute multiple file operations in a single request',\n inputSchema: {\n ...targetSchema,\n operations: z.array(z.object({\n type: z.enum(['read', 'write', 'mkdir', 'delete', 'exec']).describe('Operation type'),\n path: z.string().optional().describe('File/directory path'),\n content: z.string().optional().describe('Content for write operations'),\n command: z.string().optional().describe('Command for exec operations'),\n recursive: z.boolean().optional().describe('Recursive flag for delete')\n })).describe('Array of operations to execute'),\n stopOnError: z.boolean().optional().describe('Stop on first error (default: false)')\n }\n },\n async (args) => {\n interface BatchResponse {\n success: boolean\n data?: {\n results: Array<{\n success: boolean\n data?: unknown\n error?: string\n }>\n completedCount: number\n failedCount: number\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<BatchResponse>(\n 'POST',\n devPath('/batch', args.moduleUid),\n {\n operations: args.operations,\n stopOnError: args.stopOnError || false\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Batch failed'}` }],\n isError: true\n }\n }\n\n const { completedCount, failedCount, results } = result.data\n\n let summary = `Batch complete: ${completedCount} succeeded, ${failedCount} failed\\n\\n`\n\n results.forEach((r, i) => {\n const op = args.operations[i]\n const status = r.success ? 'OK' : 'FAILED'\n summary += `[${i + 1}] ${op.type} ${op.path || op.command || ''}: ${status}`\n if (!r.success && r.error) summary += ` - ${r.error}`\n summary += '\\n'\n })\n\n return {\n content: [{ type: 'text', text: summary }],\n isError: failedCount > 0\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error executing batch: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Utility Functions\n// ============================================\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`\n}\n\n// ============================================\n// Start Server\n// ============================================\n\nasync function main() {\n const runtimeConfig = await hydrateRuntimeConfig()\n EPISODA_API_URL = runtimeConfig.apiUrl\n EPISODA_SESSION_TOKEN = runtimeConfig.sessionToken\n EPISODA_PROJECT_ID = runtimeConfig.projectId\n EPISODA_WORKSPACE_ID = runtimeConfig.workspaceId\n\n if (!MODULE_UID && !DEV_ENVIRONMENT_ID) {\n console.warn('[episoda-dev] Warning: MODULE_UID/DEV_ENVIRONMENT_ID not set. Provide moduleUid per tool call.')\n }\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n\n const devTarget = MODULE_UID || DEV_ENVIRONMENT_ID || 'NOT SET'\n console.error('[episoda-dev] MCP server started')\n console.error(`[episoda-dev] API URL: ${EPISODA_API_URL}`)\n console.error(`[episoda-dev] Dev Target: ${devTarget}`)\n console.error(`[episoda-dev] Token: ${EPISODA_SESSION_TOKEN ? '****' : 'NOT SET'}`)\n}\n\nmain().catch((error) => {\n console.error('[episoda-dev] Fatal error:', error)\n process.exit(1)\n})\n","import * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\nconst DEFAULT_API_URL = 'https://episoda.dev'\nconst DEFAULT_CONFIG_FILE = 'config.json'\n\ninterface EpisodaLocalConfig {\n access_token?: string\n project_id?: string\n workspace_id?: string\n api_url?: string\n}\n\nexport interface RuntimeConfig {\n apiUrl: string\n sessionToken: string\n projectId: string\n workspaceId: string\n}\n\nconst normalizeEnv = (value?: string): string | undefined => {\n if (!value) return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nconst readEnvConfig = () => ({\n apiUrl: normalizeEnv(process.env.EPISODA_API_URL),\n sessionToken: normalizeEnv(process.env.EPISODA_SESSION_TOKEN),\n projectId: normalizeEnv(process.env.EPISODA_PROJECT_ID),\n workspaceId: normalizeEnv(process.env.EPISODA_WORKSPACE_ID)\n})\n\nconst buildMissingMessage = (missing: string[], apiUrl: string): string => {\n return [\n `[episoda-mcp] Missing auth context: ${missing.join(', ')}`,\n '[episoda-mcp] Set EPISODA_* env vars or run:',\n `[episoda-mcp] episoda auth --api-url ${apiUrl}`\n ].join('\\n')\n}\n\nconst getConfigPath = (): string => {\n // MCP supports a full-path override in addition to the core-style config dir.\n if (process.env.EPISODA_CONFIG_PATH) {\n return process.env.EPISODA_CONFIG_PATH\n }\n const configDir = process.env.EPISODA_CONFIG_DIR || path.join(os.homedir(), '.episoda')\n return path.join(configDir, DEFAULT_CONFIG_FILE)\n}\n\nconst loadLocalConfig = (): EpisodaLocalConfig | null => {\n const configPath = getConfigPath()\n if (!fs.existsSync(configPath)) {\n return null\n }\n try {\n const content = fs.readFileSync(configPath, 'utf8')\n return JSON.parse(content) as EpisodaLocalConfig\n } catch (error) {\n console.error('[episoda-mcp] Failed to load config:', error)\n return null\n }\n}\n\nexport async function resolveRuntimeConfig(): Promise<RuntimeConfig> {\n const envConfig = readEnvConfig()\n\n let fileConfig: EpisodaLocalConfig | null = null\n if (!envConfig.sessionToken || !envConfig.projectId || !envConfig.workspaceId || !envConfig.apiUrl) {\n fileConfig = loadLocalConfig()\n }\n\n const resolved: RuntimeConfig = {\n apiUrl: envConfig.apiUrl || fileConfig?.api_url || DEFAULT_API_URL,\n sessionToken: envConfig.sessionToken || fileConfig?.access_token || '',\n projectId: envConfig.projectId || fileConfig?.project_id || '',\n workspaceId: envConfig.workspaceId || fileConfig?.workspace_id || ''\n }\n\n const missing: string[] = []\n if (!resolved.sessionToken) missing.push('EPISODA_SESSION_TOKEN')\n if (!resolved.projectId) missing.push('EPISODA_PROJECT_ID')\n if (!resolved.workspaceId) missing.push('EPISODA_WORKSPACE_ID')\n\n if (missing.length > 0) {\n throw new Error(buildMissingMessage(missing, resolved.apiUrl))\n }\n\n return resolved\n}\n\nexport async function hydrateRuntimeConfig(): Promise<RuntimeConfig> {\n const resolved = await resolveRuntimeConfig()\n\n if (!normalizeEnv(process.env.EPISODA_API_URL)) {\n process.env.EPISODA_API_URL = resolved.apiUrl\n }\n if (!normalizeEnv(process.env.EPISODA_SESSION_TOKEN)) {\n process.env.EPISODA_SESSION_TOKEN = resolved.sessionToken\n }\n if (!normalizeEnv(process.env.EPISODA_PROJECT_ID)) {\n process.env.EPISODA_PROJECT_ID = resolved.projectId\n }\n if (!normalizeEnv(process.env.EPISODA_WORKSPACE_ID)) {\n process.env.EPISODA_WORKSPACE_ID = resolved.workspaceId\n }\n\n return resolved\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../src/dev-server.ts","../src/runtime-config.ts","../src/request-executors.ts"],"sourcesContent":["/**\n * EP908: Episoda Dev MCP Server\n *\n * MCP server for Claude Code agents to perform file and environment operations\n * on dev environments (cloud VMs or local machines).\n *\n * Provides tools for:\n * - File operations (read, write, edit, delete, mkdir)\n * - Directory operations (list, search)\n * - Code search (grep)\n * - Command execution\n * - Batch operations\n *\n * Usage:\n * npx -y @episoda/mcp dev\n *\n * Required environment variables:\n * EPISODA_API_URL - Base URL for Episoda API (e.g., https://episoda.dev)\n * EPISODA_SESSION_TOKEN - Bearer token for API authentication\n *\n * Optional environment variables:\n * MODULE_UID - Module UID (preferred, e.g., EP123) or provide moduleUid per tool call\n * DEV_ENVIRONMENT_ID - UUID of the dev environment (legacy fallback)\n * EPISODA_PROJECT_ID - Project ID (for scoped requests)\n * EPISODA_WORKSPACE_ID - Workspace ID (for scoped requests)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport { hydrateRuntimeConfig } from './runtime-config.js'\nimport { apiRequest as executeApiRequest } from './request-executors.js'\n\n// Environment configuration\nlet EPISODA_API_URL = process.env.EPISODA_API_URL || 'https://episoda.dev'\nlet EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || ''\nconst DEV_ENVIRONMENT_ID = process.env.DEV_ENVIRONMENT_ID || ''\nconst MODULE_UID = process.env.MODULE_UID || ''\nlet EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || ''\nlet EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || ''\nlet EPISODA_MACHINE_UUID = process.env.EPISODA_MACHINE_UUID || ''\n\nconst targetSchema = {\n moduleUid: z.string().optional().describe('Module UID to target (overrides server default)')\n}\n\n/**\n * Make an authenticated API request\n */\nasync function apiRequest<T>(\n method: 'GET' | 'POST' | 'DELETE',\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\n return executeApiRequest<T>(\n {\n apiUrl: EPISODA_API_URL,\n sessionToken: EPISODA_SESSION_TOKEN,\n projectId: EPISODA_PROJECT_ID,\n workspaceId: EPISODA_WORKSPACE_ID,\n // EP1376: Intentional parity with workflow/git MCP servers.\n machineUuid: EPISODA_MACHINE_UUID\n },\n method,\n path,\n body\n )\n}\n\n/**\n * Helper to build API path with dev environment ID\n */\nfunction devPath(endpoint: string, overrideTarget?: string): string {\n const target = overrideTarget || MODULE_UID || DEV_ENVIRONMENT_ID\n if (!target) {\n throw new Error('Module target missing. Provide moduleUid in the tool call or set MODULE_UID/DEV_ENVIRONMENT_ID.')\n }\n return `/api/dev/${target}${endpoint}`\n}\n\n// Create MCP server\nconst server = new McpServer({\n name: 'episoda-dev',\n version: '1.0.0'\n}, {\n capabilities: {\n tools: {}\n },\n instructions: `\n Episoda Dev MCP Server\n\n This server provides file and environment operations for dev environments\n (cloud VMs or local machines). All operations execute in the context of\n the configured dev environment.\n You may pass moduleUid per call to target a specific module.\n\n Categories:\n - File Operations: read_file, write_file, edit_file, delete_file\n - Directory Operations: list_directory, mkdir, search_files\n - Code Search: grep\n - Execution: exec_command\n - Batch: batch_operations\n\n All paths should be absolute paths within the dev environment.\n `\n})\n\n// ============================================\n// File Operations\n// ============================================\n\nserver.registerTool(\n 'read_file',\n {\n description: 'Read contents of a file',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n encoding: z.enum(['utf8', 'base64']).optional().describe('File encoding (default: utf8)')\n }\n },\n async (args) => {\n interface ReadResponse {\n success: boolean\n data?: { content: string }\n error?: string\n }\n\n try {\n const result = await apiRequest<ReadResponse>(\n 'POST',\n devPath('/read-file', args.moduleUid),\n {\n path: args.path,\n encoding: args.encoding || 'utf8'\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to read file'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: result.data.content }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error reading file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'write_file',\n {\n description: 'Write content to a file (creates or overwrites)',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n content: z.string().describe('Content to write'),\n encoding: z.enum(['utf8', 'base64']).optional().describe('Content encoding (default: utf8)'),\n createDirs: z.boolean().optional().describe('Create parent directories if missing (default: true)')\n }\n },\n async (args) => {\n interface WriteResponse {\n success: boolean\n data?: { size: number }\n error?: string\n }\n\n try {\n const result = await apiRequest<WriteResponse>(\n 'POST',\n devPath('/write-file', args.moduleUid),\n {\n path: args.path,\n content: args.content,\n encoding: args.encoding || 'utf8',\n createDirs: args.createDirs !== false\n }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to write file'}` }],\n isError: true\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Wrote ${result.data?.size || 0} bytes to ${args.path}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error writing file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'edit_file',\n {\n description: 'Edit a file by replacing a specific string with another',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to the file'),\n old_string: z.string().describe('The exact string to find and replace'),\n new_string: z.string().describe('The replacement string'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces first only)')\n }\n },\n async (args) => {\n interface EditResponse {\n success: boolean\n data?: { replacements: number; newSize: number }\n error?: string\n }\n\n try {\n const result = await apiRequest<EditResponse>(\n 'POST',\n devPath('/edit-file', args.moduleUid),\n {\n path: args.path,\n old_string: args.old_string,\n new_string: args.new_string,\n replace_all: args.replace_all || false\n }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to edit file'}` }],\n isError: true\n }\n }\n\n const replacements = result.data?.replacements || 0\n if (replacements === 0) {\n return {\n content: [{ type: 'text', text: 'Warning: No replacements made (string not found)' }]\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Made ${replacements} replacement(s) in ${args.path}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error editing file: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'delete_file',\n {\n description: 'Delete a file or directory',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to delete'),\n recursive: z.boolean().optional().describe('Delete directories recursively (default: false)')\n }\n },\n async (args) => {\n interface DeleteResponse {\n success: boolean\n error?: string\n }\n\n try {\n const result = await apiRequest<DeleteResponse>(\n 'DELETE',\n `${devPath('/delete-file', args.moduleUid)}?path=${encodeURIComponent(args.path)}&recursive=${args.recursive || false}`\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to delete'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: `Deleted ${args.path}` }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error deleting: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Directory Operations\n// ============================================\n\nserver.registerTool(\n 'list_directory',\n {\n description: 'List contents of a directory',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path to directory'),\n recursive: z.boolean().optional().describe('List recursively (default: false)'),\n includeHidden: z.boolean().optional().describe('Include hidden files (default: false)')\n }\n },\n async (args) => {\n interface ListResponse {\n success: boolean\n data?: {\n entries: Array<{\n name: string\n type: 'file' | 'directory'\n size: number\n }>\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<ListResponse>(\n 'POST',\n devPath('/list-dir', args.moduleUid),\n {\n path: args.path,\n recursive: args.recursive || false,\n includeHidden: args.includeHidden || false\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to list directory'}` }],\n isError: true\n }\n }\n\n if (result.data.entries.length === 0) {\n return {\n content: [{ type: 'text', text: `Directory ${args.path} is empty` }]\n }\n }\n\n const listing = result.data.entries.map(e => {\n const icon = e.type === 'directory' ? '/' : ''\n const size = e.type === 'file' ? ` (${formatSize(e.size)})` : ''\n return `${e.name}${icon}${size}`\n }).join('\\n')\n\n return {\n content: [{ type: 'text', text: listing }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error listing directory: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'mkdir',\n {\n description: 'Create a directory (including parent directories)',\n inputSchema: {\n ...targetSchema,\n path: z.string().describe('Absolute path of directory to create')\n }\n },\n async (args) => {\n interface MkdirResponse {\n success: boolean\n error?: string\n }\n\n try {\n const result = await apiRequest<MkdirResponse>(\n 'POST',\n devPath('/mkdir', args.moduleUid),\n { path: args.path }\n )\n\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to create directory'}` }],\n isError: true\n }\n }\n\n return {\n content: [{ type: 'text', text: `Created directory ${args.path}` }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error creating directory: ${error}` }],\n isError: true\n }\n }\n }\n)\n\nserver.registerTool(\n 'search_files',\n {\n description: 'Search for files by glob pattern',\n inputSchema: {\n ...targetSchema,\n pattern: z.string().describe('Glob pattern (e.g., \"*.ts\", \"**/*.tsx\")'),\n basePath: z.string().describe('Base directory to search from'),\n maxResults: z.number().optional().describe('Maximum results (default: 100)')\n }\n },\n async (args) => {\n interface SearchResponse {\n success: boolean\n data?: { files: string[] }\n error?: string\n }\n\n try {\n const result = await apiRequest<SearchResponse>(\n 'POST',\n devPath('/search-files', args.moduleUid),\n {\n pattern: args.pattern,\n basePath: args.basePath,\n maxResults: args.maxResults || 100\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to search'}` }],\n isError: true\n }\n }\n\n if (result.data.files.length === 0) {\n return {\n content: [{ type: 'text', text: `No files matching \"${args.pattern}\" found` }]\n }\n }\n\n return {\n content: [{\n type: 'text',\n text: `Found ${result.data.files.length} file(s):\\n${result.data.files.join('\\n')}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error searching: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Code Search\n// ============================================\n\nserver.registerTool(\n 'grep',\n {\n description: 'Search for pattern in file contents',\n inputSchema: {\n ...targetSchema,\n pattern: z.string().describe('Search pattern (regex supported)'),\n path: z.string().describe('File or directory to search'),\n filePattern: z.string().optional().describe('Filter by file pattern (e.g., \"*.ts\")'),\n caseSensitive: z.boolean().optional().describe('Case sensitive search (default: true)'),\n maxResults: z.number().optional().describe('Maximum results (default: 100)')\n }\n },\n async (args) => {\n interface GrepResponse {\n success: boolean\n data?: {\n matches: Array<{\n file: string\n line: number\n content: string\n }>\n }\n error?: string\n }\n\n try {\n const flags = args.caseSensitive === false ? '-rni' : '-rn'\n const result = await apiRequest<GrepResponse>(\n 'POST',\n devPath('/grep', args.moduleUid),\n {\n pattern: args.pattern,\n path: args.path,\n flags\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Failed to search'}` }],\n isError: true\n }\n }\n\n if (result.data.matches.length === 0) {\n return {\n content: [{ type: 'text', text: `No matches found for \"${args.pattern}\"` }]\n }\n }\n\n const matches = result.data.matches.map(m =>\n `${m.file}:${m.line}: ${m.content}`\n ).join('\\n')\n\n return {\n content: [{\n type: 'text',\n text: `Found ${result.data.matches.length} match(es):\\n${matches}`\n }]\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error searching: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Command Execution\n// ============================================\n\nserver.registerTool(\n 'exec_command',\n {\n description: 'Execute a shell command',\n inputSchema: {\n ...targetSchema,\n command: z.string().describe('Command to execute'),\n cwd: z.string().optional().describe('Working directory'),\n timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)')\n }\n },\n async (args) => {\n interface ExecResponse {\n success: boolean\n data?: {\n stdout: string\n stderr: string\n exitCode: number\n timedOut?: boolean\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<ExecResponse>(\n 'POST',\n devPath('/exec', args.moduleUid),\n {\n command: args.command,\n cwd: args.cwd,\n timeout: args.timeout || 30000\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Command failed'}` }],\n isError: true\n }\n }\n\n const { stdout, stderr, exitCode, timedOut } = result.data\n\n if (timedOut) {\n return {\n content: [{ type: 'text', text: `Command timed out\\n\\nOutput so far:\\n${stdout}` }],\n isError: true\n }\n }\n\n let output = ''\n if (stdout) output += stdout\n if (stderr) output += `\\n\\nSTDERR:\\n${stderr}`\n if (exitCode !== 0) output += `\\n\\nExit code: ${exitCode}`\n\n return {\n content: [{ type: 'text', text: output || '(no output)' }],\n isError: exitCode !== 0\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error executing command: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Batch Operations\n// ============================================\n\nserver.registerTool(\n 'batch_operations',\n {\n description: 'Execute multiple file operations in a single request',\n inputSchema: {\n ...targetSchema,\n operations: z.array(z.object({\n type: z.enum(['read', 'write', 'mkdir', 'delete', 'exec']).describe('Operation type'),\n path: z.string().optional().describe('File/directory path'),\n content: z.string().optional().describe('Content for write operations'),\n command: z.string().optional().describe('Command for exec operations'),\n recursive: z.boolean().optional().describe('Recursive flag for delete')\n })).describe('Array of operations to execute'),\n stopOnError: z.boolean().optional().describe('Stop on first error (default: false)')\n }\n },\n async (args) => {\n interface BatchResponse {\n success: boolean\n data?: {\n results: Array<{\n success: boolean\n data?: unknown\n error?: string\n }>\n completedCount: number\n failedCount: number\n }\n error?: string\n }\n\n try {\n const result = await apiRequest<BatchResponse>(\n 'POST',\n devPath('/batch', args.moduleUid),\n {\n operations: args.operations,\n stopOnError: args.stopOnError || false\n }\n )\n\n if (!result.success || !result.data) {\n return {\n content: [{ type: 'text', text: `Error: ${result.error || 'Batch failed'}` }],\n isError: true\n }\n }\n\n const { completedCount, failedCount, results } = result.data\n\n let summary = `Batch complete: ${completedCount} succeeded, ${failedCount} failed\\n\\n`\n\n results.forEach((r, i) => {\n const op = args.operations[i]\n const status = r.success ? 'OK' : 'FAILED'\n summary += `[${i + 1}] ${op.type} ${op.path || op.command || ''}: ${status}`\n if (!r.success && r.error) summary += ` - ${r.error}`\n summary += '\\n'\n })\n\n return {\n content: [{ type: 'text', text: summary }],\n isError: failedCount > 0\n }\n } catch (error) {\n return {\n content: [{ type: 'text', text: `Error executing batch: ${error}` }],\n isError: true\n }\n }\n }\n)\n\n// ============================================\n// Utility Functions\n// ============================================\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`\n}\n\n// ============================================\n// Start Server\n// ============================================\n\nexport async function startServer() {\n const runtimeConfig = await hydrateRuntimeConfig()\n EPISODA_API_URL = runtimeConfig.apiUrl\n EPISODA_SESSION_TOKEN = runtimeConfig.sessionToken\n EPISODA_PROJECT_ID = runtimeConfig.projectId\n EPISODA_WORKSPACE_ID = runtimeConfig.workspaceId\n EPISODA_MACHINE_UUID = runtimeConfig.machineUuid || ''\n\n if (!MODULE_UID && !DEV_ENVIRONMENT_ID) {\n console.warn('[episoda-dev] Warning: MODULE_UID/DEV_ENVIRONMENT_ID not set. Provide moduleUid per tool call.')\n }\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n\n const devTarget = MODULE_UID || DEV_ENVIRONMENT_ID || 'NOT SET'\n console.error('[episoda-dev] MCP server started')\n console.error(`[episoda-dev] API URL: ${EPISODA_API_URL}`)\n console.error(`[episoda-dev] Dev Target: ${devTarget}`)\n console.error(`[episoda-dev] Token: ${EPISODA_SESSION_TOKEN ? '****' : 'NOT SET'}`)\n}\n\nif (process.env.EPISODA_MCP_NO_AUTOSTART !== '1') {\n startServer().catch((error) => {\n console.error('[episoda-dev] Fatal error:', error)\n process.exit(1)\n })\n}\n","import * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\nconst DEFAULT_API_URL = 'https://episoda.dev'\nconst DEFAULT_CONFIG_FILE = 'config.json'\n\ninterface EpisodaLocalConfig {\n access_token?: string\n project_id?: string\n workspace_id?: string\n api_url?: string\n machine_uuid?: string\n device_id?: string // deprecated alias for machine_uuid\n}\n\nexport interface RuntimeConfig {\n apiUrl: string\n sessionToken: string\n projectId: string\n workspaceId: string\n machineUuid?: string\n}\n\nconst normalizeEnv = (value?: string): string | undefined => {\n if (!value) return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nconst readEnvConfig = () => ({\n apiUrl: normalizeEnv(process.env.EPISODA_API_URL),\n sessionToken: normalizeEnv(process.env.EPISODA_SESSION_TOKEN),\n projectId: normalizeEnv(process.env.EPISODA_PROJECT_ID),\n workspaceId: normalizeEnv(process.env.EPISODA_WORKSPACE_ID),\n machineUuid: normalizeEnv(process.env.EPISODA_MACHINE_UUID)\n})\n\nconst buildMissingMessage = (missing: string[], apiUrl: string): string => {\n return [\n `[episoda-mcp] Missing auth context: ${missing.join(', ')}`,\n '[episoda-mcp] Set EPISODA_* env vars or run:',\n `[episoda-mcp] episoda auth --api-url ${apiUrl}`\n ].join('\\n')\n}\n\nconst getConfigPath = (): string => {\n // MCP supports a full-path override in addition to the core-style config dir.\n if (process.env.EPISODA_CONFIG_PATH) {\n return process.env.EPISODA_CONFIG_PATH\n }\n const configDir = process.env.EPISODA_CONFIG_DIR || path.join(os.homedir(), '.episoda')\n return path.join(configDir, DEFAULT_CONFIG_FILE)\n}\n\nconst loadLocalConfig = (): EpisodaLocalConfig | null => {\n const configPath = getConfigPath()\n if (!fs.existsSync(configPath)) {\n return null\n }\n try {\n const content = fs.readFileSync(configPath, 'utf8')\n return JSON.parse(content) as EpisodaLocalConfig\n } catch (error) {\n console.error('[episoda-mcp] Failed to load config:', error)\n return null\n }\n}\n\nexport async function resolveRuntimeConfig(): Promise<RuntimeConfig> {\n const envConfig = readEnvConfig()\n\n let fileConfig: EpisodaLocalConfig | null = null\n if (!envConfig.sessionToken || !envConfig.projectId || !envConfig.workspaceId || !envConfig.apiUrl || !envConfig.machineUuid) {\n fileConfig = loadLocalConfig()\n }\n\n const resolved: RuntimeConfig = {\n apiUrl: envConfig.apiUrl || fileConfig?.api_url || DEFAULT_API_URL,\n sessionToken: envConfig.sessionToken || fileConfig?.access_token || '',\n projectId: envConfig.projectId || fileConfig?.project_id || '',\n workspaceId: envConfig.workspaceId || fileConfig?.workspace_id || '',\n machineUuid: envConfig.machineUuid || fileConfig?.machine_uuid || fileConfig?.device_id || undefined\n }\n\n const missing: string[] = []\n if (!resolved.sessionToken) missing.push('EPISODA_SESSION_TOKEN')\n if (!resolved.projectId) missing.push('EPISODA_PROJECT_ID')\n if (!resolved.workspaceId) missing.push('EPISODA_WORKSPACE_ID')\n\n if (missing.length > 0) {\n throw new Error(buildMissingMessage(missing, resolved.apiUrl))\n }\n\n return resolved\n}\n\nexport async function hydrateRuntimeConfig(): Promise<RuntimeConfig> {\n const resolved = await resolveRuntimeConfig()\n\n if (!normalizeEnv(process.env.EPISODA_API_URL)) {\n process.env.EPISODA_API_URL = resolved.apiUrl\n }\n if (!normalizeEnv(process.env.EPISODA_SESSION_TOKEN)) {\n process.env.EPISODA_SESSION_TOKEN = resolved.sessionToken\n }\n if (!normalizeEnv(process.env.EPISODA_PROJECT_ID)) {\n process.env.EPISODA_PROJECT_ID = resolved.projectId\n }\n if (!normalizeEnv(process.env.EPISODA_WORKSPACE_ID)) {\n process.env.EPISODA_WORKSPACE_ID = resolved.workspaceId\n }\n if (resolved.machineUuid && !normalizeEnv(process.env.EPISODA_MACHINE_UUID)) {\n process.env.EPISODA_MACHINE_UUID = resolved.machineUuid\n }\n\n return resolved\n}\n","export interface McpRuntimeContext {\n apiUrl: string\n sessionToken: string\n projectId?: string\n workspaceId?: string\n machineUuid?: string\n}\n\nexport interface GitExecResponse {\n success: boolean\n data?: {\n stdout: string\n stderr: string\n exitCode: number\n timedOut?: boolean\n }\n error?: string\n}\n\nexport function buildMcpHeaders(runtime: McpRuntimeContext): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${runtime.sessionToken}`,\n }\n\n if (runtime.projectId) {\n headers['x-project-id'] = runtime.projectId\n }\n if (runtime.workspaceId) {\n headers['x-workspace-id'] = runtime.workspaceId\n }\n if (runtime.machineUuid) {\n headers['x-machine-uuid'] = runtime.machineUuid\n }\n\n return headers\n}\n\nexport async function apiRequest<T>(\n runtime: McpRuntimeContext,\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE',\n path: string,\n body?: Record<string, unknown>,\n fetchImpl: typeof fetch = fetch\n): Promise<T> {\n const url = `${runtime.apiUrl}${path}`\n const options: RequestInit = {\n method,\n headers: buildMcpHeaders(runtime)\n }\n\n if (body && method !== 'GET') {\n options.body = JSON.stringify(body)\n }\n\n const response = await fetchImpl(url, options)\n if (!response.ok) {\n const text = await response.text()\n throw new Error(`API error ${response.status}: ${text}`)\n }\n\n return response.json() as Promise<T>\n}\n\nexport async function executeTransitionModule(\n runtime: McpRuntimeContext,\n args: { module_uid: string; target_state: 'ready' | 'doing' | 'review' | 'done' },\n fetchImpl: typeof fetch = fetch\n) {\n interface TransitionResponse {\n success: boolean\n module?: { uid: string; state: string }\n error?: string\n }\n\n const result = await apiRequest<TransitionResponse>(\n runtime,\n 'POST',\n `/api/modules/${args.module_uid}/transition`,\n { targetState: args.target_state },\n fetchImpl\n )\n\n if (!result.success) {\n return { content: [{ type: 'text' as const, text: `Error: ${result.error}` }], isError: true }\n }\n\n return {\n content: [{\n type: 'text' as const,\n text: `Module ${args.module_uid} transitioned to ${args.target_state}`\n }]\n }\n}\n\nexport async function executeGitCommandRequest(\n runtime: McpRuntimeContext,\n args: { target: string; command: string; cwd?: string; timeout?: number },\n fetchImpl: typeof fetch = fetch\n): Promise<GitExecResponse> {\n const response = await fetchImpl(`${runtime.apiUrl}/api/dev/${args.target}/exec`, {\n method: 'POST',\n headers: buildMcpHeaders(runtime),\n body: JSON.stringify({\n command: args.command,\n cwd: args.cwd,\n timeout: args.timeout ?? 30000\n })\n })\n\n if (!response.ok) {\n return {\n success: false,\n error: `HTTP ${response.status}: ${response.statusText}`,\n }\n }\n\n return response.json() as Promise<GitExecResponse>\n}\n\n"],"mappings":"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","names":["path","apiRequest","path"]}
@@ -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,9 +86,48 @@ 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
 
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 executeGitCommandRequest(runtime, args, fetchImpl = fetch) {
113
+ const response = await fetchImpl(`${runtime.apiUrl}/api/dev/${args.target}/exec`, {
114
+ method: "POST",
115
+ headers: buildMcpHeaders(runtime),
116
+ body: JSON.stringify({
117
+ command: args.command,
118
+ cwd: args.cwd,
119
+ timeout: args.timeout ?? 3e4
120
+ })
121
+ });
122
+ if (!response.ok) {
123
+ return {
124
+ success: false,
125
+ error: `HTTP ${response.status}: ${response.statusText}`
126
+ };
127
+ }
128
+ return response.json();
129
+ }
130
+
90
131
  // src/git-server.ts
91
132
  var EPISODA_API_URL = process.env.EPISODA_API_URL || "https://episoda.dev";
92
133
  var EPISODA_SESSION_TOKEN = process.env.EPISODA_SESSION_TOKEN || "";
@@ -94,6 +135,7 @@ var DEV_ENVIRONMENT_ID = process.env.DEV_ENVIRONMENT_ID || "";
94
135
  var MODULE_UID = process.env.MODULE_UID || "";
95
136
  var EPISODA_PROJECT_ID = process.env.EPISODA_PROJECT_ID || "";
96
137
  var EPISODA_WORKSPACE_ID = process.env.EPISODA_WORKSPACE_ID || "";
138
+ var EPISODA_MACHINE_UUID = process.env.EPISODA_MACHINE_UUID || "";
97
139
  var targetSchema = {
98
140
  moduleUid: z.string().optional().describe("Module UID to target (overrides server default)")
99
141
  };
@@ -106,28 +148,17 @@ async function execCommand(command, options = {}) {
106
148
  error: "Module target missing. Provide moduleUid in the tool call or set MODULE_UID/DEV_ENVIRONMENT_ID."
107
149
  };
108
150
  }
109
- const url = `${EPISODA_API_URL}/api/dev/${target}/exec`;
110
- const headers = {
111
- "Content-Type": "application/json",
112
- "Authorization": `Bearer ${EPISODA_SESSION_TOKEN}`
113
- };
114
- if (EPISODA_PROJECT_ID) {
115
- headers["x-project-id"] = EPISODA_PROJECT_ID;
116
- }
117
- if (EPISODA_WORKSPACE_ID) {
118
- headers["x-workspace-id"] = EPISODA_WORKSPACE_ID;
119
- }
120
- const response = await fetch(url, {
121
- method: "POST",
122
- headers,
123
- body: JSON.stringify({ command, cwd, timeout })
124
- });
125
- if (!response.ok) {
126
- const text = await response.text();
127
- return { success: false, error: `API error ${response.status}: ${text}` };
128
- }
129
- const result = await response.json();
130
- return result;
151
+ return executeGitCommandRequest(
152
+ {
153
+ apiUrl: EPISODA_API_URL,
154
+ sessionToken: EPISODA_SESSION_TOKEN,
155
+ projectId: EPISODA_PROJECT_ID,
156
+ workspaceId: EPISODA_WORKSPACE_ID,
157
+ // EP1376: Intentional parity with workflow/dev MCP servers.
158
+ machineUuid: EPISODA_MACHINE_UUID
159
+ },
160
+ { target, command, cwd, timeout }
161
+ );
131
162
  }
132
163
  function formatGitOutput(result, errorPrefix = "Git error") {
133
164
  if (!result.success || !result.data) {
@@ -644,12 +675,13 @@ server.registerTool(
644
675
  };
645
676
  }
646
677
  );
647
- async function main() {
678
+ async function startServer() {
648
679
  const runtimeConfig = await hydrateRuntimeConfig();
649
680
  EPISODA_API_URL = runtimeConfig.apiUrl;
650
681
  EPISODA_SESSION_TOKEN = runtimeConfig.sessionToken;
651
682
  EPISODA_PROJECT_ID = runtimeConfig.projectId;
652
683
  EPISODA_WORKSPACE_ID = runtimeConfig.workspaceId;
684
+ EPISODA_MACHINE_UUID = runtimeConfig.machineUuid || "";
653
685
  if (!MODULE_UID && !DEV_ENVIRONMENT_ID) {
654
686
  console.warn("[episoda-git] Warning: MODULE_UID/DEV_ENVIRONMENT_ID not set. Provide moduleUid per tool call.");
655
687
  }
@@ -661,8 +693,13 @@ async function main() {
661
693
  console.error(`[episoda-git] Dev Target: ${devTarget}`);
662
694
  console.error(`[episoda-git] Token: ${EPISODA_SESSION_TOKEN ? "****" : "NOT SET"}`);
663
695
  }
664
- main().catch((error) => {
665
- console.error("[episoda-git] Fatal error:", error);
666
- process.exit(1);
667
- });
696
+ if (process.env.EPISODA_MCP_NO_AUTOSTART !== "1") {
697
+ startServer().catch((error) => {
698
+ console.error("[episoda-git] Fatal error:", error);
699
+ process.exit(1);
700
+ });
701
+ }
702
+ export {
703
+ startServer
704
+ };
668
705
  //# sourceMappingURL=git-server.js.map