들어가며
현재 진행중인 AI Agent의 목표는, 문자 또는 이메일로 명령을 내리면 프로젝트가 생성되게 하는 것인데, 문자 서비스의 경우 무료가 거의 없고 막혀있는 경우도 꽤 있어서 무료로 지원하는 Discord를 사용해서 "Discord에 특정 명령 입력 -> LLM에 명령 입력 -> LLM 분석 후 프로젝트에 맞는 spec 생성" 구조로 실행하기 위해 Discord를 도입해봤다 ! 이에 Discord 연동방법을 기록해둔다.
discord를 연동하기에 앞서 ngrok을 다운받고 설정했는데,
ngrok을 사용한 이유는
Discord 서버의 경우 인터넷을 통해 내 서버의 "HTTPS" 엔드포인트로 POST요청을 보낸다. 즉, "공개된 HTTPS 주소"로만 요청을 보낸다.
나의 경우에는 로컬에서만 테스트중이었기때문에, 공개되지도않았고 https도 아니고,,, 그런 이유로 "공개 HTTPS 주소를 만들어주는 터널링 도구"인 ngrok을 사용했다
1. ngrok 다운
https://dashboard.ngrok.com/get-started/setup/windows
2. 명렁어 실행
위 사이트에서 ngrok을 다운받은 후 토큰을 발급받는다.

토큰은 자동으로 위 이미지와 같이 명령어에 포함돼서 나오기에, 해당 명령어를 그대로 복붙해서 내 powershell에 붙여넣으면된다.
설정 후
ngrok http 8000 (서버가 열린 포트번호)
위와 같은 명령어를 powershell에서 실행시킨다.
이때 본인이 터널링할 로컬의 포트번호를 써주면된다.
이를 실행시키면 ngrok은
🌍 https://abcd1234.ngrok-free.app
↓
http://localhost:8000
위와 같은 공개 HTTPS 주소를 만들어서, 해당 https로 보내는 요청을 내 로컬의 8000포트와 터널링해준다.

위 사진과 같이 나오면 성공 !
https로 요청이 들어올때마다 저기에 로그가 남는다
위에서 ngrok로 https 주소를 만들고 이를 현재 실행중인 파이썬 서버 8000포트와 연동해줬으므로, discord가 해당 https주소로 명령을 보낼 수 있도록 설정한다.
1. Discord developer portal에서 어플리케이션 설정하기
https://discord.com/developers/applications
위 링크에 들어가면 왼쪽 상단 메뉴바 클릭 시 Applications이 뜨고, 들어가면 New Application 버튼이 있다.
클릭

어플리케이션 이름 입력 후 create 누르면 끝.
참고로 discord 이메일 인증을 완료해야지 create가 되더라

생성 후 위와 같은 페이지로 이동하는데, Application ID와 Public Key를 저장해놓자 ! 유출되면 안된당
2. bot 생성
위에서 만든 어플리케이션을 클릭하면 화면이 바뀌고, 다시 왼쪽상단메뉴바를 눌러보면 아래 사진과 같이 뜬다.

이 메뉴에서 Bot을 누르자

여기서 Token란의 Reset Token을 누르면 토큰이 생성되는데, 이것도 복사해놓자.
3. Bot을 discord 서버에 초대하기
메뉴에서 Oauth2를 누르고 Oauth2 URL Generator에서 Scopes는 bot, applications.commands.
Bot permission에서는 Send Message를 클릭하면 아래에 Generated URL이 뜬다.

위 Generated URL로 들어가면

위와 같이 discord가 연결되는데 내가 사용할 서버(길드)를 선택 후 계속하기 버튼을 누르면 봇이 내 서버에 초대된다.

MyAIAgent가 내 서버에 초대됐다 !
4. ngrok URL과 연동

Application ID가 있던 페이지 밑에 Interactions Endpoint URL란이 있다.
여기에 ngrok 주소 + 요청을 받을 내 파이썬 서버 API를 적어준다
나는 localhost:8000/discord/interactions 경로로 만들어둔 API에서 discord에서 온 명령에 대해서 처리할 것이었기때문에, 위 사진과 같이 적어주었다
참고로 위 서버가 연동되기 위해서는, python에 api가 먼저 정의되어있는 상태로 실행중이어야하고, 해당 API 코드에는 "핑"확인 코드가 있어야한다.
@app.post("/discord/interactions")
async def discord_interactions(request: Request, background: BackgroundTasks):
raw = await request.body()
verify_discord_signature(request, raw)
payload = json.loads(raw.decode("utf-8"))
# Discord가 endpoint 검증할 때 보내는 PING
if payload.get("type") == 1:
return {"type": 1}
# Slash command 호출
if payload.get("type") == 2:
data = payload.get("data", {})
options = data.get("options", [])
instruction = None
for opt in options:
if opt.get("name") == "instruction":
instruction = opt.get("value")
break
if not instruction:
return {
"type": 4,
"data": {"content": "instruction 값이 없어. 예: /run instruction: ...", "flags": 64}
}
interaction_token = payload.get("token")
# 3초 내 응답 필수 → 즉시 ACK(에페메랄)
background.add_task(run_agent_and_notify, instruction, interaction_token)
return {
"type": 4,
"data": {"content": "✅ 지시 받았어! 실행 시작할게.", "flags": 64}
}
return {"type": 4, "data": {"content": "지원하지 않는 이벤트 타입이야.", "flags": 64}}
즉 위와 같은 코드가 있어야한다.
참고로 Discord의 Intercation Type은 다음과 같다.
Interaction Event Type (Discord → 서버)
1 → Ping 확인
2 → Slash Command 실행
3 → 버튼 클릭
Interaction Response Type (서버 → Discord)
1 → pong
4 → 즉시 메시지 응답
5 → ACK만 보내고 나중에 응답
즉, 나는 Discord와 연결할때 pong 응답 (type:1)을 보내서 성공했고, 위 코드에서 Discord 요청에 3초이내 대답하기 위해서는 type:4를 사용했다.
위 코드에서 type:2의 경우 슬래시커맨드를 처리한다고 했는데,
슬래시 커맨드란 Discord에서 / 로 시작하는 공식 명령어 시스템이다.
예를들어 /run, /help, /ping 처럼 입력하면 Discord가 해당 명령을 봇에게 전달해서 특정 기능을 실행하게 해주는 구조라고 할 수 있다.
슬래시 커맨드의 특징은
{
"name": "instruction",
"type": 3,
"required": true
}
이러한 슬래시커맨드는 코드 안에서 자동으로 생성되는 것이 아니라, discord 서버에 "명령어를 등록"해야지 직접 사용이 가능한데, 맨 처음에 new Application과 bot 등록을 통해 얻은 Application ID 등을 이때 사용한다.
나는 프로젝트에서 /run 커맨드를 등록하여 사용했는데, 등록 시의 절차는 다음과 같았다.
1. 환경 변수 설정
$env:DISCORD_APP_ID="your_Application_ID"
$env:DISCORD_GUILD_ID="your_Guild_ID"
$env:DISCORD_BOT_TOKEN="your_Bot_Token"
위와 같이 슬래시커맨드를 등록할때 사용할 ID, KEY값들을 현재 터미널 세션에서만 사용가능하도록 임시 저장한다.
2. 커맨드 저장을 위한 명령 json 저장
Discord 슬래시 커맨드는 "구조화된 Application Command 객체"로 반드시 JSON 형식으로 등록해야한다.
json 스키마 형식은 discord에서 공식적으로 정의해둔 것이 있다.
https://docs.discord.com/developers/interactions/application-commands?utm_source=chatgpt.com
스키마, 필수값과 선택값 등은 위 공식문서를 확인해보면 된다.

나는 위 사진과 같이 json 스키마를 등록했는데,
"/run" 커맨드를 사용할 것이고, instruction이라는 옵션을 가지게 할 것이며 이 옵션은 (type:3) String 값을 받는다는 뜻이다.
참고로 여기에 쓰인 type 값과 discord와 server를 연결할 때 사용했던 interaction type은 다르다.
option type에는 다음 값들이 있다.
type:3 -> String
type:4 -> Integer
type:5 -> Boolean
type:6 -> User
type:7 -> Channel
type:8 -> Role
3. 등록 API 호출
위와 같이 json을 만들고,
이를 curl 명령어를 통해 슬래스 커맨드 등록 API를 호출하여 커맨드를 등록한다.
[System.IO.File]::WriteAllText("$PWD\command.json", $json, (New-Object System.Text.UTF8Encoding($false)))
## 위 명령어로 현재폴더에 command.json 생성 후 저장
curl.exe -X POST "https://discord.com/api/v10/applications/$env:DISCORD_APP_ID/guilds/$env:DISCORD_GUILD_ID/commands" `
>> -H "Authorization: Bot $env:DISCORD_BOT_TOKEN" `
>> -H "Content-Type: application/json" `
>> --data-binary "@command.json"
curl~~~ 명령어로 등록 API를 호출하여 등록하면되는데, 이때 Discord bot token과 guild id(서버id), 디스코드 어플리케이션 id가 필요하다.
다만, 위 명령어의 경우에는 "디스코드의 특정서버"에만 커맨드를 등록하겠다는 것이므로, 만약 내 디스코드의 모든 서버에 커맨드를 등록하려고 한다면, guilds와 guild_id를 명령어 경로에서 제외시키면 된다!
명령어가 성공하면, 내 디스코드 서버에 슬래시만 입력해도

위 사진과 같이 /run instruction이 뜬다 !
아래는 내가 /discord/interactions 외에 추가한 함수들에 대한 설명이다.
verify_discord_signature
def verify_discord_signature(request: Request, raw_body: bytes):
sig = request.headers.get("X-Signature-Ed25519")
ts = request.headers.get("X-Signature-Timestamp")
if not sig or not ts:
raise HTTPException(status_code=401, detail="Missing Discord signature headers")
try:
verify_key = VerifyKey(bytes.fromhex(DISCORD_PUBLIC_KEY))
verify_key.verify(ts.encode() + raw_body, bytes.fromhex(sig))
except BadSignatureError:
raise HTTPException(status_code=401, detail="Invalid Discord signature")
send_followup
async def send_followup(interaction_token: str, content: str):
# Follow-up message endpoint
url = f"https://discord.com/api/v10/webhooks/{DISCORD_APP_ID}/{interaction_token}"
async with httpx.AsyncClient(timeout=10) as client:
await client.post(url, json={"content": content})

run_agent_and_notify
def run_agent_and_notify(instruction: str, interaction_token: str):
try:
result = run_agent_from_instruction(instruction, "specs/app.json")
# result는 Pydantic 모델이면 dict으로 변환
msg = f"✅ 완료!\n- project: {result.project_name}\n- dir: {result.project_dir}"
except Exception as e:
msg = f"❌ 실패: {e}"
# background task는 sync라서 httpx async를 직접 못 쓰니 간단히 동기 호출로 바꿔도 됨
# 여기서는 가장 단순하게 새 이벤트루프 대신, httpx sync로 처리:
import httpx as _httpx
url = f"https://discord.com/api/v10/webhooks/{DISCORD_APP_ID}/{interaction_token}"
_httpx.post(url, json={"content": msg}, timeout=10)
