-
Notifications
You must be signed in to change notification settings - Fork 0
Configure MCP servers, implement "Add Docker Compose Service" Claude Code command #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Add Docker Compose Service | ||
|
|
||
| ## Variables | ||
|
|
||
| APPLICATION_NAME: $ARGUMENTS | ||
| APPLICATION_HOMEPAGE: $ARGUMENTS | ||
|
|
||
| ## Instructions | ||
|
|
||
| Implement a container-based service by creating and configuring a Docker Compose file. | ||
| Follow the architectural pattern in the docker/guidelines.md file. | ||
|
|
||
| Implementation steps: | ||
| - Visit the APPLICATION_NAME homepage at APPLICATION_HOMEPAGE and the application's GitHub page (if available). | ||
| - Search the homepage and docs for Docker Compose deployment examples. If none are found, fall back to plain Docker examples. | ||
| - Based on the application's type, determine which existing category (subfolder under docker/) the application belongs to. Do not create a new category; use "tools" as a fallback. | ||
| - Following docker/guidelines.md and the found examples, create the Docker Compose as "docker/<category>/<application>.yaml". | ||
| - Ensure the compose file contains a brief description of the project and links to the homepage, GitHub page, and any Docker(-Compose) setup example (if available). | ||
| - If the installation guide mentions further improvements (e.g., using an optional external database instead of a built-in one, or enabling SSO), add TODOs for these in the head section of the compose file. | ||
| - Also add TODOs for any new environment variables required. If necessary, add them to the "config-example/docker/myhost/.env" file. | ||
| - After writing the compose file, run: `pre-commit run --files <docker-compose-filename>` and fix any reported issues. | ||
| - Pull the container image(s) with the command `docker/labctl.py service pull <category>/<application>` and verify success. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "mcpServers": { | ||
| "context7": { | ||
| "type": "http", | ||
| "url": "https://mcp.context7.com/mcp" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,51 @@ | ||||||||||||||||||||||||||||||||||
| // https://code.visualstudio.com/docs/copilot/chat/mcp-servers | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| "inputs": [ | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| "type": "promptString", | ||||||||||||||||||||||||||||||||||
| "id": "github-personal-access-token", | ||||||||||||||||||||||||||||||||||
| "description": "GitHub personal access token", | ||||||||||||||||||||||||||||||||||
| "password": true | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| "type": "promptString", | ||||||||||||||||||||||||||||||||||
| "id": "postgresql-database-url", | ||||||||||||||||||||||||||||||||||
| "description": "PostgreSQL database URL (e.g. postgres://user:password@localhost:5432/dbname)", | ||||||||||||||||||||||||||||||||||
| "password": true | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||
| "servers": { | ||||||||||||||||||||||||||||||||||
| // The "servers" section defines the MCP servers you want to use. | ||||||||||||||||||||||||||||||||||
| "homelab-infra": { | ||||||||||||||||||||||||||||||||||
| "url": "http://127.0.0.1:9876/mcp/" | ||||||||||||||||||||||||||||||||||
| "type": "stdio", | ||||||||||||||||||||||||||||||||||
| "command": "uv", | ||||||||||||||||||||||||||||||||||
| "args": ["run", "--directory", "scripts/task-mcp", "server.py"] | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| "context7": { | ||||||||||||||||||||||||||||||||||
| "type": "stdio", | ||||||||||||||||||||||||||||||||||
| "command": "npx", | ||||||||||||||||||||||||||||||||||
| "args": ["-y", "@upstash/context7-mcp"] | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| "github": { | ||||||||||||||||||||||||||||||||||
| "type": "stdio", | ||||||||||||||||||||||||||||||||||
| "command": "npx", | ||||||||||||||||||||||||||||||||||
| "args": ["-y", "@modelcontextprotocol/server-github"], | ||||||||||||||||||||||||||||||||||
| "env": { | ||||||||||||||||||||||||||||||||||
| "GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github-personal-access-token}" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| "postgres": { | ||||||||||||||||||||||||||||||||||
| "type": "stdio", | ||||||||||||||||||||||||||||||||||
| "command": "docker", | ||||||||||||||||||||||||||||||||||
| "args": ["run", "--rm", "-i", "-e", "POSTGRES_URL", "docker.io/mcp/postgres:latest", "${input:postgresql-database-url}"], | ||||||||||||||||||||||||||||||||||
| "env": { | ||||||||||||||||||||||||||||||||||
| "POSTGRES_URL": "${input:postgresql-database-url}" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid leaking the database URL via process args; rely on env-only. Passing the DSN as a positional argument makes it visible via "postgres": {
"type": "stdio",
"command": "docker",
- "args": ["run", "--rm", "-i", "-e", "POSTGRES_URL", "docker.io/mcp/postgres:latest", "${input:postgresql-database-url}"],
+ "args": ["run", "--rm", "-i", "-e", "POSTGRES_URL", "docker.io/mcp/postgres:latest"],
"env": {
"POSTGRES_URL": "${input:postgresql-database-url}"
}
},Optional hardening:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| "playwright": { | ||||||||||||||||||||||||||||||||||
| "type": "stdio", | ||||||||||||||||||||||||||||||||||
| "command": "npx", | ||||||||||||||||||||||||||||||||||
| "args": ["-y", "@playwright/mcp@latest"] | ||||||||||||||||||||||||||||||||||
|
bubacoder marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import requests | ||
| import sys | ||
| import re | ||
| from bs4 import BeautifulSoup | ||
| from urllib.parse import urljoin | ||
|
|
||
|
|
||
| class AppIconFinder: | ||
| """ | ||
| A class for finding application icons from either the dashboard-icons repository | ||
| or by extracting favicons from an application's homepage. | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| """ | ||
| Initialize the AppIconFinder with default headers for HTTP requests. | ||
| """ | ||
| self.headers = { | ||
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' | ||
| } | ||
|
|
||
| def get_app_icon(self, app_name, homepage_url): | ||
| """ | ||
| Main function to get an application icon. | ||
|
|
||
| Args: | ||
| app_name (str): The name of the application. | ||
| homepage_url (str): The URL of the application's homepage. | ||
|
|
||
| Returns: | ||
| str: Either the normalized app_name (if found in dashboard-icons), | ||
| a favicon URL, or "default" if no icon is found. | ||
| """ | ||
| # First check if the icon exists in dashboard-icons | ||
| dashboard_icon = self._find_dashboard_icon(app_name) | ||
| if dashboard_icon: | ||
| return dashboard_icon | ||
|
|
||
| # If not, try to find the favicon | ||
| favicon_url = self._find_favicon_url(homepage_url) | ||
| if favicon_url: | ||
| return favicon_url | ||
|
|
||
| # Return default if no favicon found | ||
| return "default" | ||
|
|
||
| def _find_dashboard_icon(self, app_name): | ||
| normalized_name = app_name.lower().replace(" ", "-") | ||
| url = f"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/{normalized_name}.png" | ||
| try: | ||
| response = requests.head( | ||
| url, | ||
| headers=self.headers, | ||
| timeout=10, | ||
| allow_redirects=True | ||
| ) | ||
| if response.status_code == 200: | ||
| return normalized_name | ||
| return None | ||
| except requests.RequestException: | ||
| return None | ||
|
|
||
| def _find_favicon_url(self, homepage_url): | ||
| try: | ||
| if not homepage_url: | ||
| return None | ||
|
|
||
| # Make sure URL has a scheme | ||
| homepage_url = homepage_url.strip() | ||
| if not homepage_url.startswith(('http://', 'https://')): | ||
| homepage_url = 'https://' + homepage_url | ||
|
|
||
| # Fetch the homepage | ||
| response = requests.get(homepage_url, headers=self.headers, timeout=10) | ||
| response.raise_for_status() | ||
|
|
||
| # Parse the HTML | ||
| soup = BeautifulSoup(response.text, 'html.parser') | ||
|
|
||
| # Look for favicon in different ways | ||
| # 1. Check for link tags with rel="icon" or rel="shortcut icon" | ||
| icon_links = soup.find_all('link', rel=re.compile(r'(shortcut icon|icon|apple-touch-icon)', re.I)) | ||
| if icon_links: | ||
| # Sort by preference: apple-touch-icon > icon > shortcut icon | ||
| def get_priority(link): | ||
| rel_attr = link.get('rel', []) | ||
| if isinstance(rel_attr, str): | ||
| rel_attr = [rel_attr] | ||
| rel_lower = ' '.join(rel_attr).lower() | ||
| if 'apple-touch-icon' in rel_lower: | ||
| return 3 | ||
| elif 'icon' in rel_lower and 'shortcut' not in rel_lower: | ||
| return 2 | ||
| else: | ||
| return 1 | ||
|
|
||
| icon_links = sorted(icon_links, key=get_priority, reverse=True) | ||
| for link in icon_links: | ||
| if 'href' in link.attrs: | ||
| # Make relative URLs absolute | ||
| favicon_url = urljoin(homepage_url, link['href']) | ||
| return favicon_url | ||
|
|
||
| # 2. Check for the default location | ||
| default_favicon = urljoin(homepage_url, '/favicon.ico') | ||
| favicon_response = requests.head(default_favicon, headers=self.headers, timeout=5) | ||
| if favicon_response.status_code == 200: | ||
| return default_favicon | ||
| return None | ||
| except Exception as e: | ||
| print(f"Error finding favicon: {e}", file=sys.stderr) | ||
| return None | ||
|
|
||
|
|
||
| def main(): | ||
| """ | ||
| Test the AppIconFinder with some popular applications. | ||
| """ | ||
| # Create an instance of AppIconFinder | ||
| icon_finder = AppIconFinder() | ||
|
|
||
| # Test cases - popular applications and their homepages | ||
| test_cases = [ | ||
| ("GitHub", "github.com"), | ||
| ("Gitea", "about.gitea.com"), | ||
| ("Plex", "plex.tv"), | ||
| ("Sonarr", "sonarr.tv"), | ||
| ("Radarr", "radarr.video"), | ||
| ("Grafana", "grafana.com"), | ||
| ("Jellyfin", "jellyfin.org"), | ||
| ("HUP", "hup.hu") | ||
| ] | ||
|
|
||
| # Test each application | ||
| for app_name, homepage in test_cases: | ||
| icon_result = icon_finder.get_app_icon(app_name, homepage) | ||
| print(f"App: {app_name}, Homepage: {homepage}, Icon: {icon_result}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| fastmcp>=2.10.0 | ||
| requests>=2.25.0 | ||
| beautifulsoup4>=4.10.0 |
Uh oh!
There was an error while loading. Please reload this page.