junis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,793 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server/mcp.ts
31
+ var mcp_exports = {};
32
+ __export(mcp_exports, {
33
+ handleMCPRequest: () => handleMCPRequest,
34
+ startMCPServer: () => startMCPServer
35
+ });
36
+ module.exports = __toCommonJS(mcp_exports);
37
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
38
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
39
+ var import_http = require("http");
40
+
41
+ // src/tools/filesystem.ts
42
+ var import_child_process = require("child_process");
43
+ var import_util = require("util");
44
+ var import_promises = __toESM(require("fs/promises"));
45
+ var import_path = __toESM(require("path"));
46
+ var import_glob = require("glob");
47
+ var import_zod = require("zod");
48
+ var execAsync = (0, import_util.promisify)(import_child_process.exec);
49
+ var FilesystemTools = class {
50
+ register(server) {
51
+ server.tool(
52
+ "execute_command",
53
+ "\uD130\uBBF8\uB110 \uBA85\uB839 \uC2E4\uD589",
54
+ {
55
+ command: import_zod.z.string().describe("\uC2E4\uD589\uD560 \uC258 \uBA85\uB839"),
56
+ timeout_ms: import_zod.z.number().optional().default(3e4).describe("\uD0C0\uC784\uC544\uC6C3 (ms)"),
57
+ background: import_zod.z.boolean().optional().default(false).describe("\uBC31\uADF8\uB77C\uC6B4\uB4DC \uC2E4\uD589")
58
+ },
59
+ async ({ command, timeout_ms, background }) => {
60
+ if (background) {
61
+ (0, import_child_process.exec)(command);
62
+ return { content: [{ type: "text", text: "\uBC31\uADF8\uB77C\uC6B4\uB4DC \uC2E4\uD589 \uC2DC\uC791\uB428" }] };
63
+ }
64
+ try {
65
+ const { stdout, stderr } = await execAsync(command, {
66
+ timeout: timeout_ms
67
+ });
68
+ return {
69
+ content: [{ type: "text", text: stdout || stderr || "(\uCD9C\uB825 \uC5C6\uC74C)" }]
70
+ };
71
+ } catch (err) {
72
+ const error = err;
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `\uC624\uB958 (exit ${error.code ?? "?"}): ${error.message}
78
+ ${error.stderr ?? ""}`
79
+ }
80
+ ],
81
+ isError: true
82
+ };
83
+ }
84
+ }
85
+ );
86
+ server.tool(
87
+ "read_file",
88
+ "\uD30C\uC77C \uC77D\uAE30",
89
+ {
90
+ path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
91
+ encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("\uC778\uCF54\uB529")
92
+ },
93
+ async ({ path: filePath, encoding }) => {
94
+ const content = await import_promises.default.readFile(filePath, encoding);
95
+ return { content: [{ type: "text", text: content }] };
96
+ }
97
+ );
98
+ server.tool(
99
+ "write_file",
100
+ "\uD30C\uC77C \uC4F0\uAE30/\uC0DD\uC131",
101
+ {
102
+ path: import_zod.z.string().describe("\uD30C\uC77C \uACBD\uB85C"),
103
+ content: import_zod.z.string().describe("\uD30C\uC77C \uB0B4\uC6A9")
104
+ },
105
+ async ({ path: filePath, content }) => {
106
+ await import_promises.default.mkdir(import_path.default.dirname(filePath), { recursive: true });
107
+ await import_promises.default.writeFile(filePath, content, "utf-8");
108
+ return { content: [{ type: "text", text: "\uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC" }] };
109
+ }
110
+ );
111
+ server.tool(
112
+ "list_directory",
113
+ "\uB514\uB809\uD1A0\uB9AC \uBAA9\uB85D \uC870\uD68C",
114
+ {
115
+ path: import_zod.z.string().describe("\uB514\uB809\uD1A0\uB9AC \uACBD\uB85C")
116
+ },
117
+ async ({ path: dirPath }) => {
118
+ const entries = await import_promises.default.readdir(dirPath, { withFileTypes: true });
119
+ const lines = entries.map((e) => `${e.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`);
120
+ return { content: [{ type: "text", text: lines.join("\n") }] };
121
+ }
122
+ );
123
+ server.tool(
124
+ "search_code",
125
+ "\uCF54\uB4DC/\uD14D\uC2A4\uD2B8 \uAC80\uC0C9",
126
+ {
127
+ pattern: import_zod.z.string().describe("\uAC80\uC0C9 \uD328\uD134 (\uC815\uADDC\uC2DD \uC9C0\uC6D0)"),
128
+ directory: import_zod.z.string().optional().default(".").describe("\uAC80\uC0C9 \uB514\uB809\uD1A0\uB9AC"),
129
+ file_pattern: import_zod.z.string().optional().default("**/*").describe("\uD30C\uC77C \uD328\uD134")
130
+ },
131
+ async ({ pattern, directory, file_pattern }) => {
132
+ try {
133
+ const { stdout } = await execAsync(
134
+ `rg --no-heading -n "${pattern}" ${directory}`,
135
+ { timeout: 1e4 }
136
+ );
137
+ return { content: [{ type: "text", text: stdout || "\uACB0\uACFC \uC5C6\uC74C" }] };
138
+ } catch {
139
+ const files = await (0, import_glob.glob)(file_pattern, { cwd: directory });
140
+ const results = [];
141
+ for (const file of files.slice(0, 100)) {
142
+ try {
143
+ const content = await import_promises.default.readFile(
144
+ import_path.default.join(directory, file),
145
+ "utf-8"
146
+ );
147
+ const lines = content.split("\n");
148
+ const re = new RegExp(pattern, "gi");
149
+ lines.forEach((line, i) => {
150
+ if (re.test(line)) results.push(`${file}:${i + 1}: ${line}`);
151
+ });
152
+ } catch {
153
+ }
154
+ }
155
+ return {
156
+ content: [
157
+ { type: "text", text: results.join("\n") || "\uACB0\uACFC \uC5C6\uC74C" }
158
+ ]
159
+ };
160
+ }
161
+ }
162
+ );
163
+ server.tool(
164
+ "list_processes",
165
+ "\uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4 \uBAA9\uB85D",
166
+ {},
167
+ async () => {
168
+ const cmd = process.platform === "win32" ? "tasklist" : process.platform === "darwin" ? "ps aux | sort -rk 3 | head -30" : "ps aux --sort=-%cpu | head -30";
169
+ const { stdout } = await execAsync(cmd);
170
+ return { content: [{ type: "text", text: stdout }] };
171
+ }
172
+ );
173
+ server.tool(
174
+ "kill_process",
175
+ "\uD504\uB85C\uC138\uC2A4 \uC885\uB8CC (SIGTERM \uD6C4 3\uCD08 \uB300\uAE30, \uC0B4\uC544\uC788\uC73C\uBA74 SIGKILL \uC790\uB3D9 \uC801\uC6A9)",
176
+ {
177
+ pid: import_zod.z.number().describe("\uC885\uB8CC\uD560 \uD504\uB85C\uC138\uC2A4 PID"),
178
+ signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("\uCD08\uAE30 \uC2DC\uADF8\uB110 (\uAE30\uBCF8\uAC12: SIGTERM). SIGKILL \uC9C0\uC815 \uC2DC \uC989\uC2DC \uAC15\uC81C \uC885\uB8CC)")
179
+ },
180
+ async ({ pid, signal }) => {
181
+ const isWindows = process.platform === "win32";
182
+ if (isWindows) {
183
+ await execAsync(`taskkill /PID ${pid} /F`);
184
+ return {
185
+ content: [{ type: "text", text: `PID ${pid} \uC885\uB8CC \uC644\uB8CC (taskkill /F)` }]
186
+ };
187
+ }
188
+ if (signal === "SIGKILL") {
189
+ await execAsync(`kill -9 ${pid}`);
190
+ return {
191
+ content: [{ type: "text", text: `PID ${pid} \uAC15\uC81C \uC885\uB8CC \uC644\uB8CC (SIGKILL)` }]
192
+ };
193
+ }
194
+ try {
195
+ await execAsync(`kill -15 ${pid}`);
196
+ } catch {
197
+ return {
198
+ content: [
199
+ { type: "text", text: `PID ${pid} \uC885\uB8CC \uC2E4\uD328: \uD504\uB85C\uC138\uC2A4\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uAC70\uB098 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.` }
200
+ ],
201
+ isError: true
202
+ };
203
+ }
204
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
205
+ const isAlive = await execAsync(`kill -0 ${pid}`).then(() => true).catch(() => false);
206
+ if (!isAlive) {
207
+ return {
208
+ content: [{ type: "text", text: `PID ${pid} \uC885\uB8CC \uC644\uB8CC (SIGTERM)` }]
209
+ };
210
+ }
211
+ await execAsync(`kill -9 ${pid}`);
212
+ return {
213
+ content: [
214
+ {
215
+ type: "text",
216
+ text: `PID ${pid} \uAC15\uC81C \uC885\uB8CC \uC644\uB8CC (SIGTERM \uBB34\uC751\uB2F5 \u2192 SIGKILL \uC790\uB3D9 \uC801\uC6A9)`
217
+ }
218
+ ]
219
+ };
220
+ }
221
+ );
222
+ }
223
+ };
224
+
225
+ // src/tools/browser.ts
226
+ var import_playwright = require("playwright");
227
+ var import_zod2 = require("zod");
228
+ var BrowserTools = class {
229
+ browser = null;
230
+ page = null;
231
+ async init() {
232
+ try {
233
+ this.browser = await import_playwright.chromium.launch({ headless: true });
234
+ this.page = await this.browser.newPage();
235
+ } catch {
236
+ console.warn(
237
+ "\u26A0\uFE0F Playwright \uBBF8\uC124\uCE58. \uBE0C\uB77C\uC6B0\uC800 \uB3C4\uAD6C \uBE44\uD65C\uC131\uD654.\n \uD65C\uC131\uD654: npx playwright install chromium"
238
+ );
239
+ }
240
+ }
241
+ async cleanup() {
242
+ await this.browser?.close();
243
+ }
244
+ register(server) {
245
+ const requirePage = () => {
246
+ if (!this.page) throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uBBF8\uCD08\uAE30\uD654. playwright \uC124\uCE58 \uD655\uC778.");
247
+ return this.page;
248
+ };
249
+ server.tool(
250
+ "browser_navigate",
251
+ "URL\uB85C \uC774\uB3D9",
252
+ { url: import_zod2.z.string().describe("\uC774\uB3D9\uD560 URL") },
253
+ async ({ url }) => {
254
+ const page = requirePage();
255
+ await page.goto(url, { waitUntil: "domcontentloaded" });
256
+ return {
257
+ content: [{ type: "text", text: `\uC774\uB3D9 \uC644\uB8CC: ${page.url()}` }]
258
+ };
259
+ }
260
+ );
261
+ server.tool(
262
+ "browser_click",
263
+ "\uC694\uC18C \uD074\uB9AD",
264
+ { selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790") },
265
+ async ({ selector }) => {
266
+ await requirePage().click(selector);
267
+ return { content: [{ type: "text", text: "\uD074\uB9AD \uC644\uB8CC" }] };
268
+ }
269
+ );
270
+ server.tool(
271
+ "browser_type",
272
+ "\uD14D\uC2A4\uD2B8 \uC785\uB825",
273
+ {
274
+ selector: import_zod2.z.string().describe("CSS \uC120\uD0DD\uC790"),
275
+ text: import_zod2.z.string().describe("\uC785\uB825\uD560 \uD14D\uC2A4\uD2B8"),
276
+ clear: import_zod2.z.boolean().optional().default(false).describe("\uAE30\uC874 \uB0B4\uC6A9 \uC0AD\uC81C \uD6C4 \uC785\uB825")
277
+ },
278
+ async ({ selector, text, clear }) => {
279
+ const page = requirePage();
280
+ if (clear) await page.fill(selector, text);
281
+ else await page.type(selector, text);
282
+ return { content: [{ type: "text", text: "\uC785\uB825 \uC644\uB8CC" }] };
283
+ }
284
+ );
285
+ server.tool(
286
+ "browser_screenshot",
287
+ "\uD604\uC7AC \uD398\uC774\uC9C0 \uC2A4\uD06C\uB9B0\uC0F7",
288
+ {
289
+ path: import_zod2.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 base64 \uBC18\uD658)"),
290
+ full_page: import_zod2.z.boolean().optional().default(false)
291
+ },
292
+ async ({ path: path2, full_page }) => {
293
+ const page = requirePage();
294
+ const screenshot = await page.screenshot({
295
+ path: path2 ?? void 0,
296
+ fullPage: full_page
297
+ });
298
+ if (path2) {
299
+ return { content: [{ type: "text", text: `\uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
300
+ }
301
+ return {
302
+ content: [
303
+ {
304
+ type: "image",
305
+ data: screenshot.toString("base64"),
306
+ mimeType: "image/png"
307
+ }
308
+ ]
309
+ };
310
+ }
311
+ );
312
+ server.tool(
313
+ "browser_snapshot",
314
+ "\uD398\uC774\uC9C0 \uC811\uADFC\uC131 \uD2B8\uB9AC \uC870\uD68C (\uAD6C\uC870 \uD30C\uC545\uC6A9)",
315
+ {},
316
+ async () => {
317
+ const page = requirePage();
318
+ const snapshot = await page.locator("body").ariaSnapshot();
319
+ return {
320
+ content: [
321
+ { type: "text", text: snapshot }
322
+ ]
323
+ };
324
+ }
325
+ );
326
+ server.tool(
327
+ "browser_evaluate",
328
+ "JavaScript \uC2E4\uD589",
329
+ { code: import_zod2.z.string().describe("\uC2E4\uD589\uD560 JavaScript \uCF54\uB4DC") },
330
+ async ({ code }) => {
331
+ const result = await requirePage().evaluate(code);
332
+ return {
333
+ content: [
334
+ { type: "text", text: JSON.stringify(result, null, 2) }
335
+ ]
336
+ };
337
+ }
338
+ );
339
+ server.tool(
340
+ "browser_pdf",
341
+ "\uD604\uC7AC \uD398\uC774\uC9C0 PDF \uC800\uC7A5",
342
+ { path: import_zod2.z.string().describe("\uC800\uC7A5 \uACBD\uB85C (.pdf)") },
343
+ async ({ path: path2 }) => {
344
+ await requirePage().pdf({ path: path2 });
345
+ return { content: [{ type: "text", text: `PDF \uC800\uC7A5 \uC644\uB8CC: ${path2}` }] };
346
+ }
347
+ );
348
+ }
349
+ };
350
+
351
+ // src/tools/notebook.ts
352
+ var import_zod3 = require("zod");
353
+ var import_promises2 = __toESM(require("fs/promises"));
354
+ var import_child_process2 = require("child_process");
355
+ var import_util2 = require("util");
356
+ var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
357
+ async function readNotebook(filePath) {
358
+ const raw = await import_promises2.default.readFile(filePath, "utf-8");
359
+ return JSON.parse(raw);
360
+ }
361
+ async function writeNotebook(filePath, nb) {
362
+ await import_promises2.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
363
+ }
364
+ var NotebookTools = class {
365
+ register(server) {
366
+ server.tool(
367
+ "notebook_read",
368
+ ".ipynb \uB178\uD2B8\uBD81 \uC77D\uAE30",
369
+ { path: import_zod3.z.string().describe("\uB178\uD2B8\uBD81 \uD30C\uC77C \uACBD\uB85C") },
370
+ async ({ path: filePath }) => {
371
+ const nb = await readNotebook(filePath);
372
+ const cells = nb.cells.map((cell, i) => ({
373
+ index: i,
374
+ type: cell.cell_type,
375
+ source: cell.source.join(""),
376
+ outputs: cell.outputs?.length ?? 0
377
+ }));
378
+ return {
379
+ content: [{ type: "text", text: JSON.stringify(cells, null, 2) }]
380
+ };
381
+ }
382
+ );
383
+ server.tool(
384
+ "notebook_edit_cell",
385
+ "\uB178\uD2B8\uBD81 \uD2B9\uC815 \uC140 \uC218\uC815",
386
+ {
387
+ path: import_zod3.z.string(),
388
+ cell_index: import_zod3.z.number().describe("0\uBD80\uD130 \uC2DC\uC791\uD558\uB294 \uC140 \uC778\uB371\uC2A4"),
389
+ source: import_zod3.z.string().describe("\uC0C8 \uC18C\uC2A4 \uCF54\uB4DC")
390
+ },
391
+ async ({ path: filePath, cell_index, source }) => {
392
+ const nb = await readNotebook(filePath);
393
+ if (cell_index < 0 || cell_index >= nb.cells.length) {
394
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC140 \uC778\uB371\uC2A4: ${cell_index}`);
395
+ }
396
+ nb.cells[cell_index].source = source.split("\n").map(
397
+ (l, i, arr) => i < arr.length - 1 ? l + "\n" : l
398
+ );
399
+ await writeNotebook(filePath, nb);
400
+ return { content: [{ type: "text", text: "\uC140 \uC218\uC815 \uC644\uB8CC" }] };
401
+ }
402
+ );
403
+ server.tool(
404
+ "notebook_execute",
405
+ "\uB178\uD2B8\uBD81 \uC2E4\uD589 (nbconvert --execute)",
406
+ {
407
+ path: import_zod3.z.string().describe("\uB178\uD2B8\uBD81 \uD30C\uC77C \uACBD\uB85C"),
408
+ timeout: import_zod3.z.number().optional().default(300).describe("\uC140\uB2F9 \uD0C0\uC784\uC544\uC6C3 (\uCD08)")
409
+ },
410
+ async ({ path: filePath, timeout }) => {
411
+ const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
412
+ const candidates = [
413
+ "jupyter",
414
+ `${process.env.HOME}/Library/Python/3.9/bin/jupyter`,
415
+ `${process.env.HOME}/Library/Python/3.10/bin/jupyter`,
416
+ `${process.env.HOME}/Library/Python/3.11/bin/jupyter`,
417
+ `${process.env.HOME}/Library/Python/3.12/bin/jupyter`,
418
+ "/usr/local/bin/jupyter",
419
+ "/opt/homebrew/bin/jupyter"
420
+ ];
421
+ for (const jupyter of candidates) {
422
+ try {
423
+ const { stdout, stderr } = await execAsync2(`${jupyter} ${nbconvertArgs}`);
424
+ return { content: [{ type: "text", text: stdout || stderr || "\uC2E4\uD589 \uC644\uB8CC" }] };
425
+ } catch (err) {
426
+ const error = err;
427
+ if (error.code !== "127" && !error.message?.includes("not found") && !error.message?.includes("No such file")) {
428
+ throw err;
429
+ }
430
+ }
431
+ }
432
+ throw new Error("jupyter\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694: pip install jupyter");
433
+ }
434
+ );
435
+ }
436
+ };
437
+
438
+ // src/tools/device.ts
439
+ var import_child_process3 = require("child_process");
440
+ var import_util3 = require("util");
441
+ var import_zod4 = require("zod");
442
+ var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
443
+ function platform() {
444
+ if (process.platform === "darwin") return "mac";
445
+ if (process.platform === "win32") return "win";
446
+ return "linux";
447
+ }
448
+ var DeviceTools = class {
449
+ register(server) {
450
+ server.tool(
451
+ "screen_capture",
452
+ "\uD654\uBA74 \uC2A4\uD06C\uB9B0\uC0F7 (OS \uB124\uC774\uD2F0\uBE0C)",
453
+ {
454
+ output_path: import_zod4.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (\uC5C6\uC73C\uBA74 temp \uC800\uC7A5 \uD6C4 base64 \uBC18\uD658)")
455
+ },
456
+ async ({ output_path }) => {
457
+ const p = platform();
458
+ const tmpPath = output_path ?? `/tmp/junis_screen_${Date.now()}.png`;
459
+ const cmd = {
460
+ mac: `screencapture -x "${tmpPath}"`,
461
+ win: `nircmd.exe savescreenshot "${tmpPath}"`,
462
+ linux: `scrot "${tmpPath}"`
463
+ }[p];
464
+ await execAsync3(cmd);
465
+ const { readFileSync } = await import("fs");
466
+ const data = readFileSync(tmpPath).toString("base64");
467
+ return {
468
+ content: [{ type: "image", data, mimeType: "image/png" }]
469
+ };
470
+ }
471
+ );
472
+ server.tool(
473
+ "camera_capture",
474
+ "\uCE74\uBA54\uB77C \uC0AC\uC9C4 \uCD2C\uC601",
475
+ {
476
+ output_path: import_zod4.z.string().optional()
477
+ },
478
+ async ({ output_path }) => {
479
+ const p = platform();
480
+ const tmpPath = output_path ?? `/tmp/junis_cam_${Date.now()}.jpg`;
481
+ const cmd = {
482
+ mac: `imagesnap "${tmpPath}"`,
483
+ win: `ffmpeg -f dshow -i video="Default" -frames:v 1 "${tmpPath}"`,
484
+ linux: `fswebcam -r 1280x720 "${tmpPath}"`
485
+ }[p];
486
+ await execAsync3(cmd);
487
+ const { readFileSync } = await import("fs");
488
+ const data = readFileSync(tmpPath).toString("base64");
489
+ return {
490
+ content: [{ type: "image", data, mimeType: "image/jpeg" }]
491
+ };
492
+ }
493
+ );
494
+ server.tool(
495
+ "notification_send",
496
+ "OS \uC54C\uB9BC \uC804\uC1A1",
497
+ {
498
+ title: import_zod4.z.string().describe("\uC54C\uB9BC \uC81C\uBAA9"),
499
+ message: import_zod4.z.string().describe("\uC54C\uB9BC \uB0B4\uC6A9")
500
+ },
501
+ async ({ title, message }) => {
502
+ const p = platform();
503
+ const cmd = {
504
+ mac: `osascript -e 'display notification "${message}" with title "${title}"'`,
505
+ win: `powershell -Command "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime]::CreateToastNotifier('Junis').Show([Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime]::new())"`,
506
+ linux: `notify-send "${title}" "${message}"`
507
+ }[p];
508
+ await execAsync3(cmd);
509
+ return { content: [{ type: "text", text: "\uC54C\uB9BC \uC804\uC1A1 \uC644\uB8CC" }] };
510
+ }
511
+ );
512
+ server.tool(
513
+ "clipboard_read",
514
+ "\uD074\uB9BD\uBCF4\uB4DC \uC77D\uAE30",
515
+ {},
516
+ async () => {
517
+ const p = platform();
518
+ const cmd = { mac: "pbpaste", win: "powershell Get-Clipboard", linux: "xclip -o" }[p];
519
+ const { stdout } = await execAsync3(cmd);
520
+ return { content: [{ type: "text", text: stdout }] };
521
+ }
522
+ );
523
+ server.tool(
524
+ "clipboard_write",
525
+ "\uD074\uB9BD\uBCF4\uB4DC \uC4F0\uAE30",
526
+ { text: import_zod4.z.string() },
527
+ async ({ text }) => {
528
+ const p = platform();
529
+ const cmd = {
530
+ mac: `echo "${text}" | pbcopy`,
531
+ win: `powershell Set-Clipboard "${text}"`,
532
+ linux: `echo "${text}" | xclip -selection clipboard`
533
+ }[p];
534
+ await execAsync3(cmd);
535
+ return { content: [{ type: "text", text: "\uD074\uB9BD\uBCF4\uB4DC \uC800\uC7A5 \uC644\uB8CC" }] };
536
+ }
537
+ );
538
+ }
539
+ };
540
+
541
+ // src/server/mcp.ts
542
+ var mcpPort = 3e3;
543
+ var globalBrowserTools = null;
544
+ function createMcpServer() {
545
+ const server = new import_mcp.McpServer({
546
+ name: "junis",
547
+ version: "0.1.0"
548
+ });
549
+ const fsTools = new FilesystemTools();
550
+ fsTools.register(server);
551
+ if (globalBrowserTools) {
552
+ globalBrowserTools.register(server);
553
+ }
554
+ const notebookTools = new NotebookTools();
555
+ notebookTools.register(server);
556
+ const deviceTools = new DeviceTools();
557
+ deviceTools.register(server);
558
+ return server;
559
+ }
560
+ function readBody(req) {
561
+ return new Promise((resolve, reject) => {
562
+ const chunks = [];
563
+ req.on("data", (chunk) => chunks.push(chunk));
564
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
565
+ req.on("error", reject);
566
+ });
567
+ }
568
+ function tryListen(httpServer, port, maxRetries) {
569
+ return new Promise((resolve, reject) => {
570
+ let currentPort = port;
571
+ let attempts = 0;
572
+ const attempt = () => {
573
+ httpServer.listen(currentPort, () => {
574
+ resolve(currentPort);
575
+ });
576
+ httpServer.once("error", (err) => {
577
+ if (err.code === "EADDRINUSE" && attempts < maxRetries) {
578
+ attempts++;
579
+ currentPort++;
580
+ httpServer.removeAllListeners("error");
581
+ attempt();
582
+ } else {
583
+ reject(err);
584
+ }
585
+ });
586
+ };
587
+ attempt();
588
+ });
589
+ }
590
+ function handleCors(res) {
591
+ res.writeHead(204, {
592
+ "Access-Control-Allow-Origin": "*",
593
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
594
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, MCP-Protocol-Version, mcp-protocol-version, MCP-Session-Id"
595
+ });
596
+ res.end();
597
+ }
598
+ function handleOAuthDiscovery(req, res, port) {
599
+ const url = req.url ?? "";
600
+ const baseUrl = `http://localhost:${port}`;
601
+ if (url === "/.well-known/oauth-protected-resource" || url === "/.well-known/oauth-protected-resource/mcp") {
602
+ const metadata = {
603
+ resource: `${baseUrl}/mcp`,
604
+ // authorization_servers 필드 자체를 생략 → 클라이언트가 AS를 찾지 않음
605
+ bearer_methods_supported: []
606
+ };
607
+ res.writeHead(200, {
608
+ "Content-Type": "application/json",
609
+ "Access-Control-Allow-Origin": "*"
610
+ });
611
+ res.end(JSON.stringify(metadata));
612
+ return true;
613
+ }
614
+ if (url === "/.well-known/oauth-authorization-server") {
615
+ const metadata = {
616
+ issuer: baseUrl,
617
+ authorization_endpoint: `${baseUrl}/oauth/authorize`,
618
+ token_endpoint: `${baseUrl}/oauth/token`,
619
+ registration_endpoint: `${baseUrl}/oauth/register`,
620
+ response_types_supported: ["code"],
621
+ grant_types_supported: ["authorization_code"],
622
+ code_challenge_methods_supported: ["S256"],
623
+ token_endpoint_auth_methods_supported: ["none"]
624
+ };
625
+ res.writeHead(200, {
626
+ "Content-Type": "application/json",
627
+ "Access-Control-Allow-Origin": "*"
628
+ });
629
+ res.end(JSON.stringify(metadata));
630
+ return true;
631
+ }
632
+ if (url === "/oauth/register" && req.method === "POST") {
633
+ const clientInfo = {
634
+ client_id: `local-${Date.now()}`,
635
+ client_id_issued_at: Math.floor(Date.now() / 1e3),
636
+ redirect_uris: [],
637
+ grant_types: ["authorization_code"],
638
+ response_types: ["code"],
639
+ token_endpoint_auth_method: "none"
640
+ };
641
+ res.writeHead(201, {
642
+ "Content-Type": "application/json",
643
+ "Access-Control-Allow-Origin": "*"
644
+ });
645
+ res.end(JSON.stringify(clientInfo));
646
+ return true;
647
+ }
648
+ if (url === "/oauth/token" && req.method === "POST") {
649
+ const tokenResponse = {
650
+ access_token: `local-token-${Date.now()}`,
651
+ token_type: "Bearer",
652
+ expires_in: 86400
653
+ };
654
+ res.writeHead(200, {
655
+ "Content-Type": "application/json",
656
+ "Access-Control-Allow-Origin": "*"
657
+ });
658
+ res.end(JSON.stringify(tokenResponse));
659
+ return true;
660
+ }
661
+ return false;
662
+ }
663
+ async function startMCPServer(port) {
664
+ globalBrowserTools = new BrowserTools();
665
+ await globalBrowserTools.init();
666
+ let resolvedPort = port;
667
+ const httpServer = (0, import_http.createServer)(
668
+ async (req, res) => {
669
+ try {
670
+ if (req.method === "OPTIONS") {
671
+ handleCors(res);
672
+ return;
673
+ }
674
+ if (handleOAuthDiscovery(req, res, resolvedPort)) {
675
+ return;
676
+ }
677
+ const url = req.url ?? "";
678
+ if (url === "/mcp") {
679
+ if (req.method === "POST") {
680
+ const mcpServer = createMcpServer();
681
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({
682
+ sessionIdGenerator: void 0
683
+ // stateless
684
+ });
685
+ try {
686
+ const rawBody = await readBody(req);
687
+ const parsedBody = rawBody ? JSON.parse(rawBody) : void 0;
688
+ await mcpServer.connect(transport);
689
+ await transport.handleRequest(req, res, parsedBody);
690
+ res.on("close", () => {
691
+ transport.close().catch(() => {
692
+ });
693
+ mcpServer.close().catch(() => {
694
+ });
695
+ });
696
+ } catch (err) {
697
+ console.error("MCP request error:", err);
698
+ if (!res.headersSent) {
699
+ res.writeHead(500, { "Content-Type": "application/json" });
700
+ res.end(
701
+ JSON.stringify({
702
+ jsonrpc: "2.0",
703
+ error: { code: -32603, message: "Internal server error" },
704
+ id: null
705
+ })
706
+ );
707
+ }
708
+ }
709
+ } else if (req.method === "GET") {
710
+ res.writeHead(405, { "Content-Type": "application/json" });
711
+ res.end(
712
+ JSON.stringify({
713
+ jsonrpc: "2.0",
714
+ error: { code: -32e3, message: "Method not allowed." },
715
+ id: null
716
+ })
717
+ );
718
+ } else if (req.method === "DELETE") {
719
+ res.writeHead(405, { "Content-Type": "application/json" });
720
+ res.end(
721
+ JSON.stringify({
722
+ jsonrpc: "2.0",
723
+ error: { code: -32e3, message: "Method not allowed." },
724
+ id: null
725
+ })
726
+ );
727
+ } else {
728
+ res.writeHead(405);
729
+ res.end("Method Not Allowed");
730
+ }
731
+ } else {
732
+ res.writeHead(404);
733
+ res.end("Not found");
734
+ }
735
+ } catch (err) {
736
+ console.error("HTTP server error:", err);
737
+ if (!res.headersSent) {
738
+ res.writeHead(500);
739
+ res.end("Internal server error");
740
+ }
741
+ }
742
+ }
743
+ );
744
+ const actualPort = await tryListen(httpServer, port, 10);
745
+ resolvedPort = actualPort;
746
+ mcpPort = actualPort;
747
+ console.log(`MCP \uC11C\uBC84 \uC2DC\uC791: http://localhost:${actualPort}/mcp`);
748
+ const cleanup = async () => {
749
+ if (globalBrowserTools) {
750
+ await globalBrowserTools.cleanup();
751
+ }
752
+ httpServer.close();
753
+ };
754
+ process.on("SIGINT", async () => {
755
+ await cleanup();
756
+ process.exit(0);
757
+ });
758
+ process.on("SIGTERM", async () => {
759
+ await cleanup();
760
+ process.exit(0);
761
+ });
762
+ return actualPort;
763
+ }
764
+ async function handleMCPRequest(id, payload) {
765
+ const url = `http://localhost:${mcpPort}/mcp`;
766
+ const res = await fetch(url, {
767
+ method: "POST",
768
+ headers: {
769
+ "Content-Type": "application/json",
770
+ Accept: "application/json, text/event-stream"
771
+ },
772
+ body: JSON.stringify(payload)
773
+ });
774
+ if (!res.ok) {
775
+ throw new Error(`MCP request failed: ${res.status} ${res.statusText}`);
776
+ }
777
+ const text = await res.text();
778
+ const lines = text.split("\n");
779
+ for (const line of lines) {
780
+ if (line.startsWith("data: ")) {
781
+ try {
782
+ return JSON.parse(line.slice(6));
783
+ } catch {
784
+ }
785
+ }
786
+ }
787
+ return null;
788
+ }
789
+ // Annotate the CommonJS export names for ESM import in node:
790
+ 0 && (module.exports = {
791
+ handleMCPRequest,
792
+ startMCPServer
793
+ });