@yoonion/mimi-seed-mcp 0.3.18 → 0.3.20

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.
@@ -0,0 +1,156 @@
1
+ import { z } from 'zod';
2
+ import { loadCiConfig, requireCiConfig, saveCiConfig } from '../ci/config.js';
3
+ import * as github from '../ci/github.js';
4
+ import * as gitlab from '../ci/gitlab.js';
5
+ function dispatch(fn, cfg, ...args) {
6
+ return fn[cfg.provider](cfg, ...args);
7
+ }
8
+ export function registerCiTools(server) {
9
+ server.tool('ci_save_config', [
10
+ 'GitHub Actions 또는 GitLab CI 연결 설정을 저장합니다.',
11
+ '저장 위치: ~/.mimi-seed/ci.json (mode 0600).',
12
+ 'GitHub: provider="github", token="ghp_..." (repo+workflow 스코프 필요)',
13
+ 'GitLab.com: provider="gitlab", token="glpat-..."',
14
+ 'Self-hosted GitLab: host="https://gitlab.example.com" 추가',
15
+ ].join(' '), {
16
+ provider: z.enum(['github', 'gitlab']).describe('CI 프로바이더'),
17
+ token: z.string().describe('Personal Access Token'),
18
+ owner: z.string().describe('GitHub org/user 또는 GitLab namespace'),
19
+ repo: z.string().describe('저장소 이름 (경로 없이 repo명만)'),
20
+ host: z.string().optional().describe('GitLab self-hosted URL (기본: https://gitlab.com)'),
21
+ }, async ({ provider, token, owner, repo, host }) => {
22
+ saveCiConfig({ provider, token, owner, repo, host });
23
+ const current = loadCiConfig();
24
+ return {
25
+ content: [{
26
+ type: 'text',
27
+ text: [
28
+ `✅ CI 설정 저장 완료 (${current.provider})`,
29
+ ` 저장소: ${current.owner}/${current.repo}`,
30
+ current.host ? ` Host: ${current.host}` : '',
31
+ '',
32
+ 'ci_list_workflows 로 사용 가능한 워크플로를 확인하세요.',
33
+ ].filter(Boolean).join('\n'),
34
+ }],
35
+ };
36
+ });
37
+ server.tool('ci_list_workflows', [
38
+ 'GitHub Actions 워크플로 목록 또는 GitLab 파이프라인 스케줄/트리거 목록을 조회합니다.',
39
+ 'ci_trigger_build의 workflow 파라미터에 파일명(deploy.yml)을 사용하세요.',
40
+ ].join(' '), {}, async () => {
41
+ const cfg = requireCiConfig();
42
+ const result = cfg.provider === 'github'
43
+ ? await github.listWorkflows(cfg)
44
+ : await gitlab.listWorkflows(cfg);
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: JSON.stringify(result, null, 2),
49
+ }],
50
+ };
51
+ });
52
+ server.tool('ci_trigger_build', [
53
+ '빌드를 트리거합니다.',
54
+ 'GitHub: workflow 필수 (파일명 "deploy.yml" 또는 숫자 ID). workflow_dispatch 트리거가 설정된 워크플로만 실행 가능.',
55
+ 'GitLab: workflow 불필요 — ref(브랜치)만 지정하면 .gitlab-ci.yml 즉시 실행.',
56
+ '완료 직후 최신 빌드 정보를 반환합니다. run_id를 ci_get_build_status에 사용하세요.',
57
+ ].join(' '), {
58
+ ref: z.string().default('main').describe('브랜치 또는 태그 (기본: main)'),
59
+ workflow: z.string().optional().describe('GitHub 전용: 워크플로 파일명 또는 ID (예: deploy.yml)'),
60
+ inputs: z.record(z.string()).optional().describe('워크플로 입력값 (GitHub inputs / GitLab variables)'),
61
+ }, async ({ ref, workflow, inputs }) => {
62
+ const cfg = requireCiConfig();
63
+ let result;
64
+ if (cfg.provider === 'github') {
65
+ if (!workflow)
66
+ throw new Error('GitHub 빌드 트리거에는 workflow 파라미터가 필요합니다. (예: "deploy.yml")');
67
+ result = await github.triggerBuild(cfg, workflow, ref, inputs ?? {});
68
+ }
69
+ else {
70
+ result = await gitlab.triggerBuild(cfg, ref, inputs ?? {});
71
+ }
72
+ if (!result) {
73
+ return {
74
+ content: [{
75
+ type: 'text',
76
+ text: '✅ 빌드 트리거 완료. run_id 조회 불가 — 잠시 후 ci_list_recent_builds로 확인하세요.',
77
+ }],
78
+ };
79
+ }
80
+ return {
81
+ content: [{
82
+ type: 'text',
83
+ text: [
84
+ `✅ 빌드 트리거 완료`,
85
+ ` run_id: ${result.id}`,
86
+ ` 상태: ${result.status}`,
87
+ ` 브랜치: ${result.branch}`,
88
+ result.commit ? ` 커밋: ${result.commit}` : '',
89
+ ` URL: ${result.url}`,
90
+ '',
91
+ `ci_get_build_status(run_id="${result.id}") 로 진행 상황을 확인하세요.`,
92
+ ].filter(Boolean).join('\n'),
93
+ }],
94
+ };
95
+ });
96
+ server.tool('ci_get_build_status', '특정 빌드(GitHub Actions run / GitLab pipeline)의 현재 상태를 조회합니다.', {
97
+ run_id: z.string().describe('빌드 ID (ci_trigger_build 또는 ci_list_recent_builds 반환값)'),
98
+ }, async ({ run_id }) => {
99
+ const cfg = requireCiConfig();
100
+ const build = cfg.provider === 'github'
101
+ ? await github.getBuildStatus(cfg, run_id)
102
+ : await gitlab.getBuildStatus(cfg, run_id);
103
+ const statusEmoji = {
104
+ pending: '⏳', running: '🔄', success: '✅', failed: '❌', cancelled: '⛔',
105
+ };
106
+ const emoji = statusEmoji[build.status] ?? '❓';
107
+ return {
108
+ content: [{
109
+ type: 'text',
110
+ text: [
111
+ `${emoji} 빌드 #${build.id} — ${build.status.toUpperCase()}`,
112
+ build.workflow ? ` 워크플로: ${build.workflow}` : '',
113
+ ` 브랜치: ${build.branch}`,
114
+ build.commit ? ` 커밋: ${build.commit}` : '',
115
+ ` 생성: ${build.createdAt}`,
116
+ ` 갱신: ${build.updatedAt}`,
117
+ ` URL: ${build.url}`,
118
+ ].filter(Boolean).join('\n'),
119
+ }],
120
+ };
121
+ });
122
+ server.tool('ci_list_recent_builds', '최근 빌드 목록을 조회합니다. 특정 브랜치나 워크플로로 필터링 가능.', {
123
+ workflow: z.string().optional().describe('GitHub 전용: 워크플로 파일명 또는 ID'),
124
+ ref: z.string().optional().describe('GitLab 전용: 브랜치 필터'),
125
+ limit: z.number().int().min(1).max(50).default(10).describe('최대 개수 (기본: 10)'),
126
+ }, async ({ workflow, ref, limit }) => {
127
+ const cfg = requireCiConfig();
128
+ const builds = cfg.provider === 'github'
129
+ ? await github.listRecentBuilds(cfg, workflow, limit)
130
+ : await gitlab.listRecentBuilds(cfg, ref, limit);
131
+ if (builds.length === 0) {
132
+ return { content: [{ type: 'text', text: '최근 빌드 없음.' }] };
133
+ }
134
+ const statusEmoji = {
135
+ pending: '⏳', running: '🔄', success: '✅', failed: '❌', cancelled: '⛔',
136
+ };
137
+ const lines = builds.map((b) => {
138
+ const emoji = statusEmoji[b.status] ?? '❓';
139
+ const parts = [
140
+ `${emoji} #${b.id}`,
141
+ b.workflow ? `[${b.workflow}]` : '',
142
+ b.branch,
143
+ b.commit ? `@${b.commit}` : '',
144
+ `→ ${b.status}`,
145
+ `(${new Date(b.createdAt).toLocaleString('ko-KR')})`,
146
+ ].filter(Boolean);
147
+ return parts.join(' ');
148
+ });
149
+ return {
150
+ content: [{
151
+ type: 'text',
152
+ text: `최근 빌드 ${builds.length}개:\n\n${lines.join('\n')}`,
153
+ }],
154
+ };
155
+ });
156
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerFirebaseTools(server: McpServer): void;
@@ -0,0 +1,122 @@
1
+ import { z } from 'zod';
2
+ import * as firebase from '../firebase/tools.js';
3
+ import { requireAuth } from '../helpers.js';
4
+ export function registerFirebaseTools(server) {
5
+ server.tool('firebase_list_projects', '내 Firebase 프로젝트 목록 조회', {}, async () => {
6
+ const auth = requireAuth();
7
+ const projects = await firebase.listProjects(auth);
8
+ return { content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }] };
9
+ });
10
+ server.tool('firebase_get_project', 'Firebase 프로젝트 상세 정보 조회', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
11
+ const auth = requireAuth();
12
+ const project = await firebase.getProject(auth, projectId);
13
+ return { content: [{ type: 'text', text: JSON.stringify(project, null, 2) }] };
14
+ });
15
+ server.tool('firebase_list_android_apps', 'Firebase 프로젝트의 Android 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
16
+ const auth = requireAuth();
17
+ const apps = await firebase.listAndroidApps(auth, projectId);
18
+ return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
19
+ });
20
+ server.tool('firebase_create_android_app', 'Firebase에 새 Android 앱 등록', {
21
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
22
+ packageName: z.string().describe('Android 패키지명 (예: com.example.myapp)'),
23
+ displayName: z.string().describe('앱 표시 이름'),
24
+ }, async ({ projectId, packageName, displayName }) => {
25
+ const auth = requireAuth();
26
+ const result = await firebase.createAndroidApp(auth, projectId, packageName, displayName);
27
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
28
+ });
29
+ server.tool('firebase_get_android_config', 'google-services.json 다운로드', {
30
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
31
+ appId: z.string().describe('Firebase 앱 ID'),
32
+ }, async ({ projectId, appId }) => {
33
+ const auth = requireAuth();
34
+ const config = await firebase.getAndroidConfig(auth, projectId, appId);
35
+ return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
36
+ });
37
+ server.tool('firebase_delete_android_app', 'Firebase Android 앱 삭제', {
38
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
39
+ appId: z.string().describe('삭제할 Firebase 앱 ID'),
40
+ }, async ({ projectId, appId }) => {
41
+ const auth = requireAuth();
42
+ const result = await firebase.deleteAndroidApp(auth, projectId, appId);
43
+ return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
44
+ });
45
+ server.tool('firebase_list_ios_apps', 'Firebase 프로젝트의 iOS 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
46
+ const auth = requireAuth();
47
+ const apps = await firebase.listIosApps(auth, projectId);
48
+ return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
49
+ });
50
+ server.tool('firebase_create_ios_app', 'Firebase에 새 iOS 앱 등록', {
51
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
52
+ bundleId: z.string().describe('iOS Bundle ID (예: com.example.myapp)'),
53
+ displayName: z.string().describe('앱 표시 이름'),
54
+ }, async ({ projectId, bundleId, displayName }) => {
55
+ const auth = requireAuth();
56
+ const result = await firebase.createIosApp(auth, projectId, bundleId, displayName);
57
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
58
+ });
59
+ server.tool('firebase_get_ios_config', 'GoogleService-Info.plist 다운로드', {
60
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
61
+ appId: z.string().describe('Firebase 앱 ID'),
62
+ }, async ({ projectId, appId }) => {
63
+ const auth = requireAuth();
64
+ const config = await firebase.getIosConfig(auth, projectId, appId);
65
+ return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
66
+ });
67
+ server.tool('firebase_delete_ios_app', 'Firebase iOS 앱 삭제', {
68
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
69
+ appId: z.string().describe('삭제할 Firebase 앱 ID'),
70
+ }, async ({ projectId, appId }) => {
71
+ const auth = requireAuth();
72
+ const result = await firebase.deleteIosApp(auth, projectId, appId);
73
+ return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
74
+ });
75
+ server.tool('firebase_list_web_apps', 'Firebase 프로젝트의 Web 앱 목록', { projectId: z.string().describe('Firebase 프로젝트 ID') }, async ({ projectId }) => {
76
+ const auth = requireAuth();
77
+ const apps = await firebase.listWebApps(auth, projectId);
78
+ return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] };
79
+ });
80
+ server.tool('firebase_create_web_app', 'Firebase에 새 Web 앱 등록', {
81
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
82
+ displayName: z.string().describe('앱 표시 이름'),
83
+ }, async ({ projectId, displayName }) => {
84
+ const auth = requireAuth();
85
+ const result = await firebase.createWebApp(auth, projectId, displayName);
86
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
87
+ });
88
+ server.tool('firebase_get_web_config', 'Firebase Web 설정 (firebaseConfig 객체) 조회', {
89
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
90
+ appId: z.string().describe('Firebase 앱 ID'),
91
+ }, async ({ projectId, appId }) => {
92
+ const auth = requireAuth();
93
+ const config = await firebase.getWebConfig(auth, projectId, appId);
94
+ return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }] };
95
+ });
96
+ server.tool('firebase_delete_web_app', 'Firebase Web 앱 삭제', {
97
+ projectId: z.string().describe('Firebase 프로젝트 ID'),
98
+ appId: z.string().describe('삭제할 Firebase 앱 ID'),
99
+ }, async ({ projectId, appId }) => {
100
+ const auth = requireAuth();
101
+ const result = await firebase.deleteWebApp(auth, projectId, appId);
102
+ return { content: [{ type: 'text', text: `삭제 완료: ${JSON.stringify(result)}` }] };
103
+ });
104
+ server.tool('firebase_enable_service', 'GCP 서비스 활성화 (예: firestore.googleapis.com)', {
105
+ projectId: z.string().describe('프로젝트 ID'),
106
+ serviceId: z.string().describe('서비스 ID (예: firestore.googleapis.com)'),
107
+ }, async ({ projectId, serviceId }) => {
108
+ const auth = requireAuth();
109
+ const result = await firebase.enableService(auth, projectId, serviceId);
110
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
111
+ });
112
+ server.tool('firebase_enable_common_services', 'Firebase 기본 서비스 일괄 활성화 (Firestore, Auth, Storage, FCM 등)', { projectId: z.string().describe('프로젝트 ID') }, async ({ projectId }) => {
113
+ const auth = requireAuth();
114
+ const results = await firebase.enableCommonServices(auth, projectId);
115
+ return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
116
+ });
117
+ server.tool('firebase_list_enabled_services', '프로젝트에서 활성화된 GCP 서비스 목록', { projectId: z.string().describe('프로젝트 ID') }, async ({ projectId }) => {
118
+ const auth = requireAuth();
119
+ const services = await firebase.listEnabledServices(auth, projectId);
120
+ return { content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] };
121
+ });
122
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerIamTools(server: McpServer): void;
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import * as iam from '../iam/tools.js';
3
+ import { requireAuth } from '../helpers.js';
4
+ export function registerIamTools(server) {
5
+ server.tool('iam_list_service_accounts', '주어진 projectId의 서비스 계정 목록. 이메일 / displayName / disabled 상태 반환.', {
6
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
7
+ }, async ({ projectId }) => {
8
+ const auth = requireAuth();
9
+ const accounts = await iam.listServiceAccounts(auth, projectId);
10
+ return { content: [{ type: 'text', text: JSON.stringify(accounts, null, 2) }] };
11
+ });
12
+ server.tool('iam_create_service_account', '새 서비스 계정 생성. accountId는 이메일의 로컬 파트 (예: "onesub-play-verifier" → onesub-play-verifier@<project>.iam.gserviceaccount.com).', {
13
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
14
+ accountId: z
15
+ .string()
16
+ .regex(/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/, 'lowercase letters, digits, hyphens; 6-30 chars')
17
+ .describe('서비스 계정 ID (이메일 로컬 파트)'),
18
+ displayName: z.string().describe('사람이 읽을 표시 이름 (예: "onesub Play verifier")'),
19
+ }, async ({ projectId, accountId, displayName }) => {
20
+ const auth = requireAuth();
21
+ const account = await iam.createServiceAccount(auth, projectId, accountId, displayName);
22
+ return {
23
+ content: [
24
+ {
25
+ type: 'text',
26
+ text: [
27
+ '✓ 서비스 계정 생성 완료',
28
+ '',
29
+ `**email**: \`${account.email}\``,
30
+ `**displayName**: ${account.displayName}`,
31
+ `**uniqueId**: \`${account.uniqueId}\``,
32
+ '',
33
+ '다음 단계:',
34
+ `1. \`iam_create_key("${account.email}")\` — JSON 키 발급`,
35
+ `2. \`playstore_verify_service_account\` 로 Play Console 권한까지 확인 (Play Console에서 수동으로 이메일 초대 + 'View financial data' 권한 부여는 별도)`,
36
+ ].join('\n'),
37
+ },
38
+ ],
39
+ };
40
+ });
41
+ server.tool('iam_list_keys', '주어진 서비스 계정의 기존 키 목록. keyId / keyType / 만료시간 반환. 회수할 키 찾을 때 사용.', {
42
+ serviceAccount: z.string().describe('서비스 계정 이메일'),
43
+ }, async ({ serviceAccount }) => {
44
+ const auth = requireAuth();
45
+ const keys = await iam.listServiceAccountKeys(auth, serviceAccount);
46
+ return { content: [{ type: 'text', text: JSON.stringify(keys, null, 2) }] };
47
+ });
48
+ server.tool('iam_create_key', [
49
+ '주어진 서비스 계정의 새 JSON 키를 발급. 응답에 전체 JSON 포함 — 그대로 onesub의 GOOGLE_SERVICE_ACCOUNT_KEY 환경변수로 쓸 수 있음.',
50
+ '주의: 반환된 JSON은 영구 자격증명이므로 안전하게 보관. 한 서비스 계정당 키 최대 10개 (기존 키 회수 후 발급 필요하면 iam_list_keys로 먼저 확인).',
51
+ ].join(' '), {
52
+ serviceAccount: z.string().describe('서비스 계정 이메일'),
53
+ }, async ({ serviceAccount }) => {
54
+ const auth = requireAuth();
55
+ const key = await iam.createServiceAccountKey(auth, serviceAccount);
56
+ return {
57
+ content: [
58
+ {
59
+ type: 'text',
60
+ text: [
61
+ '✓ 새 키 발급 완료 — 아래 JSON을 안전하게 보관하세요. **다시 볼 수 없습니다.**',
62
+ '',
63
+ `**keyId**: \`${key.keyId}\``,
64
+ `**clientEmail**: \`${key.clientEmail}\``,
65
+ `**projectId**: \`${key.projectId}\``,
66
+ '',
67
+ '## Service account JSON',
68
+ '```json',
69
+ key.json,
70
+ '```',
71
+ '',
72
+ 'onesub 서버 `GOOGLE_SERVICE_ACCOUNT_KEY` env에 한 줄로 넣을 때:',
73
+ '```bash',
74
+ "cat service-account.json | tr -d '\\n' | jq -c .",
75
+ '```',
76
+ ].join('\n'),
77
+ },
78
+ ],
79
+ };
80
+ });
81
+ server.tool('iam_add_iam_policy_binding', [
82
+ '프로젝트 IAM 정책에 (member → role) 바인딩 추가. 이미 같은 조합이 있으면 no-op.',
83
+ 'member 형식: `serviceAccount:<email>` / `user:<email>` / `group:<email>`.',
84
+ '주의: 이건 Google Cloud IAM 역할입니다. Play Console의 "View financial data" 권한은 별도 — Play Console → Users and permissions에서 부여.',
85
+ ].join(' '), {
86
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
87
+ member: z
88
+ .string()
89
+ .describe('권한 받을 주체 (예: serviceAccount:my-sa@my-project.iam.gserviceaccount.com)'),
90
+ role: z.string().describe('IAM 역할 (예: roles/iam.serviceAccountTokenCreator)'),
91
+ }, async ({ projectId, member, role }) => {
92
+ const auth = requireAuth();
93
+ const result = await iam.addProjectIamPolicyBinding(auth, projectId, member, role);
94
+ return {
95
+ content: [
96
+ {
97
+ type: 'text',
98
+ text: result.added
99
+ ? `✓ 바인딩 추가: \`${member}\` → \`${role}\``
100
+ : `• 이미 존재: \`${member}\` → \`${role}\` (no-op)`,
101
+ },
102
+ ],
103
+ };
104
+ });
105
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPlaystoreTools(server: McpServer): void;