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
+
+
+
+
Fetching repositories...
+
+
+
+
+
+
+
+
GitHub Repository
+
+ github.com/username/repo
+ →
+ gitmcp.io/username/repo
+
+
+
+
GitHub Pages
+
+ username.github.io/repo
+ →
+ username.gitmcp.io/repo
+
+
+
+
+
+
+
+
+
+
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;
+ }
+}