
MCP 서버를 단순한 API 래퍼로 끝내지 않기: Slack 연동을 안전한 업무 조작으로 만드는 Go 설계
요약
MCP(Model Context Protocol) 서버를 단순한 API 래퍼를 넘어 안전한 업무 조작 도구로 설계하는 방법을 다룹니다. Go SDK를 활용해 Slack API를 연동할 때, AI의 실수를 방지하기 위한 읽기/쓰기 분리 및 검증 설계 원칙을 소개합니다.
핵심 포인트
- MCP 서버는 저수준 API를 AI가 안전하게 사용할 수 있는 업무 문맥으로 변환하는 경계임
- 읽기와 쓰기 동작을 분리하여 AI의 무분별한 조작을 방지해야 함
- 비멱등적 조작에 대한 무분별한 재시도를 막는 설계가 중요함
- AI가 실수하기 어렵도록 좁고 의미가 부여된 스키마를 제공해야 함
서론
MCP (Model Context Protocol)를 통해 기존 SaaS를 사용할 수 있게 한다고 하면, 흔히 "API를 도구로서 공개할 뿐"이라고 생각하기 쉽습니다.
예를 들어 Slack이라면, chat.postMessage를 호출하는 도구를 하나 만들면 AI는 메시지를 게시할 수 있습니다. 구현 자체는 어렵지 않습니다.
하지만 실무에서 중요한 것은 API를 호출할 수 있는 능력이 아닙니다. AI가 모호한 입력, 잘못된 수신자, 중간 실패, 너무 넓은 검색 결과를 다루더라도 업무상의 사고로 이어지지 않게 하는 것입니다.
Slack의 공식 문서를 읽어보면 이 차이는 상당히 명확합니다.
- Slack MCP Server는 AI 클라이언트가 Slack상의 검색, 메시지 취득, 게시, Canvas, 사용자 정보 등을 다루기 위한 MCP 서버입니다.
- Slack Web API는
https://slack.com/api/METHOD_FAMILY.method형식의 HTTP RPC 스타일 메서드 군입니다.
즉, Web API는 "Slack을 조작하기 위한 저수준(low-level) 입구"이며, MCP 서버는 "AI가 안전하게 Slack을 업무 문맥에서 사용하기 위한 경계"입니다. 양자는 경쟁 관계가 아니라 책임 범위가 다릅니다.
이 기사에서는 modelcontextprotocol/go-sdk를 사용하여 Go로 구현한 ap-mcp-slack을 소재로, Slack API를 MCP 도구로 변환할 때 의식했던 설계를 소개합니다.
MCP 클라이언트
│ stdio / JSON-RPC
▼
...
결론부터 말하자면, MCP 서버의 품질은 도구의 수가 아니라 다음의 경계를 어디까지 구현할 수 있느냐에 따라 결정됩니다.
- 읽기와 쓰기를 분리한다
- 모호한 대상을 마음대로 확정하지 않는다
- 쓰기 전에 실제 payload를 확인할 수 있다
- 비멱등적 (non-idempotent) 조작을 안이하게 재시도하지 않는다
- 외부 API의 페이지네이션 (paging)이나 입력 미비 사항을 그대로 AI에게 떠넘기지 않는다
Slack 공식 문서에서 보는 API와 MCP의 차이점
Slack Web API 문서는 개발자가 HTTP 요청을 구성하기 위한 정보를 제공합니다. 메서드 이름, URL, 인자, 인증 헤더, POST body 형식, 응답의 ok / error / warning을 읽고 개발자가 클라이언트 코드를 작성한다는 전제입니다.
반면, Slack MCP Server 문서에서는 MCP의 구성 요소로서 host, client, server가 설명되어 있습니다. 여기서 MCP 서버는 외부 도구나 데이터 소스로의 입구이며, AI 클라이언트에게 "무엇을 할 수 있는지"를 제시하고, 표준화된 요청을 받아 대상 시스템에 대한 조작으로 변환합니다.
이 차이는 Slack 공식 문서상의 비교에도 나타나 있습니다. API는 소프트웨어 간의 결정적인 통신에 적합하며, MCP는 AI 모델이 외부 데이터나 도구를 다루는 상호작용에 적합합니다. Web API의 출력은 JSON과 ID 중심이지만, MCP에서는 AI나 사람이 해석하기 쉬운 형태로 채널명이나 사용자명 등의 문맥을 포함한 출력이 중요해집니다.
여기서부터 MCP 서버의 설계 방침이 바뀝니다.
| 관점 | Web API를 직접 호출하는 설계 | MCP 도구로서 설계하는 경우 |
|---|---|---|
| 호출 단위 | chat.postMessage나 users.list 등의 API 메서드 | "수신자를 해결한다", "게시물을 미리보기(preview)한다", "확인 후 게시한다" 등의 업무 조작 |
| 입력 | API 사양에 맞는 파라미터 | AI가 실수하기 어려운, 좁고 의미가 부여된 스키마 |
| 출력 | JSON, ID, API 고유의 에러 코드 | 다음에 취해야 할 행동을 알 수 있는 상태, 후보, 확인용 payload |
| 실패 시 | 호출 측 앱이 판단함 | MCP 서버가 업무상 위험한 지속을 중단함 |
| 권한 | 토큰과 scope로 제한됨 | scope에 더해, 도구 설계를 통해 조작 범위를 좁힘 |
"API를 호출할 수 있다"는 필요조건입니다. 하지만 MCP 서버로서는 "AI에게 어느 정도 입도의 조작을 허용할 것인가", "어느 상태에서 멈출 것인가", "인간이 확인할 수 있는 정보를 어떻게 반환할 것인가"까지 포함하여 설계 대상이 됩니다.
공식 MCP 서버가 존재하더라도, 개별 MCP의 여지는 있다
Slack에는 공식 MCP 서버가 있습니다. 검색, 메시지 가져오기/전송, 채널 생성, 리액션(Reaction), Canvas, 사용자 정보 등 Slack 워크스페이스를 폭넓게 다룰 수 있는 범용적인 입구입니다.
그렇다면, 개별적으로 Slack용 MCP 서버를 만들 의미는 없을까요? 그렇지 않습니다.
공식 MCP 서버는 Slack의 능력을 AI 클라이언트에게 연결하기 위한 표준적인 입구입니다. 반면, 사내 또는 프로덕트 고유의 MCP 서버는 Slack API 위에 업무 고유의 안전장치를 두는 장소가 됩니다.
예를 들어 다음과 같은 제약 사항은 Slack 전체의 범용 기능이라기보다, 팀별 운영 규칙에 가깝습니다.
- 게시 대상을 특정 채널로 한정함
- 운영 장애(Production Failure) 채널에 대한 게시만 미리보기(preview)를 필수화함
- 외부 공유 채널에서는 멘션(Mention)을 금지함
- 게시 본문에 자동 실행임을 나타내는 라벨을 붙임
- 사용자 이름의 모호한 일치(Ambiguous match)로는 절대로 전송 단계로 진행하지 않음
- 검색 결과를 일정 건수 이상 반환하지 않고, 반드시 필터링을 유도함
이러한 종류의 규칙은 Web API의 얇은 래퍼(Wrapper)로는 표현하기 어렵습니다. API 메서드는 "무엇이 가능한가"를 나타내지만, 업무 조작은 "이 팀에서는 무엇을 해도 되는가"를 나타냅니다. MCP 서버를 개별적으로 구현하는 가치는 바로 이 차이점에 있습니다.
API 엔드포인트가 아니라, 이용자의 의도를 도구(Tool)로 만든다
Slack API에는 chat.postMessage, conversations.list, users.list와 같은 엔드포인트(Endpoint)가 있습니다. 이것들을 그대로 MCP 도구로 만들어도 동작은 합니다.
하지만 AI가 필요로 하는 것은 HTTP에 대한 지식이 아닙니다.
Slack Web API를 직접 다루는 앱이라면, 개발자가 문서를 읽고 users.list의 결과에서 사용자를 찾아 chat.postMessage에 전달할 channel이나 text를 구성합니다. 그러나 MCP에서는 그 판단의 일부를 AI가 담당합니다. AI에게 API 메서드를 그대로 전달하면, HTTP 클라이언트의 책임과 업무 판단의 책임이 뒤섞이게 됩니다.
예를 들어 "다나카 씨에게 확인을 요청하고 싶어"라는 지시에는 다음과 같은 순서가 필요합니다.
- 이름으로 사용자 후보를 찾음
- 후보가 유일하다면 멘션 형식으로 변환함
- 게시 대상과 본문을 확정함
- 게시 전에 내용을 확인함
- 게시함
이 때문에 ap-mcp-slack은 users.list를 단순히 공개하는 것에 그치지 않고, resolve_slack_user를 준비해 두었습니다.
// internal/tools/users.go
type ResolveSlackUserInput struct {
Name string `json:"name,omitempty"`
...
이름 검색 시에는 먼저 name / real_name / display_name의 완전 일치(Exact match)를 찾고, 없으면 부분 일치(Partial match)로 넘어갑니다. 그럼에도 후보가 여러 명이라면 첫 번째 사람을 바로 반환하지 않습니다.
// internal/client/users.go
switch len(candidates) {
case 0:
...
이 설계에서는 모호함을 에러로 뭉개버리는 것이 아니라, MCP의 구조화된 출력(Structured output)으로 반환합니다. AI는 후보 중에서 확인을 요청하거나, 이메일 주소와 같은 추가 정보를 요구할 수 있습니다.
"검색에 성공하면 무언가를 반환한다"보다, "유일하게 결정되었을 때만 쓰기(Write)로 진행한다"를 우선시합니다.
Slack Web API의 ok: true / ok: false는 API 호출 자체의 성공/실패를 나타냅니다. 반면, "다나카 씨"가 3명 있는 상태는 API 에러가 아닙니다. 업무 조작으로서 미확정된 상태입니다.
이 구분을 MCP 도구의 출력에 도입하면 AI의 다음 행동이 안정됩니다.
{
"ok": true,
"status": "ambiguous",
...
}
여기서 HTTP 상으로는 성공한 것입니다. 하지만 MCP 도구로서는 "아직 게시해서는 안 된다"라는 상태를 반환하고 있습니다. API 응답을 그대로 전달하는 것이 아니라, AI가 안전하게 멈출 수 있는 상태로 번역하는 것이 MCP 서버의 역할입니다.
쓰기 조작에는 preview를 쌍으로 제공한다
AI를 통한 쓰기 작업에서 무서운 것은 API 실패보다 성공해 버리는 오조작입니다. Slack 게시물은 삭제할 수 있어도, 알림, 멘션, 외부 공유 채널로의 노출은 되돌릴 수 없습니다.
그래서 게시 도구에는 반드시 부작용이 없는 preview를 쌍으로 구성했습니다.
| 실행할 작업 | 부작용 없이 확인하는 작업 |
|---|---|
post_slack_message | preview_slack_message |
post_slack_message_as_user | preview_slack_message_as_user |
preview는 실제 전송 시와 동일한 payload 생성 과정을 거칩니다.
func (t *SlackTools) previewSlackMessage(
_ context.Context,
_ *mcp.CallToolRequest,
...
여기서 중요한 점은 "거의 비슷한 payload를 구성하는" 것이 아니라, 운영 환경과 preview에서 동일한 PreviewMessage를 사용하는 것입니다. 처리가 다르면 preview에서는 문제가 없었는데 전송 시에만 Block Kit 가공이나 기본값 부여가 들어가는 등의 차이가 발생할 수 있습니다.
Slack Web API에서는 JSON body를 보낼 때 Content-type: application/json을 명시하고, token은 Authorization: Bearer ...로 보내는 것과 같이 HTTP 상의 정확성이 요구됩니다. MCP 서버 측에서는 여기에 더해 "이 payload를 정말로 Slack에 내보내도 되는지"를 사람이 확인할 수 있는 형태로 만들어야 합니다.
preview에서 반환해야 하는 것은 단순한 자연어 요약만이 아닙니다.
- 실제로 전송될
channel - 실제로 전송될
text - 실제로 전송될
blocks - 스레드(thread) 게시물인 경우
thread_ts - 멘션(mention)이나 링크 확장 설정
- 게시 출처를 나타내는 라벨
AI에게 "이런 느낌으로 보냅니다"라고 설명하게 하는 것만으로는 실제 API payload와의 차이를 검출할 수 없습니다. 확인 대상은 AI의 설명이 아니라, 서버가 생성한 전송 예정 payload입니다.
게시 출처를 명시하기
MCP를 통한 게시물은 사람이 Slack에서 직접 보낸 게시물과 구분할 수 있는 편이 좋을 때가 있습니다. ap-mcp-slack에서는 설정된 라벨을 Block Kit의 context로 추가합니다.
// internal/client/slack.go
func appendRawSourceLabelBlock(
blocks []map[string]any,
...
인수에 text가 있는 이유는 이 함수가 Block Kit 사양상의 함정을 흡수하고 있기 때문입니다. Slack에서는 blocks를 지정하는 순간 본문 렌더링은 blocks 측이 담당하게 되며, 최상위 레벨의 text는 알림이나 접근성(accessibility)을 위한 폴백(fallback) 역할로 물러납니다. 즉, text만으로 게시하던 메시지에 라벨용 context를 하나 추가하면 본문이 사라진 것처럼 보이게 됩니다. 라벨 부여는 "정보를 한 줄 추가하는 것"처럼 보이지만, 실제로는 메시지의 렌더링 모델을 전환하는 작업입니다.
이것이 인가(authorization)나 감사 로그(audit log)를 대신할 수는 없습니다. 그럼에도 업무용 채팅에서 "이 게시물은 자동화의 결과이다"라는 것을 알 수 있다는 점은 리뷰나 정정의 초동 대응을 빠르게 만듭니다.
Slack MCP Server의 공식 문서에서도 MCP 클라이언트에게 Slack 데이터에 대한 접근 권한을 부여하는 것에 대한 주의사항이 언급되어 있습니다. 특히 여러 MCP 서버를 동시에 사용하는 경우, 한 서버에서 얻은 문맥(context)이 다른 서버의 조작으로 흘러 들어갈 가능성이 있습니다.
그렇기에 Slack 측에 남는 게시물에는 "누구의 토큰으로", "어떤 앱을 경유하여"뿐만 아니라, "어떤 자동화 경로로 생성되었는지"를 알 수 있는 단서를 남겨두는 것이 운영하기에 수월합니다. 이는 기술적인 완전성보다는 사고 대응 시의 초동 조치를 돕기 위한 설계입니다.
멱등성(Idempotency)으로 리트라이(Retry) 방침을 바꾸기
외부 HTTP 호출에는 리트라이를 넣고 싶어집니다. 하지만 게시 처리에 일괄적으로 리트라이를 적용하면, 타임아웃 시 동일한 내용이 중복 게시될 가능성이 있습니다.
Incoming Webhook을 통한 게시물은 비멱등(non-idempotent)입니다. 따라서 본 구현에서는 Webhook 게시물에 대해서만 명시적으로 리트라이를 비활성화했습니다.
func newWebhookTransport(cfg SlackClientConfig) webhookTransport {
return webhookTransport{
webhookURL: strings.TrimSpace(cfg.WebhookURL),
...
여기서 중요한 것은 "리트라이(retry) 여부"를 통신 종류만으로 결정하지 않는 것입니다. HTTP POST라 하더라도, 읽기 전용 검색인지 아니면 메시지를 새로 생성하는 것인지에 따라 그 의미가 달라집니다.
| 작업 | 실패 시 중시할 점 |
|---|---|
| 채널·사용자 목록 가져오기 | 일시적 장애로부터의 회복, 페이지네이션 (Pagination)의 지속 |
| ... |
MCP에서는 AI가 "다시 한번 해줘"라고 자연스럽게 요청할 수 있습니다. 그렇기 때문에 서버 측에서 자동 재시도 (automatic retry)를 반복하기 전에, 해당 작업이 재실행 가능한지 설계해야 합니다.
Slack MCP Server의 공식 문서에는 MCP 툴을 통한 작업에도 Slack Web API와 동일한 레이트 리밋 (Rate Limit)이 적용된다고 명시되어 있습니다. 이는 매우 중요합니다. MCP는 Slack API의 제약을 우회하는 메커니즘이 아닙니다.
따라서 MCP 서버에서는 최소한 다음 두 가지를 분리해서 생각해야 합니다.
- Slack 측의 레이트 리밋 (Rate Limit)을 준수하기 위한 제어
- 업무상 동일한 작업을 다시 실행해도 되는지에 대한 판단
전자는 HTTP 클라이언트나 Slack SDK의 책임 영역에 가깝습니다. 후자는 MCP 툴의 설계 책임입니다. 429 Too Many Requests로부터 회복하기 위한 대기와, 게시물 타임아웃 후에 동일한 메시지를 재전송할지 여부는 동일한 "리트라이 (retry)"라는 단어로 묶어서는 안 됩니다.
페이지네이션 (Pagination) 오류를 AI에게 넘기지 않기
목록 조회 계열의 API는 AI와 궁합이 좋지 않은 부분입니다. 응답 데이터가 크고, 다음 데이터가 이어지며, API의 동작이 불안정할 때도 있습니다.
conversations.list 처리에서는 상한선을 검증하고, 커서 (cursor)의 중복을 감지합니다.
for len(channels) < limit {
apiChannels, nextCursor, err := fetch(ctx, cursor, requestLimit)
if err != nil {
...
포인트는 단순히 무한 루프를 방지하는 것만이 아닙니다. 커서가 깨진 상태에서 "가져올 수 있는 만큼만 반환"하게 되면, AI는 불완전한 목록을 완전한 정보로 오인하여 잘못된 채널 선택이나 조사 결과를 만들어내게 됩니다.
외부 API의 불일치는 가능한 한 경계(boundary)에서 감지하여 의미 있는 에러로 변환해야 합니다. MCP 툴이 반환하는 데이터의 완전성 또한 AI가 올바르게 추론하기 위한 입력 품질의 일부입니다.
Slack Web API는 메서드마다 인자와 반환값이 정의되어 있습니다. 일반적인 애플리케이션이라면 페이지네이션 (Pagination)이나 커서 (cursor) 관리는 애플리케이션 코드가 책임을 집니다. MCP에서는 그 책임을 AI에게 넘겨서는 안 됩니다.
AI에게 "다음 cursor를 사용해서 나머지를 가져와"라고 맡기는 것 자체는 가능합니다. 하지만 검색어의 변경, 중간의 실패, 중복된 cursor, 건수 상한, 권한 부족 등이 뒤섞였을 때, AI는 "결과가 적은 것"인지 "중간에 누락된 것"인지 구분할 수 없습니다.
따라서 목록 조회 계열의 툴은 다음과 같이 동작하는 것이 다루기 쉽습니다.
- 최대 가져올 건수를 툴 측에서 명시한다.
- 다음 데이터가 있는 경우
has_more나next_cursor를 구조화하여 반환한다. - 커서 (cursor)의 중복이나 불일치는 부분 성공으로 처리하지 않는다.
- 건수가 많은 경우 검색 조건의 추가를 유도한다.
- 표시 이름(display name)과 ID를 모두 반환한다.
Web API의 세계에서는 ID 중심의 JSON만으로도 충분합니다. 하지만 MCP의 세계에서는 AI가 다음 판단을 내릴 때 사용할 수 있는 문맥 (context)을 덧붙여 주어야 합니다.
Webhook과 Web API를 억지로 하나로 통합하지 않기
Slack으로의 게시에는 Incoming Webhook과 Web API라는 두 가지 경로가 있습니다.
- Incoming Webhook: 게시 대상이나 게시자를 Webhook 설정에 종속시킨 단순한 게시 방식
- Web API: 유저 토큰(User Token) 또는 봇 토큰(Bot Token)을 사용하여 게시, 수정, 삭제, 채널 참조까지 수행하는 작업
겉보기에는 둘 다 "메시지 게시"이지만, 인증, 권한, 반환되는 식별자, 사용할 수 있는 작업이 다릅니다. 따라서 클라이언트 내부에서는 하나의 거대한 구조체에 nullable한 설정을 가득 채우지 않고, 두 개의 transport로 나누어 구현합니다.
type SlackClient struct {
webhookTransport webhookTransport
webAPITransport webAPITransport
...
}
공통화한 것은 본문(Text), Block Kit, 첨부 파일(attachments)과 같은 메시지 내용뿐입니다.
type MessageContent struct {
Text string
Blocks []map[string]any
...
}
이렇게 분리함으로써 Webhook에만 있는 제약 사항이나 Web API에만 있는 channel_id를 구현상에서도, 도구(tool)의 스키마상에서도 모호하게 만들지 않습니다.
Slack Web API는 REST API가 아니라 HTTP RPC 스타일의 메서드 집합입니다. 예를 들어 chat.postMessage, conversations.create, users.list는 각각 독립된 메서드이며, 인자(argument)와 응답(response)도 메서드마다 다릅니다.
Incoming Webhook은 성격이 더욱 다릅니다. Webhook URL 자체가 게시 경로이며, 설정에 따라 게시 대상과 표시 방식이 결정됩니다. Web API 토큰을 사용하는 게시와는 실패 시의 처리 방식, 권한의 가시성, 반환할 수 있는 식별자(identifier)가 다릅니다.
MCP 도구의 사용자 입장에서 보면 둘 다 "Slack에 게시한다"는 점은 같습니다. 하지만 구현상으로는 하나의 추상화로 묶어버리기에는 서로 같지 않습니다. 무리하게 공통화하면 다음과 같은 모호함이 발생합니다.
channel을 입력으로 받을 수 있는지, 아니면 Webhook 설정을 따르는지- 게시 후에
ts나channel_id를 반환할 수 있는지 - 스레드(thread) 게시가 가능한지
- 게시자 표시를 어디까지 변경할 수 있는지
- 삭제나 수정의 대상으로 추적할 수 있는지
이러한 모호함은 AI에게도 위험합니다. 사용할 수 없는 동작을 사용할 수 있는 것처럼 보여주는 것보다, transport별로 도구나 출력을 나누는 것이 실패의 의미를 명확하게 만듭니다.
인증과 scope를 실행 시의 제약 사항으로 다루기
Slack MCP Server의 공식 문서에서는 MCP 클라이언트가 이미 등록된 Slack 앱을 전제로 하며, OAuth를 통해 사용자가 앱을 승인하는 흐름을 설명하고 있습니다. 또한 검색, 게시, Canvas, 사용자 정보, 채널 멤버 가져오기 등 도구마다 필요한 scope가 나뉘어 있습니다.
이는 MCP 서버를 만드는 측에서도 중요합니다. 토큰은 단순한 비밀 문자열이 아니라, "무엇을 할 수 있는지"를 결정하는 실행 시의 경계입니다.
예를 들어, 다음과 같이 설계해 두면 AI에게 불필요한 실패 경로를 보여주지 않아도 됩니다.
- Webhook URL만 설정된 경우에는 Webhook 게시와 preview 기능만 활성화한다.
- Web API token이 없는 경우에는 사용자 검색이나 채널 목록 도구를 등록하지 않는다.
- 필요한 scope가 없는 동작은 시작 시 또는 첫 실행 시에 명확한 설정 에러로 반환한다.
- user token과 bot token에 따라 동작이 달라지는 작업은 도구 이름이나 출력에 실행 주체를 포함한다.
MCP에서는 AI 클라이언트가 서버에 사용 가능한 도구를 문의합니다. 그렇기 때문에 사용할 수 없는 도구를 형태만 갖추어 공개하는 것은 피해야 합니다. missing_scope와 같은 Slack API 에러를 받은 후에 AI가 경로를 수정하도록 유도하는 것보다, 서버 시작 시점에 능력을 제한하는 것이 대화의 흐름과 안전성 측면에서 더 안정적입니다.
이러한 사고방식은 Slack 공식 MCP 서버의 App Identity 설명과도 연결됩니다. 어떤 Slack 앱으로 접속하고 있는지, 관리자가 승인 및 관리할 수 있는지, 어떤 scope로 무엇을 할 수 있는지. MCP 서버는 이러한 전제를 숨기는 것이 아니라 도구의 표면에 반영해야 합니다.
MCP 서버의 책임 범위
MCP 서버가 모든 통제를 담당하는 것은 아닙니다.
예를 들어 토큰의 보관 및 로테이션(rotation), 조직 간 감사 로그(audit log), 누가 어떤 도구를 사용할 수 있는지에 대한 권한 설계는 개별 MCP 구현만으로 해결하기보다 사내 인프라나 ID 기반 시스템에서 다루어야 할 문제입니다.
반면, 개별 MCP가 맡아야 할 책임도 있습니다.
- API 고유의 입력 제약을 MCP 스키마로 번역한다.
- 모호한 후보를 자동으로 선택하지 않는다.
- 쓰기(write) 전에 확인할 수 있는 경로를 마련한다.
- 작업의 부작용(side effect)에 따라 재시도(retry)를 제어한다.
- 외부 API의 이상을 조기에 감지한다.
- 토큰이나 scope에 따라 공개할 도구를 제한한다.
- API 응답을 AI가 다음 행동을 선택할 수 있는 상태로 변환한다.
이 경계를 분리하면, 인프라 표준화를 기다리지 않고도 프로덕트 팀은 자신들의 업무를 안전하게 자동화할 수 있습니다. 동시에 MCP가 사내 표준으로 성장했을 때도, 인증(Authentication)이나 감사(Audit)를 외부로 이전하기가 쉬워집니다.
요약
MCP는 LLM(Large Language Model)이 외부 서비스를 다룰 수 있게 해주는 편리한 연결 규격입니다. 하지만 실무에서 가치를 창출하는 것은 "무엇을 호출할 수 있는가"보다 "어떻게 안전하게 호출할 수 있는가"입니다.
Slack 연동의 경우, 다음 체크리스트가 출발점이 될 수 있습니다.
- 쓰기 작업(Write operation)에 미리보기(Preview) 기능이 있는가
- 수신자나 사용자가 모호할 때 중단할 수 있는가
- 비멱등적(Non-idempotent)인 작업을 자동 재시도(Retry)하고 있지는 않은가
- 목록 조회 시 상한(Limit), 커서(Cursor), 페이지네이션(Pagination)을 제어하고 있는가
- MCP를 통한 게시물을 사람이 식별할 수 있는가
- 토큰(Token), 권한(Permission), 감사의 책임 범위를 명확히 하고 있는가
- scope 부족이나 설정 미비인 도구를 공개하고 있지는 않은가
- JSON이나 ID뿐만 아니라, AI가 판단할 수 있는 상태를 반환하고 있는가
AI에게 조작을 위임할수록, 기존에는 사람의 주의력에 의존했던 부분을 프로토콜과 구현의 경계로 옮겨야 합니다.
MCP 서버를 단순한 API 래퍼(Wrapper)로 끝내지 않고, 업무 조작의 안전장치로 설계하는 것. 그 축적이 AI를 일상적인 개발과 운영에 안심하고 도입하기 위한 토대가 됩니다.
참고
- shouni/ap-mcp-slack - GitHub (본 기사의 소스 코드)
- modelcontextprotocol/go-sdk - GitHub
- Slack MCP Server - Slack Developer Docs
- Slack Web API - Slack Developer Docs
※ Slack은 Salesforce, Inc.의 상표입니다. 본 기사 및 ap-mcp-slack은 Slack 공식 프로젝트가 아닙니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기