AI๊ฐ '์ด ๋ฒํผ ๊ณ ์ณ์ค'๋ฅผ ์์๋ฃ๊ฒ ํ๊ธฐ โ ์ฝ๋ ๋ก์ผ์ดํฐ๋ฅผ MCP๋ก ์ด๋ค
react-code-locator ์๋ฆฌ์ฆ์ ๋ง์ง๋ง ๊ธ. ์ง๊ธ๊น์ง๋ ์ฌ๋์ด ํ๋ฉด์์ ์ฐํด๋ฆญํด ์์ค๋ก ์ ํํ๋ ์ด์ผ๊ธฐ์๋ค. ๊ทธ๋ฐ๋ฐ ์์ฆ์ ์ฝ๋๋ฅผ ๊ณ ์น๋ ์ฃผ์ฒด๊ฐ ์ฌ๋๋ง์ด ์๋๋ค. AI ์์ด์ ํธ๋ ๊ณ ์น๋ค. ๊ทธ๋ ๋ค๋ฉด AI์๊ฒ๋ "์ด ํ๋ฉด ์์ โ ์ด ์์ค ํ์ผ"์ ์๋ ค์ค ์ ์์ง ์์๊น? ์ด ์ง๋ฌธ์์ MCP ํ์ฅ์ด ์์๋๋ค.
AI๊ฐ ๋ชป ํธ๋ ๋ฌธ์
AI ์์ด์ ํธ๋ ๋ธ๋ผ์ฐ์ ์์ ์์๋ฅผ ์ฐพ๋ ๊ฑด ๊ฝค ์ํ๋ค.
- ํ ์คํธ๋ก, CSS ์ ๋ ํฐ๋ก, ์ญํ (role)๋ก, ์ ๊ทผ์ฑ ํธ๋ฆฌ๋ก, ํ๋ฉด ์ขํ๋กโฆ
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ๋งํ๋ค.
- "์ด ๋ฒํผ"์ด ์ด๋ ํ์ผ์ ์์ง?
- ๊ณ ์ณ์ผ ํ ๊ณณ์ด **๋ด๊ฐ ์ด ์๋ฆฌ(ํ๋ฉด)**์ผ, ์๋๋ฉด ๊ณต์ ์ปดํฌ๋ํธ ์ ์์ผ?
styled(),memo,forwardRef, ๋์์ธ ์์คํ ๋ํผ์ ์ ๋น ์ง๊ณ ์ง์ง ํธ์ง ์ง์ ์ ์ด๋ป๊ฒ ์ฐพ์ง?
์ฆ AI์๊ฒ ๋ถ์กฑํ ๊ฑด "์์ ์ฐพ๊ธฐ"๊ฐ ์๋๋ผ "์์ โ ์ฌ๋ฐ๋ฅธ ์์ค ์์น ๋งคํ" ์ด๋ค. ์ด๊ฑด ์ ํํ react-code-locator๊ฐ ํ๋ ์ผ์ด๋ค. ์ด ๋๊ตฌ๋ ์์๋ฅผ ์ฐพ๋ ์์ง์ด ์๋๋ผ ์ฝ๋ ๋งคํ ์์ง์ ๊ฐ๊น๋ค.
MCP๊ฐ ๋ญ๊ฐ, ๊ทธ๋ฆฌ๊ณ ์ ์ฌ๊ธฐ์ ๋ง๋
MCP(Model Context Protocol) ๋ AI ์์ด์ ํธ๊ฐ ์ธ๋ถ ๋๊ตฌ๋ฅผ ๊ตฌ์กฐํ๋ ๋ฐฉ์์ผ๋ก ํธ์ถํ๊ฒ ํด์ฃผ๋ ํ์ค์ด๋ค. ์๋ฒ๊ฐ "์ด๋ฐ ๋๊ตฌ๊ฐ ์๊ณ , ์ ๋ ฅ์ ์ด๋ฐ ๋ชจ์์ด์ผ"๋ฅผ ์ ์ธํ๋ฉด, AI๊ฐ ๊ทธ ๋๊ตฌ๋ฅผ ํ์ํ ๋ ํธ์ถํ๋ค.
MCP๋ ๋ก์ผ์ดํฐ๋ฅผ ๋์ฒดํ์ง ์๋๋ค. ๋ก์ผ์ดํฐ๋ฅผ AI๊ฐ ์ธ ์ ์๋ ๋๊ตฌ๋ก ๋ ธ์ถํ ๋ฟ์ด๋ค. ํ๋ฆ์ ์ด๋ ๋ค.
- ์ฌ์ฉ์๊ฐ AI์๊ฒ "์ฒดํฌ์์ ํ์ด์ง์ ์ ์ถ ๋ฒํผ ๊ณ ์ณ์ค"๋ผ๊ณ ํ๋ค
- AI๊ฐ ๋์ ์์๋ฅผ ์๋ณํ๋ค
- MCP ๋๊ตฌ๋ฅผ ํธ์ถํด ๊ทธ ์์์ ์์ค ์์น๋ฅผ ์ป๋๋ค
- AI๊ฐ ๊ทธ ํ์ผ์ ์ด์ด ํธ์งํ๋ค
๋ ๊ฐ์ง ์ ๊ทผ: ๋ฐํ์ vs ์ ์ ์ธ๋ฑ์ค
๋ก์ผ์ดํฐ๋ฅผ AI์ ํ์ฐ๋ ๊ธธ์ ๋ ๊ฐ์ง์๋ค.
A. ๋ฐํ์(๋ธ๋ผ์ฐ์ + Fiber) โ ์์ ๊ธ๋ค์์ ๋ง๋ ๊ทธ ๋ฐฉ์. Playwright๋ก ํ์ด์ง๋ฅผ ๋์ฐ๊ณ , ์ค์ ๋ ๋๋ Fiber์์ ์์ค๋ฅผ ๋ฝ๋๋ค. i18n ํ ์คํธ๋ ์กฐ๊ฑด๋ถ ๋ ๋์ฒ๋ผ ์คํ๋ผ์ผ๋ง ์ ์ ์๋ ๊ฒ์ ๊ฐํ๋ค. ๋์ ๊ฐ๋ฐ ์๋ฒยท๋ธ๋ผ์ฐ์ ๊ฐ ํ์ํ๋ค.
B. ์ ์ AST ์ธ๋ฑ์ค โ ์๋ก ๋ง๋ react-code-locator-mcp.
๋ธ๋ผ์ฐ์ ๋ ๊ฐ๋ฐ ์๋ฒ๋ ์์ด, ํ๋ก์ ํธ์ JSX๋ฅผ ๋น๋ ์์ด AST๋ก ์ธ๋ฑ์ฑํด
์ง์ํ๋ค. ๋ฐ๋ฆฌ์ด ๋จ์๋ก ๋น ๋ฅด๊ณ , ๊ตฌ์กฐ์ ์ง์์ ๊ฐํ๋ค.
์ด๋ฒ MCP ํ๋กํ ํ์ ์ B๋ก ๊ฐ๋ค. AI ์ํฌํ๋ก์ "๋ธ๋ผ์ฐ์ ์ ๋์ฐ๊ณ ๋ฐ๋ก ๋ต์ด ๋์ค๋" ์ ์ ์ธ๋ฑ์ค๊ฐ ํจ์ฌ ์ ๋ง์๊ธฐ ๋๋ฌธ์ด๋ค.
"grep์ผ๋ก ํ๋ฉด ๋์ง ์๋?"
๊ฐ์ฅ ๋จผ์ ๋์จ ๋ฐ๋ฌธ์ด๋ค. ํ์ง๋ง grep์ ํ ์คํธ ๊ธฐ๋ฐ์ด๋ผ ํ๊ณ๊ฐ ๋๋ ทํ๋ค.
- ๊ฐ์ ํ ์คํธ๊ฐ ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ์์ผ๋ฉด ๊ตฌ๋ถ์ ๋ชป ํ๋ค
- "
onSubmit์ ๊ฐ์ง<button>" ๊ฐ์ ๊ตฌ์กฐ์ ์กฐ๊ฑด์ ๋ชป ๊ฑด๋ค styled(Button)๋ํผ๋ฅผ ๋ชป ํ์ด๋ธ๋ค
์ธ๋ฑ์ค๋ AST์์ ๋ฝ์ ๊ตฌ์กฐ๋ผ ์ด๋ฐ ๊ฑธ ํ ๋ฐฉ์ ๊ตฌ์กฐ์ ์ง์๋ก ๋ตํ๋ค. ๊ทธ๊ฒ๋ grep๊ธ ์๋๋ก. (300ํ์ผยท9์ฒ ์์ ์ฝํผ์ค ์ค์ธก: ์ฝ๋ ์ฒซ ์ง์ ~25ms, ์์ ์บ์ ~1.3ms. ํ ์คํธ๊ฐ ๋ชจ๋ ํ์ผ์ ์๋ ๋ชจํธํ ์ง์์์ grep์ 300๊ฐ ํํธ, ์ธ๋ฑ์ค๋ ์ ํํ 1๊ฑด.)
๋น๊ฒฐ์ "AST ํ์ฑ์ด ๋น์ธ๋ค"๋ ํต๋ ์ ์ฐํํ ๊ฒ์ด๋ค. ๋จผ์ ๊ฐ์ผ ๋ฆฌํฐ๋ด ์ค์บ (ripgrep)์ผ๋ก ํ๋ณด ํ์ผ๋ง ์ถ๋ฆฌ๊ณ , ๊ทธ ์์กด์๋ค๋ง AST ํ์ฑํ๋ค. ๊ทธ๋์ ์ฒซ ์ง์๋ grep๊ณผ ๋น์ทํ๊ณ , ๊ฐ์ ์ธ์ ์ฌ์ง์๋ ์บ์๋ก ~15๋ฐฐ ๋นจ๋ผ์ง๋ค.
MCP ์๋ฒ๋ ์ด๋ ๊ฒ ์๊ฒผ๋ค
MCP SDK๋ก ๋๊ตฌ ํ๋๋ฅผ ์ ์ธํ๋ ๊ฑด ๋๋ ๋งํผ ๋จ์ํ๋ค. zod๋ก ์
๋ ฅ ์คํค๋ง๋ฅผ
์ ์ํ๋ฉด AI๊ฐ ๊ทธ ๋ชจ์๋๋ก ํธ์ถํ๋ค.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const index = new ProjectIndex(process.argv[2] ?? process.cwd());
const server = new McpServer({ name: "react-code-locator-mcp", version: "0.0.1" });
server.tool(
"find_element",
"JSX ์์๋ฅผ ๊ฐ๋
์ผ๋ก ์ฐพ์ ์ ํํ ์์ค ์์น(file:line:col)๋ฅผ ๋๋ ค์ค๋ค.",
{
text: z.string().optional(), // ์์ ์์ ๋ฆฌํฐ๋ด ํ
์คํธ
testid: z.string().optional(), // data-testid
tag: z.string().optional(), // "button" | "Card"
component: z.string().optional(), // ๊ฐ์ธ๋ ์ปดํฌ๋ํธ ์ด๋ฆ
hasProp: z.string().optional(), // ์ด prop์ ๊ฐ์ง ๊ฒ๋ง
dir: z.string().optional(),
limit: z.number().optional(),
},
async (args) => {
const results = index.find(args);
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
},
);
์ด ๋ฐ์ ์ธ๋ฑ์ค๊ฐ ๋ช ๊ฐ์ ํ์ผ์ ์ปค๋ฒํ๋์ง ์๋ ค์ฃผ๋ index_stats ๋๊ตฌ๋
๊ฐ์ด ๋
ธ์ถํด ๋๋ค. AI๊ฐ "์ธ๋ฑ์ค๊ฐ ์ ๋๋ก ์กํ๋"๋ถํฐ ํ์ธํ ์ ์๊ฒ.
๋๋ ค์ฃผ๋ ๊ฒฐ๊ณผ์ file:line:col๋ฟ ์๋๋ผ ๊ฐ์ธ๋ ์ปดํฌ๋ํธ ์ด๋ฆ๊ณผ ์กฐ์ ํ๊ทธ
์ฒด์ธ๊น์ง ๋ด๋๋ค. AI๊ฐ "์ด๊ฒ ๋ง๋ ์์น์ธ์ง" ํ๋จํ ๋ฌธ๋งฅ์ ํจ๊ป ์ฃผ๋ ๊ฒ์ด๋ค.
{ "source": "src/CheckoutPage.tsx:42:7", "tag": "button",
"component": "CheckoutPage", "text": "๊ฒฐ์ ํ๊ธฐ",
"ancestors": ["form", "div", "CheckoutPage"] }
Claude Code ๊ฐ์ ํด๋ผ์ด์ธํธ์ ์ด๋ ๊ฒ ๋ฑ๋กํ๋ค.
{
"mcpServers": {
"react-locator": {
"command": "node",
"args": ["/abs/path/react-code-locator-mcp/dist/server.js",
"/abs/path/your-react-app"]
}
}
}
์ด์ "์ฒดํฌ์์ ํ์ด์ง์์ ๊ฒฐ์ ๋ฒํผ ์ฐพ์์ค" ๋ผ๊ณ ํ๋ฉด AI๊ฐ find_element๋ฅผ
ํธ์ถํด ์ ํํ file:line:col์ ๋ฐ์ ์จ๋ค.
๋ ๊น์ ํต์ฐฐ: ๊ฐ์ฅ ๋ณดํธ์ ์ธ ์ง์ ์ ์ด๋์ธ๊ฐ
MCP๋ฅผ ๋ถ์ด๋ฉฐ ์ํคํ ์ฒ๋ฅผ ๋ค์ ์๊ฐํ๊ฒ ๋๋ค. "React ํ๋ก์ ํธ ์ต๋ํ ๋ง์ด ์ง์ํ๊ธฐ"์ ์ง์ง ๊ณตํต ์ง์ ์ Babel๋, ํน์ ๋ฒ๋ค๋ฌ๋ ์๋์๋ค. ๋ฐ๋ก React element ํฉํ ๋ฆฌ ํธ์ถ์ด๋ค.
์ด๋ค ๋๊ตฌ๋ก JSX๋ฅผ ๋ณํํ๋ , ๊ฒฐ๊ตญ ์ฝ๋๋
jsx("div", { children: "Hello" }) // ๋๋ jsxs / jsxDEV / React.createElement
๋ก ์๋ ดํ๋ค. ๊ทธ๋์ ์ฅ๊ธฐ์ ์ผ๋ก ์ด์์ ์ธ ๊ตฌ์กฐ๋ ์ด๋ ๋ค.
- ๊ณตํต ๋ฐํ์ ์ฝ์ด โ element ๋ํยท๋ฉํ๋ฐ์ดํฐ ์ ์ฅยท๋ฐํ์ ์กฐํ
- ์์ ๋ณํ ์ด๋ํฐ โ Babel/SWC/esbuild/Vite/Webpack๋ณ๋ก ์์ค ์์น๋ง ๋ฝ์ ์ฝ์ด์ ๋๊น
๋จ, ํฉํ ๋ฆฌ ๋ํ๋ง์ผ๋ก๋ ๋ถ์กฑํ๋ค. ๊ฐ๋ฐ ๋ชจ๋์ jsxDEV๋ ์ง๊ธ๋ source
์ธ์๋ก ํ์ผยท์คยท์นธ์ ๋ฐ์ง๋ง, ํ๋ก๋์
๋น๋๋ ์ผ๋ฐ jsx ๋ฐํ์์์๋ ๋ณดํต
๊ทธ ์ ๋ณด๊ฐ ์๋ค. ํ์คํ๊ฒ ์์น๋ฅผ ์๋ ๊ฑด AST ๋
ธ๋๋ฅผ ๋ค๊ณ ์๋ ๋ณํ
๋จ๊ณ๋ฟ์ด๋ค.
๊ทธ๋์ "๋ณํ ์ด๋ํฐ(์์น ์ฃผ์
) + ๊ณตํต ๋ฐํ์(์กฐํ) + MCP(AI ์ธํฐํ์ด์ค)"์
3์ธต ๊ตฌ์กฐ๊ฐ ๊ฐ์ฅ ํ์ค์ ์ด๋ผ๋ ๊ฒฐ๋ก ์ ๋ฟ์๋ค.
MCP๊ฐ ํ์ง ๋ชปํ๋ ๊ฒ
์์งํ ํ๊ณ๋ ์ ์ด๋๋ค. MCP๋ "์ด ๋ฒํผ์ด ๋ญ์ง"๋ฅผ ๋์ ์ ํด์ฃผ์ง ์๋๋ค. AI๋ ์ฌ์ ํ ์ ๋ ํฐยท์ขํยท์ด์ ์ ์ ํํ ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋์์ ๋จผ์ ์๋ณํด์ผ ํ๋ค. ๊ทธ๋ฌ๋ ์ด ๋ก์ผ์ดํฐ MCP๋ ์์ ์๋ณ ์ดํ์ ๊ฐ์ฅ ์ธ๋ชจ๊ฐ ์๋ค โ ์๋ณ๋ ์์๋ฅผ "์ฌ๋ฐ๋ฅธ ํธ์ง ์ง์ "์ผ๋ก ๋ฐ๋ ค๋ค์ฃผ๋ ๋ง์ง๋ง ํ ๊ฑธ์.
์๋ฆฌ์ฆ๋ฅผ ๋ง์น๋ฉฐ
์ฌ์ํ ๋ถํธ("์ด ๋ฒํผ ์ด๋ ํ์ผ์ด์ผ?") ํ๋์์ ์์ํด, AST ํ์ฑ โ React Fiber โ ์ ๋๋ฒ์ค ๋ฒ๋ค๋ฌ ํ๋ฌ๊ทธ์ธ โ AI ์์ด์ ํธ์ฉ MCP๊น์ง ์๋ค. ๋ฌธ์ ํ๋๋ฅผ ๋๊น์ง ํ๋ฉด, ๊ทธ ๊ณผ์ ์์ ๋ง๋๋ ๊ณ๊ฐ์ง๋ค์ด ์ ๋ถ ๊ฐ์์ ๋ฐฐ์์ด ๋๋ค๋ ๊ฑธ ๋ค์ ๋๊ผ๋ค. ๊ทธ๊ฒ ๋ด๊ฐ ๊ณ์ ๋ญ๊ฐ๋ฅผ ๋ง๋๋ ์ด์ ๋ค.
- ๋ฐํ์ ๋๊ตฌ: react-code-locator
- MCP ์ธ๋ฑ์ค: react-code-locator-mcp
- ์๋ฆฌ์ฆ: ๋ง๋ ์ด์ผ๊ธฐ ยท AST ํ์ฑ ยท React Fiber ยท unplugin