Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ id 문자셋 방어: `.wsb` 주입 전 `^[A-Za-z0-9._-]+$` 만 허용.
- 템플릿: `shared/wsb-template.xml` 로드 후 `__SITEIDS__` 를 사이트 사전선택 구문으로 치환.
- 사이트 주입 채널: 환경변수 `TABLECLOTH_SITE_IDS`(PARAMETERIZED_WSB_SPEC.md §0.5). 치환 구문 예:
` $env:TABLECLOTH_SITE_IDS = ''<id1> <id2>'';` (id 없으면 빈 문자열).
- 다중 id honor: 여러 service id가 오면 **모두** 공백으로 join 해 `TABLECLOTH_SITE_IDS`에 **한 번만** 대입한다(처음 하나로 자르지 않는다). 이 계약은 `node/test/conformance.mjs`의 다중 id 회귀 검사로 고정한다. 실행 배관(러너/준비 스크립트/Spork)은 이 값을 받아 지정한 모든 사이트를 각각 **한 번씩** 연다(다운스트림 계약은 [TableCloth](https://github.com/yourtablecloth/TableCloth); 관련 이슈 [#2](https://github.com/yourtablecloth/TableClothMcp/issues/2)).
- 실행 자산은 전부 GitHub 릴리스 공개 URL(`tablecloth-prepare.ps1` 등). 무설치 Express 레인.
- `securityNote`: 응답에 항상 포함. 문구는 `strings.json` 의 `sandbox.securityNote`.
- 알려진 이슈/하드닝: 명령이 원격 스크립트 실행 형태라 오탐될 수 있음 → [#1](https://github.com/yourtablecloth/TableClothMcp/issues/1).
Expand Down
9 changes: 9 additions & 0 deletions TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,12 @@ MCP 서버는 클라이언트가 시작할 때 프로세스로 떠서 실행되
- 원인: 그 `.wsb`의 `<Command>`는 공식 준비 스크립트를 원격에서 받아 실행하는 형태입니다(`ExecutionPolicy Bypass` + 원격 스크립트 `iex` + DNS 폴백). 이 조합이 일반적인 악성 다운로더와 겉모습이 같아 모델의 안전 검토가 발동합니다. 명령이 매우 긴 단일 라인이라 검토 도중 응답이 끊기기도 합니다.
- 실제 동작: 이 스크립트는 공식 GitHub 릴리스에서 HTTPS로 받아 **일회용 샌드박스 내부에서만** 실행됩니다. 호스트 시스템에는 접근하거나 영향을 주지 않고, 샌드박스를 닫으면 모두 사라집니다. 자격증명이나 로그인은 다루지 않습니다. 따라서 어시스턴트의 실행 전 확인은 정상 동작으로 보시면 됩니다. 참고로 `generate_wsb`와 `launch_sandbox` 응답에는 이 동작을 명시하는 `securityNote` 필드가 포함됩니다.
- 개선 방향: 정본 `.wsb`의 실행 명령을 최소화(로직을 준비 스크립트로 이관)하고, 다운로드 스크립트를 해시로 검증하는 방식으로 바꿔 오탐과 검토 끊김을 줄이는 작업을 추적 중입니다([#1](https://github.com/yourtablecloth/TableClothMcp/issues/1)).

### 여러 은행/사이트를 지정해도 첫 번째만 열리거나, 사이트가 중복으로 열림

- 증상: `국민은행과 우리은행을 실행해줘`처럼 여러 사이트를 요청했는데 처음 하나만 열립니다. 또한 샌드박스가 사이트를 커맨드라인(사전선택) 모드로 열 때 같은 사이트가 두 번 열리기도 합니다([#2](https://github.com/yourtablecloth/TableClothMcp/issues/2)).
- MCP 서버의 역할(정상): `launch_sandbox`/`generate_wsb`는 넘어온 **모든** service id를 유지해 `.wsb`의 `TABLECLOTH_SITE_IDS` 환경변수 채널에 공백으로 join 해 한 번에 전달합니다(SPEC.md §7). 즉 다중 인자 honor는 MCP 경계에서 유지되며, 이는 `node/test/conformance.mjs`의 다중 id 회귀 테스트로 고정됩니다.
- 실제 원인(다운스트림): 사이트 사전선택을 실제로 실행하는 실행 배관은 GitHub 릴리스 자산인 [TableCloth](https://github.com/yourtablecloth/TableCloth) 의 무설치 러너입니다. `.wsb` 가 넘긴 여러 id 가 이 지점에서 소실됩니다.
- 첫 번째만 열림: `tools/no-install/tablecloth-prepare.ps1` 이 `Start-Process $launcher -ArgumentList @('--site-ids', $SiteIds)` 로 넘기면서 공백으로 join 된 값이 개별 인자로 흩어지고, `Spork.Bootstrapper/BootstrapOptions.cs` 의 `--site-ids` 파서가 **첫 토큰만** 값으로 취합니다. 준비 스크립트가 값을 인용(따옴표)해 넘기거나 부트스트래퍼가 나머지 토큰을 모두 수집하도록 고쳐야 합니다.
- 중복 열림: Spork 커맨드라인 모드에서 사이트를 여는 경로가 이중으로 발화되던 문제로, TableCloth `main` 에서 단계 구성(`StepsComposer`)의 `OpenWebSiteStep` 제거로 정리되었습니다(릴리스 반영 대기).
- 정리: 이 저장소(MCP)에서 고칠 부분은 없습니다. 러너/준비 스크립트 수정과 다음 릴리스 반영으로 해소됩니다.
15 changes: 15 additions & 0 deletions node/test/conformance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ async function collect(cmd, args) {
[4, "tools/call", { name: "list_categories", arguments: {} }],
[5, "tools/call", { name: "get_service", arguments: { id: "Hometax" } }],
[6, "tools/call", { name: "list_companions", arguments: {} }],
// 여러 사이트를 한 번에 지정했을 때 모든 id가 .wsb에 유지되는지(다중 인자 honor, 이슈 #2) 검증한다.
[7, "tools/call", { name: "generate_wsb", arguments: { serviceIds: ["Hometax", "ShinhanBank"] } }],
];
for (const [id, method, params] of reqs) c.send({ jsonrpc: "2.0", id, method, params });
const body = async (id) => JSON.parse((await c.wait(id)).result.content[0].text);
Expand All @@ -60,6 +62,7 @@ async function collect(cmd, args) {
cats: await body(4),
getSvc: await body(5),
companions: await body(6),
genWsbMulti: await body(7),
};
c.close();
return out;
Expand Down Expand Up @@ -116,6 +119,18 @@ check("securityNote==shared (both)", net.genWsb.securityNote === shared.sandbox.
check("usage==shared (both)", net.genWsb.usage === shared.tools.generate_wsb.usage && node.genWsb.usage === shared.tools.generate_wsb.usage);
check("siteIds equal", canon(net.genWsb.siteIds) === canon(node.genWsb.siteIds));

// 다중 사이트 id honor (이슈 #2): 여러 개를 넘겨도 처음 하나로 잘리지 않고 모두 .wsb에 실려야 하며,
// TABLECLOTH_SITE_IDS 환경변수 채널에 공백으로 join 되어 한 번만 대입돼야 한다(SPEC.md §7).
const multiIds = ["Hometax", "ShinhanBank"];
const assignmentCount = (wsb) => (wsb.match(/TABLECLOTH_SITE_IDS\s*=/g) ?? []).length;
for (const [impl, r] of [["net", net.genWsbMulti], ["node", node.genWsbMulti]]) {
check(`multi-id siteIds honored (${impl})`, canon(r.siteIds) === canon(multiIds), `siteIds=${JSON.stringify(r.siteIds)}`);
check(`multi-id all ids present in .wsb (${impl})`, multiIds.every((id) => r.wsb.includes(id)));
check(`multi-id single env assignment (${impl})`, assignmentCount(r.wsb) === 1, `count=${assignmentCount(r.wsb)}`);
check(`multi-id ids space-joined in env channel (${impl})`, r.wsb.includes(multiIds.join(" ")));
}
check("multi-id .wsb byte-equal (.NET vs Node)", net.genWsbMulti.wsb === node.genWsbMulti.wsb);

console.log("\n[5] list_categories parity");
check("totalServices equal", net.cats.totalServices === node.cats.totalServices, `net=${net.cats.totalServices} node=${node.cats.totalServices}`);
check("category counts equal", canon(catMap(net.cats)) === canon(catMap(node.cats)), `net=${JSON.stringify(catMap(net.cats))}\n node=${JSON.stringify(catMap(node.cats))}`);
Expand Down