@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.
- package/dist/cli.js +218 -143
- package/dist/cli.js.map +1 -1
- package/dist/dev-server.js +77 -41
- package/dist/dev-server.js.map +1 -1
- package/dist/git-server.js +67 -30
- package/dist/git-server.js.map +1 -1
- package/dist/workflow-server.js +124 -73
- package/dist/workflow-server.js.map +1 -1
- package/package.json +1 -1
package/dist/dev-server.js
CHANGED
|
@@ -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/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 (
|
|
110
|
-
|
|
101
|
+
if (runtime.projectId) {
|
|
102
|
+
headers["x-project-id"] = runtime.projectId;
|
|
111
103
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
package/dist/dev-server.js.map
CHANGED
|
@@ -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"]}
|
package/dist/git-server.js
CHANGED
|
@@ -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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|