포스트

Cursor·Copilot·Windsurf, 2026년 7월 기준 “에이전트 코딩” 실전 운영법: 규칙(Rules)·스킬(Skills)·브라우저·MCP로 생산성 올리기

Cursor·Copilot·Windsurf, 2026년 7월 기준 “에이전트 코딩” 실전 운영법: 규칙(Rules)·스킬(Skills)·브라우저·MCP로 생산성 올리기

들어가며

2026년 7월의 AI 코딩 도구들은 더 이상 “autocomplete 잘해주는 플러그인”이 아니라, 작업을 분해하고(Plan) → 여러 파일을 수정하고 → 테스트/검증까지 반복하는 agentic workflow가 중심입니다. 문제는 여기서부터입니다.

  • 해결되는 구체적 문제
    • “이 이슈 고치려면 파일 10개 건드려야 함” 같은 다중 파일 변경 작업의 전환 비용(context switching)
    • “내 코드베이스의 규칙(아키텍처/컨벤션/보안/배포 방식)을 매번 설명해야 하는 비용”
    • “문서/웹에서 근거 찾아서 적용 → 코드 반영” 같은 탐색+적용 루프
  • 언제 쓰면 좋은가
    • 리팩터링/마이그레이션/테스트 보강처럼 반복 패턴이 있고 검증 가능한 작업
    • 팀 컨벤션이 확립돼 있고, 이를 Rules/Skills로 외부화할 수 있을 때
    • PR 단위로 변경을 묶고 싶은 경우(특히 Copilot의 GitHub 연동 흐름)
  • 언제 쓰면 안 되는가
    • 요구사항이 계속 흔들리는 초기 기획 단계에서 “그냥 다 맡기기”
    • 보안/비용/권한 경계가 불명확한 상태에서 MCP/브라우저 자동 실행을 켜는 것(승인 모델과 allowlist 체계부터 정리 필요)
    • 테스트가 부실한 레거시에서 대규모 자동 리팩터링(결국 사람이 수동 디버깅 지옥)

🔧 핵심 개념

1) “지능”보다 중요한 것: 컨텍스트 주입 레이어

세 도구 모두 본질은 비슷합니다. LLM은 매 호출마다 망각하므로, 어떤 텍스트/파일/규칙을 시스템 프롬프트에 ‘항상’ 혹은 ‘상황에 따라’ 넣느냐가 품질을 좌우합니다.

  • Cursor: Rules가 1급 시민
    • .cursor/rules에 규칙을 두고(버전 관리), Always / Auto Attached(glob) / Agent Requested / Manual(@rule)적용 스코프를 설계합니다. 규칙 내용은 모델 컨텍스트 “앞부분”에 들어가 일관성을 만듭니다. (docs.cursor.com)
    • 핵심 포인트: “규칙을 문서처럼 길게”가 아니라, 에이전트가 결정을 내려야 하는 지점에만 최소 규칙을 둡니다(나머지는 스킬/템플릿으로).
  • Windsurf: Cascade(Flow) + Skills로 ‘워크플로우’를 주입
    • Cascade는 다단계 작업에 특화된 에이전트 모드로, Skills(재사용 가능한 지식/절차 번들)를 로드해 흐름을 잡는 접근이 자주 언급됩니다. (agentskills.help)
  • Copilot(VS Code): Agents + 브라우저 + 비용/모델 가시성
    • 2026년 6~7월 릴리스에서 parallel sessions, cost visibility, model 선택, autopilot 개선 등 “에이전트 운영”에 필요한 기능들이 강화됐습니다. (github.blog)
    • 특히 브라우저 도구가 GA가 되면서, 에이전트가 실제 브라우저를 조작하고 결과를 채팅에 피드백하는 루프가 가능해졌습니다. (github.blog)

2) 내부 작동 흐름(현실적인 추상화)

실무에서 체감되는 에이전트 흐름을 “구조”로 보면 보통 이렇습니다.

  1. Task ingestion: 사용자의 요구(이슈/PR/채팅)를 받아 목표/제약을 정리
  2. Context assembly:
    • 코드베이스 검색/열람
    • Rules/Skills/정책(allowlist, 보안 정책) 주입
    • 필요시 브라우저/MCP/터미널 도구 사용 준비
  3. Plan: 단계별 계획 생성(큰 작업일수록 중요) — Cursor도 Plan mode를 제공 (cursor.com)
  4. Act loop: (편집 → 실행/테스트 → 실패 분석 → 수정) 반복
  5. Gate/Review: 사람이 diff/로그를 보고 승인. 여기서 비용/리스크가 결정됨.

여기서 생산성의 차이는 모델보다 (a) 컨텍스트 조립 정확도 (b) 도구 실행 권한 설계 (c) 실패 시 복구 루프에서 벌어집니다.

3) 다른 접근과의 차이점(선택 기준)

  • Cursor는 “프로젝트 규칙(.cursor/rules) + 에이전트 중심 편집”이 강점인 방향으로 문서화돼 있고, 규칙 스코핑이 꽤 정교합니다. (docs.cursor.com)
  • Copilot은 VS Code/ GitHub 생태계에서 에이전트 운영(세션, 비용, 브라우저 등)을 강화하는 흐름이 뚜렷합니다. (github.blog)
  • Windsurf는 Cascade를 중심으로 “흐름(Flow) 기반” 작업을 강조하며, Skills를 통해 절차/베스트프랙티스를 통째로 가져오는 사용 패턴이 보입니다. (agentskills.help)

💻 실전 코드

목표: “내 프로젝트에 바로 적용 가능한” 시나리오로, TypeScript(Node) 기반 API 서버(Express)에서 결제 웹훅(stripe-like)을 도입한다고 가정하겠습니다.
이 작업은 현실적으로:

  • 라우트 추가
  • 서명 검증
  • idempotency 처리
  • DB 저장
  • 테스트 추가
  • 문서 업데이트 까지 여러 파일이 필요합니다. 즉, 에이전트가 잘하면 생산성이 크게 오릅니다.

아래는 Cursor/Windsurf/Copilot 공통으로 먹히는 방식으로 “규칙/스킬/명령”을 파일로 만들어 에이전트가 자동으로 따라야 할 운영체계를 구축하는 예시입니다.

0) 초기 셋업: 프로젝트 규칙(Cursor 기준) 만들기

.cursor/rules/backend-webhook.mdc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
description: Backend webhook implementation conventions (signature, idempotency, tests)
globs:
  - "src/**/*.ts"
alwaysApply: false
---

- All webhook endpoints must:
  - Verify signature header before reading JSON body (use raw body).
  - Implement idempotency: store event_id and ignore duplicates.
  - Log with requestId + event_id.
  - Add tests for signature verification + duplicate event handling.

- Prefer small pure functions for:
  - verifySignature(rawBody, headers) -> boolean
  - normalizeEvent(payload) -> DomainEvent
  - applyEvent(event) -> void

- Never hardcode secrets. Read from process.env and fail fast if missing.

이 규칙은 “항상”이 아니라 Auto Attached 성격으로 쓰는 게 보통 안전합니다(너무 많은 규칙이 항상 붙으면 컨텍스트가 비대해짐). Cursor는 .cursor/rules 기반 규칙 타입/스코프 개념을 공식 문서로 제공합니다. (docs.cursor.com)

1) 에이전트가 실행할 작업을 “검증 루프 포함”으로 지시하는 프롬프트 템플릿

세 도구 모두에서 통하는 핵심은 “파일 변경 + 실행 + 실패시 수정”을 명시하는 것입니다.

아래 프롬프트를 Cursor Composer / Windsurf Cascade / Copilot Agent에 그대로 넣고 시작합니다.

1
2
3
4
5
6
7
8
9
목표: 결제 웹훅 엔드포인트를 추가하고(서명 검증+idempotency), 테스트까지 통과시키기.

제약:
- src/payments/webhook.ts 라우트 추가
- raw body를 사용해 서명 검증
- event_id 중복 처리 (DB에 저장 후 중복이면 200 반환)
- 최소 2개 테스트: (1) 올바른 서명 통과 (2) 중복 이벤트 무시
- 규칙: backend-webhook 규칙을 적용해서 구현
- 마지막에: 실행 명령어 + 예상 출력(테스트 통과)을 정리

2) 현실적인 코드 베이스(예시) + 실행 가능 코드

Express + Vitest + SQLite(로컬) 조합으로 잡겠습니다.

(a) 의존성 설치

1
2
3
4
npm i express
npm i -D typescript tsx vitest supertest @types/express @types/supertest
npm i better-sqlite3
npm i -D @types/better-sqlite3

(b) DB 및 idempotency 저장소

1
2
3
4
5
6
7
8
9
10
11
// src/db.ts
import Database from "better-sqlite3";

export const db = new Database(process.env.SQLITE_PATH ?? ":memory:");

db.exec(`
  CREATE TABLE IF NOT EXISTS webhook_events (
    event_id TEXT PRIMARY KEY,
    received_at TEXT NOT NULL
  );
`);

(c) 웹훅 서명 검증(간단 HMAC 예시)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/payments/signature.ts
import crypto from "node:crypto";

export function verifySignature(rawBody: Buffer, signature: string | undefined) {
  const secret = process.env.WEBHOOK_SECRET;
  if (!secret) throw new Error("WEBHOOK_SECRET is missing");

  if (!signature) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // timingSafeEqual
  const a = Buffer.from(signature, "utf8");
  const b = Buffer.from(expected, "utf8");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

(d) 웹훅 라우트(핵심: raw body + idempotency)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// src/payments/webhook.ts
import { Router } from "express";
import { verifySignature } from "./signature";
import { db } from "../db";

export const paymentsWebhookRouter = Router();

// raw body 미들웨어: express.json() 대신 직접 Buffer 확보
paymentsWebhookRouter.post("/webhook", async (req, res) => {
  const chunks: Buffer[] = [];
  req.on("data", (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
  req.on("end", () => {
    try {
      const rawBody = Buffer.concat(chunks);

      const sig = req.header("x-webhook-signature");
      if (!verifySignature(rawBody, sig)) {
        return res.status(400).json({ ok: false, error: "invalid signature" });
      }

      const payload = JSON.parse(rawBody.toString("utf8")) as {
        id: string; // event_id
        type: string;
        data: unknown;
      };

      const insert = db.prepare(
        `INSERT INTO webhook_events(event_id, received_at) VALUES(?, ?)`
      );

      try {
        insert.run(payload.id, new Date().toISOString());
      } catch (e: any) {
        // PRIMARY KEY violation => duplicate
        return res.status(200).json({ ok: true, duplicate: true });
      }

      // TODO: payload.type별 처리(도메인 로직)
      return res.status(200).json({ ok: true });
    } catch (e: any) {
      return res.status(500).json({ ok: false, error: e?.message ?? "error" });
    }
  });
});

(e) 앱에 라우터 연결

1
2
3
4
5
6
7
8
// src/app.ts
import express from "express";
import { paymentsWebhookRouter } from "./payments/webhook";

export const app = express();

// 다른 API들은 json 써도 되지만, webhook 라우트는 raw body 필요
app.use("/payments", paymentsWebhookRouter);

(f) 테스트(Vitest + Supertest)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// test/webhook.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import request from "supertest";
import crypto from "node:crypto";
import { app } from "../src/app";
import { db } from "../src/db";

function sign(raw: Buffer) {
  const secret = process.env.WEBHOOK_SECRET!;
  return crypto.createHmac("sha256", secret).update(raw).digest("hex");
}

describe("payments webhook", () => {
  beforeEach(() => {
    db.exec("DELETE FROM webhook_events;");
  });

  it("accepts valid signature", async () => {
    process.env.WEBHOOK_SECRET = "test_secret";
    const body = Buffer.from(JSON.stringify({ id: "evt_1", type: "paid" }), "utf8");

    const res = await request(app)
      .post("/payments/webhook")
      .set("x-webhook-signature", sign(body))
      .send(body);

    expect(res.status).toBe(200);
    expect(res.body.ok).toBe(true);
  });

  it("returns duplicate=true for same event_id", async () => {
    process.env.WEBHOOK_SECRET = "test_secret";
    const body = Buffer.from(JSON.stringify({ id: "evt_dup", type: "paid" }), "utf8");
    const sig = sign(body);

    const r1 = await request(app).post("/payments/webhook").set("x-webhook-signature", sig).send(body);
    const r2 = await request(app).post("/payments/webhook").set("x-webhook-signature", sig).send(body);

    expect(r1.status).toBe(200);
    expect(r2.status).toBe(200);
    expect(r2.body.duplicate).toBe(true);
  });
});

(g) 실행

1
npx vitest run

예상 출력(요지):

  • 2 tests passed

이 예시는 “toy”가 아닌 이유가 명확합니다: 서명 검증과 raw body, idempotency, DB, 테스트까지 한 번에 묶여 있고, 실제 서비스에서 자주 터지는 포인트(duplicate 이벤트/서명 검증 순서)를 포함합니다.
그리고 이걸 에이전트로 시키면, 잘 되는 팀은 1~2회 반복으로 끝내고, 못 되는 팀은 10회 삽질합니다. 차이는 “규칙/검증 루프/권한 설계”입니다.


⚡ 실전 팁 & 함정

Best Practice 1) Rules는 “항상 적용”을 최소화하고, glob로 쪼개라

Cursor는 .cursor/rules에서 Auto Attached(glob) 같은 스코프 설계를 공식화하고 있습니다. (docs.cursor.com)
실무 팁:

  • Always 규칙은 보안/금지사항/프로젝트 정체성만 (10~20줄 수준)
  • 나머지는 src/payments/**, src/frontend/** 처럼 폴더 단위 glob 규칙으로 분리
  • “두 번째로 같은 실수”가 나오면 그때 규칙화(초반부터 규칙 과적재 금지)

Best Practice 2) Copilot의 “브라우저 도구”는 ‘검증’에만 쓰고, ‘결정’은 사람이 내려라

Copilot Agents의 브라우저 도구가 GA가 되면서(2026-07-01), 에이전트가 웹을 탐색하고 결과를 가져올 수 있습니다. (github.blog)
하지만 이 기능은:

  • 문서 버전 차이
  • 로그인/세션 이슈
  • 잘못된 페이지 인용 을 만들기 쉬워서, 추천 패턴은:
  • “문서에서 근거 문장/코드 스니펫 찾기”까지 에이전트
  • “우리 시스템에 맞는 결정(예: 보안 정책, 트랜잭션 경계)”은 사람이 확정
  • 확정 후 Rules/Skills로 다시 고정

Best Practice 3) MCP/allowlist는 ‘생산성’이 아니라 ‘보안 경계’다

Cursor 쪽 커뮤니티/문서 흐름을 보면 allowlist/permissions.json은 결국 “자동 실행”의 안전장치입니다. 실제로 2026년 7월 초에 allowlist 동작/회귀 이슈가 논의됐고, 클라이언트 업데이트로 수정된 사례가 공유됩니다. (forum.cursor.com)
운영 팁:

  • *:tool 같은 광범위 allowlist는 팀 규모 커질수록 위험
  • “읽기 전용 도구(검색/조회)”와 “쓰기/삭제 도구”를 분리하고, 후자는 항상 승인
  • 자동화는 테스트 실행/포맷/린트 같은 결정적 작업부터

흔한 함정/안티패턴

  • 규칙 파일을 장문 문서로 만들기: 모델 컨텍스트를 갉아먹고, 오히려 중요한 제약을 흐립니다.
  • 에이전트가 만든 변경을 테스트 없이 머지: 생산성 향상이 아니라 품질 부채를 미래로 넘기는 것.
  • 모든 작업을 에이전트 모드로 시작: 작은 수정은 inline edit/autocomplete가 더 빠릅니다(툴 선택도 생산성).

비용/성능/안정성 트레이드오프

  • Copilot은 최근 릴리스에서 비용 가시성/모델 선택/세션 관리 쪽 개선이 언급됩니다. 에이전트가 길게 돌수록 비용은 “예측 가능성”이 핵심입니다. (github.blog)
  • Cursor/Windsurf는 강한 멀티파일 편집 경험을 주지만, 그만큼 대화/컨텍스트가 길어지고 토큰 비용이 증가할 수 있습니다.
  • 결론: “큰 작업만 에이전트”, “작은 작업은 코파일럿/인라인”으로 포트폴리오 운영이 비용 최적화에 유리합니다.

🚀 마무리

2026년 7월 기준으로 Cursor·Copilot·Windsurf를 “내 프로젝트에 적용”하는 핵심은 도구의 브랜드가 아니라:

1) 지속 컨텍스트를 어떻게 관리하느냐(Rules/Skills)
2) 검증 루프(테스트/린트/실행)를 에이전트 작업 정의에 포함시키느냐
3) 브라우저/MCP 같은 강력한 도구에 권한 경계를 세우느냐
입니다.

도입 판단 기준(현실적인 체크리스트):

  • 내 팀은 “반복되는 구현 패턴”이 있는가? → 있으면 Rules/Skills로 이득이 큼
  • 테스트/CI가 최소한 돌아가나? → 안 돌아가면 에이전트는 오히려 혼란 증폭
  • 보안/권한/비용을 누가 운영할 건가? → 없으면 브라우저/MCP는 일단 보류

다음 학습 추천:

  • Cursor를 쓴다면: .cursor/rules를 “스코프 설계” 관점에서 재구성(Always 최소화 + glob 분리). (docs.cursor.com)
  • Copilot을 쓴다면: VS Code Agents 업데이트(세션/비용/브라우저) 중심으로 팀 가이드라인을 문서화. (github.blog)
  • Windsurf를 쓴다면: Cascade 작업을 “Flow 단계 + Skills 로딩”으로 표준화하고, 프로젝트별 skills.json로 재현성을 확보. (agentskills.help)

원하면, 위 웹훅 예시를 당신의 스택(Next.js/NestJS/Spring/FastAPI, Postgres, Prisma 등)으로 바꿔서 Cursor Rules + Windsurf Skills + Copilot Agent 프롬프트까지 “팀 표준 템플릿” 형태로 재작성해줄게요.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.