From 6770526ea4f905a517e7f8292806fb1add5af5f5 Mon Sep 17 00:00:00 2001 From: Ido Salomon Date: Sat, 12 Apr 2025 00:02:46 +0300 Subject: [PATCH 1/2] fix: cline example (#73) Fixes #72 --- app/components/content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/content.tsx b/app/components/content.tsx index fb8fae9..59438a5 100644 --- a/app/components/content.tsx +++ b/app/components/content.tsx @@ -307,7 +307,7 @@ export default function Content({ code={`{ "mcpServers": { "${serverName}": { - "url": "${url}" + "url": "${url}", "disabled": false, "autoApprove": [] } From 76a848e72d85c0c89bcfb2d7b672abde66890154 Mon Sep 17 00:00:00 2001 From: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com> Date: Sat, 12 Apr 2025 01:34:08 +0200 Subject: [PATCH 2/2] Add-on: getGitMCP a quick fetcher interface --- web-tools/get-git-mcp-ui/favicon.svg | 8 + web-tools/get-git-mcp-ui/index.html | 88 ++++++ web-tools/get-git-mcp-ui/logo.svg | 8 + web-tools/get-git-mcp-ui/package.json | 8 + web-tools/get-git-mcp-ui/script.js | 207 ++++++++++++++ web-tools/get-git-mcp-ui/styles.css | 375 ++++++++++++++++++++++++++ 6 files changed, 694 insertions(+) create mode 100644 web-tools/get-git-mcp-ui/favicon.svg create mode 100644 web-tools/get-git-mcp-ui/index.html create mode 100644 web-tools/get-git-mcp-ui/logo.svg create mode 100644 web-tools/get-git-mcp-ui/package.json create mode 100644 web-tools/get-git-mcp-ui/script.js create mode 100644 web-tools/get-git-mcp-ui/styles.css diff --git a/web-tools/get-git-mcp-ui/favicon.svg b/web-tools/get-git-mcp-ui/favicon.svg new file mode 100644 index 0000000..a22d4c2 --- /dev/null +++ b/web-tools/get-git-mcp-ui/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web-tools/get-git-mcp-ui/index.html b/web-tools/get-git-mcp-ui/index.html new file mode 100644 index 0000000..b57a29d --- /dev/null +++ b/web-tools/get-git-mcp-ui/index.html @@ -0,0 +1,88 @@ + + + + + + getGitMCP - Paste GitHub URLs, get MCP Endpoints via GitMCP + + + + + +
+
+ + +
+ +
+
+

Paste GitHub URLs, get MCP Endpoints

+

powered by GitMCP

+
+

GitMCP converts any GitHub project into a remote Model Context Protocol endpoint for your AI assistants to use.

+
+ +
+
+ + +
+
+ Examples: + github.com/username/repo + username.github.io/repo + github.com/username +
+ + + + +
+
+
+

GitHub Repository

+
+ github.com/username/repo + + gitmcp.io/username/repo +
+
+
+

GitHub Pages

+
+ username.github.io/repo + + username.gitmcp.io/repo +
+
+
+
+
+

getGitMCP is a quick tool to retrive GitMCP configuration from Github links

.GitMCP is a free, open-source service. Contribute on GitHub

+
+
+ + + + diff --git a/web-tools/get-git-mcp-ui/logo.svg b/web-tools/get-git-mcp-ui/logo.svg new file mode 100644 index 0000000..af4b7cd --- /dev/null +++ b/web-tools/get-git-mcp-ui/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web-tools/get-git-mcp-ui/package.json b/web-tools/get-git-mcp-ui/package.json new file mode 100644 index 0000000..e89b1e9 --- /dev/null +++ b/web-tools/get-git-mcp-ui/package.json @@ -0,0 +1,8 @@ +{ + "name": "my-v0-project", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "echo 'no build script'" + } +} diff --git a/web-tools/get-git-mcp-ui/script.js b/web-tools/get-git-mcp-ui/script.js new file mode 100644 index 0000000..3df07ef --- /dev/null +++ b/web-tools/get-git-mcp-ui/script.js @@ -0,0 +1,207 @@ +document.addEventListener("DOMContentLoaded", () => { + const githubUrlInput = document.getElementById("github-url") + const convertBtn = document.getElementById("convert-btn") + const loadingElement = document.getElementById("loading") + const errorMessageElement = document.getElementById("error-message") + const resultElement = document.getElementById("result") + const resultLinkElement = document.getElementById("result-link") + const reposListElement = document.getElementById("repos-list") + const reposContainerElement = document.getElementById("repos-container") + + // Regular expressions for URL validation and parsing + const githubRepoRegex = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/]+)\/([^/]+)(?:\/.*)?$/i + const githubPagesRegex = /^(?:https?:\/\/)?([^/]+)\.github\.io\/([^/]+)(?:\/.*)?$/i + const githubUserRegex = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/]+)\/?$/i + + // Handle input on Enter key + githubUrlInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + processUrl() + } + }) + + // Handle paste event + githubUrlInput.addEventListener("paste", (e) => { + // Use setTimeout to allow the paste to complete before processing + setTimeout(() => { + const pastedUrl = githubUrlInput.value.trim() + if (pastedUrl) { + const sanitizedUrl = sanitizeUrl(pastedUrl) + githubUrlInput.value = sanitizedUrl + processUrl() + } + }, 0) + }) + + // Handle button click + convertBtn.addEventListener("click", processUrl) + + // Sanitize URL by removing tracking parameters, fragments, and normalizing + function sanitizeUrl(url) { + try { + // Create URL object to easily manipulate parts + const urlObj = new URL(url) + + // Remove common tracking parameters + const paramsToRemove = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "ref", "source"] + paramsToRemove.forEach((param) => urlObj.searchParams.delete(param)) + + // For GitHub URLs, clean up paths + if (urlObj.hostname === "github.com" || urlObj.hostname.endsWith(".github.io")) { + const pathParts = urlObj.pathname.split("/") + + // If it's a GitHub repo with additional paths (blob, tree, etc.) + if (pathParts.length > 3 && urlObj.hostname === "github.com") { + // Keep only username and repo name + urlObj.pathname = `/${pathParts[1]}/${pathParts[2]}` + } else if (pathParts.length > 2 && urlObj.hostname.endsWith(".github.io")) { + // For GitHub Pages, keep only the first path segment + urlObj.pathname = `/${pathParts[1]}` + } + + // Remove hash + urlObj.hash = "" + } + + // Return the cleaned URL without trailing slash + return urlObj.toString().replace(/\/$/, "") + } catch (e) { + // If URL parsing fails, return the original URL + return url + } + } + + // Main function to process the URL + function processUrl() { + const url = githubUrlInput.value.trim() + + // Reset UI state + hideAllElements() + + if (!url) { + showError("Please enter a GitHub URL") + return + } + + // Check if URL is a GitHub repository + const repoMatch = url.match(githubRepoRegex) + if (repoMatch) { + const [, owner, repo] = repoMatch + const gitMcpUrl = `https://gitmcp.io/${owner}/${repo}` + showResult(gitMcpUrl) + window.open(gitMcpUrl, "_blank") + return + } + + // Check if URL is a GitHub Pages site + const pagesMatch = url.match(githubPagesRegex) + if (pagesMatch) { + const [, owner, repo] = pagesMatch + const gitMcpUrl = `https://${owner}.gitmcp.io/${repo}` + showResult(gitMcpUrl) + window.open(gitMcpUrl, "_blank") + return + } + + // Check if URL is a GitHub user or organization + const userMatch = url.match(githubUserRegex) + if (userMatch) { + const [, owner] = userMatch + fetchUserRepositories(owner) + return + } + + // If none of the patterns match, just keep the sanitized URL in the input + // No error message needed as per requirements + } + + // Fetch repositories for a GitHub user or organization + async function fetchUserRepositories(owner) { + showLoading() + + try { + const response = await fetch(`https://api.github.com/users/${owner}/repos?sort=updated&per_page=100`) + + if (!response.ok) { + if (response.status === 404) { + showError(`User or organization "${owner}" not found.`) + } else { + showError(`Error fetching repositories: ${response.statusText}`) + } + return + } + + const repos = await response.json() + + if (repos.length === 0) { + showError(`No public repositories found for "${owner}".`) + return + } + + displayRepositories(owner, repos) + } catch (error) { + showError(`Error: ${error.message}`) + } finally { + hideLoading() + } + } + + // Display the list of repositories + function displayRepositories(owner, repos) { + reposContainerElement.innerHTML = "" + + repos.forEach((repo) => { + const li = document.createElement("li") + const a = document.createElement("a") + a.href = `https://gitmcp.io/${owner}/${repo.name}` + a.textContent = repo.name + a.target = "_blank" + + // Add click event to open in new tab + a.addEventListener("click", (e) => { + e.preventDefault() + window.open(a.href, "_blank") + }) + + li.appendChild(a) + + if (repo.description) { + const description = document.createElement("div") + description.className = "repo-description" + description.textContent = repo.description + li.appendChild(description) + } + + reposContainerElement.appendChild(li) + }) + + reposListElement.classList.remove("hidden") + } + + // UI Helper Functions + function hideAllElements() { + errorMessageElement.classList.add("hidden") + resultElement.classList.add("hidden") + reposListElement.classList.add("hidden") + loadingElement.classList.add("hidden") + } + + function showError(message) { + errorMessageElement.textContent = message + errorMessageElement.classList.remove("hidden") + } + + function showResult(url) { + resultLinkElement.href = url + resultLinkElement.textContent = url + resultElement.classList.remove("hidden") + } + + function showLoading() { + loadingElement.classList.remove("hidden") + } + + function hideLoading() { + loadingElement.classList.add("hidden") + } +}) diff --git a/web-tools/get-git-mcp-ui/styles.css b/web-tools/get-git-mcp-ui/styles.css new file mode 100644 index 0000000..e6c7029 --- /dev/null +++ b/web-tools/get-git-mcp-ui/styles.css @@ -0,0 +1,375 @@ +:root { + --primary-color: #0366d6; + --secondary-color: #24292e; + --accent-color: #2ea44f; + --text-color: #24292e; + --text-secondary: #586069; + --bg-color: #ffffff; + --bg-secondary: #f6f8fa; + --border-color: #e1e4e8; + --shadow-color: rgba(0, 0, 0, 0.1); + --error-color: #cb2431; + --success-color: #2ea44f; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + color: var(--text-color); + background-color: var(--bg-color); + line-height: 1.5; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 3rem; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo img { + height: 2.5rem; + width: auto; +} + +.logo h1 { + font-size: 1.5rem; + font-weight: 600; + color: var(--secondary-color); +} + +nav { + display: flex; + gap: 1.5rem; +} + +nav a { + color: var(--text-secondary); + text-decoration: none; + font-weight: 500; + transition: color 0.2s ease; +} + +nav a:hover { + color: var(--primary-color); +} + +main { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 3rem; +} + +.hero { + text-align: center; + max-width: 800px; +} + +.hero h2 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient(90deg, var(--primary-color), var(--accent-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-fill-color: transparent; +} + +.hero p { + font-size: 1.25rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto; +} + +.converter-box { + width: 100%; + max-width: 800px; + background-color: var(--bg-secondary); + border-radius: 12px; + padding: 2rem; + box-shadow: 0 8px 24px var(--shadow-color); +} + +.input-container { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; +} + +input { + flex: 1; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 6px; + background-color: var(--bg-color); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.3); +} + +button { + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: 500; + color: white; + background-color: var(--primary-color); + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +button:hover { + background-color: #0256b3; +} + +.examples { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 1.5rem; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.examples code { + background-color: var(--bg-color); + padding: 0.25rem 0.5rem; + border-radius: 4px; + border: 1px solid var(--border-color); +} + +.loading { + display: flex; + align-items: center; + gap: 0.75rem; + margin: 1.5rem 0; +} + +.spinner { + width: 1.25rem; + height: 1.25rem; + border: 2px solid rgba(3, 102, 214, 0.3); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.error-message { + color: var(--error-color); + background-color: rgba(203, 36, 49, 0.1); + padding: 0.75rem 1rem; + border-radius: 6px; + margin: 1.5rem 0; +} + +.result { + margin: 1.5rem 0; +} + +.result h3 { + font-size: 1rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.result a { + display: inline-block; + color: var(--primary-color); + text-decoration: none; + font-weight: 500; + padding: 0.75rem 1rem; + background-color: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: 6px; + transition: background-color 0.2s ease; +} + +.result a:hover { + background-color: #f0f4f8; +} + +.repos-list { + margin: 1.5rem 0; +} + +.repos-list h3 { + font-size: 1rem; + font-weight: 600; + margin-bottom: 1rem; +} + +.repos-list ul { + list-style: none; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1rem; +} + +.repos-list li { + background-color: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 1rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.repos-list li:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px var(--shadow-color); +} + +.repos-list a { + color: var(--primary-color); + text-decoration: none; + font-weight: 500; +} + +.repos-list .repo-description { + font-size: 0.875rem; + color: var(--text-secondary); + margin-top: 0.5rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.conversion-examples { + display: flex; + flex-wrap: wrap; + gap: 2rem; + justify-content: center; + width: 100%; + max-width: 800px; +} + +.example-card { + background-color: var(--bg-secondary); + border-radius: 8px; + padding: 1.5rem; + flex: 1; + min-width: 300px; +} + +.example-card h3 { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--secondary-color); +} + +.example-conversion { + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: center; +} + +.example-conversion code { + background-color: var(--bg-color); + padding: 0.5rem 0.75rem; + border-radius: 4px; + border: 1px solid var(--border-color); + width: 100%; + text-align: center; +} + +.arrow { + color: var(--accent-color); + font-size: 1.25rem; + font-weight: bold; +} + +footer { + margin-top: 4rem; + text-align: center; + color: var(--text-secondary); + font-size: 0.875rem; +} + +footer a { + color: var(--primary-color); + text-decoration: none; +} + +.hidden { + display: none; +} + +.attribution { + font-size: 0.875rem; + color: var(--text-secondary); + margin-top: 1rem; + padding: 0.75rem; + background-color: var(--bg-secondary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.attribution a { + color: var(--primary-color); + text-decoration: none; + font-weight: 500; +} + +.attribution a:hover { + text-decoration: underline; +} + +@media (max-width: 768px) { + .container { + padding: 1.5rem; + } + + .hero h2 { + font-size: 2rem; + } + + .hero p { + font-size: 1rem; + } + + .input-container { + flex-direction: column; + } + + .conversion-examples { + flex-direction: column; + } +}