AI 비서에게 Microsoft Loop에 읽기 전용 접근 권한을 부여하는 방법 (권한 손상 없이)
요약
Microsoft Loop의 API 부재와 권한 보안 문제를 해결하기 위해 MCP 서버를 구축한 사례를 다룹니다. Graph Search의 보안 트리밍 특성을 활용하여 사용자의 권한을 유지하면서도 앱 전용 ID로 콘텐츠를 안전하게 읽어오는 엔지니어링 접근법을 설명합니다.
핵심 포인트
- Microsoft Loop는 현재 공식적인 콘텐츠 API를 제공하지 않음
- SharePoint Embedded 환경에서 앱 전용 ID 사용 시 권한 유출 위험 존재
- Graph Search의 보안 트리밍 기능을 활용해 권한 문제를 해결
- 검색(Discovery)과 데이터 가져오기(Retrieval)를 분리하여 보안 강화
- 오픈 소스 MCP 서버 loop-reader-mcp 공개
AI 비서에게 Microsoft Loop에 읽기 전용 접근 권한을 부여하는 방법 — 권한 손상 없이
내 AI 비서가 팀의 Microsoft Loop 페이지를 읽고, 작업 공간(workspace)을 요약하고, 최신 OKR을 초안으로 가져오며, 'X에 대해 우리가 결정한 것이 무엇이었나요?'라는 질문에 답하기를 원했습니다. 간단한 요청이었습니다. 하지만 이것은 정말 흥미로운 엔지니어링 문제로 변했고, 저는 이를 해결하기 위해 작은 오픈 소스 MCP 서버를 구축하게 되었습니다. 여기에는 도전 과제, 해결책, 그리고 그 과정에서 겪었던 함정들이 있습니다.
결과는 오픈 소스로 공개되었습니다 (MIT): loop-reader-mcp.
도전 과제
처음부터 두 개의 벽에 부딪혔습니다.
벽 1: Microsoft Loop에는 콘텐츠 API가 없습니다. 2026년 중반 기준으로, Loop 페이지의 내용을 읽거나 쓸 수 있는 Graph 엔드포인트가 없습니다. 즉, '페이지 가져오기(get page)'나 '작업 공간 목록 보기(list workspace)' 기능이 없다는 뜻입니다. Loop는 Notion이나 Confluence와 경쟁하고 있지만, 구조화된 방식으로 자체 콘텐츠를 프로그래밍 방식으로 추출할 수 없습니다. 2026년 Loop 로드맵은 콘텐츠 API가 아닌 거버넌스에 관한 것입니다.
벽 2: Loop가 실제로 데이터를 저장하는 방식. Loop 작업 공간 페이지는 SharePoint Embedded (SPE) 컨테이너에 존재하며, Teams/Outlook의 Loop 컴포넌트는 OneDrive 내의 .loop 파일로 존재합니다. 문서화된 트릭이 하나 있습니다. Microsoft Graph를 사용하면 GET /drives/{id}/items/{id}/content?format=html을 통해 .loop 파일을 즉석에서 HTML로 변환할 수 있다는 것입니다. 따라서 Loop API가 없더라도 파일 계층을 통해 읽기가 가능합니다.
하지만 진짜 문제가 나타났고, 이것이 글을 쓰기에 가치가 있었습니다.
권한 함정
SharePoint Embedded는 콘텐츠 다운로드 시 **위임된(delegated) 토큰(사용자별)**을 허용하지 않습니다. 오직 앱 전용(app-only) ID만 바이트를 가져올 수 있습니다.
만약 나이브하게, 즉 자신의 앱 ID를 사용하여 Loop를 읽는 서비스를 구축한다면, 당신은 권한 평탄화 장치(permission-flattening machine)를 만든 것입니다. 이 앱은 모든 것을 읽을 수 있기 때문에, 서비스에 접근할 수 있는 누구라도 자신이 개인적으로 접근할 수 있는 내용과 관계없이 테넌트 내의 모든 Loop 페이지를 읽을 수 있게 됩니다. 이것은 추가 단계가 붙은 데이터 유출이며, 용납할 수 없습니다.
따라서 질문은 다음과 같았습니다. 어떻게 하면 각 사용자가 허용된 내용만 볼 수 있도록 보장하면서, 서비스가 앱 ID (app identity)를 사용하여 콘텐츠를 읽게 할 수 있을까?
해결책: 검색(Discovery)과 검색 결과 가져오기(Retrieval)의 분리
문제를 해결한 핵심 통찰은 다음과 같습니다. Graph Search는 위임된 호출자(delegated callers)에 대해, 심지어 SPE 콘텐츠에 대해서도 보안 트리밍 (security-trimmed)이 적용된다는 점입니다. 검색은 사용자의 권한을 존중하며, 콘텐츠를 다운로드 (download) 할 때만 앱 ID (app identity)가 필요합니다.
그래서 저는 두 작업을 두 개의 ID로 분리했습니다:
-
검색(Discovery)은 사용자로 실행됩니다. 어시스턴트가 Loop를 검색할 때, 서버는 OAuth On-Behalf-Of (OBO) 흐름을 통해 사용자의 토큰을 교환하고, 해당 사용자로서 Graph Search를 호출합니다. Microsoft는 사용자가 접근할 수 있는 내용으로 정확히 결과를 트리밍 (trim) 합니다. 서버는 검색된 모든 항목의
(driveId, itemId)를 사용자별로 생성된 수명이 짧은 캐시 (cache)에 기록합니다. -
가져오기(Retrieval)는 해당 검색 결과에 의해 제어됩니다. 어시스턴트가 페이지를 읽어달라고 요청하면, 서버는 해당 정확한
(driveId, itemId)쌍이 이 사용자의 캐시에 들어있지 않은 한 요청을 거부합니다. 즉, 사용자가 자신의 트리밍된 검색을 통해 직접 해당 항목을 발견한 경우가 아니라면 거부하는 것입니다. 오직 그 경우에만 앱 ID (app identity)를 사용하여 바이트 (bytes)를 가져오고 변환합니다.
검색 (search) -- OBO (사용자 ID) --> Graph Search -> Microsoft에 의해 결과 트리밍
-> 사용자별 (driveId, itemId) 캐싱
가져오기 (read) -- 이 쌍이 호출자의 캐시에 있는가? --> 아니오 -> 거부 (Graph 호출 없음)
...
권한 부여 결정은 제가 아닌 Microsoft의 몫입니다. 사용자는 접근할 수 없는 페이지를 발견할 수 없고 (검색이 사용자로서 실행되므로), 발견하지 않은 페이지는 읽을 수 없습니다. 앱 ID (app identity)는 사용자가 이미 볼 수 있음을 증명한 바이트 (bytes)를 가져오기 위한 검색 메커니즘일 뿐입니다. 권한 평탄화 (Permission flattening) 문제가 해결되었습니다.
적절한 MCP 서버로 만들기
저는 이를 세 가지 읽기 전용 (read-only) 도구인 loop_search, loop_list_components, loop_get_page를 가진 원격 Model Context Protocol (MCP) 서버로 노출했습니다. 읽기 전용은 단순히 정책 스위치를 끄는 것이 아닙니다. Graph 클라이언트는 GET과 POST /search/query만 허용하므로, 구조적으로 쓰기(write)가 불가능합니다. (이는 다행스러운 일인데, .loop 파일을 다른 것으로 덮어쓰면 파일이 손상될 뿐만 아니라, 어차피 지원되는 쓰기 API도 없기 때문입니다.)
인증 (auth)을 위해, 처음에는 서버 앞에 플랫폼의 "easy auth" 게이트웨이를 두려고 시도했습니다. 이는 큰 실수였습니다. 게이트웨이가 OAuth 핸드셰이크 (handshake)를 가로채는 바람에 MCP 클라이언트가 어디로 로그인해야 할지 전혀 찾을 수 없었습니다. 해결책은 서버 자체를 독립적인 OAuth 리소스 서버 (resource server)로 만드는 것이었습니다. 서버는 클라이언트가 Microsoft Entra를 가리키도록 하는 디스커버리 문서 (/.well-known/oauth-protected-resource 및 /.well-known/oauth-authorization-server)를 게시하며, 들어오는 토큰(signature via Entra's JWKS, audience, issuer, expiry)을 직접 검증합니다. 게이트웨이가 없으므로 가로채기도 발생하지 않으며, Entra가 앱에 할당된 사용자에게만 토큰을 발급하기 때문에 액세스 제어는 여전히 완벽하게 이루어집니다.
교훈 (화려하지 않은 측면)
- 위임된 검색(Delegated search) + 앱 전용 검색(app-only retrieval)은 SharePoint Embedded 상에 구축된 모든 서비스에 있어 정당하고 강력한 패턴입니다. 검색 시에는 정보를 최소화(Trim)하고, 검색(Retrieval) 시에 데이터를 가져오십시오.
- 플랫폼 인증 게이트웨이가 원격 MCP 서버를 감싸게 하지 마십시오 — MCP OAuth 흐름은 클라이언트가 서버 자체의 검색/챌린지(discovery/challenge)에 도달할 수 있어야 합니다. 인증은 앱이 직접 관리하도록 하십시오.
- 검색 게이트를 단순히 아이템 ID(item ID)가 아닌
(driveId, itemId)쌍에 바인딩하십시오 — 그렇지 않으면 호출자가 발견된 ID를 다른 드라이브에 대해 재사용(replay)할 수 있습니다. - 모든 곳에서 실패 시 차단(Fail closed) 원칙을 적용하십시오: 토큰 없음, 신원(identity) 없음, 캐시 미스(cache miss), 만료된 항목 -> 모두 거부합니다. 멀티 인스턴스 호스팅 환경에서 사용자가 다시 검색하게 만드는 것은 안전한 실패(safe failure)이지, 데이터 유출이 아닙니다.
- 앱 자격 증명(App credential)은 가장 중요한 핵심 자산(crown jewel)입니다. 이는 테넌트 전체에 대한 읽기 권한을 가집니다. 이를 비밀 관리자(secrets manager)에 저장하고, 인증서 사용을 권장하며, 정기적으로 교체(rotate)하고, 서버에 대한 IP 허용 목록(IP-allowlisting) 설정을 고려하십시오.
- 권한 취소 지연(revocation lag)을 주의하십시오. 검색 결과는 몇 분 동안 캐시되므로, 액세스 권한을 잃더라도 즉시 반영되지 않습니다. TTL(Time To Live)을 위험 허용 범위에 맞춰 조정하십시오.
직접 시도해보기
코드는 GitHub에 MIT 라이선스로 공개되어 있습니다: github.com/DenizV/loop-reader-mcp. README에는 Entra 앱 등록, 일회성 SharePoint Embedded 게스트 등록, 호스팅, 그리고 MCP 클라이언트에 연결하는 방법이 설명되어 있습니다. SECURITY.md에는 모델과 프로덕션 환경에 적용하기 전 해결해야 할 잔류 위험(residual risks)이 문서화되어 있습니다.
이것은 커뮤니티 코드이며 인증된 제품이 아닙니다. 실제 데이터에 적용하기 전에 코드를 검토하고 종속성 스캔(dependency scan)을 수행하십시오. 하지만 어시스턴트에게 테넌트 전체의 열쇠를 넘겨주지 않고 Loop 콘텐츠를 읽을 수 있게 하고 싶었다면, 이 패턴은 효과적입니다.
이 패턴을 기반으로 구축하거나 더 정교한 접근 방식을 찾으신다면, 의견을 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기