sweetspot-remote-agent 1.7.0 → 1.8.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.
- package/mcp-server.js +107 -0
- package/package.json +2 -2
package/mcp-server.js
CHANGED
|
@@ -125,6 +125,113 @@ function createServer() {
|
|
|
125
125
|
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
// ── 파일 시스템 ──
|
|
129
|
+
srv.tool("list_files", "디렉토리 내용 조회. depth=0이면 해당 폴더만, depth=1이면 하위 1단계까지", {
|
|
130
|
+
path: z.string().default("~").describe("탐색할 경로"),
|
|
131
|
+
depth: z.number().default(0).describe("하위 탐색 깊이 (0=현재만, 1=하위1단계)"),
|
|
132
|
+
}, async ({ path: dirPath, depth }) => {
|
|
133
|
+
const { execSync } = await import("child_process");
|
|
134
|
+
const resolved = dirPath.replace(/^~/, process.env.HOME || "~");
|
|
135
|
+
try {
|
|
136
|
+
const cmd = depth > 0
|
|
137
|
+
? `find "${resolved}" -maxdepth ${depth + 1} -not -name '.*' | head -200`
|
|
138
|
+
: `ls -la "${resolved}" | head -100`;
|
|
139
|
+
const output = execSync(cmd, { timeout: 10000, encoding: "utf-8" });
|
|
140
|
+
return { content: [{ type: "text", text: output || "(빈 디렉토리)" }] };
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return { content: [{ type: "text", text: `오류: ${e.message}` }] };
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
srv.tool("read_file", "텍스트 파일 읽기 (txt, md, json, csv, tsv 등). 바이너리(PDF, 이미지, Excel)는 extract_file 사용", {
|
|
147
|
+
path: z.string().describe("파일 경로"),
|
|
148
|
+
lines: z.number().default(200).describe("최대 읽을 줄 수"),
|
|
149
|
+
}, async ({ path: filePath, lines }) => {
|
|
150
|
+
const { execSync } = await import("child_process");
|
|
151
|
+
const resolved = filePath.replace(/^~/, process.env.HOME || "~");
|
|
152
|
+
const ext = resolved.split(".").pop()?.toLowerCase() || "";
|
|
153
|
+
try {
|
|
154
|
+
// CSV → 탭 구분으로 변환해서 가독성 향상
|
|
155
|
+
if (ext === "csv") {
|
|
156
|
+
const output = execSync(`head -n ${lines} "${resolved}"`, { timeout: 10000, encoding: "utf-8", maxBuffer: 1024 * 100 });
|
|
157
|
+
const tsv = output.split("\\n").map(line => {
|
|
158
|
+
// 간단한 CSV→TSV (따옴표 내 쉼표 처리 포함)
|
|
159
|
+
const cells = [];
|
|
160
|
+
let cell = "", inQuote = false;
|
|
161
|
+
for (const ch of line) {
|
|
162
|
+
if (ch === '"') { inQuote = !inQuote; }
|
|
163
|
+
else if (ch === ',' && !inQuote) { cells.push(cell); cell = ""; }
|
|
164
|
+
else { cell += ch; }
|
|
165
|
+
}
|
|
166
|
+
cells.push(cell);
|
|
167
|
+
return cells.join("\\t");
|
|
168
|
+
}).join("\\n");
|
|
169
|
+
return { content: [{ type: "text", text: `[CSV → TSV 변환]\\n${tsv}` }] };
|
|
170
|
+
}
|
|
171
|
+
const output = execSync(`head -n ${lines} "${resolved}"`, { timeout: 10000, encoding: "utf-8", maxBuffer: 1024 * 50 });
|
|
172
|
+
return { content: [{ type: "text", text: output || "(빈 파일)" }] };
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return { content: [{ type: "text", text: `오류: ${e.message}` }] };
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
srv.tool("extract_file", "바이너리 파일(PDF, 이미지, Excel)을 base64로 추출. 서버에서 Gemini Vision 등으로 처리", {
|
|
179
|
+
path: z.string().describe("파일 경로"),
|
|
180
|
+
}, async ({ path: filePath }) => {
|
|
181
|
+
const { readFileSync, statSync } = await import("fs");
|
|
182
|
+
const resolved = filePath.replace(/^~/, process.env.HOME || "~");
|
|
183
|
+
try {
|
|
184
|
+
const stat = statSync(resolved);
|
|
185
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
186
|
+
return { content: [{ type: "text", text: "오류: 파일이 10MB를 초과합니다" }] };
|
|
187
|
+
}
|
|
188
|
+
const ext = resolved.split(".").pop()?.toLowerCase() || "";
|
|
189
|
+
const buf = readFileSync(resolved);
|
|
190
|
+
const base64 = buf.toString("base64");
|
|
191
|
+
const mimeMap = {
|
|
192
|
+
pdf: "application/pdf", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
|
|
193
|
+
gif: "image/gif", webp: "image/webp", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
194
|
+
xls: "application/vnd.ms-excel", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
195
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
196
|
+
};
|
|
197
|
+
const mime = mimeMap[ext] || "application/octet-stream";
|
|
198
|
+
return { content: [{ type: "text", text: JSON.stringify({ type: "base64_file", mime, ext, size: stat.size, name: resolved.split("/").pop(), data: base64 }) }] };
|
|
199
|
+
} catch (e) {
|
|
200
|
+
return { content: [{ type: "text", text: `오류: ${e.message}` }] };
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
srv.tool("search_files", "파일명 또는 파일 내용 검색", {
|
|
205
|
+
query: z.string().describe("검색어"),
|
|
206
|
+
path: z.string().default("~").describe("검색 시작 경로"),
|
|
207
|
+
content: z.boolean().default(false).describe("true면 파일 내용도 검색 (grep)"),
|
|
208
|
+
}, async ({ query, path: searchPath, content: searchContent }) => {
|
|
209
|
+
const { execSync } = await import("child_process");
|
|
210
|
+
const resolved = searchPath.replace(/^~/, process.env.HOME || "~");
|
|
211
|
+
try {
|
|
212
|
+
const cmd = searchContent
|
|
213
|
+
? `grep -rl --include='*.{txt,md,json,csv,js,ts,py,html,css}' "${query}" "${resolved}" 2>/dev/null | head -20`
|
|
214
|
+
: `find "${resolved}" -maxdepth 4 -iname "*${query}*" -not -path '*/.*' 2>/dev/null | head -30`;
|
|
215
|
+
const output = execSync(cmd, { timeout: 15000, encoding: "utf-8" });
|
|
216
|
+
return { content: [{ type: "text", text: output || "검색 결과 없음" }] };
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return { content: [{ type: "text", text: `오류: ${e.message}` }] };
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
srv.tool("file_info", "파일/폴더 메타데이터 (크기, 수정일, 타입)", {
|
|
223
|
+
path: z.string().describe("파일 경로"),
|
|
224
|
+
}, async ({ path: filePath }) => {
|
|
225
|
+
const { execSync } = await import("child_process");
|
|
226
|
+
const resolved = filePath.replace(/^~/, process.env.HOME || "~");
|
|
227
|
+
try {
|
|
228
|
+
const output = execSync(`stat -f "name: %N\nsize: %z bytes\nmodified: %Sm\ntype: %HT" "${resolved}"`, { timeout: 5000, encoding: "utf-8" });
|
|
229
|
+
return { content: [{ type: "text", text: output }] };
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return { content: [{ type: "text", text: `오류: ${e.message}` }] };
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
128
235
|
// ── AppleScript 실행 (macOS 전용, 앱 직접 제어) ──
|
|
129
236
|
srv.tool("applescript", "AppleScript 실행 (macOS). 메모 작성, 앱 내부 조작 등에 사용", {
|
|
130
237
|
script: z.string().describe("실행할 AppleScript 코드"),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sweetspot-remote-agent",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Sweetspot 원격 제어 MCP 서버 — 스크린샷, 마우스/키보드, 앱 제어, 셸 실행",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Sweetspot 원격 제어 MCP 서버 — 스크린샷, 마우스/키보드, 앱 제어, 파일 탐색, 셸 실행",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "mcp-server.js",
|
|
7
7
|
"bin": {
|