AdMob API MCP 서버 만들기 - Part 3: 애드몹 계정 정보를 도구로 제공하기
지난 2부에서는 OAuth 인증을 통해 애드몹 API와 소통할 준비를 마쳤습니다. 이제 정말 재미있는 단계가 남았네요! 이번 포스트에서는 우리가 만든 서버가 가진 ‘애드몹 API 연동 능력’을 LLM이 마음껏 활용할 수 있도록 ‘도구(Tool)’ 형태로 제공하는 방법을 알아보겠습니다.
도구(Tool)란 무엇일까요?
MCP에서 말하는 도구(Tool)는 우리가 AI 언어 모델에게 “넌 이제 이런 것도 할 수 있어!”라고 알려주는 ‘새로운 스킬’과 같습니다. 이 스킬을 통해 AI는 채팅창 너머의 외부 세계(예: 애드몹 API)와 소통하며 자신의 능력을 무한히 확장할 수 있죠.
각 도구는 고유한 이름과 함께 어떤 입력값이 필요하고 어떤 결과가 반환되는지 명시된 ‘사용 설명서(Schema)’를 가집니다. 덕분에 AI는 “아, 이 도구를 쓰려면 이런 정보가 필요하고, 결과는 이런 형태로 오는구나!” 하고 명확하게 이해하고 활용할 수 있습니다.
어떤 도구를 만들어 볼까요?
애드몹 API는 정말 다양한 기능을 제공합니다. 처음부터 너무 복잡한 기능에 도전하기보다는, 간단하면서도 핵심적인 API 몇 가지를 먼저 도구로 만들어보겠습니다. 이번에는 아래 세 가지 API를 구현해 볼 거예요.
리소스 | 설명 |
---|---|
accounts.get | 애드몹 계정의 기본 정보를 조회합니다. |
accounts.adUnits.list | 계정에 등록된 모든 광고 단위 목록을 가져옵니다. |
accounts.apps.list | 계정에 등록된 모든 앱 목록을 가져옵니다. |
본격적으로 도구 만들기
자, 이제 코드로 직접 도구를 만들어볼 시간입니다! 먼저 src/tools.ts
파일을 새로 만들고, 우리가 만들 도구들의 이름을 enum
으로 깔끔하게 정리해 봅시다.
// src/tools.ts
enum ToolName {
GET_ACCOUNT = "get_account",
LIST_AD_UNITS = "list_ad_units",
LIST_APPS = "list_apps",
}
LLM은 어떤 작업을 처리하기 전에 먼저 서버에 “혹시 네가 쓸 수 있는 도구가 있니?”라고 물어봅니다. 우리는 이 요청에 응답하며 우리가 가진 도구 목록과 각각의 ‘사용 설명서’를 친절하게 알려줘야 합니다.
configureTools()
함수를 만들고, 도구 목록을 알려달라는 요청(ListToolsRequestSchema
)을 처리하는 핸들러를 추가해 봅시다. 이번에 만들 도구들은 별다른 입력 파라미터가 필요 없으므로, inputSchema
의 properties
는 비워두면 됩니다.
// src/tools.ts
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
// ToolName 선언부 아래에 추가합니다.
export function configureTools(server: Server): void {
// "사용 가능한 도구 목록 좀 알려줘!" 라는 요청을 처리하는 핸들러
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: ToolName.GET_ACCOUNT,
description: "Gets information about the AdMob publisher account.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: ToolName.LIST_AD_UNITS,
description: "List the ad units under the AdMob publisher account.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: ToolName.LIST_APPS,
description: "List the apps under the AdMob publisher account.",
inputSchema: {
type: "object",
properties: {},
},
},
],
};
});
}
이제 LLM이 “좋아, 그럼 이 도구를 실행해 줘!”라고 요청했을 때 실제로 애드몹 API를 호출하고 결과를 돌려주는 핸들러를 만들 차례입니다.
애드몹 API 호출에 필요한 게시자 코드(pub-xxxxxxx
)는 편의상 환경 변수(PUBLISHER_CODE
)에서 가져오도록 하고, 인증 정보가 없거나 게시자 코드가 설정되지 않았을 때는 친절한 에러 메시지를 반환하도록 만들겠습니다.
각 도구는 애드몹 API를 호출하여 받은 데이터를 보기 좋은 JSON 텍스트 형태로 가공해서 반환합니다.
// src/tools.ts
// CallToolRequestSchema 와 google, admob_v1 등을 추가로 import 합니다.
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { google, admob_v1 } from "googleapis";
import { loadSavedCredentialsIfExist } from "./auth.js";
// ... ToolName enum
export function configureTools(server: Server): void {
// ... ListToolsRequestSchema 핸들러
// "이 도구를 실행해줘!" 라는 요청을 처리하는 핸들러
server.setRequestHandler(CallToolRequestSchema, async request => {
// 1. 인증 정보 확인
const auth = await loadSavedCredentialsIfExist();
if (!auth) {
return {
content: [{ type: "text", text: "Not Authenticated. Try running `npm run auth` to authenticate." }],
isError: true,
};
}
// 2. 게시자 코드 확인
const publisherCode = process.env.PUBLISHER_CODE;
if (!publisherCode) {
return {
content: [{ type: "text", text: "PUBLISHER_CODE environment variable not set." }],
isError: true,
};
}
const { name } = request.params;
const admob = google.admob({ version: "v1", auth });
// 3. 요청된 도구 이름에 따라 분기 처리
if (name === ToolName.GET_ACCOUNT) {
const result = await admob.accounts.get({ name: `accounts/${publisherCode}` });
if (!result.ok) {
return {
content: [{ type: "text", text: `API request failed: ${result.status}` }],
isError: true,
};
}
const data = result.data;
return {
content: [{ type: "text", text: JSON.stringify({
name: data.name,
publisherId: data.publisherId,
reportingTimeZone: data.reportingTimeZone,
currencyCode: data.currencyCode,
}, null, 2)}],
};
}
if (name === ToolName.LIST_AD_UNITS) {
const allAdUnits: admob_v1.Schema$AdUnit[] = [];
let pageToken: string | undefined;
try {
// API가 페이지 단위로 데이터를 주기 때문에, nextPageToken이 없을 때까지 반복 호출합니다.
do {
const result = await admob.accounts.adUnits.list({
parent: `accounts/${publisherCode}`,
pageToken: pageToken,
});
if (!result.ok) {
return {
content: [{ type: "text", text: `API request failed with status: ${result.status}` }],
isError: true,
};
}
const data = result.data;
if (data.adUnits) {
allAdUnits.push(...data.adUnits);
}
pageToken = data.nextPageToken ?? undefined;
} while (pageToken);
return {
content: [{ type: "text", text: JSON.stringify(allAdUnits, null, 2) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `An error occurred: ${error}` }],
isError: true,
};
}
}
if (name === ToolName.LIST_APPS) {
const allApps: admob_v1.Schema$App[] = [];
let pageToken: string | undefined;
try {
// 광고 단위와 마찬가지로, 모든 앱 목록을 가져오기 위해 반복 호출합니다.
do {
const result = await admob.accounts.apps.list({
parent: `accounts/${publisherCode}`,
pageToken: pageToken,
});
if (!result.ok) {
return {
content: [{ type: "text", text: `API request failed with status: ${result.status}` }],
isError: true,
};
}
const data = result.data;
if (data.apps) {
allApps.push(...data.apps);
}
pageToken = data.nextPageToken ?? undefined;
} while (pageToken);
return {
content: [{ type: "text", text: JSON.stringify(allApps, null, 2) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `An error occurred: ${error}` }],
isError: true,
};
}
}
// 정의되지 않은 도구가 호출되면 에러를 발생시킵니다.
throw new Error(`Unknown tool: ${name}`);
});
}
훌륭합니다! 이제 도구 구현이 끝났으니, src/index.ts
파일로 돌아가서 방금 만든 configureTools()
함수를 호출해 우리 서버에 새로운 스킬을 장착해 줍시다.
// src/index.ts
// .. 다른 import 문은 그대로 둡니다.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { configureTools } from "./tools.js"; // 이 부분을 추가해 주세요.
const server = new Server(...);
// 서버가 도구를 사용할 수 있도록 설정합니다.
configureTools(server);
// ... 그 외 다른 부분은 그대로 둡니다.
편리한 테스트 환경 만들기
이제 우리가 만든 도구가 잘 작동하는지 테스트해 볼 시간입니다. 테스트에는 MCP Inspector를 사용할 건데, PUBLISHER_CODE
같은 설정값을 실행할 때마다 입력하는 건 조금 번거롭죠.
이럴 때 dotenv
패키지를 사용하면 .env
라는 파일에 환경 변수를 저장해두고, 서버가 시작될 때 자동으로 불러오게 할 수 있어 아주 편리합니다. 이렇게 하면 중요한 정보를 코드와 분리하여 안전하게 관리할 수 있다는 장점도 있고요!
먼저, 프로젝트에 dotenv
패키지를 설치합니다.
$ npm install dotenv
다음으로, 프로젝트 최상단에 .env
파일을 만들고 애드몹 게시자 코드를 추가합니다. (이 파일은 .gitignore
에 추가해서 git에 올라가지 않도록 주의하세요!)
# .env
PUBLISHER_CODE=pub-xxxxxxxxx
이제 src/index.ts
파일 맨 위에 아래 코드를 추가해서, 서버가 시작될 때 .env
파일을 읽어오도록 설정해 줍시다.
// src/index.ts
import * as dotenv from "dotenv";
dotenv.config();
// ... 나머지 코드는 그대로 둡니다.
마지막으로, package.json
에 MCP Inspector를 편하게 실행할 수 있는 스크립트를 추가해 볼까요?
// package.json
{
"scripts": {
"inspect": "npx @modelcontextprotocol/inspector node build/index.js",
...
},
...
}
두근두근, 첫 도구 테스트!
모든 준비가 끝났습니다! 아래 명령어로 MCP Inspector를 실행해서 우리의 첫 도구를 테스트해 봅시다.
$ npm run inspect
Inspector 화면이 나타나면 Tools 탭으로 이동해서 List Tools 버튼을 눌러보세요. 우리가 configureTools
함수에 정성껏 등록한 도구 목록이 짜잔! 하고 나타나는 것을 볼 수 있습니다.
이제 목록에서 get_account
를 선택하고 Run Tool 버튼을 눌러보세요. 잠시 후 애드몹 API를 통해 가져온 계정 정보가 멋지게 출력되는 것을 확인할 수 있을 겁니다. 성공입니다!
마무리하며
이번 포스트에서는 애드몹 API를 호출하는 MCP 도구를 직접 만들어 보았습니다. 생각보다 간단하지 않나요? 이제 우리는 AI에게 애드몹 계정 정보를 가져올 수 있는 능력을 부여했습니다.
다음 마지막 파트에서는 드디어 우리가 만든 이 도구를 Gemini CLI와 연결해서, “내 애드몹 계정 정보 알려줘” 같은 자연어 질문에 AI가 멋지게 대답하도록 만들어 보겠습니다. 기대해 주세요!