feat(A1.3): Add submit_config MCP tool for community submissions
- Add submit_config tool to MCP server (10th tool) - Validates config JSON before submission - Creates GitHub issue in skill-seekers-configs repo - Auto-detects category from config name - Requires GITHUB_TOKEN for authentication - Returns issue URL for tracking Features: - Accepts config_path or config_json parameter - Validates required fields (name, description, base_url) - Auto-categorizes configs (web-frameworks, game-engines, devops, etc.) - Creates formatted issue with testing notes - Adds labels: config-submission, needs-review Closes #11
This commit is contained in:
@@ -438,6 +438,32 @@ async def list_tools() -> list[Tool]:
|
|||||||
"required": [],
|
"required": [],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Tool(
|
||||||
|
name="submit_config",
|
||||||
|
description="Submit a custom config file to the community. Creates a GitHub issue in skill-seekers-configs repo for review.",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"config_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to config JSON file to submit (e.g., 'configs/myframework.json')",
|
||||||
|
},
|
||||||
|
"config_json": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Config JSON as string (alternative to config_path)",
|
||||||
|
},
|
||||||
|
"testing_notes": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Notes about testing (e.g., 'Tested with 20 pages, works well')",
|
||||||
|
},
|
||||||
|
"github_token": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "GitHub personal access token (or use GITHUB_TOKEN env var)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -470,6 +496,8 @@ async def call_tool(name: str, arguments: Any) -> list[TextContent]:
|
|||||||
return await scrape_github_tool(arguments)
|
return await scrape_github_tool(arguments)
|
||||||
elif name == "fetch_config":
|
elif name == "fetch_config":
|
||||||
return await fetch_config_tool(arguments)
|
return await fetch_config_tool(arguments)
|
||||||
|
elif name == "submit_config":
|
||||||
|
return await submit_config_tool(arguments)
|
||||||
else:
|
else:
|
||||||
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
||||||
|
|
||||||
@@ -1193,6 +1221,131 @@ Next steps:
|
|||||||
return [TextContent(type="text", text=f"❌ Error: {str(e)}")]
|
return [TextContent(type="text", text=f"❌ Error: {str(e)}")]
|
||||||
|
|
||||||
|
|
||||||
|
async def submit_config_tool(args: dict) -> list[TextContent]:
|
||||||
|
"""Submit a custom config to skill-seekers-configs repository via GitHub issue"""
|
||||||
|
try:
|
||||||
|
from github import Github, GithubException
|
||||||
|
except ImportError:
|
||||||
|
return [TextContent(type="text", text="❌ Error: PyGithub not installed.\n\nInstall with: pip install PyGithub")]
|
||||||
|
|
||||||
|
config_path = args.get("config_path")
|
||||||
|
config_json_str = args.get("config_json")
|
||||||
|
testing_notes = args.get("testing_notes", "")
|
||||||
|
github_token = args.get("github_token") or os.environ.get("GITHUB_TOKEN")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Load config data
|
||||||
|
if config_path:
|
||||||
|
config_file = Path(config_path)
|
||||||
|
if not config_file.exists():
|
||||||
|
return [TextContent(type="text", text=f"❌ Error: Config file not found: {config_path}")]
|
||||||
|
|
||||||
|
with open(config_file, 'r') as f:
|
||||||
|
config_data = json.load(f)
|
||||||
|
config_json_str = json.dumps(config_data, indent=2)
|
||||||
|
config_name = config_data.get("name", config_file.stem)
|
||||||
|
|
||||||
|
elif config_json_str:
|
||||||
|
try:
|
||||||
|
config_data = json.loads(config_json_str)
|
||||||
|
config_name = config_data.get("name", "unnamed")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return [TextContent(type="text", text=f"❌ Error: Invalid JSON: {str(e)}")]
|
||||||
|
|
||||||
|
else:
|
||||||
|
return [TextContent(type="text", text="❌ Error: Must provide either config_path or config_json")]
|
||||||
|
|
||||||
|
# Validate required fields
|
||||||
|
required_fields = ["name", "description", "base_url"]
|
||||||
|
missing_fields = [field for field in required_fields if field not in config_data]
|
||||||
|
|
||||||
|
if missing_fields:
|
||||||
|
return [TextContent(type="text", text=f"❌ Error: Missing required fields: {', '.join(missing_fields)}\n\nRequired: name, description, base_url")]
|
||||||
|
|
||||||
|
# Detect category
|
||||||
|
name_lower = config_name.lower()
|
||||||
|
category = "other"
|
||||||
|
if any(x in name_lower for x in ["react", "vue", "django", "laravel", "fastapi", "astro", "hono"]):
|
||||||
|
category = "web-frameworks"
|
||||||
|
elif any(x in name_lower for x in ["godot", "unity", "unreal"]):
|
||||||
|
category = "game-engines"
|
||||||
|
elif any(x in name_lower for x in ["kubernetes", "ansible", "docker"]):
|
||||||
|
category = "devops"
|
||||||
|
elif any(x in name_lower for x in ["tailwind", "bootstrap", "bulma"]):
|
||||||
|
category = "css-frameworks"
|
||||||
|
|
||||||
|
# Check for GitHub token
|
||||||
|
if not github_token:
|
||||||
|
return [TextContent(type="text", text="❌ Error: GitHub token required.\n\nProvide github_token parameter or set GITHUB_TOKEN environment variable.\n\nCreate token at: https://github.com/settings/tokens")]
|
||||||
|
|
||||||
|
# Create GitHub issue
|
||||||
|
try:
|
||||||
|
gh = Github(github_token)
|
||||||
|
repo = gh.get_repo("yusufkaraaslan/skill-seekers-configs")
|
||||||
|
|
||||||
|
# Build issue body
|
||||||
|
issue_body = f"""## Config Submission
|
||||||
|
|
||||||
|
### Framework/Tool Name
|
||||||
|
{config_name}
|
||||||
|
|
||||||
|
### Category
|
||||||
|
{category}
|
||||||
|
|
||||||
|
### Configuration JSON
|
||||||
|
```json
|
||||||
|
{config_json_str}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Results
|
||||||
|
{testing_notes if testing_notes else "Not provided"}
|
||||||
|
|
||||||
|
### Documentation URL
|
||||||
|
{config_data.get('base_url', 'N/A')}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
- [ ] Config validated
|
||||||
|
- [ ] Test scraping completed
|
||||||
|
- [ ] Added to appropriate category
|
||||||
|
- [ ] API updated
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create issue
|
||||||
|
issue = repo.create_issue(
|
||||||
|
title=f"[CONFIG] {config_name}",
|
||||||
|
body=issue_body,
|
||||||
|
labels=["config-submission", "needs-review"]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = f"""✅ Config submitted successfully!
|
||||||
|
|
||||||
|
📝 Issue created: {issue.html_url}
|
||||||
|
🏷️ Issue #{issue.number}
|
||||||
|
📦 Config: {config_name}
|
||||||
|
📊 Category: {category}
|
||||||
|
🏷️ Labels: config-submission, needs-review
|
||||||
|
|
||||||
|
What happens next:
|
||||||
|
1. Maintainers will review your config
|
||||||
|
2. They'll test it with the actual documentation
|
||||||
|
3. If approved, it will be added to official/{category}/
|
||||||
|
4. The API will auto-update and your config becomes available!
|
||||||
|
|
||||||
|
💡 Track your submission: {issue.html_url}
|
||||||
|
📚 All configs: https://github.com/yusufkaraaslan/skill-seekers-configs
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [TextContent(type="text", text=result)]
|
||||||
|
|
||||||
|
except GithubException as e:
|
||||||
|
return [TextContent(type="text", text=f"❌ GitHub Error: {str(e)}\n\nCheck your token permissions (needs 'repo' or 'public_repo' scope).")]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return [TextContent(type="text", text=f"❌ Error: {str(e)}")]
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
"""Run the MCP server"""
|
"""Run the MCP server"""
|
||||||
if not MCP_AVAILABLE or app is None:
|
if not MCP_AVAILABLE or app is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user