[MCP] Tesla MCP 만들기 — Authorization 붙이고 LLM으로 내 차 조작해보기

MCP OAuth 서버 구현 + Tesla Fleet API — Cursor Connect 한 번이면 배터리·문 잠금·내비까지

Posted by lim.Chuck on July 22, 2026

[MCP]

  1. [DEVELOP] MCP(Model Context Protocol)
  2. [DEVELOP] MCP claude 적용하고 사용해보기
  3. [MCP] 통신 구조 flow 심층분석
  4. [MCP] AI Agent이용한 mcp서버 테스트 환경 구축해보자
  5. [MCP] 커서 mcp 추천
  6. [MCP] Playwright 사용해보기
  7. [MCP] 커서 mcp 추천2
  8. [MCP] Claude Desktop에 Voice Mode 설치하기
  9. [MCP] 커서 mcp 추천3 - 2026년 새로 떠오르는 MCP
  10. [MCP] 찾아줘 주차장! — Agentic Player 10 MCP 서버 개발 회고
  11. [MCP] Cursor에서 Figma·Sentry 로그인은 어떻게 되나
  12. [MCP] Tesla MCP 만들기 — Authorization 붙이고 LLM으로 내 차 조작해보기

이전 글에서 Figma·Sentry는 클라이언트가 OAuth로 로그인하는 구조를 봤습니다. 이번엔 반대로, 내가 만든 MCP 서버가 Authorization Server가 되는 쪽을 직접 붙여 봤습니다. 그리고 연결 대상이 문서가 아니라 내 Tesla였습니다.

  • 프로젝트: Tesla Fleet API 기반 MCP (v0.2.0, 15 tools)
  • 배포 예: https://tesla-mcp-12nv.onrender.com/mcp

한 줄 요약

Cursor Connect → MCP OAuth → Tesla 로그인 → Fleet API로 내 차 조회·제어.
AI는 tool만 고르고, 토큰은 Cursor와 MCP 서버가 각각 들고, 실제 차 명령은 내 Tesla 계정 권한으로 나갑니다.

서버 URL이 공개돼 있어도 Connect한 본인 Tesla만 접근합니다. A가 로그인하면 A 차량, B가 로그인하면 B 차량 — 남의 차를 건드리는 구조가 아닙니다.

헤더 이미지는 전체 흐름을 한눈에 보여 줍니다. 채팅 한 줄 → MCP tool → Fleet API → 내 차. OAuth 자물쇠는 두 번 붙는다는 점이 이 프로젝트의 핵심입니다.


왜 만들었나

Figma MCP OAuth 글을 쓰면서 “클라이언트 쪽 로그인”은 이해했는데, 서버 쪽 MCP Authorization은 직접 안 붙여 보면 감이 안 온다고 느꼈습니다.

그때 떠오른 게 Tesla였습니다.

  • Fleet API가 공식적으로 있음
  • “배터리 몇 %야?” “문 잠궈줘” 같은 자연어 명령이 딱 맞음
  • Remote MCP + OAuth 흐름을 끝까지 검증하기 좋음

솔직히 재미있었습니다. 채팅창에 “실내 23도로 맞춰줘” 치면 HVAC tool이 돌아가는 순간이, MCP가 왜 재밌는지 바로 체감됩니다.


이전 글과 뭐가 다른가

  Figma / Sentry MCP Tesla MCP (내가 만든 것)
OAuth 역할 서비스가 Authorization Server 내 MCP 서버가 Authorization Server
로그인 Cursor → Figma/Sentry Cursor → 내 서버 → Tesla
API Figma/Sentry REST Tesla Fleet API
토큰 저장 주로 Cursor (클라이언트) Cursor(MCP JWT) + 서버(Tesla 토큰)

이전 글은 “토큰은 Cursor가 든다”가 핵심이었고, 이번엔 서버도 OAuth를 구현해서 이중 로그인 체인을 만든 셈입니다.

왼쪽은 Cursor가 들고 있는 MCP JWT, 가운데 tesla_mcp가 Provider이면서 동시에 Tesla OAuth Client, 오른쪽은 서버가 userId별로 보관하는 Fleet access/refresh입니다. tool이 실제로 나가려면 두 토큰 체인이 모두 살아 있어야 합니다.

1
2
3
4
Cursor ──MCP OAuth──► tesla_mcp ──Tesla OAuth──► Tesla 계정
         (JWT)              (Fleet access/refresh)
                              │
                              └── Fleet API → 내 차

기술 스택

항목 선택
런타임 Node.js 20 · TypeScript
MCP @modelcontextprotocol/sdk — Streamable HTTP /mcp
MCP Auth mcpAuthRouter · requireBearerAuth · TeslaMcpOAuthProvider
Tesla Fleet API (na 리전) · Virtual Key · (선택) tesla-http-proxy
배포 Render · Docker Hub pocodingwer/tesla-mcp

찾아줘 주차장! MCP 때와 비슷하게 Express 위에 MCP SDK를 올리는 구조입니다. 차이는 Authorization 레이어를 직접 구현했다는 점입니다.


MCP Authorization — 서버에서 한 일

이전 OAuth 글에서 “AI는 토큰을 안 든다”는 표는 그대로입니다. Tesla MCP에서는 Cursor가 MCP JWT, tesla_mcp 서버가 Tesla Fleet 토큰을 각각 보관합니다. 채팅에 배터리 %가 보여도 Bearer 문자열은 안 나옵니다.

핵심 파일은 src/auth/teslaOAuthProvider.tssrc/index.ts입니다.

1. MCP OAuth 라우터 마운트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app.use(
  mcpAuthRouter({
    provider: oauthProvider,
    issuerUrl,
    scopesSupported: ["mcp:tools"],
    resourceName: MCP_NAME,
    resourceServerUrl: mcpServerUrl,
  }),
);

app.use(
  mcpAuthMetadataRouter({
    oauthMetadata,
    resourceServerUrl: mcpServerUrl,
    scopesSupported: ["mcp:tools"],
    resourceName: MCP_NAME,
  }),
);

app.use(
  "/mcp",
  requireBearerAuth({
    verifier: oauthProvider,
    requiredScopes: [],
    resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
  }),
);

Cursor가 Connect를 누르면 MCP 스펙대로 내 서버가 OAuth Provider 역할을 합니다. Cursor·Claude 같은 클라이언트는 PKCE public client로 Dynamic Client Registration을 쓰고, 메타데이터는 mcpAuthMetadataRouter가 노출합니다.

2. Tesla 로그인으로 이어지기

authorize()에서 내부 state를 만들고, 사용자를 Tesla OAuth로 redirect합니다.

1
res.redirect(buildTeslaAuthorizeUrl(internalState));

Tesla 로그인이 끝나면 /auth/tesla/callback → Tesla code를 userId와 매핑 → MCP authorization code를 Cursor redirect URI로 돌려줍니다.

Tesla OAuth 스코프에는 vehicle_device_data, vehicle_location, vehicle_cmds 등이 포함됩니다. v0.2.0에서 위치 tool을 넣으면서 vehicle_location을 추가했고, 이미 Connect한 세션은 Logout → 재로그인이 필요할 수 있습니다.

3. MCP JWT 발급

Tesla 인증이 끝나면 서버가 MCP용 access_token(JWT) 과 refresh_token을 발급합니다.
이후 /mcp 요청마다 requireBearerAuth가 JWT를 검증하고, userId어느 Tesla 계정 토큰을 쓸지 결정합니다.

4. Tesla 토큰은 서버 in-memory

1
2
const teslaTokens = new Map<string, TeslaTokenSet>();
export const sessionUsers = new Map<string, string>(); // MCP session → userId
  • Cursor → MCP Bearer JWT
  • tesla_mcp 서버 → Tesla Fleet access/refresh (사용자별)

이전 OAuth 글에서 “AI는 토큰을 안 든다”는 말은 그대로고, 여기서 추가되는 건 MCP 서버가 Tesla 토큰을 대신 보관한다는 점입니다.

서버 재시작 시 in-memory라 MCP Logout → Connect 재인증이 필요합니다. 프로덕션에서는 Redis 등으로 옮기는 게 다음 단계입니다.


15개 tool — 뭘 할 수 있나

조회·기후·출입·충전·내비로 나뉜 15개 tool입니다. LLM은 자연어를 오른쪽 tool 이름으로 바꿉니다. wake_vehicle은 조회 전에 자주 끼어드는 전처리 tool이고, get_virtual_key_link는 제어 명령이 막혔을 때 먼저 확인하는 용도입니다.

구분 Tool
조회 list_vehicles, get_vehicle_status, get_vehicle_location, wake_vehicle
기후 set_climate, set_seat_climate, set_steering_wheel_heat
출입 set_door_lock, actuate_trunk
충전 set_charge_port, control_charging
기타 set_sentry_mode, control_windows, send_navigation, get_virtual_key_link

Fleet API 지원 범위 안만 넣었습니다. 테슬라 앱 전용 기능(세차 모드 등)이나 API 미지원(글로브박스 등)은 의도적으로 빼 두었습니다.

¹ get_vehicle_location은 오너 계정 + vehicle_location 스코프 필요. 공유 운전자(DRIVER)는 좌표가 비어 올 수 있음 → 아래 삽질 메모 §3 참고.

채팅으로 해본 것

1
2
3
4
5
6
7
내 테슬라 배터리 알려줘
차 깨우고 상태 알려줘
실내 온도 23도로 맞춰줘
문 잠궈줘
프렁크 열어줘
충전 한도 90%로
강남역으로 내비 보내줘

조회는 비교적 잘 됩니다. 명령(잠금·트렁크·충전) 은 Virtual Key 페어링 + (대부분) tesla-http-proxy 명령 서명이 추가로 필요합니다. “API 붙였는데 왜 안 돼?”의 80%는 여기서 걸립니다.


삽질 메모

1. 차가 offline / asleep

주차 후 차가 자동으로 잠듭니다. get_vehicle_status만 치면 vehicle unavailable이 나올 수 있습니다.

해결: wake_vehicle 먼저 → online 될 때까지 대기(최대 60초) → 다시 조회.

2. Virtual Key + 명령 서명

Fleet API 원격 명령은 Virtual Key 등록이 필요하고, 많은 명령은 서명된 프록시를 거칩니다. 조회(get_vehicle_status)만 할 때와 제어(set_door_lock)할 때 요구사항이 다릅니다 — 아래 아키텍처에서 Fleet API 옆 (+ tesla-http-proxy) 분기가 그 차이입니다.

1
./tesla-http-proxy -key-file keys/private-key.pem -host 127.0.0.1 -port 4443
TESLA_COMMAND_PROXY_URL=https://127.0.0.1:4443

proxy 없이 Fleet API 직접 호출은 일부 차량·명령에서만 동작합니다. 제어가 막히면 Virtual Key → proxy 순으로 확인합니다.

3. 위치 스코프 + 공유 운전자

get_vehicle_location은 다음이 모두 맞아야 합니다.

  • 오너 계정으로 Connect (공유 운전자 access_type: DRIVER는 정밀 위치가 API로 안 내려올 수 있음)
  • Tesla OAuth에 vehicle_location 스코프 허용
  • 차량 online (wake_vehicle 선행)

스코프를 서버에 추가한 뒤엔 Logout → Connect로 재로그인해야 합니다. 그래도 좌표가 비면 응답의 hide_private계정 권한 문제일 가능성이 큽니다 — Tesla 앱에서 위치가 보이는지 먼저 확인하면 됩니다.

4. Render cold start

무료 플랜은 15분 미사용 시 슬립 → 첫 요청 30초~1분. MCP Connect 직후 첫 tool이 느리면 서버가 깨는 중일 수 있습니다.

5. 테슬라 앱이 더 빠름

일상 제어는 Tesla 앱이 낫습니다. 이 MCP는 “AI 대화 안에서 차 상태 묻기”, “다른 MCP와 시나리오 묶기” 용도에 맞춰 두었습니다.


Cursor에서 연결하기

실제 순서는 위 그림과 같습니다. ① Connect② 브라우저 Tesla 로그인③ MCP JWT 발급④ 채팅으로 자연어⑤ tool 결과. ②에서 Tesla 계정을 바꾸면 이후 조회·제어 대상 차량도 그 계정 기준으로 바뀝니다.

.cursor/mcp.json:

1
2
3
4
5
6
7
{
  "mcpServers": {
    "tesla": {
      "url": "https://YOUR_DOMAIN/mcp"
    }
  }
}
  1. Settings → MCP → teslaConnect
  2. 브라우저에서 Tesla 로그인
  3. “내 테슬라 배터리 알려줘”

URL 끝 /mcp 필수입니다. OAuth 글에서 본 것처럼, Connect 후 Logout이 보이면 MCP JWT 세션이 붙은 상태입니다. Tesla 로그인까지 끝났는데 tool이 401이면 서버 쪽 Tesla 토큰 만료·재시작(in-memory 초기화)을 의심하면 됩니다.

로컬 개발 시 AUTH_DISABLED=true + TESLA_DEV_ACCESS_TOKEN으로 OAuth를 건너뛸 수도 있습니다.

Claude에서 연결하기

Cursor와 동일하게 커스텀 커넥터에 Remote MCP URL만 넣으면 됩니다.

항목
이름 Tesla MCP
URL https://YOUR_DOMAIN/mcp
OAuth Client ID/Secret 비워 둠 (Dynamic Client Registration)

추가 후 Connect → 브라우저 Tesla 로그인. 흐름은 Cursor와 같습니다.

Fleet 파트너 등록 (배포 후 1회)

공개 HTTPS 도메인으로 배포한 뒤, 로컬에서 한 번 실행합니다.

1
2
3
4
5
PUBLIC_BASE_URL=https://YOUR_DOMAIN \
TESLA_CLIENT_ID=... \
TESLA_CLIENT_SECRET=... \
TESLA_PUBLIC_KEY_PATH=./deploy/public-key.pem \
npm run register:partner

Virtual Key용 공개키/.well-known/appspecific/com.tesla.3p.public-key.pem 에 서빙됩니다. 개인키(keys/private-key.pem)는 Git·블로그 어디에도 올리지 않습니다.


아키텍처

전체 컴포넌트 배치입니다. /mcp 앞의 requireBearerAuth가 MCP JWT를 검증하고, tool handler 안에서 userId로 Tesla 토큰을 꺼내 Fleet API를 호출합니다.

조회 API는 Fleet API만으로 충분한 경우가 많고, 잠금·트렁크·충전 같은 명령은 오른쪽 tesla-http-proxy 분기로 서명된 요청을 보냅니다. Virtual Key용 public-key.pem은 Fleet API가 차량과 신뢰 관계를 맺을 때 씁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Cursor / PlayMCP]
      │  Connect (MCP OAuth, PKCE)
      ▼
[tesla_mcp /mcp]  ← requireBearerAuth (JWT)
      │
      ├─ TeslaMcpOAuthProvider (MCP JWT 발급·검증)
      ├─ tokenStore (Tesla access/refresh, userId별)
      │
      ├─ GET  /auth/tesla/callback  (Tesla OAuth 복귀)
      ├─ GET  /.well-known/appspecific/com.tesla.3p.public-key.pem  (Virtual Key)
      │
      ▼
[Tesla Fleet API]  (+ tesla-http-proxy, 명령 서명 시)
      ▼
[내 차]

이전 MCP 글들과 이어지는 지점

이번 프로젝트에서 쓴 것
stdio / SSE Streamable HTTP transport
테스트 환경 /health, tool 등록 패턴
찾아줘 주차장! Express + MCP SDK, KC/Render 배포 감각
Cursor OAuth 이번엔 서버가 Provider — 개념이 맞물림

클라이언트 OAuth만 알면 “로그인 UI가 생겼구나”에서 끝나는데, 직접 Provider를 구현해 보니 authorize → callback → exchangeAuthorizationCodeverifyAccessToken 흐름이 손에 잡힙니다.


실제 스크린샷으로 더 보강하면 좋은 것

다이어그램은 개념 정리용이고, 아래는 직접 찍어 넣으면 글이 훨씬 살아납니다.

캡처 대상 설명
Cursor Settings → MCP → tesla Connected Logout·tool 개수(15개)가 보이면 MCP JWT 세션 OK
브라우저 Tesla OAuth 동의 화면 이중 OAuth 중 두 번째 로그인 — Fleet API 스코프 확인
채팅 + TOOL 호출 “배터리 알려줘” → get_vehicle_status 인자만 보이고 Bearer는 안 보임
tool 결과 JSON 배터리 %, 실내 온도, state: online/asleep 등 실제 응답
wake_vehicle 후 재조회 asleep → wake → online 전환 과정 (삽질 메모와 연결)
위치 조회 실패 메시지 공유 운전자·스코프 미허용 시 tool이 빈 값 대신 안내 문구 반환

토큰·VIN·위치 좌표는 마스킹해서 올리면 됩니다.


마무리

Tesla MCP는 “재미 프로젝트”에 가깝지만, 배운 건 꽤 실전입니다.

  1. MCP Authorization을 서버에 붙이는 방법
  2. 이중 OAuth — MCP 클라이언트 ↔ 내 서버 ↔ Tesla
  3. Fleet API·Virtual Key·wake·명령 서명 같은 IoT API 현실
  4. LLM으로 내 차를 조작해 보는 경험 (이건 진짜 재밌음)

다음에 손대고 싶은 것: Tesla 토큰 Redis 영속화, PlayMCP 등록, 스타터 메시지로 “배터리 + 문 잠금” 데모.