sgkstudio.
엔지니어링

MCP 서버 메모리, 창 하나에 428MB가 붙던 이유

MCP 서버 메모리는 원인이 안 꺼지는 프로세스가 아니라, 거의 쓰지 않는 서버까지 창을 열 때마다 전부 미리 띄우는 방식에 있었다. 실측해 보니 창 하나가 연결된 서버 3종 때문에 최소 276MB에서 최대 428MB를 쓰고 있었는데, 그중 하나는 최근 14일 동안 단 한 번도(0%) 쓰이지 않았다. 실제로 쓸 때만 서버를 켜는 중개 프로그램을 앞에 세운 뒤 창 하나당 메모리는 24MB로 줄었다.

2026-09-15메모리 관측 기록 1,001건(2026년 8월 21일부터 9월 11일까지) + 프로세스 전체 스캔(`ps`): MCP 고아 프로세스 0건 확인최근 14일 대화형 세션 208개 대상 서버별 실사용률 실측: playwright 6.2%(13개)·google-workspace 8.7%(18개)·veilbrowser 0%새 세션 3가지 실측 비교: 단독 프로토콜 시험·새 tmux 세션·비대화형 호출(43초, 서버 지연 기동 35초·38초)재시작하지 않은 기존 세션 7개의 적용 전후 메모리 합계(2,297MB → 1,827MB, 새 설정 적용 2개·미적용 5개)

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개를 대상으로 서버별 실사용률을 다시 재 봤다.

최근 14일 대화형 세션 208개 기준 서버별 실사용률: playwright 13개(6.2%), google-workspace 18개(8.7%), veilbrowser 0개(0%)
연결은 됐지만 대부분 안 쓰이는 서버가 메모리를 쥐고 있었다

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개였다.

시험은 세 단계로 나눠 했다. 먼저 중개 프로그램만 따로 켜서 프로토콜 자체가 의도대로 동작하는지 봤고, 그다음 실제로 쓰는 환경과 똑같은 새 대화형 세션을 열어 다시 쟀고, 마지막으로 사람이 아니라 프로그램이 자동으로 부르는 비대화형 호출로도 문제없이 동작하는지 확인했다. 세 단계 모두에서 같은 그림이 나와야 이 방식을 실제로 적용해도 된다고 판단할 수 있었다.

각 단계에서 확인한 항목은 같았다. 첫 응답이 얼마나 빨리 오는지, 실제 호출 전까지 떠 있는 프로세스가 몇 개인지, 실제 호출이 성공하는지, 종료 뒤에 남는 프로세스가 있는지. 네 가지를 전부 통과해야 다음 단계로 넘어갔고, 어느 하나라도 걸렸으면 적용을 미뤘을 것이다.

새 세션 기준 창 하나당 MCP 메모리: 적용 전 276~428MB, 적용 후 24MB
창 하나가 쓰는 메모리가 428MB에서 24MB로 줄었다

실제 사용 환경인 새 tmux 대화형 세션에서도 같은 그림이 나왔다. 적용 전에는 MCP 관련 프로세스가 6개에서 8개, 메모리는 276MB에서 428MB를 차지했는데, 적용 후에는 프로세스 2개·메모리 24MB로 줄었다. 창 하나당 메모리 절감폭은 약 95%다.

프로세스 개수도 함께 줄었다는 점이 눈에 띈다. 적용 전 6개에서 8개였던 프로세스가 적용 후 2개로 줄었는데, 이 2개는 세 서버를 대신하는 중개 프로그램 자체이지 실제 서버가 아니다. 진짜 서버는 도구를 쓸 때만 잠깐 떴다가 다시 정리된다.

적용 후 실측: 캐시 응답 0.02초, 도구 첫 호출 지연 2~2.5초, 창 하나당 메모리 절감 95%
지연은 도구를 처음 쓸 때 한 번, 절감은 창마다 매번

비대화형 호출로도 실제 시험을 해 봤다. 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개 재시작, 노출된 구글 인증 정보 재발급

이번 사례에서 남는 교훈은 하나다. '연결해 뒀다'와 '실제로 쓰고 있다'는 다른 말이고, 그 차이를 재 보기 전에는 어느 쪽이 진짜 비용인지 알 수 없다. 처음 의심대로 프로세스를 정리하는 감시 장치를 만들었다면 문제의 겉모습만 다스리고 원인은 그대로 남았을 것이다.

비슷하게 '연결은 해 뒀지만 거의 안 쓰는 도구'가 다른 업무 자동화에도 숨어 있을 수 있다. 어디서 메모리나 처리 시간이 새고 있는지부터 확인하고 싶다면 상담에서 시작할 수 있다.

같은 방식으로 우리 업무를 재보고 싶다면

이 글에 쓴 계측·판정·자동화는 저희가 실제로 매일 돌리는 방식 그대로입니다. 어떤 작업을 자동화할 수 있고 어디를 사람이 잡아야 하는지, 현재 업무 흐름을 놓고 같이 짚어 드립니다.

새 글이 올라오면 알려 드립니다

이런 계측 기록을 새로 쓰면 메일로 한 통 보냅니다. 광고나 판촉은 보내지 않습니다.