server.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  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. /**
  121. * Create and configure an MCP server with all tools and handlers
  122. */
  123. export function createMcpServer(gogsClient: GogsClient): Server {
  124. const server = new Server(
  125. {
  126. name: 'gogs-mcp-server',
  127. version: '1.0.0',
  128. },
  129. {
  130. capabilities: {
  131. tools: {},
  132. resources: {},
  133. },
  134. }
  135. );
  136. // Register tools
  137. server.setRequestHandler(ListToolsRequestSchema, async () => {
  138. return {
  139. tools: [
  140. {
  141. name: 'get_current_user',
  142. description: 'Get information about the authenticated user',
  143. inputSchema: {
  144. type: 'object',
  145. properties: {},
  146. },
  147. },
  148. {
  149. name: 'get_user',
  150. description: 'Get information about a specific user',
  151. inputSchema: {
  152. type: 'object',
  153. properties: {
  154. username: {
  155. type: 'string',
  156. description: 'Username to get information for',
  157. },
  158. },
  159. required: ['username'],
  160. },
  161. },
  162. {
  163. name: 'search_users',
  164. description: 'Search for users by username',
  165. inputSchema: {
  166. type: 'object',
  167. properties: {
  168. query: {
  169. type: 'string',
  170. description: 'Search query for users',
  171. },
  172. limit: {
  173. type: 'number',
  174. description: 'Maximum number of results (default: 10)',
  175. },
  176. },
  177. required: ['query'],
  178. },
  179. },
  180. {
  181. name: 'list_user_repositories',
  182. description: 'List repositories for a user (or authenticated user if no username provided)',
  183. inputSchema: {
  184. type: 'object',
  185. properties: {
  186. username: {
  187. type: 'string',
  188. description: 'Username (omit for authenticated user)',
  189. },
  190. },
  191. },
  192. },
  193. {
  194. name: 'search_repositories',
  195. description: 'Search for repositories',
  196. inputSchema: {
  197. type: 'object',
  198. properties: {
  199. query: {
  200. type: 'string',
  201. description: 'Search query for repositories',
  202. },
  203. uid: {
  204. type: 'number',
  205. description: 'User ID to filter repositories',
  206. },
  207. limit: {
  208. type: 'number',
  209. description: 'Maximum number of results (default: 10)',
  210. },
  211. page: {
  212. type: 'number',
  213. description: 'Page number (default: 1)',
  214. },
  215. },
  216. required: ['query'],
  217. },
  218. },
  219. {
  220. name: 'get_repository',
  221. description: 'Get information about a specific repository',
  222. inputSchema: {
  223. type: 'object',
  224. properties: {
  225. owner: {
  226. type: 'string',
  227. description: 'Repository owner username',
  228. },
  229. repo: {
  230. type: 'string',
  231. description: 'Repository name',
  232. },
  233. },
  234. required: ['owner', 'repo'],
  235. },
  236. },
  237. {
  238. name: 'create_repository',
  239. description: 'Create a new repository for the authenticated user',
  240. inputSchema: {
  241. type: 'object',
  242. properties: {
  243. name: {
  244. type: 'string',
  245. description: 'Repository name',
  246. },
  247. description: {
  248. type: 'string',
  249. description: 'Repository description',
  250. },
  251. private: {
  252. type: 'boolean',
  253. description: 'Create as private repository (default: false)',
  254. },
  255. auto_init: {
  256. type: 'boolean',
  257. description: 'Initialize with README (default: false)',
  258. },
  259. gitignores: {
  260. type: 'string',
  261. description: 'Gitignore templates (e.g., "Go,VisualStudioCode")',
  262. },
  263. license: {
  264. type: 'string',
  265. description: 'License template (e.g., "MIT License")',
  266. },
  267. readme: {
  268. type: 'string',
  269. description: 'README template (default: "Default")',
  270. },
  271. },
  272. required: ['name'],
  273. },
  274. },
  275. {
  276. name: 'delete_repository',
  277. description: 'Delete a repository (requires admin access)',
  278. inputSchema: {
  279. type: 'object',
  280. properties: {
  281. owner: {
  282. type: 'string',
  283. description: 'Repository owner username',
  284. },
  285. repo: {
  286. type: 'string',
  287. description: 'Repository name',
  288. },
  289. },
  290. required: ['owner', 'repo'],
  291. },
  292. },
  293. {
  294. name: 'get_contents',
  295. description: 'Get file or directory contents from a repository',
  296. inputSchema: {
  297. type: 'object',
  298. properties: {
  299. owner: {
  300. type: 'string',
  301. description: 'Repository owner username',
  302. },
  303. repo: {
  304. type: 'string',
  305. description: 'Repository name',
  306. },
  307. path: {
  308. type: 'string',
  309. description: 'File or directory path',
  310. },
  311. ref: {
  312. type: 'string',
  313. description: 'Branch, tag, or commit SHA (default: default branch)',
  314. },
  315. },
  316. required: ['owner', 'repo', 'path'],
  317. },
  318. },
  319. {
  320. name: 'get_raw_content',
  321. description: 'Get raw file content from a repository',
  322. inputSchema: {
  323. type: 'object',
  324. properties: {
  325. owner: {
  326. type: 'string',
  327. description: 'Repository owner username',
  328. },
  329. repo: {
  330. type: 'string',
  331. description: 'Repository name',
  332. },
  333. ref: {
  334. type: 'string',
  335. description: 'Branch, tag, or commit SHA',
  336. },
  337. path: {
  338. type: 'string',
  339. description: 'File path',
  340. },
  341. },
  342. required: ['owner', 'repo', 'ref', 'path'],
  343. },
  344. },
  345. {
  346. name: 'list_branches',
  347. description: 'List all branches in a repository',
  348. inputSchema: {
  349. type: 'object',
  350. properties: {
  351. owner: {
  352. type: 'string',
  353. description: 'Repository owner username',
  354. },
  355. repo: {
  356. type: 'string',
  357. description: 'Repository name',
  358. },
  359. },
  360. required: ['owner', 'repo'],
  361. },
  362. },
  363. {
  364. name: 'get_commits',
  365. description: 'Get commit history from a repository',
  366. inputSchema: {
  367. type: 'object',
  368. properties: {
  369. owner: {
  370. type: 'string',
  371. description: 'Repository owner username',
  372. },
  373. repo: {
  374. type: 'string',
  375. description: 'Repository name',
  376. },
  377. sha: {
  378. type: 'string',
  379. description: 'Branch name or commit SHA',
  380. },
  381. page: {
  382. type: 'number',
  383. description: 'Page number (default: 1)',
  384. },
  385. },
  386. required: ['owner', 'repo'],
  387. },
  388. },
  389. {
  390. name: 'list_issues',
  391. description: 'List issues in a repository',
  392. inputSchema: {
  393. type: 'object',
  394. properties: {
  395. owner: {
  396. type: 'string',
  397. description: 'Repository owner username',
  398. },
  399. repo: {
  400. type: 'string',
  401. description: 'Repository name',
  402. },
  403. state: {
  404. type: 'string',
  405. enum: ['open', 'closed', 'all'],
  406. description: 'Filter by state (default: open)',
  407. },
  408. labels: {
  409. type: 'string',
  410. description: 'Comma-separated list of label names',
  411. },
  412. page: {
  413. type: 'number',
  414. description: 'Page number (default: 1)',
  415. },
  416. per_page: {
  417. type: 'number',
  418. description: 'Results per page (default: 30)',
  419. },
  420. },
  421. required: ['owner', 'repo'],
  422. },
  423. },
  424. {
  425. name: 'get_issue',
  426. description: 'Get a specific issue by number',
  427. inputSchema: {
  428. type: 'object',
  429. properties: {
  430. owner: {
  431. type: 'string',
  432. description: 'Repository owner username',
  433. },
  434. repo: {
  435. type: 'string',
  436. description: 'Repository name',
  437. },
  438. number: {
  439. type: 'number',
  440. description: 'Issue number',
  441. },
  442. },
  443. required: ['owner', 'repo', 'number'],
  444. },
  445. },
  446. {
  447. name: 'create_issue',
  448. description: 'Create a new issue in a repository',
  449. inputSchema: {
  450. type: 'object',
  451. properties: {
  452. owner: {
  453. type: 'string',
  454. description: 'Repository owner username',
  455. },
  456. repo: {
  457. type: 'string',
  458. description: 'Repository name',
  459. },
  460. title: {
  461. type: 'string',
  462. description: 'Issue title',
  463. },
  464. body: {
  465. type: 'string',
  466. description: 'Issue description',
  467. },
  468. assignee: {
  469. type: 'string',
  470. description: 'Username to assign the issue to',
  471. },
  472. milestone: {
  473. type: 'number',
  474. description: 'Milestone ID',
  475. },
  476. labels: {
  477. type: 'array',
  478. items: {
  479. type: 'number',
  480. },
  481. description: 'Array of label IDs',
  482. },
  483. },
  484. required: ['owner', 'repo', 'title'],
  485. },
  486. },
  487. {
  488. name: 'update_issue',
  489. description: 'Update an existing issue (including closing or reopening)',
  490. inputSchema: {
  491. type: 'object',
  492. properties: {
  493. owner: {
  494. type: 'string',
  495. description: 'Repository owner username',
  496. },
  497. repo: {
  498. type: 'string',
  499. description: 'Repository name',
  500. },
  501. number: {
  502. type: 'number',
  503. description: 'Issue number',
  504. },
  505. title: {
  506. type: 'string',
  507. description: 'Issue title',
  508. },
  509. body: {
  510. type: 'string',
  511. description: 'Issue description',
  512. },
  513. assignee: {
  514. type: 'string',
  515. description: 'Username to assign the issue to',
  516. },
  517. milestone: {
  518. type: 'number',
  519. description: 'Milestone ID',
  520. },
  521. state: {
  522. type: 'string',
  523. enum: ['open', 'closed'],
  524. description: 'Issue state',
  525. },
  526. labels: {
  527. type: 'array',
  528. items: {
  529. type: 'number',
  530. },
  531. description: 'Array of label IDs',
  532. },
  533. },
  534. required: ['owner', 'repo', 'number'],
  535. },
  536. },
  537. {
  538. name: 'list_issue_comments',
  539. description: 'List all comments on an issue',
  540. inputSchema: {
  541. type: 'object',
  542. properties: {
  543. owner: {
  544. type: 'string',
  545. description: 'Repository owner username',
  546. },
  547. repo: {
  548. type: 'string',
  549. description: 'Repository name',
  550. },
  551. number: {
  552. type: 'number',
  553. description: 'Issue number',
  554. },
  555. },
  556. required: ['owner', 'repo', 'number'],
  557. },
  558. },
  559. {
  560. name: 'create_issue_comment',
  561. description: 'Add a comment to an issue',
  562. inputSchema: {
  563. type: 'object',
  564. properties: {
  565. owner: {
  566. type: 'string',
  567. description: 'Repository owner username',
  568. },
  569. repo: {
  570. type: 'string',
  571. description: 'Repository name',
  572. },
  573. number: {
  574. type: 'number',
  575. description: 'Issue number',
  576. },
  577. body: {
  578. type: 'string',
  579. description: 'Comment text',
  580. },
  581. },
  582. required: ['owner', 'repo', 'number', 'body'],
  583. },
  584. },
  585. {
  586. name: 'update_issue_comment',
  587. description: 'Edit an existing comment on an issue',
  588. inputSchema: {
  589. type: 'object',
  590. properties: {
  591. owner: {
  592. type: 'string',
  593. description: 'Repository owner username',
  594. },
  595. repo: {
  596. type: 'string',
  597. description: 'Repository name',
  598. },
  599. commentId: {
  600. type: 'number',
  601. description: 'Comment ID',
  602. },
  603. body: {
  604. type: 'string',
  605. description: 'Updated comment text',
  606. },
  607. },
  608. required: ['owner', 'repo', 'commentId', 'body'],
  609. },
  610. },
  611. ],
  612. };
  613. });
  614. // Handle tool calls
  615. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  616. try {
  617. const { name, arguments: args } = request.params;
  618. switch (name) {
  619. case 'get_current_user': {
  620. const user = await gogsClient.getCurrentUser();
  621. return {
  622. content: [
  623. {
  624. type: 'text',
  625. text: JSON.stringify(user, null, 2),
  626. },
  627. ],
  628. };
  629. }
  630. case 'get_user': {
  631. const { username } = GetUserSchema.parse(args);
  632. const user = await gogsClient.getUser(username);
  633. return {
  634. content: [
  635. {
  636. type: 'text',
  637. text: JSON.stringify(user, null, 2),
  638. },
  639. ],
  640. };
  641. }
  642. case 'search_users': {
  643. const { query, limit } = SearchUsersSchema.parse(args);
  644. const users = await gogsClient.searchUsers(query, limit);
  645. return {
  646. content: [
  647. {
  648. type: 'text',
  649. text: JSON.stringify(users, null, 2),
  650. },
  651. ],
  652. };
  653. }
  654. case 'list_user_repositories': {
  655. const { username } = ListUserReposSchema.parse(args);
  656. const repos = username
  657. ? await gogsClient.listUserRepositories(username)
  658. : await gogsClient.listMyRepositories();
  659. return {
  660. content: [
  661. {
  662. type: 'text',
  663. text: JSON.stringify(repos, null, 2),
  664. },
  665. ],
  666. };
  667. }
  668. case 'search_repositories': {
  669. const { query, uid, limit, page } = SearchReposSchema.parse(args);
  670. const repos = await gogsClient.searchRepositories(query, { uid, limit, page });
  671. return {
  672. content: [
  673. {
  674. type: 'text',
  675. text: JSON.stringify(repos, null, 2),
  676. },
  677. ],
  678. };
  679. }
  680. case 'get_repository': {
  681. const { owner, repo } = GetRepoSchema.parse(args);
  682. const repository = await gogsClient.getRepository(owner, repo);
  683. return {
  684. content: [
  685. {
  686. type: 'text',
  687. text: JSON.stringify(repository, null, 2),
  688. },
  689. ],
  690. };
  691. }
  692. case 'create_repository': {
  693. const data = CreateRepoSchema.parse(args);
  694. const repo = await gogsClient.createRepository(data);
  695. return {
  696. content: [
  697. {
  698. type: 'text',
  699. text: JSON.stringify(repo, null, 2),
  700. },
  701. ],
  702. };
  703. }
  704. case 'delete_repository': {
  705. const { owner, repo } = DeleteRepoSchema.parse(args);
  706. await gogsClient.deleteRepository(owner, repo);
  707. return {
  708. content: [
  709. {
  710. type: 'text',
  711. text: `Repository ${owner}/${repo} deleted successfully`,
  712. },
  713. ],
  714. };
  715. }
  716. case 'get_contents': {
  717. const { owner, repo, path, ref } = GetContentsSchema.parse(args);
  718. const contents = await gogsClient.getContents(owner, repo, path, ref);
  719. // If it's a file with base64 content, decode it
  720. if (!Array.isArray(contents) && contents.type === 'file' && contents.content) {
  721. const decodedContent = Buffer.from(contents.content, 'base64').toString('utf-8');
  722. return {
  723. content: [
  724. {
  725. type: 'text',
  726. text: `File: ${contents.path}\nSize: ${contents.size} bytes\nSHA: ${contents.sha}\n\nContent:\n${decodedContent}`,
  727. },
  728. ],
  729. };
  730. }
  731. return {
  732. content: [
  733. {
  734. type: 'text',
  735. text: JSON.stringify(contents, null, 2),
  736. },
  737. ],
  738. };
  739. }
  740. case 'get_raw_content': {
  741. const { owner, repo, ref, path } = GetRawContentSchema.parse(args);
  742. const content = await gogsClient.getRawContent(owner, repo, ref, path);
  743. return {
  744. content: [
  745. {
  746. type: 'text',
  747. text: content,
  748. },
  749. ],
  750. };
  751. }
  752. case 'list_branches': {
  753. const { owner, repo } = ListBranchesSchema.parse(args);
  754. const branches = await gogsClient.listBranches(owner, repo);
  755. return {
  756. content: [
  757. {
  758. type: 'text',
  759. text: JSON.stringify(branches, null, 2),
  760. },
  761. ],
  762. };
  763. }
  764. case 'get_commits': {
  765. const { owner, repo, sha, page } = GetCommitsSchema.parse(args);
  766. const commits = await gogsClient.getCommits(owner, repo, { sha, page });
  767. return {
  768. content: [
  769. {
  770. type: 'text',
  771. text: JSON.stringify(commits, null, 2),
  772. },
  773. ],
  774. };
  775. }
  776. case 'list_issues': {
  777. const { owner, repo, state, labels, page, per_page } = ListIssuesSchema.parse(args);
  778. const issues = await gogsClient.listIssues(owner, repo, { state, labels, page, per_page });
  779. return {
  780. content: [
  781. {
  782. type: 'text',
  783. text: JSON.stringify(issues, null, 2),
  784. },
  785. ],
  786. };
  787. }
  788. case 'get_issue': {
  789. const { owner, repo, number } = GetIssueSchema.parse(args);
  790. const issue = await gogsClient.getIssue(owner, repo, number);
  791. return {
  792. content: [
  793. {
  794. type: 'text',
  795. text: JSON.stringify(issue, null, 2),
  796. },
  797. ],
  798. };
  799. }
  800. case 'create_issue': {
  801. const { owner, repo, title, body, assignee, milestone, labels } = CreateIssueSchema.parse(args);
  802. const issue = await gogsClient.createIssue(owner, repo, {
  803. title,
  804. body,
  805. assignee,
  806. milestone,
  807. labels,
  808. });
  809. return {
  810. content: [
  811. {
  812. type: 'text',
  813. text: JSON.stringify(issue, null, 2),
  814. },
  815. ],
  816. };
  817. }
  818. case 'update_issue': {
  819. const { owner, repo, number, title, body, assignee, milestone, state, labels } = UpdateIssueSchema.parse(args);
  820. const issue = await gogsClient.updateIssue(owner, repo, number, {
  821. title,
  822. body,
  823. assignee,
  824. milestone,
  825. state,
  826. labels,
  827. });
  828. return {
  829. content: [
  830. {
  831. type: 'text',
  832. text: JSON.stringify(issue, null, 2),
  833. },
  834. ],
  835. };
  836. }
  837. case 'list_issue_comments': {
  838. const { owner, repo, number } = ListIssueCommentsSchema.parse(args);
  839. const comments = await gogsClient.listIssueComments(owner, repo, number);
  840. return {
  841. content: [
  842. {
  843. type: 'text',
  844. text: JSON.stringify(comments, null, 2),
  845. },
  846. ],
  847. };
  848. }
  849. case 'create_issue_comment': {
  850. const { owner, repo, number, body } = CreateIssueCommentSchema.parse(args);
  851. const comment = await gogsClient.createIssueComment(owner, repo, number, body);
  852. return {
  853. content: [
  854. {
  855. type: 'text',
  856. text: JSON.stringify(comment, null, 2),
  857. },
  858. ],
  859. };
  860. }
  861. case 'update_issue_comment': {
  862. const { owner, repo, commentId, body } = UpdateIssueCommentSchema.parse(args);
  863. const comment = await gogsClient.updateIssueComment(owner, repo, commentId, body);
  864. return {
  865. content: [
  866. {
  867. type: 'text',
  868. text: JSON.stringify(comment, null, 2),
  869. },
  870. ],
  871. };
  872. }
  873. default:
  874. throw new Error(`Unknown tool: ${name}`);
  875. }
  876. } catch (error) {
  877. if (error instanceof Error) {
  878. return {
  879. content: [
  880. {
  881. type: 'text',
  882. text: `Error: ${error.message}`,
  883. },
  884. ],
  885. isError: true,
  886. };
  887. }
  888. throw error;
  889. }
  890. });
  891. // Handle resources (optional - for browsing repository structure)
  892. server.setRequestHandler(ListResourcesRequestSchema, async () => {
  893. return {
  894. resources: [],
  895. };
  896. });
  897. server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  898. throw new Error('Resource reading not yet implemented');
  899. });
  900. return server;
  901. }