mcp-git-fetcher 0.1.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 ADDED
@@ -0,0 +1,186 @@
1
+ # mcp-git-fetcher
2
+
3
+ > **MCP 服务**:从公司内部 GitLab 仓库下载 skills/rules 文件到目标 IDE 目录。
4
+
5
+ 支持 **Python** 和 **Node.js** 双版本,选一个即可。
6
+
7
+ ---
8
+
9
+ ## 快速开始
10
+
11
+ ### 方式 A:Node.js(推荐,可 npx)
12
+
13
+ ```bash
14
+ npx -y mcp-git-fetcher
15
+ ```
16
+
17
+ 本地开发:
18
+
19
+ ```bash
20
+ git clone <repo-url>
21
+ cd mcp-git-fetcher
22
+ npm install
23
+ npm run build
24
+ ```
25
+
26
+ ### 方式 B:Python
27
+
28
+ ```bash
29
+ cd D:\AI Coding\mcp-git\mcp-git-fetcher
30
+ pip install -e .
31
+ ```
32
+
33
+ ### 配置环境变量
34
+
35
+ 复制 `.env.example` 为 `.env`,填入实际参数:
36
+
37
+ ```bash
38
+ copy .env.example .env
39
+ ```
40
+
41
+ | 变量 | 说明 | 示例 |
42
+ |------|------|------|
43
+ | `GIT_REPO_URL` | GitLab 仓库地址(**必需**) | `http://10.1.192.33:8090/.../mcp-git-resource.git` |
44
+ | `GIT_AUTH_TOKEN` | GitLab Personal Access Token | `glpat-xxxxxx` |
45
+ | `GIT_BRANCH` | 分支(默认 main) | `main` |
46
+
47
+ > **Token 说明**:Token 只配在 MCP 客户端配置的 `env` 中,不会写进代码,也不会存到 git 缓存目录。
48
+
49
+ ### 注册为 MCP 服务
50
+
51
+ #### Hermes Agent
52
+
53
+ ```yaml
54
+ mcp_servers:
55
+ git-fetcher:
56
+ command: "npx"
57
+ args: ["-y", "mcp-git-fetcher"]
58
+ env:
59
+ GIT_REPO_URL: "http://10.1.192.33:8090/project/5gboss/wasu/mcp-git-resource.git"
60
+ GIT_AUTH_TOKEN: "glpat-xxxxx" # 只在这里配置
61
+ connect_timeout: 60
62
+ timeout: 300
63
+ ```
64
+
65
+ #### Trae CN IDE
66
+
67
+ 编辑 `%APPDATA%\Trae CN\User\mcp.json`:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "git-fetcher": {
73
+ "command": "npx",
74
+ "args": ["-y", "mcp-git-fetcher"],
75
+ "env": {
76
+ "GIT_REPO_URL": "http://10.1.192.33:8090/project/5gboss/wasu/mcp-git-resource.git",
77
+ "GIT_AUTH_TOKEN": "glpat-xxxxx"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ > 如果用 Python 版,把 `command` 改为 `"uv"`,`args` 改为 `["run", "--directory", "D:\\AI Coding\\mcp-git\\mcp-git-fetcher", "mcp-git-fetcher"]`。
85
+
86
+ ---
87
+
88
+ ## 使用方式
89
+
90
+ ### `fetch_resources`
91
+
92
+ 拉取 GitLab 最新代码,同步 `skills/` 和 `rules/` 到指定目录。
93
+
94
+ **参数:**
95
+
96
+ | 参数 | 必需 | 说明 |
97
+ |------|------|------|
98
+ | `target_skills_dir` | ✅ | skills 写入目录(如 `.trae/skills` 的绝对路径) |
99
+ | `target_rules_dir` | ✅ | rules 写入目录(如 `.trae/rules` 的绝对路径) |
100
+ | `repo_url` | ❌ | 临时覆盖仓库 URL |
101
+
102
+ **返回示例:**
103
+
104
+ ```json
105
+ {
106
+ "success": true,
107
+ "message": "Synced 12 files (3 skipped, commit a1b2c3d4)",
108
+ "commit": "a1b2c3d4",
109
+ "skills": { "copied": 8, "skipped": 2, "errors": [] },
110
+ "rules": { "copied": 4, "skipped": 1, "errors": [] },
111
+ "total_copied": 12,
112
+ "total_skipped": 3
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 工作原理
119
+
120
+ ```
121
+ Agent/IDE → fetch_resources
122
+ ├─ ① git clone/pull 到本地缓存 (~/.mcp-git-fetcher/cache/)
123
+ ├─ ② 提取仓库中的 skills/ 和 rules/
124
+ └─ ③ 复制到目标目录(SHA-256 去重,相同则跳过)
125
+ ```
126
+
127
+ - **增量更新**:第二次调用只需 git pull,更快
128
+ - **内容去重**:SHA-256 对比,内容不变不写盘
129
+ - **Token 安全**:只存在于客户端配置,不进代码和 git 缓存
130
+
131
+ ---
132
+
133
+ ## 项目结构
134
+
135
+ ```
136
+ D:\AI Coding\mcp-git\mcp-git-fetcher\
137
+ ├── mcp_git_fetcher/ # Python 版源码
138
+ │ ├── server.py # MCP 服务入口
139
+ │ ├── git_ops.py # Git 操作
140
+ │ ├── file_sync.py # 文件同步
141
+ │ └── config.py # 配置管理
142
+ ├── src/ # Node.js 版源码 (TypeScript)
143
+ │ ├── cli.ts # MCP 服务入口
144
+ │ ├── git-ops.ts # Git 操作
145
+ │ ├── file-sync.ts # 文件同步
146
+ │ └── config.ts # 配置管理
147
+ ├── package.json # Node.js 依赖 + npm 发布配置
148
+ ├── pyproject.toml # Python 依赖
149
+ ├── .env.example # 环境变量模板
150
+ ├── .gitignore
151
+ └── README.md
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 环境变量
157
+
158
+ | 变量 | 必需 | 默认值 | 说明 |
159
+ |------|------|--------|------|
160
+ | `GIT_REPO_URL` | ✅ | — | GitLab 仓库 HTTP(S) 地址 |
161
+ | `GIT_AUTH_TOKEN` | ❌ | — | GitLab Personal Access Token |
162
+ | `GIT_BRANCH` | ❌ | `main` | 拉取的分支 |
163
+ | `SKILLS_SUBDIR` | ❌ | `skills` | 仓库内 skills 目录名 |
164
+ | `RULES_SUBDIR` | ❌ | `rules` | 仓库内 rules 目录名 |
165
+ | `CACHE_DIR` | ❌ | `~/.mcp-git-fetcher/cache` | 本地 Git 缓存目录 |
166
+ | `HTTP_PROXY` | ❌ | — | HTTP 代理 |
167
+ | `HTTPS_PROXY` | ❌ | — | HTTPS 代理 |
168
+
169
+ ---
170
+
171
+ ## 开发
172
+
173
+ ```bash
174
+ # Python 版
175
+ pip install -e .
176
+ python -m mcp_git_fetcher.server
177
+
178
+ # Node.js 版
179
+ npm install
180
+ npm run build
181
+ node dist/cli.js
182
+ ```
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,20 @@
1
+ export interface ResourceInfo {
2
+ name: string;
3
+ relative_path: string;
4
+ description: string;
5
+ }
6
+ /**
7
+ * Extract a short description from the beginning of a file.
8
+ *
9
+ * Strategies (tried in order):
10
+ * 1. Python: first docstring
11
+ * 2. Markdown: first # Title or first non-empty paragraph
12
+ * 3. YAML/JS/TS: first comment block
13
+ * 4. Fallback: first 2 meaningful lines
14
+ */
15
+ export declare function extractDescription(filePath: string): string;
16
+ /**
17
+ * Scan the repository for skills and rules files with descriptions.
18
+ */
19
+ export declare function scanResources(repoRoot: string, skillsSubdir: string, rulesSubdir: string): Record<string, ResourceInfo[]>;
20
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6D3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CA0ChC"}
@@ -0,0 +1,111 @@
1
+ import { readFileSync, readdirSync, statSync, existsSync } from "fs";
2
+ import { join, relative } from "path";
3
+ /**
4
+ * Extract a short description from the beginning of a file.
5
+ *
6
+ * Strategies (tried in order):
7
+ * 1. Python: first docstring
8
+ * 2. Markdown: first # Title or first non-empty paragraph
9
+ * 3. YAML/JS/TS: first comment block
10
+ * 4. Fallback: first 2 meaningful lines
11
+ */
12
+ export function extractDescription(filePath) {
13
+ let text;
14
+ try {
15
+ text = readFileSync(filePath, "utf-8");
16
+ }
17
+ catch {
18
+ return "";
19
+ }
20
+ const lines = text.split(/\r?\n/);
21
+ // Strategy 1: Python docstring
22
+ if (filePath.endsWith(".py")) {
23
+ const m = text.match(/"""(.*?)"""/s);
24
+ if (m) {
25
+ return m[1].trim().split("\n")[0].slice(0, 150);
26
+ }
27
+ }
28
+ // Strategy 2: Markdown — first # Title
29
+ if (filePath.endsWith(".md") || filePath.endsWith(".mdx")) {
30
+ for (const line of lines) {
31
+ const trimmed = line.trim();
32
+ if (trimmed.startsWith("# ")) {
33
+ return trimmed.slice(2).trim();
34
+ }
35
+ }
36
+ // fallback: first non-empty paragraph
37
+ for (const line of lines) {
38
+ const trimmed = line.trim();
39
+ if (trimmed && !trimmed.startsWith("#")) {
40
+ return trimmed.slice(0, 150);
41
+ }
42
+ }
43
+ }
44
+ // Strategy 3: YAML/JS/TS — first comment
45
+ for (const line of lines) {
46
+ const trimmed = line.trim();
47
+ if (trimmed.startsWith("# ")) {
48
+ return trimmed.slice(2).trim().slice(0, 150);
49
+ }
50
+ if (trimmed.startsWith("// ")) {
51
+ return trimmed.slice(3).trim().slice(0, 150);
52
+ }
53
+ if (trimmed.startsWith("/*")) {
54
+ const end = text.indexOf("*/");
55
+ if (end > 0) {
56
+ return text.slice(2, end).trim().slice(0, 150);
57
+ }
58
+ }
59
+ }
60
+ // Fallback: first non-empty, non-comment line
61
+ for (const line of lines) {
62
+ const trimmed = line.trim();
63
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("/*")) {
64
+ return trimmed.slice(0, 150);
65
+ }
66
+ }
67
+ return "";
68
+ }
69
+ /**
70
+ * Scan the repository for skills and rules files with descriptions.
71
+ */
72
+ export function scanResources(repoRoot, skillsSubdir, rulesSubdir) {
73
+ const result = { skills: [], rules: [] };
74
+ for (const [category, subdir] of [["skills", skillsSubdir], ["rules", rulesSubdir]]) {
75
+ const srcDir = join(repoRoot, subdir);
76
+ if (!existsSync(srcDir))
77
+ continue;
78
+ function walk(current) {
79
+ const entries = readdirSync(current);
80
+ for (const entry of entries) {
81
+ const full = join(current, entry);
82
+ let stat;
83
+ try {
84
+ stat = statSync(full);
85
+ }
86
+ catch {
87
+ continue;
88
+ }
89
+ if (stat.isDirectory()) {
90
+ if (entry.startsWith("."))
91
+ continue; // skip hidden dirs
92
+ walk(full);
93
+ continue;
94
+ }
95
+ // Skip hidden files
96
+ if (entry.startsWith("."))
97
+ continue;
98
+ const relPath = relative(repoRoot, full).replace(/\\/g, "/");
99
+ const desc = extractDescription(full);
100
+ result[category].push({
101
+ name: entry,
102
+ relative_path: relPath,
103
+ description: desc || "(no description)",
104
+ });
105
+ }
106
+ }
107
+ walk(srcDir);
108
+ }
109
+ return result;
110
+ }
111
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,275 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
5
+ import { createHash } from "crypto";
6
+ import { dirname, join } from "path";
7
+ import { loadConfig } from "./config.js";
8
+ import { ensureRepo } from "./git-ops.js";
9
+ import { syncAll } from "./file-sync.js";
10
+ import { scanResources } from "./analyzer.js";
11
+ // ── Tool definitions ──────────────────────────────────────────────────────────
12
+ const TOOLS = [
13
+ {
14
+ name: "fetch_resources",
15
+ description: "Fetch all skills and rules files from the configured internal GitLab " +
16
+ "repository and sync them to target directories. " +
17
+ "Use this for a full sync of all resources.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ target_skills_dir: {
22
+ type: "string",
23
+ description: "Absolute path for skills files. Example: C:/path/to/.trae/skills",
24
+ },
25
+ target_rules_dir: {
26
+ type: "string",
27
+ description: "Absolute path for rules files. Example: C:/path/to/.trae/rules",
28
+ },
29
+ repo_url: {
30
+ type: "string",
31
+ description: "Optional override for the Git repository URL.",
32
+ },
33
+ },
34
+ required: ["target_skills_dir", "target_rules_dir"],
35
+ },
36
+ },
37
+ {
38
+ name: "list_resources",
39
+ description: "Scan the configured GitLab repository and list all available skills/rules " +
40
+ "with their descriptions. Shows what each file does and what problem it solves. " +
41
+ "Does not download anything. Use this to see what's available, " +
42
+ "then use download_resources to pick specific ones.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ category: {
47
+ type: "string",
48
+ enum: ["skills", "rules", "all"],
49
+ description: "Filter by category: 'skills', 'rules', or 'all' (default).",
50
+ },
51
+ pattern: {
52
+ type: "string",
53
+ description: "Optional glob pattern to filter by filename (e.g. 'react*', '*.md').",
54
+ },
55
+ repo_url: {
56
+ type: "string",
57
+ description: "Optional override for the Git repository URL.",
58
+ },
59
+ },
60
+ },
61
+ },
62
+ {
63
+ name: "download_resources",
64
+ description: "Download specific skills or rules files from the GitLab repository " +
65
+ "by filename pattern. Use list_resources first to see what's available, " +
66
+ "then download only what you need. Supports glob patterns.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ target_skills_dir: {
71
+ type: "string",
72
+ description: "Absolute path for skills files. Required if downloading skills.",
73
+ },
74
+ target_rules_dir: {
75
+ type: "string",
76
+ description: "Absolute path for rules files. Required if downloading rules.",
77
+ },
78
+ patterns: {
79
+ type: "array",
80
+ items: { type: "string" },
81
+ description: "Filename patterns to match (e.g. ['react*', '*.md', 'cod*']). " +
82
+ "Only files matching any pattern will be downloaded.",
83
+ },
84
+ category: {
85
+ type: "string",
86
+ enum: ["skills", "rules", "all"],
87
+ description: "Which category to search: 'skills', 'rules', or 'all' (default).",
88
+ },
89
+ repo_url: {
90
+ type: "string",
91
+ description: "Optional override for the Git repository URL.",
92
+ },
93
+ },
94
+ required: ["patterns"],
95
+ },
96
+ },
97
+ ];
98
+ // ── Helpers ───────────────────────────────────────────────────────────────────
99
+ function makeResult(success, message, details) {
100
+ const payload = { success, message };
101
+ if (details)
102
+ Object.assign(payload, details);
103
+ return {
104
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
105
+ };
106
+ }
107
+ function fileHash(filePath) {
108
+ const hash = createHash("sha256");
109
+ hash.update(readFileSync(filePath));
110
+ return hash.digest("hex");
111
+ }
112
+ function matchGlob(name, pattern) {
113
+ const regexStr = "^" +
114
+ pattern
115
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
116
+ .replace(/\*/g, ".*")
117
+ .replace(/\?/g, ".") +
118
+ "$";
119
+ return new RegExp(regexStr, "i").test(name);
120
+ }
121
+ function filterByPatternSimple(items, patterns) {
122
+ const pats = Array.isArray(patterns) ? patterns : [patterns];
123
+ return items.filter((item) => pats.some((p) => matchGlob(item.name, p)));
124
+ }
125
+ // ── Server setup ──────────────────────────────────────────────────────────────
126
+ const server = new Server({ name: "mcp-git-fetcher", version: "0.1.0" }, { capabilities: { tools: {} } });
127
+ // ── Tools/list ────────────────────────────────────────────────────────────────
128
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
129
+ tools: TOOLS,
130
+ }));
131
+ // ── Tools/call ────────────────────────────────────────────────────────────────
132
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
133
+ const { name, arguments: args } = request.params;
134
+ if (!name)
135
+ return makeResult(false, "Missing tool name");
136
+ try {
137
+ switch (name) {
138
+ case "list_resources":
139
+ return await handleListResources(args);
140
+ case "download_resources":
141
+ return await handleDownloadResources(args);
142
+ case "fetch_resources":
143
+ return await handleFetchResources(args);
144
+ default:
145
+ return makeResult(false, `Unknown tool: ${name}`);
146
+ }
147
+ }
148
+ catch (e) {
149
+ const msg = e instanceof Error ? e.message : String(e);
150
+ console.error(`[mcp-git-fetcher] Error: ${msg}`);
151
+ return makeResult(false, msg);
152
+ }
153
+ });
154
+ // ── Handler: fetch_resources ──────────────────────────────────────────────────
155
+ async function handleFetchResources(args) {
156
+ const config = loadConfig();
157
+ if (args.repo_url)
158
+ config.repoUrl = String(args.repo_url);
159
+ const targetSkillsDir = args.target_skills_dir;
160
+ const targetRulesDir = args.target_rules_dir;
161
+ if (!targetSkillsDir || !targetRulesDir) {
162
+ return makeResult(false, "Both target_skills_dir and target_rules_dir are required");
163
+ }
164
+ console.error(`[mcp-git-fetcher] fetch_resources: repo=${config.repoUrl}`);
165
+ const repo = await ensureRepo(config);
166
+ const results = syncAll(repo.repoRoot, config.skillsSubdir, config.rulesSubdir, targetSkillsDir, targetRulesDir);
167
+ const skillsR = results.skills;
168
+ const rulesR = results.rules;
169
+ const totalCopied = (skillsR?.copied ?? 0) + (rulesR?.copied ?? 0);
170
+ const totalSkipped = (skillsR?.skipped ?? 0) + (rulesR?.skipped ?? 0);
171
+ const errors = [...(skillsR?.errors ?? []), ...(rulesR?.errors ?? [])];
172
+ return makeResult(errors.length === 0, `Synced ${totalCopied} files (${totalSkipped} skipped, commit ${repo.commitSha})`, { commit: repo.commitSha, skills: skillsR, rules: rulesR, total_copied: totalCopied, total_skipped: totalSkipped });
173
+ }
174
+ // ── Handler: list_resources ───────────────────────────────────────────────────
175
+ async function handleListResources(args) {
176
+ const config = loadConfig();
177
+ if (args.repo_url)
178
+ config.repoUrl = String(args.repo_url);
179
+ const repo = await ensureRepo(config);
180
+ const category = args.category || "all";
181
+ const pattern = args.pattern || "*";
182
+ const all = scanResources(repo.repoRoot, config.skillsSubdir, config.rulesSubdir);
183
+ const result = {};
184
+ if (category === "skills" || category === "all") {
185
+ result.skills = filterByPatternSimple(all.skills, pattern);
186
+ }
187
+ if (category === "rules" || category === "all") {
188
+ result.rules = filterByPatternSimple(all.rules, pattern);
189
+ }
190
+ const total = Object.values(result).reduce((s, v) => s + v.length, 0);
191
+ return makeResult(true, `Found ${total} resources (commit ${repo.commitSha})`, {
192
+ commit: repo.commitSha,
193
+ resources: result,
194
+ total,
195
+ });
196
+ }
197
+ // ── Handler: download_resources ───────────────────────────────────────────────
198
+ async function handleDownloadResources(args) {
199
+ const config = loadConfig();
200
+ if (args.repo_url)
201
+ config.repoUrl = String(args.repo_url);
202
+ const patterns = args.patterns;
203
+ if (!Array.isArray(patterns) || patterns.length === 0) {
204
+ return makeResult(false, "At least one pattern is required (e.g. ['react*', '*.md'])");
205
+ }
206
+ const targetSkillsDir = args.target_skills_dir;
207
+ const targetRulesDir = args.target_rules_dir;
208
+ const category = args.category || "all";
209
+ if ((category === "skills" || category === "all") && !targetSkillsDir) {
210
+ return makeResult(false, "target_skills_dir is required when downloading skills");
211
+ }
212
+ if ((category === "rules" || category === "all") && !targetRulesDir) {
213
+ return makeResult(false, "target_rules_dir is required when downloading rules");
214
+ }
215
+ console.error(`[mcp-git-fetcher] download_resources: patterns=${JSON.stringify(patterns)}, category=${category}`);
216
+ const repo = await ensureRepo(config);
217
+ const all = scanResources(repo.repoRoot, config.skillsSubdir, config.rulesSubdir);
218
+ const toDownload = [];
219
+ if (category === "skills" || category === "all") {
220
+ const skillsRoot = join(repo.repoRoot, config.skillsSubdir);
221
+ for (const item of filterByPatternSimple(all.skills, patterns)) {
222
+ const item2 = item;
223
+ const src = join(repo.repoRoot, item2.relative_path);
224
+ const rel = join(skillsRoot, item2.relative_path.replace(config.skillsSubdir + "/", "")); // Hmm, simpler:
225
+ const relpath = item2.relative_path.replace(config.skillsSubdir + "/", "");
226
+ toDownload.push({ src, dst: join(targetSkillsDir, relpath) });
227
+ }
228
+ }
229
+ if (category === "rules" || category === "all") {
230
+ const rulesRoot = join(repo.repoRoot, config.rulesSubdir);
231
+ for (const item of filterByPatternSimple(all.rules, patterns)) {
232
+ const item2 = item;
233
+ const src = join(repo.repoRoot, item2.relative_path);
234
+ const relpath = item2.relative_path.replace(config.rulesSubdir + "/", "");
235
+ toDownload.push({ src, dst: join(targetRulesDir, relpath) });
236
+ }
237
+ }
238
+ // Execute downloads
239
+ let copied = 0;
240
+ let skipped = 0;
241
+ const errors = [];
242
+ for (const { src, dst } of toDownload) {
243
+ mkdirSync(dirname(dst), { recursive: true });
244
+ if (existsSync(dst)) {
245
+ try {
246
+ if (fileHash(src) === fileHash(dst)) {
247
+ skipped++;
248
+ continue;
249
+ }
250
+ }
251
+ catch {
252
+ // fall through
253
+ }
254
+ }
255
+ try {
256
+ copyFileSync(src, dst);
257
+ copied++;
258
+ }
259
+ catch (e) {
260
+ errors.push(`Failed to copy ${src}: ${e instanceof Error ? e.message : String(e)}`);
261
+ }
262
+ }
263
+ return makeResult(errors.length === 0, `Downloaded ${copied} files (${skipped} skipped, commit ${repo.commitSha})`, { commit: repo.commitSha, patterns, copied, skipped, errors: errors.length > 0 ? errors : undefined });
264
+ }
265
+ // ── Main ──────────────────────────────────────────────────────────────────────
266
+ async function main() {
267
+ const transport = new StdioServerTransport();
268
+ await server.connect(transport);
269
+ console.error("[mcp-git-fetcher] MCP server running on stdio");
270
+ }
271
+ main().catch((e) => {
272
+ console.error("[mcp-git-fetcher] Fatal error:", e);
273
+ process.exit(1);
274
+ });
275
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export interface GitFetcherConfig {
2
+ /** GitLab repository URL (HTTP/HTTPS) */
3
+ repoUrl: string;
4
+ /** GitLab personal access token */
5
+ authToken?: string;
6
+ /** Local cache directory for cloned repositories */
7
+ cacheDir: string;
8
+ /** Branch to pull from */
9
+ branch: string;
10
+ /** Subdirectory name for skills in the repo */
11
+ skillsSubdir: string;
12
+ /** Subdirectory name for rules in the repo */
13
+ rulesSubdir: string;
14
+ /** Optional HTTP proxy */
15
+ httpProxy?: string;
16
+ /** Optional HTTPS proxy */
17
+ httpsProxy?: string;
18
+ }
19
+ export interface SyncTargets {
20
+ targetSkillsDir: string;
21
+ targetRulesDir: string;
22
+ }
23
+ export declare function loadConfig(): GitFetcherConfig;
24
+ /**
25
+ * Build repo URL with embedded token for authentication.
26
+ * Transforms: http://host/repo.git → http://token@host/repo.git
27
+ */
28
+ export declare function getAuthRepoUrl(config: GitFetcherConfig): string;
29
+ export declare function getGitEnv(config: GitFetcherConfig): Record<string, string>;
30
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,UAAU,IAAI,gBAAgB,CAW7C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAU/D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK1E"}
package/dist/config.js ADDED
@@ -0,0 +1,47 @@
1
+ import { config as dotenvConfig } from "dotenv";
2
+ import { existsSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ // Load .env from project root and ~/.mcp-git-fetcher/.env
6
+ dotenvConfig();
7
+ const homeEnvPath = join(homedir(), ".mcp-git-fetcher", ".env");
8
+ if (existsSync(homeEnvPath)) {
9
+ dotenvConfig({ path: homeEnvPath });
10
+ }
11
+ export function loadConfig() {
12
+ return {
13
+ repoUrl: process.env.GIT_REPO_URL ?? "",
14
+ authToken: process.env.GIT_AUTH_TOKEN,
15
+ cacheDir: process.env.CACHE_DIR ?? join(homedir(), ".mcp-git-fetcher", "cache"),
16
+ branch: process.env.GIT_BRANCH ?? "main",
17
+ skillsSubdir: process.env.SKILLS_SUBDIR ?? "skills",
18
+ rulesSubdir: process.env.RULES_SUBDIR ?? "rules",
19
+ httpProxy: process.env.HTTP_PROXY,
20
+ httpsProxy: process.env.HTTPS_PROXY,
21
+ };
22
+ }
23
+ /**
24
+ * Build repo URL with embedded token for authentication.
25
+ * Transforms: http://host/repo.git → http://token@host/repo.git
26
+ */
27
+ export function getAuthRepoUrl(config) {
28
+ if (!config.authToken)
29
+ return config.repoUrl;
30
+ if (config.repoUrl.includes("@"))
31
+ return config.repoUrl;
32
+ const idx = config.repoUrl.indexOf("://");
33
+ if (idx === -1)
34
+ return config.repoUrl;
35
+ const protocol = config.repoUrl.slice(0, idx + 3);
36
+ const rest = config.repoUrl.slice(idx + 3);
37
+ return `${protocol}${config.authToken}@${rest}`;
38
+ }
39
+ export function getGitEnv(config) {
40
+ const env = {};
41
+ if (config.httpProxy)
42
+ env.HTTP_PROXY = config.httpProxy;
43
+ if (config.httpsProxy)
44
+ env.HTTPS_PROXY = config.httpsProxy;
45
+ return env;
46
+ }
47
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,UAAU,EAAgB,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,0DAA0D;AAC1D,YAAY,EAAE,CAAC;AACf,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC,CAAC;AAChE,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACtC,CAAC;AA0BD,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE;QACvC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QACrC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,EAAE,OAAO,CAAC;QAC/E,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM;QACxC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,QAAQ;QACnD,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO;QAChD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;QACjC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;KACpC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAwB;IACrD,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IAC7C,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IAExD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IAEtC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC3C,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAwB;IAChD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,MAAM,CAAC,SAAS;QAAE,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,CAAC;IACxD,IAAI,MAAM,CAAC,UAAU;QAAE,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC;IAC3D,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface SyncResult {
2
+ copied: number;
3
+ skipped: number;
4
+ errors: string[];
5
+ sourceDir?: string;
6
+ targetDir?: string;
7
+ }
8
+ /**
9
+ * Recursively sync files from source to target.
10
+ *
11
+ * - Creates target dir if missing
12
+ * - Copies files that don't exist at destination
13
+ * - Skips files with identical SHA-256
14
+ * - Overwrites files with different content
15
+ */
16
+ export declare function syncDirectory(srcDir: string, dstDir: string): SyncResult;
17
+ /**
18
+ * Sync both skills/ and rules/ directories from the repo root.
19
+ */
20
+ export declare function syncAll(repoRoot: string, skillsSubdir: string, rulesSubdir: string, targetSkillsDir?: string, targetRulesDir?: string): Record<string, SyncResult>;
21
+ //# sourceMappingURL=file-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-sync.d.ts","sourceRoot":"","sources":["../src/file-sync.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAYD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CA8DxE;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,EACxB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAgB5B"}
@@ -0,0 +1,97 @@
1
+ import { createHash } from "crypto";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "fs";
3
+ import { dirname, join, relative } from "path";
4
+ /**
5
+ * Compute SHA-256 hash of a file's contents.
6
+ */
7
+ function fileHash(filePath) {
8
+ const hash = createHash("sha256");
9
+ const content = readFileSync(filePath);
10
+ hash.update(content);
11
+ return hash.digest("hex");
12
+ }
13
+ /**
14
+ * Recursively sync files from source to target.
15
+ *
16
+ * - Creates target dir if missing
17
+ * - Copies files that don't exist at destination
18
+ * - Skips files with identical SHA-256
19
+ * - Overwrites files with different content
20
+ */
21
+ export function syncDirectory(srcDir, dstDir) {
22
+ const result = {
23
+ copied: 0,
24
+ skipped: 0,
25
+ errors: [],
26
+ sourceDir: srcDir,
27
+ targetDir: dstDir,
28
+ };
29
+ if (!existsSync(srcDir)) {
30
+ result.errors.push(`Source directory not found: ${srcDir}`);
31
+ return result;
32
+ }
33
+ mkdirSync(dstDir, { recursive: true });
34
+ function walk(current) {
35
+ const entries = readdirSync(current);
36
+ for (const entry of entries) {
37
+ const srcFile = join(current, entry);
38
+ let stat;
39
+ try {
40
+ stat = statSync(srcFile);
41
+ }
42
+ catch {
43
+ continue;
44
+ }
45
+ if (stat.isDirectory()) {
46
+ walk(srcFile);
47
+ continue;
48
+ }
49
+ const relPath = relative(srcDir, srcFile);
50
+ const dstFile = join(dstDir, relPath);
51
+ // Create parent directory
52
+ mkdirSync(dirname(dstFile), { recursive: true });
53
+ // Check if destination exists with same content
54
+ if (existsSync(dstFile)) {
55
+ try {
56
+ if (fileHash(srcFile) === fileHash(dstFile)) {
57
+ result.skipped++;
58
+ continue;
59
+ }
60
+ }
61
+ catch {
62
+ // Fall through to copy
63
+ }
64
+ }
65
+ try {
66
+ copyFileSync(srcFile, dstFile);
67
+ result.copied++;
68
+ }
69
+ catch (e) {
70
+ const msg = `Failed to copy ${srcFile} → ${dstFile}: ${e instanceof Error ? e.message : String(e)}`;
71
+ result.errors.push(msg);
72
+ }
73
+ }
74
+ }
75
+ walk(srcDir);
76
+ return result;
77
+ }
78
+ /**
79
+ * Sync both skills/ and rules/ directories from the repo root.
80
+ */
81
+ export function syncAll(repoRoot, skillsSubdir, rulesSubdir, targetSkillsDir, targetRulesDir) {
82
+ const results = {};
83
+ if (targetSkillsDir) {
84
+ results.skills = syncDirectory(join(repoRoot, skillsSubdir), targetSkillsDir);
85
+ }
86
+ else {
87
+ results.skills = { copied: 0, skipped: 0, errors: ["target_skills_dir not provided"], sourceDir: join(repoRoot, skillsSubdir) };
88
+ }
89
+ if (targetRulesDir) {
90
+ results.rules = syncDirectory(join(repoRoot, rulesSubdir), targetRulesDir);
91
+ }
92
+ else {
93
+ results.rules = { copied: 0, skipped: 0, errors: ["target_rules_dir not provided"], sourceDir: join(repoRoot, rulesSubdir) };
94
+ }
95
+ return results;
96
+ }
97
+ //# sourceMappingURL=file-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-sync.js","sourceRoot":"","sources":["../src/file-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { GitFetcherConfig } from "./config.js";
2
+ export declare class GitOperationError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export interface RepoHandle {
6
+ repoRoot: string;
7
+ commitSha: string;
8
+ }
9
+ /**
10
+ * Ensure the repository is cloned and up-to-date.
11
+ *
12
+ * - cacheDir doesn't exist → git clone
13
+ * - cacheDir exists with .git → git pull (with hard reset fallback)
14
+ */
15
+ export declare function ensureRepo(config: GitFetcherConfig): Promise<RepoHandle>;
16
+ /**
17
+ * List all files recursively under a subdirectory of the repo root.
18
+ * Returns absolute paths. Returns empty array if subdir doesn't exist.
19
+ */
20
+ export declare function listSubdirFiles(repoRoot: string, subdir: string): string[];
21
+ //# sourceMappingURL=git-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.d.ts","sourceRoot":"","sources":["../src/git-ops.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAC;AAO1E,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+C9E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAwB1E"}
@@ -0,0 +1,85 @@
1
+ import { existsSync, mkdirSync, readdirSync, statSync } from "fs";
2
+ import { join } from "path";
3
+ import simpleGitModule from "simple-git";
4
+ import { getAuthRepoUrl, getGitEnv } from "./config.js";
5
+ // simple-git ESM compatibility: default export is callable, cast to avoid TS friction
6
+ const createGit = simpleGitModule;
7
+ export class GitOperationError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "GitOperationError";
11
+ }
12
+ }
13
+ /**
14
+ * Ensure the repository is cloned and up-to-date.
15
+ *
16
+ * - cacheDir doesn't exist → git clone
17
+ * - cacheDir exists with .git → git pull (with hard reset fallback)
18
+ */
19
+ export async function ensureRepo(config) {
20
+ const cacheDir = config.cacheDir;
21
+ const gitDir = join(cacheDir, ".git");
22
+ if (!config.repoUrl) {
23
+ throw new GitOperationError("GIT_REPO_URL is not configured. Set the GIT_REPO_URL environment variable.");
24
+ }
25
+ const authUrl = getAuthRepoUrl(config);
26
+ const env = getGitEnv(config);
27
+ if (existsSync(gitDir)) {
28
+ console.error(`[mcp-git-fetcher] Repository cache exists at ${cacheDir}, pulling latest...`);
29
+ const git = createGit(cacheDir);
30
+ await git.fetch();
31
+ const currentBranch = (await git.branch()).current;
32
+ try {
33
+ await git.pull("origin", currentBranch, { "--ff-only": null });
34
+ }
35
+ catch {
36
+ console.error("[mcp-git-fetcher] Fast-forward failed, doing hard reset...");
37
+ await git.reset(["--hard", `origin/${currentBranch}`]);
38
+ }
39
+ const log = await git.log({ maxCount: 1 });
40
+ return {
41
+ repoRoot: cacheDir,
42
+ commitSha: log.latest?.hash.slice(0, 8) ?? "unknown",
43
+ };
44
+ }
45
+ console.error(`[mcp-git-fetcher] Cloning repository to ${cacheDir}...`);
46
+ mkdirSync(cacheDir, { recursive: true });
47
+ // Clone via the module directly (no cwd) then re-attach
48
+ const tempGit = createGit();
49
+ await tempGit.clone(authUrl, cacheDir, ["--branch", config.branch]);
50
+ const repo = createGit(cacheDir);
51
+ const log = await repo.log({ maxCount: 1 });
52
+ const commitSha = log.latest?.hash.slice(0, 8) ?? "unknown";
53
+ console.error(`[mcp-git-fetcher] Cloned at commit ${commitSha}`);
54
+ return { repoRoot: cacheDir, commitSha };
55
+ }
56
+ /**
57
+ * List all files recursively under a subdirectory of the repo root.
58
+ * Returns absolute paths. Returns empty array if subdir doesn't exist.
59
+ */
60
+ export function listSubdirFiles(repoRoot, subdir) {
61
+ const dir = join(repoRoot, subdir);
62
+ if (!existsSync(dir))
63
+ return [];
64
+ const results = [];
65
+ function walk(current) {
66
+ const entries = readdirSync(current);
67
+ for (const entry of entries) {
68
+ const full = join(current, entry);
69
+ try {
70
+ if (statSync(full).isDirectory()) {
71
+ walk(full);
72
+ }
73
+ else {
74
+ results.push(full);
75
+ }
76
+ }
77
+ catch {
78
+ // Permission or race — skip
79
+ }
80
+ }
81
+ }
82
+ walk(dir);
83
+ return results;
84
+ }
85
+ //# sourceMappingURL=git-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.js","sourceRoot":"","sources":["../src/git-ops.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "mcp-git-fetcher",
3
+ "version": "0.1.0",
4
+ "description": "MCP server to fetch skills/rules files from internal GitLab repositories",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-git-fetcher": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepublish": "tsc",
15
+ "start": "node dist/cli.js"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "gitlab",
20
+ "skills",
21
+ "rules",
22
+ "model-context-protocol"
23
+ ],
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.11.0",
27
+ "simple-git": "^3.27.0",
28
+ "dotenv": "^16.4.7"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.14.0",
32
+ "typescript": "^5.8.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/your-org/mcp-git-fetcher"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ }
41
+ }