@mgsoftwarebv/mcp-server-bridge 2.16.0 → 2.17.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/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { ListToolsRequestSchema, ListResourcesRequestSchema, CallToolRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { createClient } from '@supabase/supabase-js';
6
6
  import { createHash } from 'crypto';
7
+ import { Octokit } from '@octokit/rest';
7
8
 
8
9
  var args = process.argv.slice(2);
9
10
  var apiKey = args.find((arg) => arg.startsWith("--api-key="))?.split("=")[1] || process.env.MG_TICKETS_API_KEY;
@@ -67,6 +68,36 @@ async function downloadImageAsBase64(storageKey) {
67
68
  return null;
68
69
  }
69
70
  }
71
+ async function getGithubTokenForProject(projectId, teamId) {
72
+ try {
73
+ const { data: repoData, error: repoError } = await supabase.from("project_github_repositories").select("repository_full_name").eq("project_id", projectId).eq("team_id", teamId).single();
74
+ if (repoError || !repoData) {
75
+ console.error(`No GitHub repository linked to project ${projectId}`);
76
+ return null;
77
+ }
78
+ const { data: appData, error: appError } = await supabase.from("apps").select("config").eq("team_id", teamId).eq("app_id", "github").single();
79
+ if (appError || !appData?.config?.access_token) {
80
+ console.error(`GitHub app not connected for team ${teamId}`);
81
+ return null;
82
+ }
83
+ const accessToken = appData.config.access_token;
84
+ const repositoryFullName = repoData.repository_full_name;
85
+ const [owner, repo] = repositoryFullName.split("/");
86
+ if (!owner || !repo) {
87
+ console.error(`Invalid repository full name: ${repositoryFullName}`);
88
+ return null;
89
+ }
90
+ return {
91
+ token: accessToken,
92
+ repositoryFullName,
93
+ owner,
94
+ repo
95
+ };
96
+ } catch (error) {
97
+ console.error("Error getting GitHub token for project:", error);
98
+ return null;
99
+ }
100
+ }
70
101
  async function transitionToNextPhase(sessionId, currentPhase) {
71
102
  try {
72
103
  const now = /* @__PURE__ */ new Date();
@@ -479,6 +510,113 @@ var TOOLS = [
479
510
  },
480
511
  required: ["workDescription", "estimatedHours"]
481
512
  }
513
+ },
514
+ // === GITHUB TOOLS ===
515
+ {
516
+ name: "search-github-code",
517
+ description: "Search for code in a GitHub repository. Use this to find relevant files, functions, or code patterns related to a ticket.",
518
+ inputSchema: {
519
+ type: "object",
520
+ properties: {
521
+ projectId: {
522
+ type: "string",
523
+ description: "Project ID (UUID) - Required to identify which GitHub repository to search"
524
+ },
525
+ query: {
526
+ type: "string",
527
+ description: "Search query (e.g., function name, class name, error message, component name)"
528
+ },
529
+ language: {
530
+ type: "string",
531
+ description: 'Optional: Filter by programming language (e.g., "typescript", "python", "javascript")'
532
+ },
533
+ path: {
534
+ type: "string",
535
+ description: 'Optional: Filter by file path pattern (e.g., "src/components")'
536
+ },
537
+ maxResults: {
538
+ type: "number",
539
+ default: 10,
540
+ maximum: 30,
541
+ description: "Maximum number of results to return (default: 10, max: 30)"
542
+ }
543
+ },
544
+ required: ["projectId", "query"]
545
+ }
546
+ },
547
+ {
548
+ name: "get-github-file",
549
+ description: "Get the contents of a specific file from a GitHub repository. Use this after finding relevant files to read their full content.",
550
+ inputSchema: {
551
+ type: "object",
552
+ properties: {
553
+ projectId: {
554
+ type: "string",
555
+ description: "Project ID (UUID)"
556
+ },
557
+ filePath: {
558
+ type: "string",
559
+ description: 'Full path to the file in the repository (e.g., "src/components/Button.tsx")'
560
+ },
561
+ ref: {
562
+ type: "string",
563
+ description: "Optional: Git reference (branch, tag, or commit SHA). Defaults to repository default branch."
564
+ }
565
+ },
566
+ required: ["projectId", "filePath"]
567
+ }
568
+ },
569
+ {
570
+ name: "list-github-directory",
571
+ description: "List files and directories in a GitHub repository directory. Use this to explore repository structure.",
572
+ inputSchema: {
573
+ type: "object",
574
+ properties: {
575
+ projectId: {
576
+ type: "string",
577
+ description: "Project ID (UUID)"
578
+ },
579
+ directoryPath: {
580
+ type: "string",
581
+ description: 'Path to directory (e.g., "src/components"). Use empty string or "/" for root directory.'
582
+ },
583
+ ref: {
584
+ type: "string",
585
+ description: "Optional: Git reference (branch, tag, or commit SHA). Defaults to repository default branch."
586
+ }
587
+ },
588
+ required: ["projectId", "directoryPath"]
589
+ }
590
+ },
591
+ {
592
+ name: "search-github-issues",
593
+ description: "Search for GitHub issues and pull requests related to a topic. Useful to find similar past issues or relevant discussions.",
594
+ inputSchema: {
595
+ type: "object",
596
+ properties: {
597
+ projectId: {
598
+ type: "string",
599
+ description: "Project ID (UUID)"
600
+ },
601
+ query: {
602
+ type: "string",
603
+ description: "Search query for issues/PRs"
604
+ },
605
+ state: {
606
+ type: "string",
607
+ enum: ["open", "closed", "all"],
608
+ default: "all",
609
+ description: "Filter by issue state"
610
+ },
611
+ maxResults: {
612
+ type: "number",
613
+ default: 10,
614
+ maximum: 30,
615
+ description: "Maximum number of results to return"
616
+ }
617
+ },
618
+ required: ["projectId", "query"]
619
+ }
482
620
  }
483
621
  ];
484
622
  var RESOURCES = [
@@ -1523,6 +1661,297 @@ ${efficiencyNotes}
1523
1661
  }]
1524
1662
  };
1525
1663
  }
1664
+ // === GITHUB TOOLS ===
1665
+ case "search-github-code": {
1666
+ const { projectId, query, language, path, maxResults = 10 } = args2;
1667
+ const githubInfo = await getGithubTokenForProject(projectId, authContext.teamId);
1668
+ if (!githubInfo) {
1669
+ return {
1670
+ content: [{
1671
+ type: "text",
1672
+ text: "\u274C GitHub not configured for this project. Please:\n1. Connect GitHub app for your team\n2. Link a GitHub repository to this project"
1673
+ }]
1674
+ };
1675
+ }
1676
+ try {
1677
+ const octokit = new Octokit({ auth: githubInfo.token });
1678
+ let searchQuery = `${query} repo:${githubInfo.repositoryFullName}`;
1679
+ if (language) searchQuery += ` language:${language}`;
1680
+ if (path) searchQuery += ` path:${path}`;
1681
+ console.error(`\u{1F50D} Searching GitHub: ${searchQuery}`);
1682
+ const { data } = await octokit.rest.search.code({
1683
+ q: searchQuery,
1684
+ per_page: Math.min(maxResults, 30)
1685
+ });
1686
+ if (!data.items || data.items.length === 0) {
1687
+ return {
1688
+ content: [{
1689
+ type: "text",
1690
+ text: `\u{1F50D} No code found matching "${query}" in ${githubInfo.repositoryFullName}`
1691
+ }]
1692
+ };
1693
+ }
1694
+ let responseText = `\u{1F50D} **Found ${data.items.length} code matches in ${githubInfo.repositoryFullName}:**
1695
+
1696
+ `;
1697
+ for (const item of data.items) {
1698
+ responseText += `\u{1F4C4} **${item.path}**
1699
+ `;
1700
+ responseText += ` Repository: ${item.repository.full_name}
1701
+ `;
1702
+ responseText += ` URL: ${item.html_url}
1703
+ `;
1704
+ if (item.score) responseText += ` Relevance: ${item.score.toFixed(2)}
1705
+ `;
1706
+ responseText += `
1707
+ `;
1708
+ }
1709
+ responseText += `
1710
+ \u{1F4A1} Use \`get-github-file\` to read the full content of any file.`;
1711
+ return {
1712
+ content: [{
1713
+ type: "text",
1714
+ text: responseText
1715
+ }]
1716
+ };
1717
+ } catch (error) {
1718
+ console.error("GitHub search error:", error);
1719
+ if (error.status === 403 && error.message?.includes("rate limit")) {
1720
+ return {
1721
+ content: [{
1722
+ type: "text",
1723
+ text: "\u26A0\uFE0F GitHub API rate limit exceeded. Please try again later."
1724
+ }]
1725
+ };
1726
+ }
1727
+ if (error.status === 401) {
1728
+ return {
1729
+ content: [{
1730
+ type: "text",
1731
+ text: "\u274C GitHub token invalid. Please reconnect the GitHub app."
1732
+ }]
1733
+ };
1734
+ }
1735
+ return {
1736
+ content: [{
1737
+ type: "text",
1738
+ text: `\u274C GitHub search failed: ${error.message || "Unknown error"}`
1739
+ }]
1740
+ };
1741
+ }
1742
+ }
1743
+ case "get-github-file": {
1744
+ const { projectId, filePath, ref } = args2;
1745
+ const githubInfo = await getGithubTokenForProject(projectId, authContext.teamId);
1746
+ if (!githubInfo) {
1747
+ return {
1748
+ content: [{
1749
+ type: "text",
1750
+ text: "\u274C GitHub not configured for this project."
1751
+ }]
1752
+ };
1753
+ }
1754
+ try {
1755
+ const octokit = new Octokit({ auth: githubInfo.token });
1756
+ console.error(`\u{1F4C4} Reading file: ${filePath} from ${githubInfo.repositoryFullName}`);
1757
+ const { data } = await octokit.rest.repos.getContent({
1758
+ owner: githubInfo.owner,
1759
+ repo: githubInfo.repo,
1760
+ path: filePath,
1761
+ ref
1762
+ });
1763
+ if (Array.isArray(data) || data.type !== "file") {
1764
+ return {
1765
+ content: [{
1766
+ type: "text",
1767
+ text: `\u274C "${filePath}" is not a file or contains multiple items.`
1768
+ }]
1769
+ };
1770
+ }
1771
+ const content = Buffer.from(data.content, "base64").toString("utf-8");
1772
+ let responseText = `\u{1F4C4} **File: ${filePath}**
1773
+ `;
1774
+ responseText += `Repository: ${githubInfo.repositoryFullName}
1775
+ `;
1776
+ responseText += `Size: ${data.size} bytes
1777
+ `;
1778
+ responseText += `URL: ${data.html_url}
1779
+
1780
+ `;
1781
+ responseText += `**Content:**
1782
+ \`\`\`
1783
+ ${content}
1784
+ \`\`\``;
1785
+ return {
1786
+ content: [{
1787
+ type: "text",
1788
+ text: responseText
1789
+ }]
1790
+ };
1791
+ } catch (error) {
1792
+ console.error("GitHub get file error:", error);
1793
+ if (error.status === 404) {
1794
+ return {
1795
+ content: [{
1796
+ type: "text",
1797
+ text: `\u274C File not found: ${filePath}`
1798
+ }]
1799
+ };
1800
+ }
1801
+ return {
1802
+ content: [{
1803
+ type: "text",
1804
+ text: `\u274C Failed to read file: ${error.message || "Unknown error"}`
1805
+ }]
1806
+ };
1807
+ }
1808
+ }
1809
+ case "list-github-directory": {
1810
+ const { projectId, directoryPath, ref } = args2;
1811
+ const githubInfo = await getGithubTokenForProject(projectId, authContext.teamId);
1812
+ if (!githubInfo) {
1813
+ return {
1814
+ content: [{
1815
+ type: "text",
1816
+ text: "\u274C GitHub not configured for this project."
1817
+ }]
1818
+ };
1819
+ }
1820
+ try {
1821
+ const octokit = new Octokit({ auth: githubInfo.token });
1822
+ const normalizedPath = !directoryPath || directoryPath === "/" ? "" : directoryPath;
1823
+ console.error(`\u{1F4C1} Listing directory: ${normalizedPath || "(root)"} in ${githubInfo.repositoryFullName}`);
1824
+ const { data } = await octokit.rest.repos.getContent({
1825
+ owner: githubInfo.owner,
1826
+ repo: githubInfo.repo,
1827
+ path: normalizedPath,
1828
+ ref
1829
+ });
1830
+ if (!Array.isArray(data)) {
1831
+ return {
1832
+ content: [{
1833
+ type: "text",
1834
+ text: `\u274C "${directoryPath}" is not a directory.`
1835
+ }]
1836
+ };
1837
+ }
1838
+ let responseText = `\u{1F4C1} **Directory: ${directoryPath || "(root)"}**
1839
+ `;
1840
+ responseText += `Repository: ${githubInfo.repositoryFullName}
1841
+ `;
1842
+ responseText += `Items: ${data.length}
1843
+
1844
+ `;
1845
+ const directories = data.filter((item) => item.type === "dir");
1846
+ const files = data.filter((item) => item.type === "file");
1847
+ if (directories.length > 0) {
1848
+ responseText += `**\u{1F4C1} Directories (${directories.length}):**
1849
+ `;
1850
+ for (const dir of directories) {
1851
+ responseText += ` - ${dir.name}/
1852
+ `;
1853
+ }
1854
+ responseText += `
1855
+ `;
1856
+ }
1857
+ if (files.length > 0) {
1858
+ responseText += `**\u{1F4C4} Files (${files.length}):**
1859
+ `;
1860
+ for (const file of files) {
1861
+ responseText += ` - ${file.name} (${file.size} bytes)
1862
+ `;
1863
+ }
1864
+ }
1865
+ return {
1866
+ content: [{
1867
+ type: "text",
1868
+ text: responseText
1869
+ }]
1870
+ };
1871
+ } catch (error) {
1872
+ console.error("GitHub list directory error:", error);
1873
+ if (error.status === 404) {
1874
+ return {
1875
+ content: [{
1876
+ type: "text",
1877
+ text: `\u274C Directory not found: ${directoryPath}`
1878
+ }]
1879
+ };
1880
+ }
1881
+ return {
1882
+ content: [{
1883
+ type: "text",
1884
+ text: `\u274C Failed to list directory: ${error.message || "Unknown error"}`
1885
+ }]
1886
+ };
1887
+ }
1888
+ }
1889
+ case "search-github-issues": {
1890
+ const { projectId, query, state = "all", maxResults = 10 } = args2;
1891
+ const githubInfo = await getGithubTokenForProject(projectId, authContext.teamId);
1892
+ if (!githubInfo) {
1893
+ return {
1894
+ content: [{
1895
+ type: "text",
1896
+ text: "\u274C GitHub not configured for this project."
1897
+ }]
1898
+ };
1899
+ }
1900
+ try {
1901
+ const octokit = new Octokit({ auth: githubInfo.token });
1902
+ let searchQuery = `${query} repo:${githubInfo.repositoryFullName} is:issue`;
1903
+ if (state !== "all") searchQuery += ` state:${state}`;
1904
+ console.error(`\u{1F50D} Searching GitHub issues: ${searchQuery}`);
1905
+ const { data } = await octokit.rest.search.issuesAndPullRequests({
1906
+ q: searchQuery,
1907
+ per_page: Math.min(maxResults, 30)
1908
+ });
1909
+ if (!data.items || data.items.length === 0) {
1910
+ return {
1911
+ content: [{
1912
+ type: "text",
1913
+ text: `\u{1F50D} No issues found matching "${query}" in ${githubInfo.repositoryFullName}`
1914
+ }]
1915
+ };
1916
+ }
1917
+ let responseText = `\u{1F50D} **Found ${data.items.length} issues in ${githubInfo.repositoryFullName}:**
1918
+
1919
+ `;
1920
+ for (const issue of data.items) {
1921
+ const isPR = !!issue.pull_request;
1922
+ const emoji = isPR ? "\u{1F500}" : "\u{1F41B}";
1923
+ const type = isPR ? "PR" : "Issue";
1924
+ responseText += `${emoji} **${type} #${issue.number}: ${issue.title}**
1925
+ `;
1926
+ responseText += ` State: ${issue.state}
1927
+ `;
1928
+ responseText += ` Author: ${issue.user?.login || "Unknown"}
1929
+ `;
1930
+ responseText += ` URL: ${issue.html_url}
1931
+ `;
1932
+ if (issue.labels && issue.labels.length > 0) {
1933
+ responseText += ` Labels: ${issue.labels.map((l) => typeof l === "string" ? l : l.name).join(", ")}
1934
+ `;
1935
+ }
1936
+ responseText += `
1937
+ `;
1938
+ }
1939
+ return {
1940
+ content: [{
1941
+ type: "text",
1942
+ text: responseText
1943
+ }]
1944
+ };
1945
+ } catch (error) {
1946
+ console.error("GitHub search issues error:", error);
1947
+ return {
1948
+ content: [{
1949
+ type: "text",
1950
+ text: `\u274C Failed to search issues: ${error.message || "Unknown error"}`
1951
+ }]
1952
+ };
1953
+ }
1954
+ }
1526
1955
  default:
1527
1956
  throw new Error(`Unknown tool: ${name}`);
1528
1957
  }