MCP 서버 메모리는 왜 창을 열 때마다 다시 쌓이는가?
MCP 서버 메모리는 원인이 사라지지 않는 프로세스가 아니라, 거의 쓰지 않는 서버까지 창을 열 때마다 전부 미리 띄우는 방식에 있었다. SGK 스튜디오는 클로드 코드로 여러 창을 동시에 켜 두고 일하는데, PC를 재부팅한 직후에는 메모리가 넉넉하다가 창을 몇 개 더 열수록 부하가 눈에 띄게 쌓이는 현상을 겪었다.
처음에는 '이미 연결해 쓰는 도구들이니 그만큼 무거운 게 당연하다'고 넘겼다. 하지만 실제로 재 보니 그 전제부터 틀렸다 — 무거운 건 '쓰는 도구'가 아니라 '연결만 해 두고 거의 안 쓰는 도구'였다.
이 문제는 MCP(Model Context Protocol, 서로 다른 외부 도구를 같은 규격으로 붙이는 표준)를 붙여 쓰는 곳이면 어디서나 남의 일이 아니다. 클로드 코드는 세션 하나를 열 때마다 그 세션에 연결된 외부 도구 서버를 전부 그 자리에서 통째로 띄운다. 서버가 필요할 때만 켜지는 '지연 시작'을 지원하지 않기 때문에, 창을 열기만 해도 연결된 서버가 매번 같이 켜진다.
이 낭비가 처음엔 심각해 보이지 않을 수 있다. 하지만 여러 창을 동시에 켜 두고 일하는 방식 자체가 점점 흔해지고 있고, 창 하나마다 붙는 고정비는 켜 놓은 창 수만큼 그대로 곱해진다. PC 한 대가 쓸 수 있는 메모리는 정해져 있는데, 그 메모리를 실제로는 거의 안 쓰는 도구가 붙잡고 있으면 정작 필요한 작업이 느려지거나 PC 전체가 버벅이는 결과로 이어진다. 이건 서버 성능 문제가 아니라 '연결해 둔 것'과 '실제로 쓰는 것' 사이의 간격을 아무도 재 본 적이 없었다는 문제였다.
처음 잰 숫자 — 창 하나가 최대 428MB를 쓰고 있었다
메모리 부하가 어디서 오는지 확인하려고 두 가지를 같이 봤다. 하나는 `ps` 명령으로 PC에 떠 있는 프로세스를 전부 훑는 것이었고, 다른 하나는 메모리 사용량을 꾸준히 남긴 관측 기록 1,001건(2026년 8월 21일부터 9월 11일까지 쌓인 것)을 들여다보는 것이었다.
결과는 명확했다. 세션 하나에 연결되는 MCP 서버는 playwright(브라우저 자동화)·google-workspace(구글 메일·캘린더 연동)·veilbrowser(별도 프로젝트 전용 브라우저 도구) 3종이었고, 이 3종이 창 하나당 최소 276MB에서 최대 428MB를 차지하고 있었다.
최소치와 최대치 사이의 차이는 그 창이 어떤 프로젝트에서 열렸는지에 따라 갈렸다. veilbrowser는 특정 프로젝트에서만 연결돼 있었기 때문에, 그 프로젝트 밖에서 연 창은 276MB 쪽에 가까웠고 그 프로젝트 안에서 연 창은 428MB에 가까웠다. 어느 쪽이든 세 서버가 실제로 쓰이는지와 무관하게 창을 여는 순간 곧바로 붙는 값이라는 점은 같았다.
- playwright — 브라우저를 자동으로 조작하는 도구
- google-workspace — 구글 메일·캘린더·드라이브를 연동하는 도구
- veilbrowser — 별도 프로젝트에서만 붙여 쓰던 브라우저 도구
숫자만 보면 창 하나가 최대 428MB를 쓰는 정도는 대단해 보이지 않을 수 있다. 하지만 창을 여러 개 동시에 켜 두고 일하는 방식에서는 이 고정비가 창 수만큼 그대로 반복해서 붙는다는 게 진짜 문제였다.
이 두 가지 방법을 같이 본 이유가 있다. 프로세스 스캔만 보면 '지금 이 순간' 무엇이 얼마나 떠 있는지는 알 수 있어도, 그게 시간이 지나면서 계속 쌓이는 패턴인지 원래부터 그런 것인지는 구분이 안 된다. 반대로 관측 기록만 보면 추세는 보이지만 그 메모리를 정확히 무엇이 차지하고 있는지는 알 수 없다. 둘을 같이 봐야 '창을 열 때마다 특정 서버 3종이 매번 같은 크기로 새로 붙는다'는 패턴이 확인된다.
의심 1 — 프로세스가 안 꺼지고 쌓이는 것 아닐까?
메모리가 계속 남아 있는 걸 보면 가장 먼저 드는 의심은 하나였다. 세션을 껐는데도 그 세션에 딸린 프로세스가 제대로 안 꺼지고 고아처럼 쌓이는 것 아닐까? 창을 여러 개 열고 닫기를 반복하다 보면 이런 '좀비 프로세스'가 흔한 원인이 되곤 한다.
이 의심이 맞다면 처방은 간단하다. 오래된 프로세스를 주기적으로 찾아 강제로 정리하는 감시 장치를 하나 만들면 된다.
이런 좀비 프로세스는 보통 프로세스를 관리하던 부모 프로세스가 먼저 죽고 자식만 남을 때 생긴다. 여러 창을 열고 닫는 일이 잦을수록 이런 흔적이 하나씩 남아, 시간이 지날수록 메모리가 계속 쌓이는 것처럼 보일 수 있다.
프로세스에는 알리바이가 있었다
그런데 `ps`로 전체 프로세스를 훑어보니 이 의심은 틀렸다. 세션을 종료하거나 화면을 정리(clear)할 때 MCP 프로세스는 매번 정상적으로 같이 정리됐고, 고아로 남은 프로세스는 0건이었다.
고아 프로세스가 0건이라는 건 곧, 지금 쌓이고 있는 메모리가 '정리를 안 해서 남은 찌꺼기'가 아니라 '지금 이 순간에도 정상적으로 떠 있는 프로세스가 쓰는 몫'이라는 뜻이다. 그래서 의심의 방향을 완전히 바꿔야 했다 — 프로세스가 남는 게 문제가 아니라, 애초에 프로세스가 너무 많이·너무 일찍 켜지는 게 문제였다.
확인 방법은 단순했다. 세션을 종료한 직후와 화면을 정리한 직후 각각 프로세스 목록을 다시 찍어, MCP와 관련된 프로세스가 남아 있는지 그때그때 대조했다. 어느 시점에도 남아 있는 프로세스는 없었다 — 정리는 매번 제대로 되고 있었다.
진짜 비용은 창 하나당 붙는 고정비였다
실비용은 창 하나를 열 때마다 붙는 고정비였다. 클로드 코드의 MCP 연결 방식(표준 입출력, stdio)은 세션이 시작되는 순간 연결된 서버 전부를 한꺼번에 기동하고, 그중 어느 것도 지연 시작을 지원하지 않는다. 서버를 실제로 쓸지 안 쓸지와 상관없이 세션을 여는 순간 3종이 다 같이 켜지는 구조였다.
표준 입출력(stdio) 방식으로 붙는 MCP 서버는 웹 서버처럼 요청이 들어올 때마다 필요한 만큼만 움직이는 구조가 아니다. 세션이 시작되면 클로드 코드가 설정에 적힌 서버를 하나씩 실행 파일로 직접 띄우고, 그 프로세스는 세션이 끝날 때까지 계속 떠 있는다. 서버 쪽에 '지금 당장은 아무도 안 부르니 잠들어 있겠다'는 선택지 자체가 없는 구조라, 연결만 해 두고 실제로는 거의 안 쓰는 서버라도 세션을 여는 값을 매번 전액 다 낸다.
그럼 그 서버들을 실제로 얼마나 쓰고 있었을까. 최근 14일 동안의 대화형 세션 208개를 대상으로 서버별 실사용률을 다시 재 봤다.

playwright는 13개(6.2%), google-workspace는 18개(8.7%)에서만 실제로 쓰였고, veilbrowser는 208개 세션 가운데 단 한 번도(0%) 쓰이지 않았다. 창을 열 때마다 매번 켜지는 서버 3종 가운데 하나(veilbrowser)는 있으나 마나였고, 나머지 둘도 열에 아홉은 그냥 떠 있기만 했다.
실사용률이 이렇게 낮은 이유는 도구 자체가 쓸모없어서가 아니다. 브라우저를 자동으로 조작하거나 구글 메일·캘린더를 연동하는 작업은 세션 전체로 보면 가끔 하는 일이고, 그 가끔을 위해 창을 여는 매 순간 서버를 미리 켜 두는 쪽이 오히려 낭비였다. 자주 쓰는 도구라면 항상 켜 두는 비용이 아깝지 않지만, 이 세 서버는 그런 도구가 아니었다.
그래서 시도한 것 — 필요할 때만 서버를 켜는 중개 프로그램
세 서버 앞에 프로그램 하나를 세웠다. 이 중개 프로그램(프록시)은 세션이 맨 처음 도구 목록을 물어볼 때(initialize·tools/list) 미리 저장해 둔 답을 즉시 돌려주고, 진짜 서버는 실제로 도구를 처음 사용하는 순간(tools/call)에만 켠다. 서버를 다시 켤 때마다 도구 목록을 새로 받아 캐시를 갱신하고, 목록이 바뀌면 알림을 함께 보낸다.

사용자 범위로 연결돼 있던 google-workspace와 playwright는 이 중개 프로그램을 거치도록 바꿨고, 프로젝트 범위로 연결돼 있던 veilbrowser는 14일 동안 실사용률이 0%로 확인돼 아예 제거했다.
이 설계를 고른 이유는 단순하다. 서버를 아예 안 켜면 도구 목록조차 알 수 없어 세션이 어떤 도구를 쓸 수 있는지 판단하지 못하고, 반대로 지금처럼 전부 켜 두면 안 쓰는 서버까지 비용을 낸다. 목록 응답과 실제 실행을 분리하면 이 둘 사이에서 하나를 고르지 않아도 된다 — 목록은 항상 즉시 준비돼 있고, 비용은 실제로 쓸 때만 든다.
까다로웠던 지점 하나는 playwright가 두 갈래로 연결돼 있었다는 점이다. 하나는 사용자 설정에 있었고, 다른 하나는 확장 기능(플러그인) 형태로 연결돼 있었는데, 플러그인 쪽 연결은 중개 프로그램으로 감쌀 수 없는 구조였다. 그래서 플러그인 쪽 연결은 꺼 버리고 같은 도구를 사용자 설정 쪽(중개 프로그램을 거치는 연결)으로 옮겼다. 이 과정에서 도구를 부르는 이름이 바뀌었는데, 옮기기 전에 옛 이름을 코드 전체에서 찾아본 결과가 0건이어서 걸릴 곳이 없다는 걸 확인한 뒤 적용했다. 원래 설정은 따로 백업해 뒀다 — 예상 밖의 문제가 생기면 언제든 이전 방식으로 되돌릴 수 있게 해 두려는 목적이다.
이 방식에는 대가가 있다. 서버를 실제로 처음 쓸 때 2초에서 5초 정도 지연(콜드 스타트)이 생긴다. 다만 이 지연은 도구를 실제로 쓸 때 딱 한 번만 생기고, 실사용률이 10%가 채 안 되는 서버가 대부분이라 대부분의 창에서는 아예 체감되지 않는다.
캐시를 무한정 믿지는 않는다. 서버가 다시 켜질 때마다 도구 목록을 새로 받아 캐시를 덮어쓰고, 그사이 목록이 바뀌었으면 알림을 보내 세션이 새 목록을 다시 읽게 한다. 빠른 응답은 '오래된 답을 그냥 돌려준다'는 뜻이 아니라, '최근에 확인한 답을 먼저 돌려주고 실제로 쓸 때 다시 확인한다'는 뜻이다.
기각한 방법 셋
- 공식 설정(disabledMcpServers) — 실제로 있는 기능이지만, 꺼 둔 서버를 세션 안에서 프로그램이 스스로 다시 켤 수 없어 자동화에는 못 쓴다.
- 환경변수(MCP_DISABLE_STDIO) — 처음 조사에서 참고한 자료에 나와 있었지만, 실제 프로그램 파일(버전 2.1.268)에서 문자열을 찾아보니 이 이름은 어디에도 없었다(검색 결과 0건). 잘못된 자료를 버리고 직접 중개 프로그램을 만드는 쪽으로 방향을 틀었다.
- veilbrowser를 그대로 두는 방법 — 14일 동안 실사용률이 0%라 지연 시작 대상에 넣을 가치도 없어서, 아예 없애는 쪽이 더 단순했다.
세 가지 모두 기존에 이미 있는 방법을 그대로 쓰는 선택이었는데, 셋 다 이번 상황에 맞지 않았다. 유지보수 부담이 새로 생기는 걸 알면서도 중개 프로그램을 직접 만드는 쪽을 택한 이유가 여기에 있다.
새로 연 창에서 다시 재 봤다
중개 프로그램만 따로 켜서 시험해 보니, 캐시가 있을 때 initialize 응답은 0.02초 만에 왔고 첫 도구 호출 전까지 뜬 프로세스는 0개(메모리 12MB)였다. 도구를 실제로 부르자 그제야 서버가 켜졌고(2초에서 2.5초 소요) 응답은 성공했으며, 종료할 때 남은 자식 프로세스는 0개였다.
시험은 세 단계로 나눠 했다. 먼저 중개 프로그램만 따로 켜서 프로토콜 자체가 의도대로 동작하는지 봤고, 그다음 실제로 쓰는 환경과 똑같은 새 대화형 세션을 열어 다시 쟀고, 마지막으로 사람이 아니라 프로그램이 자동으로 부르는 비대화형 호출로도 문제없이 동작하는지 확인했다. 세 단계 모두에서 같은 그림이 나와야 이 방식을 실제로 적용해도 된다고 판단할 수 있었다.
각 단계에서 확인한 항목은 같았다 — 첫 응답이 얼마나 빨리 오는지, 실제 호출 전까지 떠 있는 프로세스가 몇 개인지, 실제 호출이 성공하는지, 종료 뒤에 남는 프로세스가 있는지. 네 가지를 전부 통과해야 다음 단계로 넘어갔고, 어느 하나라도 걸렸으면 적용을 미뤘을 것이다.

실제 사용 환경인 새 tmux 대화형 세션에서도 같은 그림이 나왔다. 적용 전에는 MCP 관련 프로세스가 6개에서 8개, 메모리는 276MB에서 428MB를 차지했는데, 적용 후에는 프로세스 2개·메모리 24MB로 줄었다. 창 하나당 메모리 절감폭은 약 95%다.
프로세스 개수도 함께 줄었다는 점이 눈에 띈다. 적용 전 6개에서 8개였던 프로세스가 적용 후 2개로 줄었는데, 이 2개는 세 서버를 대신하는 중개 프로그램 자체이지 실제 서버가 아니다. 진짜 서버는 도구를 쓸 때만 잠깐 떴다가 다시 정리된다.

비대화형 호출로도 실제 시험을 해 봤다. google-workspace와 playwright 둘 다 성공했고, 전체 43초가 걸렸는데 그중 서버는 세션이 시작되고 35초·38초 지난 시점에야 지연 기동했다. 호출이 끝난 뒤 남은 프로세스는 0개였다. 사람이 직접 붙어 있지 않은 자동화 호출에서도 서버가 필요한 순간에 정확히 켜지고 끝난 뒤 깨끗이 정리된다는 점을 확인한 셈이다.
이미 열려 있던 창은 어떻게 됐는가?
이미 켜져 있던 창 7개는 재시작하지 않았는데도 MCP 메모리 합계가 2,297MB에서 1,827MB로 줄었다. 다만 이 중 새 설정이 실제로 적용된 창은 2개뿐이었고, 나머지 5개는 창을 다시 열어야 적용된다. 지금 당장 전체 효과를 보려면 열려 있는 창을 한 번씩 재시작해야 한다.
숫자만 보면 크지 않아 보일 수 있지만, 이 회사처럼 창을 여러 개 동시에 켜 두고 하루 종일 작업하는 환경에서는 창 하나당 줄어든 메모리가 열려 있는 창 수만큼 그대로 반복해서 쌓인다. 새로 여는 창은 곧바로 이득을 보고, 기존에 열려 있던 창은 다음에 다시 열 때 자연스럽게 새 설정을 받는다.
당장 7개를 전부 재시작하지 않은 이유는 각 창마다 진행 중인 작업이 있었기 때문이다. 재시작은 그 작업을 끊는 대가가 따르므로, 효과가 확인된 지금은 창을 다시 열 때마다 자연스럽게 넘어가는 쪽을 택했다. 급한 경우가 아니면 굳이 지금 당장 전부 끄고 다시 켤 필요는 없다.
같은 PC에서 메모리를 다시 재고 고친 사례는 WSL 메모리를 5분마다 회수하도록 바꾼 기록에도 있다. 둘 다 '얼마나 쓰나'를 먼저 재고 나서야 진짜 원인이 보였다는 점이 같다.
두 사례를 나란히 놓고 보면 공통점이 하나 더 있다. 둘 다 처음에는 '어쩔 수 없이 그만큼 무겁다'고 넘길 뻔했던 문제였고, 실제로 얼마나 쓰는지 재 보고 나서야 그 전제가 틀렸다는 게 드러났다. 무겁다고 느껴지는 것과 실제로 무거운 것은 다른 얘기다.
아직 남는 문제
설정을 들여다보는 과정에서 부수적인 문제가 하나 나왔다. 구글 인증에 쓰는 자격 증명 두 개가 이 세션의 대화 기록에 평문으로 남았다. 회사 바깥으로 나간 적은 없지만, 이 기록은 나중에 검색 대상에 포함될 예정이라 안전하게 두려면 해당 자격 증명을 새로 발급해야 한다.
이 노출은 설정값을 확인하려고 화면에 그대로 띄운 동작에서 나왔다. 값 자체는 도구를 연동하는 데 쓰는 자격 증명이지 결제나 개인정보와 직접 연결된 값은 아니지만, 한 번 평문으로 남은 값은 나중에 다른 곳에 다시 나타날 수 있다는 점에서 가볍게 넘기지 않기로 했다.
검증이 아직 안 끝난 부분도 있다. 지금 열려 있는 창 전체를 재시작한 뒤 다시 재는 작업이 남아 있는데, 이건 시간이 걸리는 일이라 아직 날짜를 정하지 못했다.
이번에 바꾼 건 서버를 켜는 시점뿐이고, 서버 자체의 동작이나 결과는 그대로다. 그래서 이 변경으로 새로운 기능이 생기거나 기존 기능이 달라지는 일은 없다 — 달라지는 건 오직 필요 없을 때 얼마나 자원을 덜 쓰느냐뿐이다. 도구를 부르는 쪽에서 보면 이전과 똑같이 동작하고, 다만 그 뒤에서 서버가 켜지는 시점만 미뤄졌을 뿐이다.
처음 세운 가설, 즉 프로세스가 안 꺼지고 고아처럼 쌓인다는 의심은 이번에 틀렸다는 게 확인됐다. 그 가설이 맞았다면 프로세스를 강제로 정리하는 감시 장치를 만들었을 텐데, 실제 원인이 달라 그 방향은 접었다.
- 창 하나당 MCP 메모리 — 276~428MB에서 24MB로, 약 95% 절감
- 처음 의심했던 원인(고아 프로세스)은 0건으로 반증됐다
- 진짜 원인 — 지연 시작을 지원하지 않는 구조 + 서버 3종 가운데 최대 8.7%만 실사용
- 남은 일 — 재시작하지 않은 창 5개 재시작, 노출된 구글 인증 정보 재발급
이번 사례에서 남는 교훈은 하나다. '연결해 뒀다'와 '실제로 쓰고 있다'는 다른 말이고, 그 차이를 재 보기 전에는 어느 쪽이 진짜 비용인지 알 수 없다. 처음 의심대로 프로세스를 정리하는 감시 장치를 만들었다면 문제의 겉모습만 다스리고 원인은 그대로 남았을 것이다.
비슷하게 '연결은 해 뒀지만 거의 안 쓰는 도구'가 다른 업무 자동화에도 숨어 있을 수 있다. 어디서 메모리나 처리 시간이 새고 있는지부터 확인하고 싶다면 상담에서 시작할 수 있다.