coupon-moa-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,9 +40,40 @@ Coupon Moa 어드민 MCP Server. Claude Desktop 또는 Claude Code에서 어드
40
40
  }
41
41
  ```
42
42
 
43
+ ### Codex
44
+
45
+ CLI로 추가:
46
+
47
+ ```bash
48
+ codex mcp add coupon-moa -- npx -y coupon-moa-mcp
49
+ ```
50
+
51
+ 또는 `~/.codex/config.toml` (또는 프로젝트 루트 `.codex/config.toml`)에 직접 추가:
52
+
53
+ ```toml
54
+ [mcp_servers.coupon-moa]
55
+ command = "npx"
56
+ args = ["-y", "coupon-moa-mcp"]
57
+
58
+ [mcp_servers.coupon-moa.env]
59
+ ADMIN_API_URL = "https://your-api-url.com"
60
+ ```
61
+
43
62
  ## 인증
44
63
 
45
- 처음 실행 브라우저가 열리면서 Google 로그인을 요청합니다. 로그인 완료 후 토큰이 `~/.coupon-moa-mcp/token.json`에 저장되며, 이후 자동으로 사용됩니다.
64
+ MCP를 사용하기 전에 먼저 로그인이 필요합니다:
65
+
66
+ ```bash
67
+ ADMIN_API_URL=https://your-api-url.com npx coupon-moa-mcp login
68
+ ```
69
+
70
+ 브라우저가 열리면 Google 로그인 후 토큰이 `~/.coupon-moa-mcp/token.json`에 저장됩니다. 이후 MCP 실행 시 자동으로 사용됩니다.
71
+
72
+ 로그아웃:
73
+
74
+ ```bash
75
+ npx coupon-moa-mcp logout
76
+ ```
46
77
 
47
78
  ## Tools
48
79
 
@@ -4,4 +4,12 @@ import { pathToFileURL } from 'node:url';
4
4
 
5
5
  register('tsx/esm', pathToFileURL('./'));
6
6
 
7
- const { default: _ } = await import('../src/index.ts');
7
+ const command = process.argv[2];
8
+
9
+ if (command === 'login') {
10
+ await import('../src/cli-login.ts');
11
+ } else if (command === 'logout') {
12
+ await import('../src/cli-logout.ts');
13
+ } else {
14
+ await import('../src/index.ts');
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coupon-moa-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for Coupon Moa admin — push store data, query brands/cities/stores via Claude",
5
5
  "private": false,
6
6
  "type": "module",
package/src/auth.ts CHANGED
@@ -14,16 +14,20 @@ interface StoredTokens {
14
14
  savedAt: number;
15
15
  }
16
16
 
17
+ export async function loadStoredToken(): Promise<string | null> {
18
+ const stored = await loadTokens();
19
+ return stored?.accessToken ?? null;
20
+ }
21
+
17
22
  export async function getAccessToken(apiUrl: string): Promise<string> {
18
23
  // 저장된 토큰 확인
19
24
  const stored = await loadTokens();
20
25
  if (stored) {
21
- // accessToken 유효성은 API 호출 시 확인 — 여기서는 일단 반환
22
26
  return stored.accessToken;
23
27
  }
24
28
 
25
- // 토큰 없으면 OAuth 로그인
26
- console.error('[MCP] 로그인이 필요합니다. 브라우저가 열립니다...');
29
+ // 토큰 없으면 OAuth 로그인 (CLI login 명령에서만 호출)
30
+ console.log('브라우저에서 로그인해주세요...');
27
31
  const tokens = await startOAuthFlow(apiUrl);
28
32
  await saveTokens(tokens);
29
33
  return tokens.accessToken;
@@ -0,0 +1,24 @@
1
+ import { getAccessToken, clearTokens } from './auth.js';
2
+
3
+ const apiUrl = process.env.ADMIN_API_URL || 'http://localhost:8080';
4
+
5
+ console.log('=== coupon-moa-mcp 로그인 ===');
6
+ console.log(`API URL: ${apiUrl}`);
7
+ console.log('');
8
+
9
+ try {
10
+ // 기존 토큰 삭제 후 새로 로그인
11
+ await clearTokens();
12
+ const token = await getAccessToken(apiUrl);
13
+ console.log('');
14
+ console.log('로그인 성공! 토큰이 저장되었습니다.');
15
+ console.log(`토큰 미리보기: ${token.slice(0, 20)}...`);
16
+ console.log('');
17
+ console.log('이제 Claude Desktop/Code/Codex에서 coupon-moa MCP를 사용할 수 있습니다.');
18
+ } catch (error) {
19
+ const message = error instanceof Error ? error.message : 'Unknown error';
20
+ console.error(`로그인 실패: ${message}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ process.exit(0);
@@ -0,0 +1,12 @@
1
+ import { clearTokens } from './auth.js';
2
+
3
+ try {
4
+ await clearTokens();
5
+ console.log('로그아웃 완료. 저장된 토큰이 삭제되었습니다.');
6
+ } catch (error) {
7
+ const message = error instanceof Error ? error.message : 'Unknown error';
8
+ console.error(`로그아웃 실패: ${message}`);
9
+ process.exit(1);
10
+ }
11
+
12
+ process.exit(0);
package/src/index.ts CHANGED
@@ -8,7 +8,11 @@ import {
8
8
  } from './tools/list-queries.js';
9
9
 
10
10
  const api = new AdminApiClient();
11
- await api.initialize();
11
+ try {
12
+ await api.initialize();
13
+ } catch (error) {
14
+ console.error(`[MCP] ${error instanceof Error ? error.message : error}`);
15
+ }
12
16
 
13
17
  const server = new McpServer({
14
18
  name: 'coupon-moa-admin',
@@ -1,4 +1,4 @@
1
- import { getAccessToken, refreshAccessToken, clearTokens } from '../auth.js';
1
+ import { loadStoredToken, refreshAccessToken, clearTokens } from '../auth.js';
2
2
 
3
3
  const DEFAULT_API_URL = 'http://localhost:8080';
4
4
 
@@ -11,12 +11,20 @@ export class AdminApiClient {
11
11
  }
12
12
 
13
13
  async initialize(): Promise<void> {
14
- this.token = await getAccessToken(this.apiUrl);
14
+ const stored = await loadStoredToken();
15
+ if (stored) {
16
+ this.token = stored;
17
+ return;
18
+ }
19
+
20
+ throw new Error(
21
+ '로그인이 필요합니다. 먼저 `npx coupon-moa-mcp login` 을 실행해주세요.'
22
+ );
15
23
  }
16
24
 
17
25
  async send(method: string, params: unknown): Promise<unknown> {
18
26
  if (!this.token) {
19
- await this.initialize();
27
+ throw new Error('로그인이 필요합니다. `npx coupon-moa-mcp login` 을 실행해주세요.');
20
28
  }
21
29
 
22
30
  let response = await this.fetchApi(method, params);
@@ -28,10 +36,8 @@ export class AdminApiClient {
28
36
  this.token = newToken;
29
37
  response = await this.fetchApi(method, params);
30
38
  } else {
31
- // refresh 실패 → 재로그인
32
39
  await clearTokens();
33
- this.token = await getAccessToken(this.apiUrl);
34
- response = await this.fetchApi(method, params);
40
+ throw new Error('토큰이 만료되었습니다. `npx coupon-moa-mcp login` 으로 다시 로그인해주세요.');
35
41
  }
36
42
  }
37
43