923 posts
WorkOS AuthKit으로 최신 MCP 인증 붙이기

WorkOS AuthKit으로 최신 MCP 인증 붙이기

원격 MCP 서버에 OAuth를 붙일 때 의외로 까다로운 부분은 로그인 화면이 아닙니다. 처음 만난 MCP 클라이언트를 인가 서버(Authorization Server)에 어떻게 등록할지가 더 큰 문제인데요. 데스크톱 앱, 코드 편집기, 에이전트 플랫폼을 인가 서버에 일일이 미리 등록할 수는 없기 때문입니다. 2025년 11월 25일 MCP 스펙은 이 문제를 풀기 위해 클라이언트 ID 메타데이터 문서(Client ID Metadata Documents, CIMD)를 권장 등록 방식으로 추가했습니다. 이전 스펙에서 사용하던 동적 클라

CIMD(Client ID Metadata Documents)

CIMD(Client ID Metadata Documents)

OAuth로 로그인을 붙일 때, 클라이언트 등록은 보통 이렇게 시작합니다. 인가 서버 콘솔에 앱을 미리 등록하고 client_id를 발급받아 코드에 박아 두는 거죠. 클라이언트와 서버가 서로를 아는 고정된 관계라 이 방식으로 충분했습니다. 그런데 이 전제가 통하지 않는 세계가 있습니다. 클라이언트와 서버가 처음 만나는 게 일상인 개방형 생태계예요. "Bluesky로 로그인"하는 서드파티 앱이 사용자마다 다른 서버에 붙고, AI 에이전트가 그날 처음 발견한 도구 서버에 연결하는 식이죠. 미리 등록할 시간도, 모두를 아우르는 중앙 등록

NAT(Network Address Translation): 공인 IP 하나를 여럿이 나눠 쓰는 법

NAT(Network Address Translation): 공인 IP 하나를 여럿이 나눠 쓰는 법

집에서 와이파이에 연결된 기기를 한번 세어 볼까요? 노트북, 스마트폰, 태블릿, TV, 어쩌면 로봇청소기까지 십수 대는 우습게 넘어갑니다. 그런데 통신사가 우리 집에 내준 공인 IP 주소는 보통 딱 하나뿐인데요. 어떻게 그 많은 기기가 주소 하나로 동시에 인터넷을 쓰는 걸까요? 🤔 비밀은 집 공유기 안에서 조용히 돌아가는 NAT(Network Address Translation)에 있습니다. 우리말로 옮기면 "네트워크 주소 변환"인데, 이름 그대로 오가는 패킷의 주소를 바꿔치기하는 기술이에요. 이 글에서는 NAT가 무엇을 바꾸는지

SSRF(Server-Side Request Forgery) 공격과 방어

SSRF(Server-Side Request Forgery) 공격과 방어

요즘 웹 서비스에는 서버가 사용자 대신 외부 URL로 요청을 보내는 기능이 흔합니다. 링크를 붙여넣으면 미리보기 카드를 만들어 주고, 이미지 주소를 넣으면 썸네일을 대신 받아오고, 웹훅 주소를 등록해 두면 이벤트가 생길 때마다 서버가 그 주소로 알림을 쏴 줍니다. 편리하죠. 그런데 "사용자가 건넨 주소로 서버가 대신 요청을 보낸다"는 이 평범한 동작이, 잘못 다루면 서버를 통째로 공격자의 손발로 내주는 통로가 됩니다. 이번 글에서 다룰 SSRF(Server-Side Request Forgery) 이야기입니다. 이름부터 CSRF와 헷

Cloudflare Cron Triggers로 정기 작업 스케줄링하기

Cloudflare Cron Triggers로 정기 작업 스케줄링하기

매일 새벽에 낡은 데이터를 지우거나, 30분마다 외부 API에서 시세를 긁어오거나, 아침마다 요약 리포트를 보내는 일. 이런 정기 작업은 흔하지만, 이거 하나 돌리자고 서버나 VM을 24시간 켜두는 건 아깝죠. crontab을 걸어둘 상시 머신이 필요하고, 그 머신은 대부분의 시간을 놀면서 요금만 축내니까요. Cloudflare Workers의 Cron Triggers는 이 문제를 관리형으로 풉니다. 정해진 시각이 되면 Cloudflare가 알아서 우리 코드를 깨워주고, 실행이 끝나면 다시 잠들어요. 상시 켜둘 서버도 cron 데몬

Cloudflare Workers로 서버리스 Discord 봇 만들기

Cloudflare Workers로 서버리스 Discord 봇 만들기

게이트웨이로 봇을 띄우면 한 가지 숙명이 따라옵니다. 봇 프로세스가 24시간 살아 있어야 한다는 거예요. 노트북을 끄면 봇도 오프라인이 되니, 늘 켜져 있는 서버나 컨테이너에 올려둬야 하죠. "간단한 슬래시 커맨드 하나 만들려는 건데 서버까지 빌려야 하나?" 싶은 순간이 옵니다. 그런데 슬래시 커맨드나 버튼에만 반응하는 봇이라면, 굳이 항상 켜둘 필요가 없습니다. 요청이 올 때만 깨어나는 서버리스로 만들 수 있거든요. 이번 글에서는 Cloudflare Workers에 슬래시 커맨드에 응답하는 봇을 올려서, 호스팅 걱정 없이 배포하는

GitHub App 제대로 이해하기: OAuth App, PAT와 무엇이 다를까

GitHub App 제대로 이해하기: OAuth App, PAT와 무엇이 다를까

자동화를 만들다 보면 GitHub에 인증할 일이 계속 생깁니다. CI에서 릴리스 커밋을 올리고, 봇이 이슈에 댓글을 달고, 외부 서비스가 PR을 검사하는 식이죠. 가장 먼저 손이 가는 건 개인 액세스 토큰(Personal Access Token, 이하 PAT)인데요. 잘 돌아가다가도 꼭 한 번씩 벽에 부딪힙니다. "이 봇이 왜 내 계정으로 커밋하지?", "내가 퇴사하면 이 자동화도 멈추나?", "왜 이 토큰에 우리 조직 전체 권한이 다 붙어 있지?" 같은 의문이죠. 🤔 이 문제들의 답이 바로 GitHub App입니다. 이름만 보면

OpenRouter: 키 하나로 모든 LLM을 부르는 통합 API

OpenRouter: 키 하나로 모든 LLM을 부르는 통합 API

LLM 호출 한 줄을 코드에 붙이는 건 쉽습니다. 그런데 제공사가 둘, 셋으로 늘어나는 순간 이야기가 달라지죠. OpenAI SDK 깔고, Anthropic SDK 또 깔고, 키를 각각 발급받아 환경변수로 빼고, 비용은 대시보드 세 군데를 들여다봐야 하고요. 게다가 새 모델이 나올 때마다 "이거 한번 써볼까?" 하면 또 가입하고 결제 정보를 등록하는 일이 반복됩니다. 정작 하고 싶은 건 "프롬프트 보내고 답 받기" 한 줄인데 말이죠. 🤔 OpenRouter는 이 번거로움을 키 하나로 정리합니다. OpenAI, Anthropic,

Discord 게이트웨이로 실시간 이벤트 받기

Discord 게이트웨이로 실시간 이벤트 받기

봇 개발을 하다 보면 결국 마주치는 통로가 **게이트웨이(Gateway)**입니다. 슬래시 커맨드를 만들 때 client.login() 한 줄로 봇을 온라인으로 띄웠는데, 그 순간 봇은 바로 이 게이트웨이로 디스코드와 연결을 맺은 거였어요. 이번 글에서는 그 통로의 안쪽을 들여다보며, 봇이 어떻게 서버의 모든 일을 실시간으로 지켜보는지, 그리고 그렇게 만든 봇을 어떻게 24시간 운영하는지까지 살펴봅니다. 게이트웨이는 항상 열려 있는 WebSocket REST API가 "내가 물어보면 답해주는" 요청-응답 방식이었다면, 게이트웨이는

Void AI: 라우트 핸들러에서 LLM을 타입 안전하게 호출하기

Void AI: 라우트 핸들러에서 LLM을 타입 안전하게 호출하기

앱에 AI 기능을 하나 붙이려고 마음먹으면, 정작 모델을 부르는 코드보다 그 주변을 차리는 일이 더 번거롭습니다. 어느 제공사를 쓸지 정하고, SDK를 설치하고, API 키를 환경변수로 빼고, 스트리밍 응답을 직접 파싱하고, 사용량이 얼마나 나왔는지 따로 추적하고… 정작 "프롬프트를 보내고 답을 받는" 본질은 한 줄인데 말이죠. Void는 이 주변 작업을 void/ai라는 클라이언트로 미리 차려둡니다. Cloudflare의 AI 게이트웨이 위에서 동작하는 타입 안전한 클라이언트인데, Void 라우팅에서 본 라우트 핸들러 안에서 모델

Rust tokio::sync: 채널과 동기화 도구 고르기

Rust tokio::sync: 채널과 동기화 도구 고르기

Tokio로 비동기 코드를 짜다 보면 생각보다 빨리 tokio::sync를 만나게 됩니다. 태스크 하나가 다른 태스크에게 일을 넘겨야 하고, 결과를 돌려받아야 하고, 여러 태스크에 종료 신호를 보내야 하고, 공유 상태를 잠가야 하니까요. 문제는 선택지가 꽤 많다는 점입니다. mpsc, oneshot, broadcast, watch, Mutex, RwLock, Semaphore, Notify가 한 모듈 안에 같이 있는데, 이름만 봐서는 언제 무엇을 써야 할지 헷갈립니다. mpsc로도 종료 신호를 보낼 수 있고, watch로도 보낼 수

SQLite FTS5로 전문 검색 구현하기

SQLite FTS5로 전문 검색 구현하기

블로그나 문서 앱을 만들다 보면 처음에는 검색 기능을 아주 단순하게 시작하게 됩니다. 제목이나 본문에 검색어가 들어 있는지 LIKE '%검색어%'로 확인하면 되니까요. 데이터가 몇십 개일 때는 이것만으로도 충분합니다. 그런데 글이 수백 개, 수천 개로 늘어나면 느낌이 달라집니다. 검색 속도가 느려지고, 관련도 순 정렬도 어렵고, 검색 결과에서 어느 부분이 매칭됐는지 보여주기도 번거로워요. "검색"이라는 단어가 한 번 들어간 글과 본문 전체가 검색에 관한 글을 같은 수준으로 다루게 되는 것도 아쉽습니다. 이럴 때 SQLite만 쓰고

State of AI 2026 설문 결과로 본 개발자들의 AI 활용 트렌드

State of AI 2026 설문 결과로 본 개발자들의 AI 활용 트렌드

매년 자바스크립트 생태계를 정리해 주던 State of JS 설문, 한 번쯤 보신 적 있으실 텐데요. 그 설문을 만들던 팀이 이번에는 흐름을 완전히 갈아탔습니다. 2026년에는 자바스크립트가 아니라 AI를 주제로 State of AI 2026 설문을 진행한 건데요. 그만큼 지난 한 해 개발자들의 관심사가 어디로 쏠렸는지를 단적으로 보여주는 변화가 아닐까 싶습니다. 이번 설문에는 2026년 4월 8일부터 5월 8일까지 전 세계 개발자 7,258명이 참여했습니다. 단순히 "어떤 AI 도구를 쓰나요?" 정도를 묻는 가벼운 조사가 아니라,

Portless로 localhost:3000 대신 이름 있는 URL 쓰기

Portless로 localhost:3000 대신 이름 있는 URL 쓰기

로컬에서 개발하다 보면 http://localhost:3000, http://localhost:3001, http://localhost:8080 같은 주소를 하루에도 수십 번씩 입력하는데요. 프로젝트가 두세 개만 돼도 어느 포트가 어느 앱이었는지 헷갈리기 시작합니다. 프런트엔드는 3000번, API는 4000번, 스토리북은 6006번... 이걸 외우고 있다가 동료에게 "그거 몇 번 포트였죠?"라고 물어본 경험, 다들 한 번쯤 있으시죠. 😅 게다가 포트가 겹치면 EADDRINUSE 에러가 나면서 서버가 안 뜨고, HTTPS가 필요한

Rust pub, crate, self, super: 모듈 경계와 공개 범위 이해하기

Rust pub, crate, self, super: 모듈 경계와 공개 범위 이해하기

Rust에서 모듈 시스템의 큰 그림을 잡고 파일을 나누는 mod, 경로를 가져오는 use까지 익히고 나면 다음으로 부딪히는 벽은 공개 범위입니다. 분명 함수에 pub을 붙였는데 외부에서 안 보이거나, 반대로 내부 구현까지 밖으로 드러나서 API가 지저분해지는 일이 생기죠. 여기에 crate::, self::, super::, pub(crate), pub use까지 섞이면 머릿속 모듈 지도가 금방 흐려집니다. 이번 글에서는 Rust 모듈 시스템에서 공개 범위가 어떻게 결정되는지 정리해보겠습니다. 핵심은 "항목이 공개인가?"만 보는 게

Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

앱에 LLM 호출 한 줄을 붙이는 건 쉽습니다. 그런데 그게 프로덕션에 올라가는 순간 이야기가 달라지죠. 같은 질문이 하루에도 수백 번 들어오는데 매번 돈을 내고 모델을 부르고 있진 않은지, OpenAI가 잠깐 흔들릴 때 서비스가 같이 멈추진 않을지, 이번 달 토큰 비용이 얼마나 나왔는지, 누가 우리 키로 폭주하고 있진 않은지… 정작 "프롬프트 보내고 답 받기"는 한 줄인데 그 주변을 지키는 일이 훨씬 많습니다. 게다가 Cloudflare Workers AI의 자체 모델로 시작했다가도, 복잡한 추론은 GPT나 Claude로 넘기고

Rust use 키워드: 긴 경로를 짧게 가져오는 법

Rust use 키워드: 긴 경로를 짧게 가져오는 법

Rust 예제를 보면 파일 맨 위에 use std::collections::HashMap; 같은 줄이 자주 나옵니다. 익숙해지면 아무렇지 않게 쓰지만, 처음에는 mod와 use가 비슷해 보여 헷갈리기 쉽습니다. "파일을 가져오는 건가?", "모듈을 만드는 건가?", "이걸 쓰면 공개되는 건가?" 같은 질문이 자연스럽게 따라오죠. 결론부터 말하면 use는 이미 존재하는 경로를 현재 스코프에서 짧은 이름으로 쓰게 해주는 키워드입니다. 모듈을 새로 만들지도 않고, 항목의 공개 범위를 바꾸지도 않습니다. 이번 글에서는 use가 하는 일과

Cloudflare Sandbox에서 안전하게 코드 실행하기

Cloudflare Sandbox에서 안전하게 코드 실행하기

AI 코딩 도구를 만들다 보면 결국 "생성한 코드를 어디서 실행할 것인가?"라는 문제를 만나게 됩니다. 단순히 코드를 문자열로 돌려주는 것만으로는 부족하거든요. 테스트도 돌려봐야 하고, 파일도 만들어야 하고, 때로는 개발 서버를 띄운 뒤 브라우저에서 확인할 수 있는 미리보기 URL도 필요합니다. 그렇다고 사용자의 코드를 내 서버에서 그대로 실행하기에는 부담이 큽니다. 코드는 파일 시스템을 건드릴 수도 있고, 오래 실행될 수도 있고, 의도치 않게 서버 자원을 많이 쓸 수도 있으니까요. 그래서 AI 에이전트나 코드 실행 서비스를 만들

1Password Service Account로 로컬 자동화 시크릿을 Touch ID 없이 관리하기

1Password Service Account로 로컬 자동화 시크릿을 Touch ID 없이 관리하기

얼마 전에 Discord MCP 서버를 클로드 코드에 붙여서 쓰고 있었는데요. 봇 토큰을 설정 파일에 평문으로 박아두기는 찜찜해서, 1Password CLI로 토큰을 주입하도록 바꿔뒀습니다. 그런데 그날부터 클로드 코드를 켤 때마다 화면 한가운데 "1Password Access Requested" 창이 뜨면서 Touch ID를 요구하기 시작했어요. 😅 하루에도 몇 번씩 터미널을 새로 여는데, 그때마다 손가락을 갖다 대는 건 생각보다 거슬립니다. 게다가 cron으로 돌리는 스크립트라면 아예 사람이 없으니 인증 자체가 불가능하죠. 이

mktemp로 안전하게 임시 파일 만들기

mktemp로 안전하게 임시 파일 만들기

쉘 스크립트를 쓰다 보면 잠깐 저장할 파일이 필요할 때가 많습니다. API 응답을 받아서 가공하거나, 여러 명령의 중간 결과를 합치거나, 압축하기 전에 작업 디렉토리를 하나 만들어두는 경우가 그렇죠. 처음에는 대충 /tmp/result.txt 같은 이름을 쓰기 쉽습니다. 조금 더 신경 쓰면 프로세스 ID를 붙여서 /tmp/myapp.$$처럼 만들기도 하고요. 혼자 쓰는 노트북에서는 별문제가 없어 보이지만, 이런 방식은 스크립트가 서버나 CI에서 돌기 시작하면 꽤 위험해집니다. 파일 이름이 예측 가능하고, 이미 같은 이름의 파일이 있

Discord