사용자 MCP 서버 ChatGPT 플러그인 등록 방법
직접 만든 PHP-FPM 기반 사용자 MCP 서버에 Keycloak의 OAuth 인증을 연동하고, 보호 자원 메타데이터(PRM)를 공개한다. ChatGPT 플러그인 등록에 필요한 OAuth 클라이언트와 콜백 주소, 권한 범위를 설정하고 도구 검색 오류를 확인한다. 마지막으로 ChatGPT 프롬프트를 통해 엔티티 타입 조회·검색·상세 조회가 동작하는지 확인하는 AI 기반 블로그 구조화 데이터 엔티티 관리 시스템 개발 기록 4편이다.
블로그의 구조화 데이터베이스를 AI로 관리하겠다고 시작한 일이 꽤나 멀리까지 왔다. 워드프레스 REST API를 정비하고 PHP로 MCP 서버를 만들었으며, 여기에 접근할 권한을 부여하기 위해 Keycloak으로 OAuth 인증 서버까지 준비했다.
이제 남은 건 이것들을 한데 묶는 것이다. 외부에서 MCP 서버에 접근할 수 있도록 공개하고, Keycloak이 발급한 토큰을 검사하도록 구성한 다음, ChatGPT에 등록해 실제 엔티티 정보를 조회한다.
사용자 MCP 서버와 OAuth 인증 정보 연동
사용자 MCP 서버를 AI 에이전트와 연동하기 위해서는 먼저, 이 에이전트가 적합한 권한을 가지고 접근하는지 확인할 수 있어야 한다. 또한, MCP 서버 본연의 업무를 수행하기 위해 외부 시스템에 접근할 인증 정보도 필요하다.
본 필자의 프로젝트에서는 워드프레스 REST API를 이용해 내부 데이터베이스에 접근한다. 따라서 AI 에이전트가 MCP 서버에 접근할 때의 인증과, MCP 서버가 워드프레스에 접근할 때의 인증을 각각 처리해야 한다.
Step 1. 인증 정보의 저장과 가져오기
MCP 서버가 Keycloak과 워드프레스에 요청을 보낼 때마다 아이디와 암호를 입력할 수는 없다. 인증 정보를 파일에 저장하고 접근 권한을 제한한 다음, 프로그램이 필요할 때 읽어 사용하게 한다.
validator-secret 파일에 Keycloak 검증용 클라이언트의 키를, wp-app-password 파일에 워드프레스의 어플리케이션 암호를 플래인 텍스트로 저장했다. 파일의 소유자는 root, 소속 그룹은 MCP 실행 계정이 속한 그룹으로 지정하고 권한은 640으로 설정했다.
- 소유자 : 읽기·쓰기
root가 파일을 읽고 수정할 수 있다.- 그룹 : 읽기
- 그룹에 속한 MCP 실행 계정이 인증 정보를 읽을 수 있다.
- 그 외 사용자 : 접근 불가
- 파일에 대한 읽기·쓰기·실행 권한을 부여하지 않는다.
이 두 파일은 MCP의 외부 HTTP 요청과 응답을 담당하는 server-http-protected.php에서 읽어 온다.
$clientSecret = @file_get_contents('/etc/stdtbank-mcp/validator-secret');
$clientSecret = is_string($clientSecret) ? rtrim($clientSecret, "\r\n") : '';
$password = @file_get_contents('/etc/stdtbank-mcp/wp-app-password');
$password = is_string($password) ? rtrim($password, "\r\n") : '';
여기서 $clientSecret은 Keycloak 검증용 클라이언트의 키이고, $password는 워드프레스 API의 어플리케이션 암호이다. 파일을 읽지 못하면 빈 문자열로 처리하고, 읽은 값의 끝에 줄바꿈 문자가 있으면 제거한다.
Step 2. PHP-FPM 풀에 설정값 적용
MCP 전용으로 만든 PHP-FPM 풀의 환경 변수를 이용해 설정값을 전달한다. fpm/pool.d/에 만들어 둔 풀 설정 파일에 아래의 내용을 추가한다.
clear_env = yes
env[STDT_MCP_SESSION_DIR] = MCP 세션 정보를 저장할 디렉토리 절대경로
env[STDT_REST_USERNAME] = 워드프레스의 API 이용 사용자 ID
env[STDT_REST_BASE_URL] = https://www.eqmaker.kr
env[STDT_OAUTH_ISSUER] = https://www.eqmaker.kr/auth/realms/stdtbank
env[STDT_OAUTH_RESOURCE] = https://www.eqmaker.kr/mcp/
env[STDT_OAUTH_INTROSPECT_URL] = https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/token/introspect
env[STDT_OAUTH_CLIENT_ID] = MCP가 토큰 상태 조회를 위해 Keycloak에게 전달하는 클라이언트 ID
env[STDT_OAUTH_RESOURCE_METADATA_URL] = https://www.eqmaker.kr/.well-known/oauth-protected-resource/mcp
STDT_OAUTH_ISSUER- 신뢰할 Keycloak Realm의 발급자 주소이다.
STDT_OAUTH_RESOURCE- 토큰의 Audience에 포함되어야 하는 MCP 서버의 식별값이다.
STDT_OAUTH_INTROSPECT_URL- 토큰 상태를 조회하는 주소이다.
STDT_OAUTH_RESOURCE_METADATA_URL- 인증되지 않은 클라이언트에게 안내할 보호 자원 메타데이터 주소이다.
세션용 디렉토리를 새로 만들어 주고 소유자를 MCP 실행 계정으로 준 다음 권한을 700으로 설정한다. 마지막으로 변경한 풀 설정을 적용한다.
sudo php-fpm8.3 -t sudo systemctl reload php8.3-fpm
Step 3. 토큰 상태 조회 구현
ChatGPT가 Keycloak에서 받은 액세스 토큰을 가지고 MCP로 들어오면 MCP에서 Keycloak에게 이 토큰이 맞는지 확인하는 과정이 필요하다. 이 과정은 IntrospectionClient.php에서 처리한다.
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['token' => $token, 'token_type_hint' => 'access_token'], '', '&', PHP_QUERY_RFC3986),
CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/x-www-form-urlencoded'],
CURLOPT_USERPWD => $this->clientId . ':' . $this->clientSecret,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_MAXREDIRS => 0,
]);
curl을 이용해 유효하지 않은 테스트용 토큰을 보내 응답을 확인한다. stdtbank-mcp-validator는 토큰 상태 조회에 사용하는 검증용 클라이언트 ID이다.
sudo -u mcpman bash -c ' SECRET="$(cat /키 파일의 경로와 이름)" || exit 1 curl -fsS \ --netrc-file <( printf "machine www.eqmaker.kr login stdtbank-mcp-validator password %s\n" "$SECRET" ) \ --data-urlencode "token=invalid-test-token" \ "https://www.eqmaker.kr/auth/realms/stdtbank/protocol/openid-connect/token/introspect" echo unset SECRET '
HTTP 요청은 200으로 처리되었고, 반환된 active 값은 false였다. 즉, 검증 요청은 정상적으로 처리되었고 테스트로 보낸 막토큰은 비활성으로 판정된 것이다.
Step 4. 토큰 검사 결과에 따른 접근 제어
OAuthGuard에서는 이 응답을 바탕으로 실제 요청의 허용 여부를 판단한다.
public function permits(?string $authorization): bool
{
$token = self::bearer($authorization);
if ($token === null) return false;
try {
$claims = ($this->introspect)($token);
if (!is_array($claims) || ($claims['active'] ?? null) !== true) return false;
if (($claims['iss'] ?? null) !== $this->issuer) return false;
$aud = $claims['aud'] ?? null;
if (is_string($aud)) $aud = [$aud];
if (!is_array($aud) || !in_array($this->audience, $aud, true)) return false;
$exp = $claims['exp'] ?? null;
if (!is_int($exp) || $exp <= time()) return false;
$scope = $claims['scope'] ?? null;
if (!is_string($scope) || !in_array($this->requiredScope, preg_split('/\s+/', trim($scope)) ?: [], true)) return false;
return true;
} catch (\Throwable) {
return false;
}
}
active: 토큰 활성 상태- Keycloak의 응답이 배열인지, 그 안의
active값이 불리언true인지 확인 iss: 토큰 발급자- 토큰을 발급한 주체가 설정한 Keycloak Realm과 일치하는지 확인,
STDT_OAUTH_ISSUER와 일치해야 함 aud: 토큰의 사용 대상- 오디언스에 MCP 서버가 포함되어 있는지 확인
exp: 토큰 만료 시각- 만료 시각이 이미 만료되었거나 현재 시각과 같거나 규격에 맞지 않으면 거부
scope: 허용된 권한 범위- 권한 목록을 공백으로 나누고 조회 권한인
stdtbank:read가 포함되어 있는지 확인
테스트용 토큰 정보를 이용해 허용과 거부가 제대로 동작 하는지 확인한다.
declare(strict_types=1);
require __DIR__.'/../src/OAuthGuard.php';
use STDTBankMcp\OAuthGuard;
$issuer='https://auth.example.test';
$resource='https://www.eqmaker.kr/mcp/';
$claims=['active'=>true,'iss'=>$issuer,'aud'=>[$resource],'exp'=>time()+3600,'scope'=>'openid stdtbank:read'];
$ok=0;
$test=static function (bool $value, string $label) use (&$ok): void { if (!$value) throw new RuntimeException('안됨'.$label); echo '통과 '.$label."\n"; $ok++; };
$guard=new OAuthGuard(static fn(string $t): array => $claims,$issuer,$resource);
$test($guard->permits('Bearer sample-token'), '토큰 정상');
$test(!$guard->permits(null), '토큰 없음');
$test(!$guard->permits('Basic sample-token'), '인증 방식 틀림');
$test(!$guard->permits('Bearer '), '빈 토큰');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['active'=>false]),$issuer,$resource))->permits('Bearer x'), '죽은 토큰');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['aud'=>['https://wrong.example']]),$issuer,$resource))->permits('Bearer x'), '오디언스 틀림');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['iss'=>'https://evil.test']),$issuer,$resource))->permits('Bearer x'), '발급자 틀림');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['scope'=>'openid']),$issuer,$resource))->permits('Bearer x'), '스코프 틀림');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['exp'=>time()-1]),$issuer,$resource))->permits('Bearer x'), '만료');
$test(!(new OAuthGuard(static fn()=>array_merge($claims,['exp'=>null]),$issuer,$resource))->permits('Bearer x'), '시간 제한 없음');
$test(!(new OAuthGuard(static function (): array { throw new RuntimeException('network'); },$issuer,$resource))->permits('Bearer x'), '토큰 조회 오류 안됨');
echo "PASS: $ok 개 검사\n";
사용자 MCP 서버의 외부 공개
MCP 서버와 인증 시스템이 잘 연동된 것을 확인했다. 다음은 외부에서 MCP 서버에 접근할 수 있는 경로를 열어 주는 것이다.
Step 1. 공개 호스트와 MCP 전송 처리 구성
인증을 통과했으면 요청을 PSR-7 객체로 만든 뒤 PHP MCP SDK에 전달한다. server-http-protected의 내용 중 일부이다.
$factory = new Psr17Factory();
$request = (new ServerRequestCreator($factory, $factory, $factory, $factory))->fromGlobals();
$request = $request->withHeader('Host', 'www.eqmaker.kr');$response = $server->run(new StreamableHttpTransport(
request: $request,
middleware: [
new CorsMiddleware(),
new DnsRebindingProtectionMiddleware([
'localhost',
'127.0.0.1',
'[::1]',
'www.eqmaker.kr',
]),
],
));
(new SapiEmitter())->emit($response);
호스트명은 자신의 공개 주소로 맞춰야 한다. 처음에는 인증은 되었지만 HTTP 403과 ChatGPT가 도구를 발견하지 못하는 문제가 발생했는데, 당시 헤더에 www.eqmaker.kr가 두 번 들어가고 있었다. nyholm/psr7 라이브러리의 withHeader()함수를 이용해 Host 헤더를 단일 값으로 교체했다.
Step 2. 사용자 MCP 서버의 사용 인증 주소 공개
외부의 AI에게 사용자 MCP 서버를 이용하기 위해 필요한 인증을 받아내는 서버의 주소를 알려주어야 하는데, 이 정보를 보호 자원 메타데이터(Protected Resource Metadata)라고 한다. 필요한 정보를 JSON 형태로 저장하고 외부에서 접근할 수 있도록 아파치 웹 서버의 호스트 설정을 추가한다.
{
"resource": "https://www.eqmaker.kr/mcp/",
"authorization_servers": [
"https://www.eqmaker.kr/auth/realms/stdtbank"
],
"scopes_supported": [
"stdtbank:read"
],
"bearer_methods_supported": [
"header"
]
}
resource- 사용자 MCP 서버의 공개 주소
authorization_servers- MCP 서버에 접근할 토큰을 발급하는 Keycloak Realm issuer 주소
scopes_supported- MCP 서버에서 제공하는 권한
bearer_methods_supported- HTTP Authorization 헤더에 Bearer 토큰을 넣어 전달하는
header사용
저장한 파일에 접근이 가능하도록 아파치 웹 서버의 설정을 변경한다. Alias를 통해 외부 URL과 서버 내부의 실제 파일을 연결시켜 준다. 해당 디렉토리는 웹 접근이 가능해야 하며 파일과 상위 디렉토리의 접근 권한도 필요하다. 추가적으로 응답 형식을 고정하고 캐시를 추가한다.
Alias "/.well-known/oauth-protected-resource/mcp" "저장한 JSON 파일의 절대 경로"
<Directory "JSON 파일 디렉토리">
Require all granted
</Directory>
<Location "/.well-known/oauth-protected-resource/mcp">
Header always set Content-Type "application/json; charset=utf-8"
Header always set Cache-Control "public, max-age=300"
</Location>
설정 문법을 확인하고, 오류가 없으면 아파치 설정을 다시 불러온다.
sudo apache2ctl configtest sudo systemctl reload apache2
웹 브라우저에서 공개 주소에 접근하면 JSON이 표시되어야 한다.
https://www.eqmaker.kr/.well-known/oauth-protected-resource/mcp
Step 3. MCP 서버 주소 공개
보호 자원 메타데이터(Protected Resource Metadata)의 정보를 이용해 인증 서버에서 허락을 받았으면, 이 인증 정보를 이용해 실제 MCP 도구를 확인하고 요청과 응답을 처리할 창구인 MCP 주소를 공개해야 한다. https://www.eqmaker.kr/mcp/로 들어오는 요청을 MCP 전용 PHP-FPM 풀로 전달하도록 구성한다.
먼저 아파치에서 PHP-FPM 연결에 사용할 모듈을 활성화한다.
sudo a2enmod proxy_fcgi
이어서 아파치의 HTTPS 가상호스트 설정 안에 다음 내용을 추가한다.
<LocationMatch "^/mcp/?$">
CGIPassAuth On
</LocationMatch>
ProxyPassMatch "^/mcp/?$" \
"unix:MCP 소켓의 절대 경로|fcgi://localhost/server-http-protected의 절대 경로"
^/mcp/?$/mcp와/mcp/요청 경로에 대응한다.CGIPassAuth On- ChatGPT가 보낸 Authorization 헤더를 PHP-FPM으로 전달한다. MCP 서버가 토큰을 읽고 검사하기 위해 필요하다.
unix:MCP 소켓의 절대 경로- MCP용 PHP-FPM 풀의 소켓 파일. 아파치는 이 소켓으로 PHP 실행 요청을 전달한다.
server-http-protected의 절대 경로- 토큰 검사와 MCP 요청 처리를 담당하는 PHP 진입점
아파치 설정을 다시 불러온다.
sudo systemctl reload apache2이제 외부에서 MCP 공개 주소로 요청을 보낼 수 있다. 웹 브라우저에서 MCP 주소로 접속하면 HTTP 401과 {"error":"unauthorized"}가 반환된다. 유효한 토큰이 없기 때문이다.
사용자 MCP의 ChatGPT 등록 방법
ChatGPT에 사용자 MCP 서버를 등록할 차례이다. MCP 서버 주소와 인증 방식, Keycloak 클라이언트 ID와 콜백 주소를 앞에서 준비한 구성에 맞춰 입력한다. 다음은 실제 작업 당시의 화면을 기준으로 정리한 절차이다.
GPT에 사용자 MCP를 등록하는 절차
-
플러그인 추가 선택
ChatGPT에 접속한 후 왼쪽 메뉴에서 플러그인을 선택하고, 우측의 추가 버튼에서 사용자 지정 MCP 서버를 추가하는 항목을 선택한다.
ChatGPT 플러그인 메뉴 -
기본 MCP 정보와 인증 방법 입력
사용자 지정 MCP 서버 추가 대화상자에서 다음 정보를 입력한다.
사용자 지정 MCP 서버 추가 참고로, 오른쪽의 OAuth 고급 설정창은 OAuth 고급 설정을 클릭해 펼쳐주어야 한다.
- 이름
- ChatGPT에서 구분할 MCP 연결 이름이다. 본 필자는
STDTBank로 지정했다. - 설명
- MCP 서버의 용도를 적는 선택 항목이다.
- 연결
- 서버 URL을 선택한다.
- 서버 URL
- 실제 MCP 요청을 받을 공개 주소
- 인증
- 쌔빠지게 준비한 OAuth를 선택한다.
-
OAuth 고급 설정 진입
고급 OAuth 설정을 누르면 오른쪽에 설정 창이 펼쳐진다. MCP 서버 값이 유효하다면 동적으로 읽어올 수 있는 정보들을 MCP 서버에서 가져와 표시해 준다. 바로 위 화면에서
stdtbank:read가 인식되어 표시된 모습을 볼 수 있다.
사용자 지정 OAuth 클라이언트 설정 창 -
사용자 지정 OAuth 클라이언트 선택
클라이언트 등록 방식을 사용자 지정 OAuth 클라이언트로 선택하면 추가 정보를 입력할 수 있는 창이 펼쳐진다.
- 콜백 URL
- 로그인 후 다시 돌아갈 주소로, ChatGPT가 지정한 주소이다. Keycloak의 Valid redirect URIs 주소가 이 주소로 설정되어야 한다.
- OAuth 클라이언트 ID
- Keycloak에 등록한 로그인용 클라이언트 ID
- OAuth 클라이언트 시크릿
- Public Client 구성에서는 비워 둔다. MCP 서버가 토큰 상태를 조회할 때 사용하는 검증용 클라이언트의 키를 여기에 입력하는 것이 아니다.
- 토큰 엔드포인트 인증 방식
none. 별도로 클라이언트 키를 제출하지 않는다. 즉, 인증 코드와 PKCE로 검증한다.
Public Client OAuth 클라이언트 설정 -
인증 서버와 MCP 리소스 주소 확인
스크롤을 아래로 내려 고급 OAuth 설정에 표시된 인증 서버와 리소스 정보를 확인한다. 각 주소가 앞에서 공개한 메타데이터와 일치해야 한다.
OAuth 엔드포인트와 MCP 리소스 주소 - 인증 URL
- 사용자의 로그인과 권한 부여 절차를 시작할 주소. OIDC Discovery 문서의
authorization_endpoint값을 사용한다. Realm 주소 뒤에/protocol/openid-connect/auth가 붙는다. - 토큰 URL
- ChatGPT가 인증 코드를 토큰으로 교환할 주소. OIDC Discovery 문서의
token_endpoint값을 사용한다. Realm 주소 뒤에/protocol/openid-connect/token이 붙는다. - 권한 부여 서버 기본 URL
- OIDC Discovery 문서의
issuer값인 Realm 발급자 주소.https://www.eqmaker.kr/auth/realms/stdtbank이다. - 리소스
- MCP 서버의 공개 주소. MCP 서버용 Audience 값을 사용한다.
https://www.eqmaker.kr/mcp/이다.
-
OpenID Connect 정보 확인
스크롤을 제일 아래로 내리면 OpenID 지원 섹션이 나온다. OIDC(OpenID Connect) 정보가 Realm의 Discovery 문서와 일치하는지 확인한다.
OIDC 활성화와 Discovery 정보 확인 - OIDC 구성 URL
- 인증 서버의 엔드포인트와 지원 기능을 안내하는 디스커버리 문서 주소. Realm 주소 뒤에
/.well-known/openid-configuration이 붙는다. - OIDC userinfo 엔드포인트
- 액세스 토큰을 사용해 사용자 정보를 조회하는 주소. 디스커버리 문서의
userinfo_endpoint값으로, Realm 뒤에/protocol/openid-connect/userinfo을 붙인다. - 지원되는 OIDC 범위
- 기본값을 그대로 둬도 무방하다.
-
플러그인 생성과 서비스 연결 진행
입력한 내용을 확인하고 이해했으며 계속 진행하겠습니다 항목을 체크한 후 플러그인으로 생성 버튼을 클릭한다. 이어지는 연결 안내 화면에서 STDTBank(으)로 계속을 선택한다.
ChatGPT 사용자 MCP 서버 연결 -
Keycloak 사용자 로그인
Keycloak 로그인 화면이 나타나면 사용할 Realm에 등록한 사용자 계정으로 로그인한다. ChatGPT 계정의 암호나 워드프레스 API의 어플리케이션 암호가 아니다.
인증을 위한 Keycloak 로그인 추가 화면이 나타나면 필요한 정보를 입력하고 제출한다. 이후 Keycloak의 인증 코드가 ChatGPT의 콜백 주소로 전달되고, ChatGPT는 이를 토큰으로 교환해 MCP 요청에 사용한다.
-
연결 상태와 도구 목록 확인
정상적으로 연결되면 세부 정보와 함께 ChatGPT에 연결 완료 상태가 표시된다.
사용자 MCP가 ChatGPT와 연결된 모습
일단, 초록색으로 나타나는 Primary 연결이 완료되었습니다라는 메시지를 만나면 연결이 정상적으로 이루어진 것이다. 하지만 기쁘더라도 이 화면에서 1분 정도 대기해야 한다.
등록 오류의 예
일단, 초록색으로 나타나는 Primary 연결이 완료되었습니다라는 메시지를 만나면 계정 연결이 완료된 것이다. 하지만 본 필자는 기쁘더라도 이 화면에서 약 1분 정도는 대기할 것을 권한다. 에러가 표시될 수 있기 때문이다.
본 필자는 연결 완료 메시지를 보고 10초가량 시간이 흐르고 나서 아래와 같이 빨간 에러 메시지를 만나게 되었다.
이 에러 메시지는 인증은 성공했지만 MCP 서버의 도구를 검색하는 과정은 실패했다는 의미이다. 로그인에 성공한 것과 실제 MCP 도구를 사용할 수 있는 것은 구분해야 한다.
본 필자의 경우 앞에서 설명한 Host 헤더 중복과 요청 처리 설정을 보완한 뒤 도구 검색이 정상적으로 진행되었다. 같은 메시지라도 원인은 다를 수 있으므로 MCP 서버의 토큰 검사 결과와 HTTP 응답, 요청 처리 로그를 확인해야 한다.
ChatGPT에서 동작 확인
이제 마지막으로 ChatGPT가 사용자 MCP를 실제로 사용할 수 있는지 시험해 본다. 본 필자가 만든 MCP는 현재 3가지 기능을 제공한다.
list_entity_types- 엔티티 유형과 필드 정의 조회
search_entities- 기존 엔티티를 검색
get_entity_details- ID로 특정 엔티티의 상세정보 조회
다음은 ChatGPT에서 새 채팅을 열어 사용자 MCP를 테스트한 화면이다.
ChatGPT가 연결된 MCP 조회 도구를 이용해 필요한 정보를 가져오고, 반환된 타입 정의와 엔티티 정보를 정리한 것이다. 엔티티 타입 6종과 ‘와우자 스트리밍 엔진’의 등록 여부, ID 1의 상세정보까지 조회되어 의도한 대로 돌아가고 있음을 확인했다.
마무리
ChatGPT를 비롯한 여러 AI 도구를 대화형 프롬프트 밖으로 끄집어 내어 실제 세상과 연결해 주기 위해서는 목적에 맞는 MCP를 선택하고 AI 도구와 연결해 주어야 한다.
본 필자는 목적에 맞는 MCP 서버를 만들고 ChatGPT와 연결했다. 이를 위해 MCP 서버의 공개 주소와 인증 정보를 ChatGPT에 플러그인으로 등록하고, 프롬프트를 이용해 정상적으로 동작하는 것을 확인했다.
플러그인으로 등록이 되었다고 끝나는 것은 아니다. 의도한 작업을 수행하고 결과를 가져오는 것까지 확인해야 한다.
본 글을 읽는 독자 제위께서도 유용한 AI 에이전트들을 더 잘 부려먹기 위해 적합한 MCP를 찾고, 없으면 만들어 더 잘 부려먹을 수 있게 되시기를, 그리고 그렇게 남는 시간으로 삶의 소소한 숨구멍을 누리실 수 있게 되시기를 기원하는 바이다.
FAQ
- 직접 만든 사용자 MCP 서버를 ChatGPT에 연결하려면 무엇을 준비해야 하는가?
- ChatGPT에서 접근할 수 있는 HTTPS MCP 서버 주소와 지원되는 전송 방식, 인증 설정을 준비해야 한다. OAuth를 사용한다면 인증 서버와 클라이언트 설정, 보호 자원 메타데이터, MCP 서버의 토큰 검사도 필요하다. 등록 후에는 실제 도구를 호출해 결과가 반환되는지 확인한다.
- Keycloak을 설치하면 MCP 서버에 OAuth 인증이 자동으로 적용되는가?
- Keycloak 설치만으로 MCP 서버가 보호되지는 않는다. MCP 서버에서 액세스 토큰을 검사하고 권한을 충족한 요청만 처리해야 한다. 토큰 검증은 Introspection으로 인증 서버에 상태를 조회하거나, JWT 서명을 검증하고 필요한 클레임을 확인하는 방식으로 구현할 수 있다.
- MCP 보호 자원 메타데이터와 OIDC Discovery 문서는 무엇이 다른가?
- MCP 보호 자원 메타데이터는 접근할 자원의 식별 주소와 사용할 인증 서버, 지원 Scope 등을 안내한다. OIDC Discovery 문서는 인증 서버의 발급자와 인증·토큰·사용자 정보 조회 엔드포인트, 지원 기능 등을 안내한다.
- ChatGPT에 MCP 서버를 등록할 때 워드프레스 어플리케이션 암호를 입력해야 하는가?
- OAuth로 보호된 MCP 서버에 연결할 때는 워드프레스 어플리케이션 암호를 OAuth 클라이언트 키 대신 입력하지 않는다. ChatGPT는 액세스 토큰으로 MCP 서버에 접근하고, MCP 서버가 워드프레스 REST API를 호출할 때 별도로 보관한 워드프레스 인증정보를 사용하도록 구성할 수 있다.
- OAuth 토큰 엔드포인트 인증 방식을 none으로 설정하면 사용자 로그인도 생략되는가?
none은 토큰 엔드포인트에서 클라이언트 키 등의 방식으로 클라이언트를 인증하지 않는다는 의미이다. 사용자 로그인이나 인증 코드 검증을 생략하는 설정은 아니다. Authorization Code와 PKCE를 사용하는 Public Client는 클라이언트 키 대신 인증 코드와 PKCE 검증값 등을 제출하며, MCP 서버는 발급된 토큰을 별도로 검사한다.- ChatGPT OAuth 콜백 URL의 역할은 무엇이며 Keycloak 어디에 등록하는가?
- 콜백 URL은 Keycloak에서 사용자 인증을 마친 뒤 인증 코드 등의 결과를 ChatGPT로 전달하는 주소이다. ChatGPT가 표시한 콜백 URL을 Keycloak 로그인용 클라이언트의 Valid redirect URIs에 등록한다. 인증 요청의 콜백 주소는 등록된 허용 조건과 일치해야 한다.
- ChatGPT가 특정 MCP 서버의 도구를 사용하도록 프롬프트를 작성하는 방법은?
- 사용할 MCP 연결 이름과 수행할 작업, 조회 대상을 구체적으로 명시한다. 예를 들어 STDTBank를 이용해 와우자 스트리밍 엔진을 검색하고 상세 등록정보를 조회하라라고 요청할 수 있다. 해당 연결과 도구가 채팅에서 사용 가능한 상태여야 하며, 응답의 도구 사용 내역으로 실제 호출 여부를 확인한다.
- ChatGPT의 Authentication succeeded, action discovery failed 오류는 무슨 뜻인가?
- 인증은 성공했지만 MCP 서버의 도구를 검색하는 과정은 실패했다는 의미이다. MCP 공개 주소의 응답, 토큰 검사 결과, 도구 목록 요청 처리, 허용 호스트와 프록시 설정 등을 확인한다. 오류 문구만으로 원인을 특정할 수 없으므로 서버 로그와 실제 응답을 함께 살펴야 한다.
- MCP 서버에서 HTTP 401이 반환되면 OAuth 연결에 실패한 것인가?
- 액세스 토큰 없이 보낸 요청에 대한 HTTP 401은 인증을 요구하는 응답일 수 있다.
WWW-Authenticate헤더의 인증 안내를 확인하고, 토큰을 발급받은 뒤 요청이 처리되는지 살핀다. 토큰을 전달했는데도 401이 반환된다면 토큰 전달 여부와 활성 상태, 발급자, Audience, 만료 시각 및 필요한 Scope를 확인한다. - ChatGPT에 MCP 연결 완료가 표시되면 도구 실행과 데이터베이스 수정도 가능한가?
- 연결 완료 표시만으로 모든 도구의 실행 성공을 보장하지는 않는다. 실제 도구를 호출해 반환 데이터와 오류 여부를 확인해야 한다. 데이터베이스 수정 가능 여부는 MCP 서버가 제공하는 도구와 사용자 권한에 따라 결정되며, 읽기 전용 도구만 제공하는 서버에서는 데이터를 수정할 수 없다.