server.ts 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. /**
  2. * MCP Server Setup
  3. * Contains all the server configuration, tool schemas, and request handlers
  4. */
  5. import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  6. import {
  7. CallToolRequestSchema,
  8. ListToolsRequestSchema,
  9. ListResourcesRequestSchema,
  10. ReadResourceRequestSchema,
  11. } from '@modelcontextprotocol/sdk/types.js';
  12. import { z } from 'zod';
  13. import { GogsClient } from './gogs-client.js';
  14. // Define tool schemas
  15. const GetUserSchema = z.object({
  16. username: z.string().describe('Username to get information for'),
  17. });
  18. const SearchUsersSchema = z.object({
  19. query: z.string().describe('Search query for users'),
  20. limit: z.number().optional().describe('Maximum number of results (default: 10)'),
  21. });
  22. const ListUserReposSchema = z.object({
  23. username: z.string().optional().describe('Username (omit for authenticated user)'),
  24. });
  25. const SearchReposSchema = z.object({
  26. query: z.string().describe('Search query for repositories'),
  27. uid: z.number().optional().describe('User ID to filter repositories'),
  28. limit: z.number().optional().describe('Maximum number of results (default: 10)'),
  29. page: z.number().optional().describe('Page number (default: 1)'),
  30. });
  31. const GetRepoSchema = z.object({
  32. owner: z.string().describe('Repository owner username'),
  33. repo: z.string().describe('Repository name'),
  34. });
  35. const CreateRepoSchema = z.object({
  36. name: z.string().describe('Repository name'),
  37. description: z.string().optional().describe('Repository description'),
  38. private: z.boolean().optional().describe('Create as private repository (default: false)'),
  39. auto_init: z.boolean().optional().describe('Initialize with README (default: false)'),
  40. gitignores: z.string().optional().describe('Gitignore templates (e.g., "Go,VisualStudioCode")'),
  41. license: z.string().optional().describe('License template (e.g., "MIT License")'),
  42. readme: z.string().optional().describe('README template (default: "Default")'),
  43. });
  44. const DeleteRepoSchema = z.object({
  45. owner: z.string().describe('Repository owner username'),
  46. repo: z.string().describe('Repository name'),
  47. });
  48. const GetContentsSchema = z.object({
  49. owner: z.string().describe('Repository owner username'),
  50. repo: z.string().describe('Repository name'),
  51. path: z.string().describe('File or directory path'),
  52. ref: z.string().optional().describe('Branch, tag, or commit SHA (default: default branch)'),
  53. });
  54. const GetRawContentSchema = z.object({
  55. owner: z.string().describe('Repository owner username'),
  56. repo: z.string().describe('Repository name'),
  57. ref: z.string().describe('Branch, tag, or commit SHA'),
  58. path: z.string().describe('File path'),
  59. });
  60. const ListBranchesSchema = z.object({
  61. owner: z.string().describe('Repository owner username'),
  62. repo: z.string().describe('Repository name'),
  63. });
  64. const GetCommitsSchema = z.object({
  65. owner: z.string().describe('Repository owner username'),
  66. repo: z.string().describe('Repository name'),
  67. sha: z.string().optional().describe('Branch name or commit SHA'),
  68. page: z.number().optional().describe('Page number (default: 1)'),
  69. });
  70. const ListIssuesSchema = z.object({
  71. owner: z.string().describe('Repository owner username'),
  72. repo: z.string().describe('Repository name'),
  73. state: z.enum(['open', 'closed', 'all']).optional().describe('Filter by state (default: open)'),
  74. labels: z.string().optional().describe('Comma-separated list of label names'),
  75. page: z.number().optional().describe('Page number (default: 1)'),
  76. per_page: z.number().optional().describe('Results per page (default: 30)'),
  77. });
  78. const GetIssueSchema = z.object({
  79. owner: z.string().describe('Repository owner username'),
  80. repo: z.string().describe('Repository name'),
  81. number: z.number().describe('Issue number'),
  82. });
  83. const CreateIssueSchema = z.object({
  84. owner: z.string().describe('Repository owner username'),
  85. repo: z.string().describe('Repository name'),
  86. title: z.string().describe('Issue title'),
  87. body: z.string().optional().describe('Issue description'),
  88. assignee: z.string().optional().describe('Username to assign the issue to'),
  89. milestone: z.number().optional().describe('Milestone ID'),
  90. labels: z.array(z.number()).optional().describe('Array of label IDs'),
  91. });
  92. const UpdateIssueSchema = z.object({
  93. owner: z.string().describe('Repository owner username'),
  94. repo: z.string().describe('Repository name'),
  95. number: z.number().describe('Issue number'),
  96. title: z.string().optional().describe('Issue title'),
  97. body: z.string().optional().describe('Issue description'),
  98. assignee: z.string().optional().describe('Username to assign the issue to'),
  99. milestone: z.number().optional().describe('Milestone ID'),
  100. state: z.enum(['open', 'closed']).optional().describe('Issue state'),
  101. labels: z.array(z.number()).optional().describe('Array of label IDs'),
  102. });
  103. const ListIssueCommentsSchema = z.object({
  104. owner: z.string().describe('Repository owner username'),
  105. repo: z.string().describe('Repository name'),
  106. number: z.number().describe('Issue number'),
  107. });
  108. const CreateIssueCommentSchema = z.object({
  109. owner: z.string().describe('Repository owner username'),
  110. repo: z.string().describe('Repository name'),
  111. number: z.number().describe('Issue number'),
  112. body: z.string().describe('Comment text'),
  113. });
  114. const UpdateIssueCommentSchema = z.object({
  115. owner: z.string().describe('Repository owner username'),
  116. repo: z.string().describe('Repository name'),
  117. commentId: z.number().describe('Comment ID'),
  118. body: z.string().describe('Updated comment text'),
  119. });
  120. const ListLabelsSchema = z.object({
  121. owner: z.string().describe('Repository owner username'),
  122. repo: z.string().describe('Repository name'),
  123. });
  124. const GetLabelSchema = z.object({
  125. owner: z.string().describe('Repository owner username'),
  126. repo: z.string().describe('Repository name'),
  127. labelId: z.number().describe('Label ID'),
  128. });
  129. const CreateLabelSchema = z.object({
  130. owner: z.string().describe('Repository owner username'),
  131. repo: z.string().describe('Repository name'),
  132. name: z.string().describe('Label name'),
  133. color: z.string().describe('7-character hex color code with leading # (e.g., #ff0000)'),
  134. });
  135. const UpdateLabelSchema = z.object({
  136. owner: z.string().describe('Repository owner username'),
  137. repo: z.string().describe('Repository name'),
  138. labelId: z.number().describe('Label ID'),
  139. name: z.string().optional().describe('Label name'),
  140. color: z.string().optional().describe('7-character hex color code with leading # (e.g., #ff0000)'),
  141. });
  142. const DeleteLabelSchema = z.object({
  143. owner: z.string().describe('Repository owner username'),
  144. repo: z.string().describe('Repository name'),
  145. labelId: z.number().describe('Label ID'),
  146. });
  147. const ListIssueLabelsSchema = z.object({
  148. owner: z.string().describe('Repository owner username'),
  149. repo: z.string().describe('Repository name'),
  150. number: z.number().describe('Issue number'),
  151. });
  152. const AddIssueLabelsSchema = z.object({
  153. owner: z.string().describe('Repository owner username'),
  154. repo: z.string().describe('Repository name'),
  155. number: z.number().describe('Issue number'),
  156. labels: z.array(z.number()).describe('Array of label IDs to add'),
  157. });
  158. const RemoveIssueLabelSchema = z.object({
  159. owner: z.string().describe('Repository owner username'),
  160. repo: z.string().describe('Repository name'),
  161. number: z.number().describe('Issue number'),
  162. labelId: z.number().describe('Label ID to remove'),
  163. });
  164. const ReplaceIssueLabelsSchema = z.object({
  165. owner: z.string().describe('Repository owner username'),
  166. repo: z.string().describe('Repository name'),
  167. number: z.number().describe('Issue number'),
  168. labels: z.array(z.number()).describe('Array of label IDs to set (replaces all existing labels)'),
  169. });
  170. const RemoveAllIssueLabelsSchema = z.object({
  171. owner: z.string().describe('Repository owner username'),
  172. repo: z.string().describe('Repository name'),
  173. number: z.number().describe('Issue number'),
  174. });
  175. /**
  176. * Create and configure an MCP server with all tools and handlers
  177. */
  178. export function createMcpServer(gogsClient: GogsClient): Server {
  179. const server = new Server(
  180. {
  181. name: 'gogs-mcp-server',
  182. version: '1.0.0',
  183. },
  184. {
  185. capabilities: {
  186. tools: {},
  187. resources: {},
  188. },
  189. }
  190. );
  191. // Register tools
  192. server.setRequestHandler(ListToolsRequestSchema, async () => {
  193. return {
  194. tools: [
  195. {
  196. name: 'get_current_user',
  197. description: 'Get information about the authenticated user',
  198. inputSchema: {
  199. type: 'object',
  200. properties: {},
  201. },
  202. },
  203. {
  204. name: 'get_user',
  205. description: 'Get information about a specific user',
  206. inputSchema: {
  207. type: 'object',
  208. properties: {
  209. username: {
  210. type: 'string',
  211. description: 'Username to get information for',
  212. },
  213. },
  214. required: ['username'],
  215. },
  216. },
  217. {
  218. name: 'search_users',
  219. description: 'Search for users by username',
  220. inputSchema: {
  221. type: 'object',
  222. properties: {
  223. query: {
  224. type: 'string',
  225. description: 'Search query for users',
  226. },
  227. limit: {
  228. type: 'number',
  229. description: 'Maximum number of results (default: 10)',
  230. },
  231. },
  232. required: ['query'],
  233. },
  234. },
  235. {
  236. name: 'list_user_repositories',
  237. description: 'List repositories for a user (or authenticated user if no username provided)',
  238. inputSchema: {
  239. type: 'object',
  240. properties: {
  241. username: {
  242. type: 'string',
  243. description: 'Username (omit for authenticated user)',
  244. },
  245. },
  246. },
  247. },
  248. {
  249. name: 'search_repositories',
  250. description: 'Search for repositories',
  251. inputSchema: {
  252. type: 'object',
  253. properties: {
  254. query: {
  255. type: 'string',
  256. description: 'Search query for repositories',
  257. },
  258. uid: {
  259. type: 'number',
  260. description: 'User ID to filter repositories',
  261. },
  262. limit: {
  263. type: 'number',
  264. description: 'Maximum number of results (default: 10)',
  265. },
  266. page: {
  267. type: 'number',
  268. description: 'Page number (default: 1)',
  269. },
  270. },
  271. required: ['query'],
  272. },
  273. },
  274. {
  275. name: 'get_repository',
  276. description: 'Get information about a specific repository',
  277. inputSchema: {
  278. type: 'object',
  279. properties: {
  280. owner: {
  281. type: 'string',
  282. description: 'Repository owner username',
  283. },
  284. repo: {
  285. type: 'string',
  286. description: 'Repository name',
  287. },
  288. },
  289. required: ['owner', 'repo'],
  290. },
  291. },
  292. {
  293. name: 'create_repository',
  294. description: 'Create a new repository for the authenticated user',
  295. inputSchema: {
  296. type: 'object',
  297. properties: {
  298. name: {
  299. type: 'string',
  300. description: 'Repository name',
  301. },
  302. description: {
  303. type: 'string',
  304. description: 'Repository description',
  305. },
  306. private: {
  307. type: 'boolean',
  308. description: 'Create as private repository (default: false)',
  309. },
  310. auto_init: {
  311. type: 'boolean',
  312. description: 'Initialize with README (default: false)',
  313. },
  314. gitignores: {
  315. type: 'string',
  316. description: 'Gitignore templates (e.g., "Go,VisualStudioCode")',
  317. },
  318. license: {
  319. type: 'string',
  320. description: 'License template (e.g., "MIT License")',
  321. },
  322. readme: {
  323. type: 'string',
  324. description: 'README template (default: "Default")',
  325. },
  326. },
  327. required: ['name'],
  328. },
  329. },
  330. {
  331. name: 'delete_repository',
  332. description: 'Delete a repository (requires admin access)',
  333. inputSchema: {
  334. type: 'object',
  335. properties: {
  336. owner: {
  337. type: 'string',
  338. description: 'Repository owner username',
  339. },
  340. repo: {
  341. type: 'string',
  342. description: 'Repository name',
  343. },
  344. },
  345. required: ['owner', 'repo'],
  346. },
  347. },
  348. {
  349. name: 'get_contents',
  350. description: 'Get file or directory contents from a repository',
  351. inputSchema: {
  352. type: 'object',
  353. properties: {
  354. owner: {
  355. type: 'string',
  356. description: 'Repository owner username',
  357. },
  358. repo: {
  359. type: 'string',
  360. description: 'Repository name',
  361. },
  362. path: {
  363. type: 'string',
  364. description: 'File or directory path',
  365. },
  366. ref: {
  367. type: 'string',
  368. description: 'Branch, tag, or commit SHA (default: default branch)',
  369. },
  370. },
  371. required: ['owner', 'repo', 'path'],
  372. },
  373. },
  374. {
  375. name: 'get_raw_content',
  376. description: 'Get raw file content from a repository',
  377. inputSchema: {
  378. type: 'object',
  379. properties: {
  380. owner: {
  381. type: 'string',
  382. description: 'Repository owner username',
  383. },
  384. repo: {
  385. type: 'string',
  386. description: 'Repository name',
  387. },
  388. ref: {
  389. type: 'string',
  390. description: 'Branch, tag, or commit SHA',
  391. },
  392. path: {
  393. type: 'string',
  394. description: 'File path',
  395. },
  396. },
  397. required: ['owner', 'repo', 'ref', 'path'],
  398. },
  399. },
  400. {
  401. name: 'list_branches',
  402. description: 'List all branches in a repository',
  403. inputSchema: {
  404. type: 'object',
  405. properties: {
  406. owner: {
  407. type: 'string',
  408. description: 'Repository owner username',
  409. },
  410. repo: {
  411. type: 'string',
  412. description: 'Repository name',
  413. },
  414. },
  415. required: ['owner', 'repo'],
  416. },
  417. },
  418. {
  419. name: 'get_commits',
  420. description: 'Get commit history from a repository',
  421. inputSchema: {
  422. type: 'object',
  423. properties: {
  424. owner: {
  425. type: 'string',
  426. description: 'Repository owner username',
  427. },
  428. repo: {
  429. type: 'string',
  430. description: 'Repository name',
  431. },
  432. sha: {
  433. type: 'string',
  434. description: 'Branch name or commit SHA',
  435. },
  436. page: {
  437. type: 'number',
  438. description: 'Page number (default: 1)',
  439. },
  440. },
  441. required: ['owner', 'repo'],
  442. },
  443. },
  444. {
  445. name: 'list_issues',
  446. description: 'List issues in a repository',
  447. inputSchema: {
  448. type: 'object',
  449. properties: {
  450. owner: {
  451. type: 'string',
  452. description: 'Repository owner username',
  453. },
  454. repo: {
  455. type: 'string',
  456. description: 'Repository name',
  457. },
  458. state: {
  459. type: 'string',
  460. enum: ['open', 'closed', 'all'],
  461. description: 'Filter by state (default: open)',
  462. },
  463. labels: {
  464. type: 'string',
  465. description: 'Comma-separated list of label names',
  466. },
  467. page: {
  468. type: 'number',
  469. description: 'Page number (default: 1)',
  470. },
  471. per_page: {
  472. type: 'number',
  473. description: 'Results per page (default: 30)',
  474. },
  475. },
  476. required: ['owner', 'repo'],
  477. },
  478. },
  479. {
  480. name: 'get_issue',
  481. description: 'Get a specific issue by number',
  482. inputSchema: {
  483. type: 'object',
  484. properties: {
  485. owner: {
  486. type: 'string',
  487. description: 'Repository owner username',
  488. },
  489. repo: {
  490. type: 'string',
  491. description: 'Repository name',
  492. },
  493. number: {
  494. type: 'number',
  495. description: 'Issue number',
  496. },
  497. },
  498. required: ['owner', 'repo', 'number'],
  499. },
  500. },
  501. {
  502. name: 'create_issue',
  503. description: 'Create a new issue in a repository',
  504. inputSchema: {
  505. type: 'object',
  506. properties: {
  507. owner: {
  508. type: 'string',
  509. description: 'Repository owner username',
  510. },
  511. repo: {
  512. type: 'string',
  513. description: 'Repository name',
  514. },
  515. title: {
  516. type: 'string',
  517. description: 'Issue title',
  518. },
  519. body: {
  520. type: 'string',
  521. description: 'Issue description',
  522. },
  523. assignee: {
  524. type: 'string',
  525. description: 'Username to assign the issue to',
  526. },
  527. milestone: {
  528. type: 'number',
  529. description: 'Milestone ID',
  530. },
  531. labels: {
  532. type: 'array',
  533. items: {
  534. type: 'number',
  535. },
  536. description: 'Array of label IDs',
  537. },
  538. },
  539. required: ['owner', 'repo', 'title'],
  540. },
  541. },
  542. {
  543. name: 'update_issue',
  544. description: 'Update an existing issue (including closing or reopening)',
  545. inputSchema: {
  546. type: 'object',
  547. properties: {
  548. owner: {
  549. type: 'string',
  550. description: 'Repository owner username',
  551. },
  552. repo: {
  553. type: 'string',
  554. description: 'Repository name',
  555. },
  556. number: {
  557. type: 'number',
  558. description: 'Issue number',
  559. },
  560. title: {
  561. type: 'string',
  562. description: 'Issue title',
  563. },
  564. body: {
  565. type: 'string',
  566. description: 'Issue description',
  567. },
  568. assignee: {
  569. type: 'string',
  570. description: 'Username to assign the issue to',
  571. },
  572. milestone: {
  573. type: 'number',
  574. description: 'Milestone ID',
  575. },
  576. state: {
  577. type: 'string',
  578. enum: ['open', 'closed'],
  579. description: 'Issue state',
  580. },
  581. labels: {
  582. type: 'array',
  583. items: {
  584. type: 'number',
  585. },
  586. description: 'Array of label IDs',
  587. },
  588. },
  589. required: ['owner', 'repo', 'number'],
  590. },
  591. },
  592. {
  593. name: 'list_issue_comments',
  594. description: 'List all comments on an issue',
  595. inputSchema: {
  596. type: 'object',
  597. properties: {
  598. owner: {
  599. type: 'string',
  600. description: 'Repository owner username',
  601. },
  602. repo: {
  603. type: 'string',
  604. description: 'Repository name',
  605. },
  606. number: {
  607. type: 'number',
  608. description: 'Issue number',
  609. },
  610. },
  611. required: ['owner', 'repo', 'number'],
  612. },
  613. },
  614. {
  615. name: 'create_issue_comment',
  616. description: 'Add a comment to an issue',
  617. inputSchema: {
  618. type: 'object',
  619. properties: {
  620. owner: {
  621. type: 'string',
  622. description: 'Repository owner username',
  623. },
  624. repo: {
  625. type: 'string',
  626. description: 'Repository name',
  627. },
  628. number: {
  629. type: 'number',
  630. description: 'Issue number',
  631. },
  632. body: {
  633. type: 'string',
  634. description: 'Comment text',
  635. },
  636. },
  637. required: ['owner', 'repo', 'number', 'body'],
  638. },
  639. },
  640. {
  641. name: 'update_issue_comment',
  642. description: 'Edit an existing comment on an issue',
  643. inputSchema: {
  644. type: 'object',
  645. properties: {
  646. owner: {
  647. type: 'string',
  648. description: 'Repository owner username',
  649. },
  650. repo: {
  651. type: 'string',
  652. description: 'Repository name',
  653. },
  654. commentId: {
  655. type: 'number',
  656. description: 'Comment ID',
  657. },
  658. body: {
  659. type: 'string',
  660. description: 'Updated comment text',
  661. },
  662. },
  663. required: ['owner', 'repo', 'commentId', 'body'],
  664. },
  665. },
  666. {
  667. name: 'list_labels',
  668. description: 'List all labels in a repository',
  669. inputSchema: {
  670. type: 'object',
  671. properties: {
  672. owner: {
  673. type: 'string',
  674. description: 'Repository owner username',
  675. },
  676. repo: {
  677. type: 'string',
  678. description: 'Repository name',
  679. },
  680. },
  681. required: ['owner', 'repo'],
  682. },
  683. },
  684. {
  685. name: 'get_label',
  686. description: 'Get a specific label by ID',
  687. inputSchema: {
  688. type: 'object',
  689. properties: {
  690. owner: {
  691. type: 'string',
  692. description: 'Repository owner username',
  693. },
  694. repo: {
  695. type: 'string',
  696. description: 'Repository name',
  697. },
  698. labelId: {
  699. type: 'number',
  700. description: 'Label ID',
  701. },
  702. },
  703. required: ['owner', 'repo', 'labelId'],
  704. },
  705. },
  706. {
  707. name: 'create_label',
  708. description: 'Create a new label in a repository',
  709. inputSchema: {
  710. type: 'object',
  711. properties: {
  712. owner: {
  713. type: 'string',
  714. description: 'Repository owner username',
  715. },
  716. repo: {
  717. type: 'string',
  718. description: 'Repository name',
  719. },
  720. name: {
  721. type: 'string',
  722. description: 'Label name',
  723. },
  724. color: {
  725. type: 'string',
  726. description: '7-character hex color code with leading # (e.g., #ff0000)',
  727. },
  728. },
  729. required: ['owner', 'repo', 'name', 'color'],
  730. },
  731. },
  732. {
  733. name: 'update_label',
  734. description: 'Update an existing label',
  735. inputSchema: {
  736. type: 'object',
  737. properties: {
  738. owner: {
  739. type: 'string',
  740. description: 'Repository owner username',
  741. },
  742. repo: {
  743. type: 'string',
  744. description: 'Repository name',
  745. },
  746. labelId: {
  747. type: 'number',
  748. description: 'Label ID',
  749. },
  750. name: {
  751. type: 'string',
  752. description: 'Label name',
  753. },
  754. color: {
  755. type: 'string',
  756. description: '7-character hex color code with leading # (e.g., #ff0000)',
  757. },
  758. },
  759. required: ['owner', 'repo', 'labelId'],
  760. },
  761. },
  762. {
  763. name: 'delete_label',
  764. description: 'Delete a label from a repository',
  765. inputSchema: {
  766. type: 'object',
  767. properties: {
  768. owner: {
  769. type: 'string',
  770. description: 'Repository owner username',
  771. },
  772. repo: {
  773. type: 'string',
  774. description: 'Repository name',
  775. },
  776. labelId: {
  777. type: 'number',
  778. description: 'Label ID',
  779. },
  780. },
  781. required: ['owner', 'repo', 'labelId'],
  782. },
  783. },
  784. {
  785. name: 'list_issue_labels',
  786. description: 'List all labels on a specific issue',
  787. inputSchema: {
  788. type: 'object',
  789. properties: {
  790. owner: {
  791. type: 'string',
  792. description: 'Repository owner username',
  793. },
  794. repo: {
  795. type: 'string',
  796. description: 'Repository name',
  797. },
  798. number: {
  799. type: 'number',
  800. description: 'Issue number',
  801. },
  802. },
  803. required: ['owner', 'repo', 'number'],
  804. },
  805. },
  806. {
  807. name: 'add_issue_labels',
  808. description: 'Add labels to an issue',
  809. inputSchema: {
  810. type: 'object',
  811. properties: {
  812. owner: {
  813. type: 'string',
  814. description: 'Repository owner username',
  815. },
  816. repo: {
  817. type: 'string',
  818. description: 'Repository name',
  819. },
  820. number: {
  821. type: 'number',
  822. description: 'Issue number',
  823. },
  824. labels: {
  825. type: 'array',
  826. items: {
  827. type: 'number',
  828. },
  829. description: 'Array of label IDs to add',
  830. },
  831. },
  832. required: ['owner', 'repo', 'number', 'labels'],
  833. },
  834. },
  835. {
  836. name: 'remove_issue_label',
  837. description: 'Remove a specific label from an issue',
  838. inputSchema: {
  839. type: 'object',
  840. properties: {
  841. owner: {
  842. type: 'string',
  843. description: 'Repository owner username',
  844. },
  845. repo: {
  846. type: 'string',
  847. description: 'Repository name',
  848. },
  849. number: {
  850. type: 'number',
  851. description: 'Issue number',
  852. },
  853. labelId: {
  854. type: 'number',
  855. description: 'Label ID to remove',
  856. },
  857. },
  858. required: ['owner', 'repo', 'number', 'labelId'],
  859. },
  860. },
  861. {
  862. name: 'replace_issue_labels',
  863. description: 'Replace all labels on an issue with a new set',
  864. inputSchema: {
  865. type: 'object',
  866. properties: {
  867. owner: {
  868. type: 'string',
  869. description: 'Repository owner username',
  870. },
  871. repo: {
  872. type: 'string',
  873. description: 'Repository name',
  874. },
  875. number: {
  876. type: 'number',
  877. description: 'Issue number',
  878. },
  879. labels: {
  880. type: 'array',
  881. items: {
  882. type: 'number',
  883. },
  884. description: 'Array of label IDs to set (replaces all existing labels)',
  885. },
  886. },
  887. required: ['owner', 'repo', 'number', 'labels'],
  888. },
  889. },
  890. {
  891. name: 'remove_all_issue_labels',
  892. description: 'Remove all labels from an issue',
  893. inputSchema: {
  894. type: 'object',
  895. properties: {
  896. owner: {
  897. type: 'string',
  898. description: 'Repository owner username',
  899. },
  900. repo: {
  901. type: 'string',
  902. description: 'Repository name',
  903. },
  904. number: {
  905. type: 'number',
  906. description: 'Issue number',
  907. },
  908. },
  909. required: ['owner', 'repo', 'number'],
  910. },
  911. },
  912. ],
  913. };
  914. });
  915. // Handle tool calls
  916. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  917. try {
  918. const { name, arguments: args } = request.params;
  919. switch (name) {
  920. case 'get_current_user': {
  921. const user = await gogsClient.getCurrentUser();
  922. return {
  923. content: [
  924. {
  925. type: 'text',
  926. text: JSON.stringify(user, null, 2),
  927. },
  928. ],
  929. };
  930. }
  931. case 'get_user': {
  932. const { username } = GetUserSchema.parse(args);
  933. const user = await gogsClient.getUser(username);
  934. return {
  935. content: [
  936. {
  937. type: 'text',
  938. text: JSON.stringify(user, null, 2),
  939. },
  940. ],
  941. };
  942. }
  943. case 'search_users': {
  944. const { query, limit } = SearchUsersSchema.parse(args);
  945. const users = await gogsClient.searchUsers(query, limit);
  946. return {
  947. content: [
  948. {
  949. type: 'text',
  950. text: JSON.stringify(users, null, 2),
  951. },
  952. ],
  953. };
  954. }
  955. case 'list_user_repositories': {
  956. const { username } = ListUserReposSchema.parse(args);
  957. const repos = username
  958. ? await gogsClient.listUserRepositories(username)
  959. : await gogsClient.listMyRepositories();
  960. return {
  961. content: [
  962. {
  963. type: 'text',
  964. text: JSON.stringify(repos, null, 2),
  965. },
  966. ],
  967. };
  968. }
  969. case 'search_repositories': {
  970. const { query, uid, limit, page } = SearchReposSchema.parse(args);
  971. const repos = await gogsClient.searchRepositories(query, { uid, limit, page });
  972. return {
  973. content: [
  974. {
  975. type: 'text',
  976. text: JSON.stringify(repos, null, 2),
  977. },
  978. ],
  979. };
  980. }
  981. case 'get_repository': {
  982. const { owner, repo } = GetRepoSchema.parse(args);
  983. const repository = await gogsClient.getRepository(owner, repo);
  984. return {
  985. content: [
  986. {
  987. type: 'text',
  988. text: JSON.stringify(repository, null, 2),
  989. },
  990. ],
  991. };
  992. }
  993. case 'create_repository': {
  994. const data = CreateRepoSchema.parse(args);
  995. const repo = await gogsClient.createRepository(data);
  996. return {
  997. content: [
  998. {
  999. type: 'text',
  1000. text: JSON.stringify(repo, null, 2),
  1001. },
  1002. ],
  1003. };
  1004. }
  1005. case 'delete_repository': {
  1006. const { owner, repo } = DeleteRepoSchema.parse(args);
  1007. await gogsClient.deleteRepository(owner, repo);
  1008. return {
  1009. content: [
  1010. {
  1011. type: 'text',
  1012. text: `Repository ${owner}/${repo} deleted successfully`,
  1013. },
  1014. ],
  1015. };
  1016. }
  1017. case 'get_contents': {
  1018. const { owner, repo, path, ref } = GetContentsSchema.parse(args);
  1019. const contents = await gogsClient.getContents(owner, repo, path, ref);
  1020. // If it's a file with base64 content, decode it
  1021. if (!Array.isArray(contents) && contents.type === 'file' && contents.content) {
  1022. const decodedContent = Buffer.from(contents.content, 'base64').toString('utf-8');
  1023. return {
  1024. content: [
  1025. {
  1026. type: 'text',
  1027. text: `File: ${contents.path}\nSize: ${contents.size} bytes\nSHA: ${contents.sha}\n\nContent:\n${decodedContent}`,
  1028. },
  1029. ],
  1030. };
  1031. }
  1032. return {
  1033. content: [
  1034. {
  1035. type: 'text',
  1036. text: JSON.stringify(contents, null, 2),
  1037. },
  1038. ],
  1039. };
  1040. }
  1041. case 'get_raw_content': {
  1042. const { owner, repo, ref, path } = GetRawContentSchema.parse(args);
  1043. const content = await gogsClient.getRawContent(owner, repo, ref, path);
  1044. return {
  1045. content: [
  1046. {
  1047. type: 'text',
  1048. text: content,
  1049. },
  1050. ],
  1051. };
  1052. }
  1053. case 'list_branches': {
  1054. const { owner, repo } = ListBranchesSchema.parse(args);
  1055. const branches = await gogsClient.listBranches(owner, repo);
  1056. return {
  1057. content: [
  1058. {
  1059. type: 'text',
  1060. text: JSON.stringify(branches, null, 2),
  1061. },
  1062. ],
  1063. };
  1064. }
  1065. case 'get_commits': {
  1066. const { owner, repo, sha, page } = GetCommitsSchema.parse(args);
  1067. const commits = await gogsClient.getCommits(owner, repo, { sha, page });
  1068. return {
  1069. content: [
  1070. {
  1071. type: 'text',
  1072. text: JSON.stringify(commits, null, 2),
  1073. },
  1074. ],
  1075. };
  1076. }
  1077. case 'list_issues': {
  1078. const { owner, repo, state, labels, page, per_page } = ListIssuesSchema.parse(args);
  1079. const issues = await gogsClient.listIssues(owner, repo, { state, labels, page, per_page });
  1080. return {
  1081. content: [
  1082. {
  1083. type: 'text',
  1084. text: JSON.stringify(issues, null, 2),
  1085. },
  1086. ],
  1087. };
  1088. }
  1089. case 'get_issue': {
  1090. const { owner, repo, number } = GetIssueSchema.parse(args);
  1091. const issue = await gogsClient.getIssue(owner, repo, number);
  1092. return {
  1093. content: [
  1094. {
  1095. type: 'text',
  1096. text: JSON.stringify(issue, null, 2),
  1097. },
  1098. ],
  1099. };
  1100. }
  1101. case 'create_issue': {
  1102. const { owner, repo, title, body, assignee, milestone, labels } = CreateIssueSchema.parse(args);
  1103. const issue = await gogsClient.createIssue(owner, repo, {
  1104. title,
  1105. body,
  1106. assignee,
  1107. milestone,
  1108. labels,
  1109. });
  1110. return {
  1111. content: [
  1112. {
  1113. type: 'text',
  1114. text: JSON.stringify(issue, null, 2),
  1115. },
  1116. ],
  1117. };
  1118. }
  1119. case 'update_issue': {
  1120. const { owner, repo, number, title, body, assignee, milestone, state, labels } = UpdateIssueSchema.parse(args);
  1121. const issue = await gogsClient.updateIssue(owner, repo, number, {
  1122. title,
  1123. body,
  1124. assignee,
  1125. milestone,
  1126. state,
  1127. labels,
  1128. });
  1129. return {
  1130. content: [
  1131. {
  1132. type: 'text',
  1133. text: JSON.stringify(issue, null, 2),
  1134. },
  1135. ],
  1136. };
  1137. }
  1138. case 'list_issue_comments': {
  1139. const { owner, repo, number } = ListIssueCommentsSchema.parse(args);
  1140. const comments = await gogsClient.listIssueComments(owner, repo, number);
  1141. return {
  1142. content: [
  1143. {
  1144. type: 'text',
  1145. text: JSON.stringify(comments, null, 2),
  1146. },
  1147. ],
  1148. };
  1149. }
  1150. case 'create_issue_comment': {
  1151. const { owner, repo, number, body } = CreateIssueCommentSchema.parse(args);
  1152. const comment = await gogsClient.createIssueComment(owner, repo, number, body);
  1153. return {
  1154. content: [
  1155. {
  1156. type: 'text',
  1157. text: JSON.stringify(comment, null, 2),
  1158. },
  1159. ],
  1160. };
  1161. }
  1162. case 'update_issue_comment': {
  1163. const { owner, repo, commentId, body } = UpdateIssueCommentSchema.parse(args);
  1164. const comment = await gogsClient.updateIssueComment(owner, repo, commentId, body);
  1165. return {
  1166. content: [
  1167. {
  1168. type: 'text',
  1169. text: JSON.stringify(comment, null, 2),
  1170. },
  1171. ],
  1172. };
  1173. }
  1174. case 'list_labels': {
  1175. const { owner, repo } = ListLabelsSchema.parse(args);
  1176. const labels = await gogsClient.listLabels(owner, repo);
  1177. return {
  1178. content: [
  1179. {
  1180. type: 'text',
  1181. text: JSON.stringify(labels, null, 2),
  1182. },
  1183. ],
  1184. };
  1185. }
  1186. case 'get_label': {
  1187. const { owner, repo, labelId } = GetLabelSchema.parse(args);
  1188. const label = await gogsClient.getLabel(owner, repo, labelId);
  1189. return {
  1190. content: [
  1191. {
  1192. type: 'text',
  1193. text: JSON.stringify(label, null, 2),
  1194. },
  1195. ],
  1196. };
  1197. }
  1198. case 'create_label': {
  1199. const { owner, repo, name, color } = CreateLabelSchema.parse(args);
  1200. const label = await gogsClient.createLabel(owner, repo, { name, color });
  1201. return {
  1202. content: [
  1203. {
  1204. type: 'text',
  1205. text: JSON.stringify(label, null, 2),
  1206. },
  1207. ],
  1208. };
  1209. }
  1210. case 'update_label': {
  1211. const { owner, repo, labelId, name, color } = UpdateLabelSchema.parse(args);
  1212. const label = await gogsClient.updateLabel(owner, repo, labelId, { name, color });
  1213. return {
  1214. content: [
  1215. {
  1216. type: 'text',
  1217. text: JSON.stringify(label, null, 2),
  1218. },
  1219. ],
  1220. };
  1221. }
  1222. case 'delete_label': {
  1223. const { owner, repo, labelId } = DeleteLabelSchema.parse(args);
  1224. await gogsClient.deleteLabel(owner, repo, labelId);
  1225. return {
  1226. content: [
  1227. {
  1228. type: 'text',
  1229. text: `Label ${labelId} deleted successfully from ${owner}/${repo}`,
  1230. },
  1231. ],
  1232. };
  1233. }
  1234. case 'list_issue_labels': {
  1235. const { owner, repo, number } = ListIssueLabelsSchema.parse(args);
  1236. const labels = await gogsClient.listIssueLabels(owner, repo, number);
  1237. return {
  1238. content: [
  1239. {
  1240. type: 'text',
  1241. text: JSON.stringify(labels, null, 2),
  1242. },
  1243. ],
  1244. };
  1245. }
  1246. case 'add_issue_labels': {
  1247. const { owner, repo, number, labels } = AddIssueLabelsSchema.parse(args);
  1248. const result = await gogsClient.addIssueLabels(owner, repo, number, labels);
  1249. return {
  1250. content: [
  1251. {
  1252. type: 'text',
  1253. text: JSON.stringify(result, null, 2),
  1254. },
  1255. ],
  1256. };
  1257. }
  1258. case 'remove_issue_label': {
  1259. const { owner, repo, number, labelId } = RemoveIssueLabelSchema.parse(args);
  1260. await gogsClient.removeIssueLabel(owner, repo, number, labelId);
  1261. return {
  1262. content: [
  1263. {
  1264. type: 'text',
  1265. text: `Label ${labelId} removed successfully from issue #${number}`,
  1266. },
  1267. ],
  1268. };
  1269. }
  1270. case 'replace_issue_labels': {
  1271. const { owner, repo, number, labels } = ReplaceIssueLabelsSchema.parse(args);
  1272. const result = await gogsClient.replaceIssueLabels(owner, repo, number, labels);
  1273. return {
  1274. content: [
  1275. {
  1276. type: 'text',
  1277. text: JSON.stringify(result, null, 2),
  1278. },
  1279. ],
  1280. };
  1281. }
  1282. case 'remove_all_issue_labels': {
  1283. const { owner, repo, number } = RemoveAllIssueLabelsSchema.parse(args);
  1284. await gogsClient.removeAllIssueLabels(owner, repo, number);
  1285. return {
  1286. content: [
  1287. {
  1288. type: 'text',
  1289. text: `All labels removed successfully from issue #${number}`,
  1290. },
  1291. ],
  1292. };
  1293. }
  1294. default:
  1295. throw new Error(`Unknown tool: ${name}`);
  1296. }
  1297. } catch (error) {
  1298. if (error instanceof Error) {
  1299. return {
  1300. content: [
  1301. {
  1302. type: 'text',
  1303. text: `Error: ${error.message}`,
  1304. },
  1305. ],
  1306. isError: true,
  1307. };
  1308. }
  1309. throw error;
  1310. }
  1311. });
  1312. // Handle resources (optional - for browsing repository structure)
  1313. server.setRequestHandler(ListResourcesRequestSchema, async () => {
  1314. return {
  1315. resources: [],
  1316. };
  1317. });
  1318. server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  1319. throw new Error('Resource reading not yet implemented');
  1320. });
  1321. return server;
  1322. }