@yoonion/mimi-seed-mcp 0.3.35 → 0.3.36

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/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { registerInstagramTools } from './registers/instagram.js';
16
16
  import { registerFacebookTools } from './registers/facebook.js';
17
17
  import { registerGoogleAdsTools } from './registers/googleads.js';
18
18
  import { registerGscTools } from './registers/gsc.js';
19
+ import { registerJenkinsTools } from './registers/jenkins.js';
19
20
  import { registerPrompts } from './prompts.js';
20
21
  import { registerResources } from './resources.js';
21
22
  // dist/index.js 기준 ../package.json — npm 패키지 루트의 버전을 단일 출처로 사용.
@@ -39,6 +40,7 @@ registerInstagramTools(server);
39
40
  registerFacebookTools(server);
40
41
  registerGoogleAdsTools(server);
41
42
  registerGscTools(server);
43
+ registerJenkinsTools(server);
42
44
  registerPrompts(server);
43
45
  registerResources(server);
44
46
  // `npx -y @yoonion/mimi-seed-mcp <subcommand>` 처리.
@@ -0,0 +1,8 @@
1
+ export interface JenkinsConfig {
2
+ url: string;
3
+ username: string;
4
+ token: string;
5
+ }
6
+ export declare function loadJenkinsConfig(): JenkinsConfig | null;
7
+ export declare function saveJenkinsConfig(config: JenkinsConfig): void;
8
+ export declare function requireJenkinsConfig(): JenkinsConfig;
@@ -0,0 +1,29 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ const CONFIG_DIR = path.join(os.homedir(), '.mimi-seed');
5
+ const JENKINS_CONFIG_PATH = path.join(CONFIG_DIR, 'jenkins.json');
6
+ export function loadJenkinsConfig() {
7
+ try {
8
+ return JSON.parse(fs.readFileSync(JENKINS_CONFIG_PATH, 'utf-8'));
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ export function saveJenkinsConfig(config) {
15
+ if (!fs.existsSync(CONFIG_DIR)) {
16
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
17
+ }
18
+ fs.writeFileSync(JENKINS_CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
19
+ }
20
+ export function requireJenkinsConfig() {
21
+ const cfg = loadJenkinsConfig();
22
+ if (!cfg) {
23
+ throw new Error('Jenkins 설정이 없습니다.\n' +
24
+ 'jenkins_save_config 도구로 먼저 설정해주세요.\n' +
25
+ '예시:\n' +
26
+ ' jenkins_save_config(url="https://jenkins.example.com", username="admin", token="11abc...")');
27
+ }
28
+ return cfg;
29
+ }
@@ -0,0 +1,10 @@
1
+ import type { JenkinsConfig } from './config.js';
2
+ export interface JenkinsCredentialSummary {
3
+ id: string;
4
+ displayName: string;
5
+ typeName: string;
6
+ }
7
+ export declare function listCredentials(cfg: JenkinsConfig): Promise<JenkinsCredentialSummary[]>;
8
+ export declare function upsertSecretText(cfg: JenkinsConfig, id: string, secret: string, description?: string): Promise<'created' | 'updated'>;
9
+ export declare function upsertSecretFile(cfg: JenkinsConfig, id: string, fileBase64: string, fileName: string, description?: string): Promise<'created' | 'updated'>;
10
+ export declare function deleteCredential(cfg: JenkinsConfig, id: string): Promise<void>;
@@ -0,0 +1,95 @@
1
+ function basicAuth(username, token) {
2
+ return 'Basic ' + Buffer.from(`${username}:${token}`).toString('base64');
3
+ }
4
+ function credentialsBase(url) {
5
+ return `${url.replace(/\/$/, '')}/credentials/store/system/domain/_`;
6
+ }
7
+ async function credentialExists(cfg, id) {
8
+ const res = await fetch(`${credentialsBase(cfg.url)}/${encodeURIComponent(id)}/api/json`, {
9
+ headers: { Authorization: basicAuth(cfg.username, cfg.token) },
10
+ });
11
+ return res.ok;
12
+ }
13
+ export async function listCredentials(cfg) {
14
+ const res = await fetch(`${credentialsBase(cfg.url)}/api/json?depth=1`, {
15
+ headers: { Authorization: basicAuth(cfg.username, cfg.token) },
16
+ });
17
+ if (!res.ok)
18
+ throw new Error(`Jenkins credentials 조회 실패 (${res.status})`);
19
+ const data = (await res.json());
20
+ return (data.credentials ?? []).map((c) => ({
21
+ id: c.id,
22
+ displayName: c.displayName,
23
+ typeName: c.typeName,
24
+ }));
25
+ }
26
+ export async function upsertSecretText(cfg, id, secret, description = '') {
27
+ const payload = JSON.stringify({
28
+ '': '0',
29
+ credentials: {
30
+ scope: 'GLOBAL',
31
+ id,
32
+ description,
33
+ secret,
34
+ 'stapler-class': 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl',
35
+ '$class': 'org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl',
36
+ },
37
+ });
38
+ const base = credentialsBase(cfg.url);
39
+ const exists = await credentialExists(cfg, id);
40
+ const endpoint = exists
41
+ ? `${base}/${encodeURIComponent(id)}/updateSubmit`
42
+ : `${base}/createCredentials`;
43
+ const res = await fetch(endpoint, {
44
+ method: 'POST',
45
+ headers: {
46
+ Authorization: basicAuth(cfg.username, cfg.token),
47
+ 'Content-Type': 'application/x-www-form-urlencoded',
48
+ },
49
+ body: new URLSearchParams({ json: payload }).toString(),
50
+ });
51
+ if (!res.ok && res.status !== 302) {
52
+ throw new Error(`Jenkins credential ${exists ? 'update' : 'create'} 실패 (${res.status})`);
53
+ }
54
+ return exists ? 'updated' : 'created';
55
+ }
56
+ export async function upsertSecretFile(cfg, id, fileBase64, fileName, description = '') {
57
+ const payload = JSON.stringify({
58
+ '': '0',
59
+ credentials: {
60
+ scope: 'GLOBAL',
61
+ id,
62
+ description,
63
+ fileName,
64
+ secretBytes: fileBase64,
65
+ 'stapler-class': 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl',
66
+ '$class': 'org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl',
67
+ },
68
+ });
69
+ const base = credentialsBase(cfg.url);
70
+ const exists = await credentialExists(cfg, id);
71
+ const endpoint = exists
72
+ ? `${base}/${encodeURIComponent(id)}/updateSubmit`
73
+ : `${base}/createCredentials`;
74
+ const res = await fetch(endpoint, {
75
+ method: 'POST',
76
+ headers: {
77
+ Authorization: basicAuth(cfg.username, cfg.token),
78
+ 'Content-Type': 'application/x-www-form-urlencoded',
79
+ },
80
+ body: new URLSearchParams({ json: payload }).toString(),
81
+ });
82
+ if (!res.ok && res.status !== 302) {
83
+ throw new Error(`Jenkins secret file credential ${exists ? 'update' : 'create'} 실패 (${res.status})`);
84
+ }
85
+ return exists ? 'updated' : 'created';
86
+ }
87
+ export async function deleteCredential(cfg, id) {
88
+ const res = await fetch(`${credentialsBase(cfg.url)}/${encodeURIComponent(id)}/doDelete`, {
89
+ method: 'POST',
90
+ headers: { Authorization: basicAuth(cfg.username, cfg.token) },
91
+ });
92
+ if (!res.ok && res.status !== 302) {
93
+ throw new Error(`Jenkins credential 삭제 실패 (${res.status})`);
94
+ }
95
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerJenkinsTools(server: McpServer): void;
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ import { requireJenkinsConfig, saveJenkinsConfig } from '../jenkins/config.js';
3
+ import * as creds from '../jenkins/credentials.js';
4
+ export function registerJenkinsTools(server) {
5
+ server.tool('jenkins_save_config', [
6
+ 'Jenkins 서버 연결 설정을 저장합니다.',
7
+ '저장 위치: ~/.mimi-seed/jenkins.json (mode 0600).',
8
+ 'API Token은 Jenkins 대시보드 → 사용자 → 설정 → API Token에서 발급할 수 있습니다.',
9
+ ].join(' '), {
10
+ url: z.string().url().describe('Jenkins 기본 URL (예: https://jenkins.example.com)'),
11
+ username: z.string().describe('Jenkins 사용자 ID'),
12
+ token: z.string().describe('Jenkins API Token'),
13
+ }, async ({ url, username, token }) => {
14
+ saveJenkinsConfig({ url, username, token });
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: [
19
+ '✅ Jenkins 설정 저장 완료',
20
+ ` URL: ${url}`,
21
+ ` 사용자: ${username}`,
22
+ '',
23
+ 'jenkins_list_credentials 로 등록된 credential 목록을 확인하세요.',
24
+ ].join('\n'),
25
+ }],
26
+ };
27
+ });
28
+ server.tool('jenkins_list_credentials', 'Jenkins에 등록된 credential 목록을 조회합니다 (id / displayName / type).', {}, async () => {
29
+ const cfg = requireJenkinsConfig();
30
+ const list = await creds.listCredentials(cfg);
31
+ if (list.length === 0) {
32
+ return { content: [{ type: 'text', text: '등록된 Jenkins credential 없음.' }] };
33
+ }
34
+ const lines = list.map((c) => `• ${c.id} [${c.typeName}] ${c.displayName}`);
35
+ return {
36
+ content: [{
37
+ type: 'text',
38
+ text: `Jenkins credentials (${list.length}개):\n\n${lines.join('\n')}`,
39
+ }],
40
+ };
41
+ });
42
+ server.tool('jenkins_create_credential', [
43
+ 'Jenkins에 Secret Text credential을 생성하거나 업데이트합니다.',
44
+ '비밀번호, API 키, 앱 시크릿 등 문자열 값에 사용하세요.',
45
+ '같은 id가 이미 존재하면 자동으로 업데이트합니다.',
46
+ ].join(' '), {
47
+ id: z.string().describe('Credential ID (예: speakmoney-android-key-password)'),
48
+ secret: z.string().describe('저장할 비밀값'),
49
+ description: z.string().optional().describe('설명 (선택)'),
50
+ }, async ({ id, secret, description }) => {
51
+ const cfg = requireJenkinsConfig();
52
+ const result = await creds.upsertSecretText(cfg, id, secret, description ?? '');
53
+ return {
54
+ content: [{
55
+ type: 'text',
56
+ text: `✅ Jenkins credential ${result}: \`${id}\``,
57
+ }],
58
+ };
59
+ });
60
+ server.tool('jenkins_upload_keystore', [
61
+ 'Jenkins에 Android keystore 파일을 Secret File credential로 업로드합니다.',
62
+ 'keystore_base64에 .jks/.p12 파일을 base64로 인코딩한 값을 전달하세요.',
63
+ '같은 id가 이미 존재하면 자동으로 교체합니다.',
64
+ ].join(' '), {
65
+ id: z.string().describe('Credential ID (예: speakmoney-android-keystore)'),
66
+ keystore_base64: z.string().describe('keystore 파일 내용을 base64로 인코딩한 값'),
67
+ file_name: z.string().default('keystore.jks').describe('파일명 (기본: keystore.jks)'),
68
+ description: z.string().optional().describe('설명 (선택)'),
69
+ }, async ({ id, keystore_base64, file_name, description }) => {
70
+ const cfg = requireJenkinsConfig();
71
+ const result = await creds.upsertSecretFile(cfg, id, keystore_base64, file_name, description ?? '');
72
+ return {
73
+ content: [{
74
+ type: 'text',
75
+ text: `✅ Jenkins keystore credential ${result}: \`${id}\` (${file_name})`,
76
+ }],
77
+ };
78
+ });
79
+ server.tool('jenkins_delete_credential', 'Jenkins credential을 삭제합니다. 비가역 작업입니다.', {
80
+ id: z.string().describe('삭제할 Credential ID'),
81
+ }, async ({ id }) => {
82
+ const cfg = requireJenkinsConfig();
83
+ await creds.deleteCredential(cfg, id);
84
+ return {
85
+ content: [{
86
+ type: 'text',
87
+ text: `🗑 Jenkins credential 삭제 완료: \`${id}\``,
88
+ }],
89
+ };
90
+ });
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {