gitea-server-mcp 1.0.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.
Files changed (3) hide show
  1. package/README.md +210 -0
  2. package/index.js +408 -0
  3. package/package.json +18 -0
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # gitea-server-mcp
2
+
3
+ Gitea MCP 服务器 — 让 AI 编程助手直接操作 Gitea 仓库、Issues、Pull Requests 等。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g gitea-server-mcp
9
+ ```
10
+
11
+ ## 前置条件
12
+
13
+ 在 Gitea 上生成一个 Access Token:
14
+ 1. 打开 Gitea → 右上角头像 → **设置** → **应用**
15
+ 2. 在 **管理 Access Token** 区域,输入 Token 名称(如 `mcp`)
16
+ 3. 勾选需要的权限(建议:`read:repository`, `write:repository`, `read:issue`, `write:issue`, `read:user`)
17
+ 4. 生成并复制 token
18
+
19
+ ## 配置
20
+
21
+ ### Claude Code
22
+
23
+ ```bash
24
+ claude mcp add --transport stdio --scope user gitea \
25
+ --env GITEA_URL=https://你的gitea域名 \
26
+ --env GITEA_TOKEN=你的token \
27
+ -- npx -y gitea-server-mcp
28
+ ```
29
+
30
+ ### Cursor / 其他 MCP 客户端
31
+
32
+ 在 `.cursor/mcp.json` 或对应配置文件中添加:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "gitea": {
38
+ "command": "npx",
39
+ "args": ["-y", "gitea-server-mcp"],
40
+ "env": {
41
+ "GITEA_URL": "https://你的gitea域名",
42
+ "GITEA_TOKEN": "你的token"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### 本地开发
50
+
51
+ ```bash
52
+ git clone <your-repo>
53
+ cd gitea-server-mcp
54
+ npm install
55
+ node index.js
56
+ ```
57
+
58
+ ## 可用工具
59
+
60
+ | 工具 | 说明 |
61
+ |---|---|
62
+ | **仓库** | |
63
+ | `gitea_list_repos` | 搜索/列出可访问的仓库 |
64
+ | `gitea_get_repo` | 获取仓库详情 |
65
+ | `gitea_create_repo` | 创建新仓库 |
66
+ | `gitea_search_repos` | 搜索仓库 |
67
+ | `gitea_list_my_repos` | 列出当前用户的仓库 |
68
+ | **Issue** | |
69
+ | `gitea_list_issues` | 列出仓库 Issues |
70
+ | `gitea_get_issue` | 获取单个 Issue 详情 |
71
+ | `gitea_create_issue` | 创建 Issue |
72
+ | `gitea_update_issue` | 更新 Issue |
73
+ | **Pull Request** | |
74
+ | `gitea_list_pull_requests` | 列出仓库 PRs |
75
+ | `gitea_get_pull_request` | 获取单个 PR 详情 |
76
+ | `gitea_create_pull_request` | 创建 PR |
77
+ | **文件** | |
78
+ | `gitea_get_file` | 获取仓库文件内容 |
79
+ | `gitea_create_file` | 创建或更新仓库文件 |
80
+ | **其他** | |
81
+ | `gitea_list_branches` | 列出分支 |
82
+ | `gitea_list_commits` | 查看提交历史 |
83
+ | `gitea_list_releases` | 列出 Releases |
84
+ | `gitea_get_user` | 获取当前用户信息 |
85
+
86
+ ## 环境变量
87
+
88
+ | 变量 | 必填 | 说明 |
89
+ |---|---|---|
90
+ | `GITEA_URL` | 是 | Gitea 实例地址,如 `https://gitea.example.com` |
91
+ | `GITEA_TOKEN` | 是 | Gitea Access Token |
92
+
93
+ ## 安全性
94
+
95
+ - Token 仅通过环境变量传入,不会写入日志或配置文件
96
+ - 所有请求通过 Gitea REST API 进行,Token 作为 HTTP Header 传递
97
+ - 代码完全开源,可自行审计
98
+ - 依赖仅 `@modelcontextprotocol/sdk` 一个核心库
99
+
100
+ ## 技术栈
101
+
102
+ - Node.js 18+
103
+ - @modelcontextprotocol/sdk
104
+ - Gitea REST API v1
105
+
106
+ ## License
107
+
108
+ ISC
109
+
110
+ ---
111
+
112
+ # gitea-server-mcp (English)
113
+
114
+ Gitea MCP Server — let AI coding assistants interact with Gitea repos, issues, pull requests, and more.
115
+
116
+ ## Installation
117
+
118
+ ```bash
119
+ npm install -g gitea-server-mcp
120
+ ```
121
+
122
+ ## Prerequisites
123
+
124
+ Generate an Access Token on your Gitea instance:
125
+ 1. Gitea → Avatar → Settings → Applications
126
+ 2. Under "Manage Access Tokens", enter a name (e.g. `mcp`)
127
+ 3. Select required scopes (recommend: `read:repository`, `write:repository`, `read:issue`, `write:issue`, `read:user`)
128
+ 4. Generate and copy the token
129
+
130
+ ## Configuration
131
+
132
+ ### Claude Code
133
+
134
+ ```bash
135
+ claude mcp add --transport stdio --scope user gitea \
136
+ --env GITEA_URL=https://your-gitea-domain \
137
+ --env GITEA_TOKEN=your-token \
138
+ -- npx -y gitea-server-mcp
139
+ ```
140
+
141
+ ### Cursor / Other MCP Clients
142
+
143
+ Add to `.cursor/mcp.json` or equivalent:
144
+
145
+ ```json
146
+ {
147
+ "mcpServers": {
148
+ "gitea": {
149
+ "command": "npx",
150
+ "args": ["-y", "gitea-server-mcp"],
151
+ "env": {
152
+ "GITEA_URL": "https://your-gitea-domain",
153
+ "GITEA_TOKEN": "your-token"
154
+ }
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Tools
161
+
162
+ | Tool | Description |
163
+ |---|---|
164
+ | **Repos** | |
165
+ | `gitea_list_repos` | Search/list accessible repos |
166
+ | `gitea_get_repo` | Get repo details |
167
+ | `gitea_create_repo` | Create a new repo |
168
+ | `gitea_search_repos` | Search repos |
169
+ | `gitea_list_my_repos` | List current user's repos |
170
+ | **Issues** | |
171
+ | `gitea_list_issues` | List repo issues |
172
+ | `gitea_get_issue` | Get a single issue |
173
+ | `gitea_create_issue` | Create an issue |
174
+ | `gitea_update_issue` | Update an issue |
175
+ | **Pull Requests** | |
176
+ | `gitea_list_pull_requests` | List repo PRs |
177
+ | `gitea_get_pull_request` | Get a single PR |
178
+ | `gitea_create_pull_request` | Create a PR |
179
+ | **Files** | |
180
+ | `gitea_get_file` | Get file content |
181
+ | `gitea_create_file` | Create or update a file |
182
+ | **Others** | |
183
+ | `gitea_list_branches` | List branches |
184
+ | `gitea_list_commits` | View commit history |
185
+ | `gitea_list_releases` | List releases |
186
+ | `gitea_get_user` | Get current user info |
187
+
188
+ ## Environment Variables
189
+
190
+ | Variable | Required | Description |
191
+ |---|---|---|
192
+ | `GITEA_URL` | Yes | Gitea instance URL, e.g. `https://gitea.example.com` |
193
+ | `GITEA_TOKEN` | Yes | Gitea Access Token |
194
+
195
+ ## Security
196
+
197
+ - Token is passed via environment variables only, never logged or saved to config files
198
+ - All requests use Gitea REST API with token as HTTP header
199
+ - Fully open source — audit the code yourself
200
+ - Only one core dependency: `@modelcontextprotocol/sdk`
201
+
202
+ ## Tech Stack
203
+
204
+ - Node.js 18+
205
+ - @modelcontextprotocol/sdk
206
+ - Gitea REST API v1
207
+
208
+ ## License
209
+
210
+ ISC
package/index.js ADDED
@@ -0,0 +1,408 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ const GITEA_URL = (process.env.GITEA_URL || "").replace(/\/+$/, "");
9
+ const GITEA_TOKEN = process.env.GITEA_TOKEN || "";
10
+
11
+ if (!GITEA_URL || !GITEA_TOKEN) {
12
+ console.error("Missing GITEA_URL or GITEA_TOKEN environment variable");
13
+ process.exit(1);
14
+ }
15
+
16
+ async function api(method, path, body) {
17
+ const url = `${GITEA_URL}/api/v1${path}`;
18
+ const opts = {
19
+ method,
20
+ headers: {
21
+ Authorization: `token ${GITEA_TOKEN}`,
22
+ "Content-Type": "application/json",
23
+ Accept: "application/json",
24
+ },
25
+ };
26
+ if (body) opts.body = JSON.stringify(body);
27
+ const res = await fetch(url, opts);
28
+ if (!res.ok) {
29
+ const text = await res.text();
30
+ throw new Error(`Gitea API error ${res.status}: ${text}`);
31
+ }
32
+ if (res.status === 204) return null;
33
+ return res.json();
34
+ }
35
+
36
+ // ---- Tools ----
37
+
38
+ const TOOLS = [
39
+ {
40
+ name: "gitea_list_repos",
41
+ description: "列出当前用户可访问的仓库",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ q: { type: "string", description: "搜索关键词" },
46
+ limit: { type: "number", description: "返回数量,默认20" },
47
+ page: { type: "number", description: "页码,默认1" },
48
+ },
49
+ },
50
+ },
51
+ {
52
+ name: "gitea_get_repo",
53
+ description: "获取仓库详情",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {
57
+ owner: { type: "string", description: "仓库所有者" },
58
+ repo: { type: "string", description: "仓库名称" },
59
+ },
60
+ required: ["owner", "repo"],
61
+ },
62
+ },
63
+ {
64
+ name: "gitea_create_repo",
65
+ description: "创建新仓库",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ name: { type: "string", description: "仓库名称" },
70
+ description: { type: "string", description: "仓库描述" },
71
+ private: { type: "boolean", description: "是否私有,默认false" },
72
+ auto_init: { type: "boolean", description: "是否初始化README,默认false" },
73
+ },
74
+ required: ["name"],
75
+ },
76
+ },
77
+ {
78
+ name: "gitea_list_issues",
79
+ description: "列出仓库的 Issues",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ owner: { type: "string", description: "仓库所有者" },
84
+ repo: { type: "string", description: "仓库名称" },
85
+ state: { type: "string", enum: ["open", "closed", "all"], description: "状态过滤,默认open" },
86
+ limit: { type: "number", description: "返回数量,默认20" },
87
+ page: { type: "number", description: "页码,默认1" },
88
+ },
89
+ required: ["owner", "repo"],
90
+ },
91
+ },
92
+ {
93
+ name: "gitea_get_issue",
94
+ description: "获取单个 Issue 详情",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ owner: { type: "string", description: "仓库所有者" },
99
+ repo: { type: "string", description: "仓库名称" },
100
+ index: { type: "number", description: "Issue 编号" },
101
+ },
102
+ required: ["owner", "repo", "index"],
103
+ },
104
+ },
105
+ {
106
+ name: "gitea_create_issue",
107
+ description: "创建 Issue",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ owner: { type: "string", description: "仓库所有者" },
112
+ repo: { type: "string", description: "仓库名称" },
113
+ title: { type: "string", description: "Issue 标题" },
114
+ body: { type: "string", description: "Issue 内容" },
115
+ labels: { type: "array", items: { type: "string" }, description: "标签列表" },
116
+ assignees: { type: "array", items: { type: "string" }, description: "指派用户列表" },
117
+ },
118
+ required: ["owner", "repo", "title"],
119
+ },
120
+ },
121
+ {
122
+ name: "gitea_update_issue",
123
+ description: "更新 Issue",
124
+ inputSchema: {
125
+ type: "object",
126
+ properties: {
127
+ owner: { type: "string", description: "仓库所有者" },
128
+ repo: { type: "string", description: "仓库名称" },
129
+ index: { type: "number", description: "Issue 编号" },
130
+ title: { type: "string", description: "新标题" },
131
+ body: { type: "string", description: "新内容" },
132
+ state: { type: "string", enum: ["open", "closed"], description: "状态" },
133
+ },
134
+ required: ["owner", "repo", "index"],
135
+ },
136
+ },
137
+ {
138
+ name: "gitea_list_pull_requests",
139
+ description: "列出仓库的 Pull Requests",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ owner: { type: "string", description: "仓库所有者" },
144
+ repo: { type: "string", description: "仓库名称" },
145
+ state: { type: "string", enum: ["open", "closed", "all"], description: "状态过滤,默认open" },
146
+ limit: { type: "number", description: "返回数量,默认20" },
147
+ page: { type: "number", description: "页码,默认1" },
148
+ },
149
+ required: ["owner", "repo"],
150
+ },
151
+ },
152
+ {
153
+ name: "gitea_get_pull_request",
154
+ description: "获取单个 PR 详情",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ owner: { type: "string", description: "仓库所有者" },
159
+ repo: { type: "string", description: "仓库名称" },
160
+ index: { type: "number", description: "PR 编号" },
161
+ },
162
+ required: ["owner", "repo", "index"],
163
+ },
164
+ },
165
+ {
166
+ name: "gitea_create_pull_request",
167
+ description: "创建 Pull Request",
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ owner: { type: "string", description: "仓库所有者" },
172
+ repo: { type: "string", description: "仓库名称" },
173
+ title: { type: "string", description: "PR 标题" },
174
+ body: { type: "string", description: "PR 内容" },
175
+ head: { type: "string", description: "源分支" },
176
+ base: { type: "string", description: "目标分支" },
177
+ },
178
+ required: ["owner", "repo", "title", "head", "base"],
179
+ },
180
+ },
181
+ {
182
+ name: "gitea_list_branches",
183
+ description: "列出仓库的分支",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ owner: { type: "string", description: "仓库所有者" },
188
+ repo: { type: "string", description: "仓库名称" },
189
+ },
190
+ required: ["owner", "repo"],
191
+ },
192
+ },
193
+ {
194
+ name: "gitea_list_commits",
195
+ description: "查看仓库提交历史",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ owner: { type: "string", description: "仓库所有者" },
200
+ repo: { type: "string", description: "仓库名称" },
201
+ sha: { type: "string", description: "分支名或commit sha,默认默认分支" },
202
+ limit: { type: "number", description: "返回数量,默认20" },
203
+ },
204
+ required: ["owner", "repo"],
205
+ },
206
+ },
207
+ {
208
+ name: "gitea_get_file",
209
+ description: "获取仓库文件内容",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ owner: { type: "string", description: "仓库所有者" },
214
+ repo: { type: "string", description: "仓库名称" },
215
+ path: { type: "string", description: "文件路径" },
216
+ ref: { type: "string", description: "分支名,默认默认分支" },
217
+ },
218
+ required: ["owner", "repo", "path"],
219
+ },
220
+ },
221
+ {
222
+ name: "gitea_create_file",
223
+ description: "创建或更新仓库文件",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ owner: { type: "string", description: "仓库所有者" },
228
+ repo: { type: "string", description: "仓库名称" },
229
+ path: { type: "string", description: "文件路径" },
230
+ content: { type: "string", description: "Base64编码的文件内容" },
231
+ message: { type: "string", description: "提交信息" },
232
+ branch: { type: "string", description: "分支名" },
233
+ sha: { type: "string", description: "更新时必填:旧文件的sha,用于冲突检测" },
234
+ },
235
+ required: ["owner", "repo", "path", "content", "message", "branch"],
236
+ },
237
+ },
238
+ {
239
+ name: "gitea_search_repos",
240
+ description: "搜索仓库",
241
+ inputSchema: {
242
+ type: "object",
243
+ properties: {
244
+ q: { type: "string", description: "搜索关键词" },
245
+ limit: { type: "number", description: "返回数量,默认20" },
246
+ },
247
+ required: ["q"],
248
+ },
249
+ },
250
+ {
251
+ name: "gitea_get_user",
252
+ description: "获取当前用户信息",
253
+ inputSchema: {
254
+ type: "object",
255
+ properties: {},
256
+ },
257
+ },
258
+ {
259
+ name: "gitea_list_my_repos",
260
+ description: "列出当前登录用户拥有的仓库",
261
+ inputSchema: {
262
+ type: "object",
263
+ properties: {
264
+ limit: { type: "number", description: "返回数量,默认20" },
265
+ },
266
+ },
267
+ },
268
+ {
269
+ name: "gitea_list_releases",
270
+ description: "列出仓库的 Releases",
271
+ inputSchema: {
272
+ type: "object",
273
+ properties: {
274
+ owner: { type: "string", description: "仓库所有者" },
275
+ repo: { type: "string", description: "仓库名称" },
276
+ limit: { type: "number", description: "返回数量,默认20" },
277
+ },
278
+ required: ["owner", "repo"],
279
+ },
280
+ },
281
+ ];
282
+
283
+ // ---- Handler map ----
284
+
285
+ const handlers = {
286
+ gitea_list_repos: (args) => {
287
+ const params = new URLSearchParams();
288
+ if (args.q) params.set("q", args.q);
289
+ if (args.limit) params.set("limit", args.limit);
290
+ if (args.page) params.set("page", args.page);
291
+ return api("GET", `/repos/search?${params}`);
292
+ },
293
+ gitea_get_repo: (args) => api("GET", `/repos/${args.owner}/${args.repo}`),
294
+ gitea_create_repo: (args) =>
295
+ api("POST", `/user/repos`, {
296
+ name: args.name,
297
+ description: args.description,
298
+ private: args.private || false,
299
+ auto_init: args.auto_init || false,
300
+ }),
301
+ gitea_list_issues: (args) => {
302
+ const params = new URLSearchParams();
303
+ if (args.state) params.set("state", args.state);
304
+ if (args.limit) params.set("limit", args.limit);
305
+ if (args.page) params.set("page", args.page);
306
+ return api("GET", `/repos/${args.owner}/${args.repo}/issues?${params}`);
307
+ },
308
+ gitea_get_issue: (args) =>
309
+ api("GET", `/repos/${args.owner}/${args.repo}/issues/${args.index}`),
310
+ gitea_create_issue: (args) =>
311
+ api("POST", `/repos/${args.owner}/${args.repo}/issues`, {
312
+ title: args.title,
313
+ body: args.body,
314
+ labels: args.labels,
315
+ assignees: args.assignees,
316
+ }),
317
+ gitea_update_issue: (args) => {
318
+ const body = {};
319
+ if (args.title !== undefined) body.title = args.title;
320
+ if (args.body !== undefined) body.body = args.body;
321
+ if (args.state !== undefined) body.state = args.state;
322
+ return api("PATCH", `/repos/${args.owner}/${args.repo}/issues/${args.index}`, body);
323
+ },
324
+ gitea_list_pull_requests: (args) => {
325
+ const params = new URLSearchParams();
326
+ if (args.state) params.set("state", args.state);
327
+ if (args.limit) params.set("limit", args.limit);
328
+ if (args.page) params.set("page", args.page);
329
+ return api("GET", `/repos/${args.owner}/${args.repo}/pulls?${params}`);
330
+ },
331
+ gitea_get_pull_request: (args) =>
332
+ api("GET", `/repos/${args.owner}/${args.repo}/pulls/${args.index}`),
333
+ gitea_create_pull_request: (args) =>
334
+ api("POST", `/repos/${args.owner}/${args.repo}/pulls`, {
335
+ title: args.title,
336
+ body: args.body,
337
+ head: args.head,
338
+ base: args.base,
339
+ }),
340
+ gitea_list_branches: (args) =>
341
+ api("GET", `/repos/${args.owner}/${args.repo}/branches`),
342
+ gitea_list_commits: (args) => {
343
+ const params = new URLSearchParams();
344
+ if (args.sha) params.set("sha", args.sha);
345
+ if (args.limit) params.set("limit", args.limit);
346
+ return api("GET", `/repos/${args.owner}/${args.repo}/commits?${params}`);
347
+ },
348
+ gitea_get_file: (args) => {
349
+ const ref = args.ref ? `?ref=${args.ref}` : "";
350
+ return api("GET", `/repos/${args.owner}/${args.repo}/contents/${args.path}${ref}`);
351
+ },
352
+ gitea_create_file: (args) => {
353
+ const body = {
354
+ content: args.content,
355
+ message: args.message,
356
+ branch: args.branch,
357
+ };
358
+ if (args.sha) body.sha = args.sha;
359
+ return api("POST", `/repos/${args.owner}/${args.repo}/contents/${args.path}`, body);
360
+ },
361
+ gitea_search_repos: (args) => {
362
+ const params = new URLSearchParams();
363
+ params.set("q", args.q);
364
+ if (args.limit) params.set("limit", args.limit);
365
+ return api("GET", `/repos/search?${params}`);
366
+ },
367
+ gitea_get_user: () => api("GET", "/user"),
368
+ gitea_list_my_repos: (args) => {
369
+ const params = new URLSearchParams();
370
+ if (args.limit) params.set("limit", args.limit);
371
+ return api("GET", `/user/repos?${params}`);
372
+ },
373
+ gitea_list_releases: (args) => {
374
+ const params = new URLSearchParams();
375
+ if (args.limit) params.set("limit", args.limit);
376
+ return api("GET", `/repos/${args.owner}/${args.repo}/releases?${params}`);
377
+ },
378
+ };
379
+
380
+ // ---- Server ----
381
+
382
+ const server = new Server(
383
+ { name: "gitea-mcp", version: "1.0.0" },
384
+ { capabilities: { tools: {} } }
385
+ );
386
+
387
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
388
+
389
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
390
+ const { name, arguments: args } = request.params;
391
+ const handler = handlers[name];
392
+ if (!handler) throw new Error(`Unknown tool: ${name}`);
393
+ const result = await handler(args || {});
394
+ return {
395
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
396
+ };
397
+ });
398
+
399
+ async function main() {
400
+ const transport = new StdioServerTransport();
401
+ await server.connect(transport);
402
+ console.error(`Gitea MCP Server connected to ${GITEA_URL}`);
403
+ }
404
+
405
+ main().catch((err) => {
406
+ console.error(err);
407
+ process.exit(1);
408
+ });
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "gitea-server-mcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "bin": { "gitea-mcp": "index.js" },
7
+ "scripts": {
8
+ "start": "node index.js"
9
+ },
10
+ "keywords": ["gitea", "mcp"],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "Custom MCP server for Gitea operations",
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.29.0",
16
+ "zod": "^4.4.3"
17
+ }
18
+ }