Skip to content

Commit ba7ffdb

Browse files
author
Jeremy Dai
authored
update (#30)
1 parent 4564cfd commit ba7ffdb

File tree

2 files changed

+150
-29
lines changed

2 files changed

+150
-29
lines changed

.github/workflows/auto-update.yml

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Scrape and Update Repo
33
permissions:
44
contents: write
55
pull-requests: write
6-
issues: read
6+
issues: write
77

88
on:
99
issues:
@@ -38,20 +38,48 @@ jobs:
3838
- name: Install dependencies
3939
run: |
4040
python -m pip install --upgrade pip
41-
pip install requests
41+
pip install requests boto3 jsonschema mcp loguru pydantic ruamel-yaml
4242
4343
- name: Create local directory
4444
run: mkdir -p local
4545

4646
- name: Run Script with Issue Body
47+
id: run_script
4748
env:
4849
ISSUE_BODY: ${{ github.event.issue.body }}
50+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
51+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
52+
AWS_REGION: us-east-1
4953
shell: /usr/bin/bash -e {0}
54+
continue-on-error: true
5055
run: |
5156
echo "Running get_manifest.py with issue body"
52-
python scripts/get_manifest.py "$ISSUE_BODY"
57+
mkdir -p local
58+
if python scripts/get_manifest.py "$ISSUE_BODY"; then
59+
echo "script_success=true" >> $GITHUB_OUTPUT
60+
else
61+
echo "script_success=false" >> $GITHUB_OUTPUT
62+
fi
63+
64+
- name: Comment on issue
65+
uses: actions/github-script@v6
66+
with:
67+
github-token: ${{ secrets.GITHUB_TOKEN }}
68+
script: |
69+
const issue_number = context.issue.number;
70+
const run_id = context.runId;
71+
const repo = context.repo;
72+
const run_url = `https://github.com/${repo.owner}/${repo.repo}/actions/runs/${run_id}`;
73+
74+
await github.rest.issues.createComment({
75+
owner: repo.owner,
76+
repo: repo.repo,
77+
issue_number: issue_number,
78+
body: `🤖 Processing your MCP server submission...\n\nTrack progress here: [GitHub Action Run #${run_id}](${run_url})`
79+
});
5380
5481
- name: Create and push new branch
82+
if: steps.run_script.outputs.script_success == 'true'
5583
env:
5684
ISSUE_NUMBER: ${{ github.event.issue.number }}
5785
shell: /usr/bin/bash -e {0}
@@ -61,6 +89,65 @@ jobs:
6189
git config user.name "GitHub Action"
6290
git config user.email "[email protected]"
6391
git checkout -b "$BRANCH_NAME"
64-
git add -f local/readme.md # Force add despite .gitignore TODO: change to real json file
65-
git commit -m "Update repo with README from issue #$ISSUE_NUMBER" || echo "No changes to commit"
66-
git push origin "$BRANCH_NAME" --force # Push to the new branch
92+
git add -f local/ mcp-registry/ # Add all files generated by the script
93+
git commit -m "Update repo with server manifest from issue #$ISSUE_NUMBER" || echo "No changes to commit"
94+
git push origin "$BRANCH_NAME" --force # Push to the new branch
95+
echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
96+
97+
- name: Create Pull Request
98+
id: create_pr
99+
if: steps.run_script.outputs.script_success == 'true'
100+
uses: peter-evans/create-pull-request@v5
101+
with:
102+
token: ${{ secrets.GITHUB_TOKEN }}
103+
commit-message: "Add server from issue #${{ github.event.issue.number }}"
104+
title: "Add server from issue #${{ github.event.issue.number }}"
105+
body: |
106+
Automated PR from issue #${{ github.event.issue.number }}
107+
108+
Server URL: ${{ github.event.issue.body }}
109+
branch: ${{ env.branch_name }}
110+
base: main
111+
112+
- name: Comment on issue with success
113+
if: steps.run_script.outputs.script_success == 'true'
114+
uses: actions/github-script@v6
115+
with:
116+
github-token: ${{ secrets.GITHUB_TOKEN }}
117+
script: |
118+
const issue_number = context.issue.number;
119+
const repo = context.repo;
120+
const pr_number = '${{ steps.create_pr.outputs.pull-request-number }}';
121+
const pr_url = pr_number ? `https://github.com/${repo.owner}/${repo.repo}/pull/${pr_number}` : '';
122+
123+
let body = '';
124+
if (pr_number) {
125+
body = `✅ Processing complete!\n\nA pull request has been created with the server manifest: [PR #${pr_number}](${pr_url})`;
126+
} else {
127+
body = `⚠️ Processing completed, but no pull request was created. Please check the action logs for details.`;
128+
}
129+
130+
await github.rest.issues.createComment({
131+
owner: repo.owner,
132+
repo: repo.repo,
133+
issue_number: issue_number,
134+
body: body
135+
});
136+
137+
- name: Comment on issue with failure
138+
if: steps.run_script.outputs.script_success == 'false'
139+
uses: actions/github-script@v6
140+
with:
141+
github-token: ${{ secrets.GITHUB_TOKEN }}
142+
script: |
143+
const issue_number = context.issue.number;
144+
const repo = context.repo;
145+
const run_id = context.runId;
146+
const run_url = `https://github.com/${repo.owner}/${repo.repo}/actions/runs/${run_id}`;
147+
148+
await github.rest.issues.createComment({
149+
owner: repo.owner,
150+
repo: repo.repo,
151+
issue_number: issue_number,
152+
body: `❌ Failed to process the server manifest. Please check the [action logs](${run_url}) for details.`
153+
});

scripts/get_manifest.py

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def _extract_server_info_from_url(self, repo_url: str) -> Dict[str, str]:
6666
else:
6767
# Fallback to repo name if structure is unexpected
6868
server_name = parts[4] # 'servers'
69-
logger.warning(f"Could not find server name in URL: {repo_url}")
69+
logger.warning(
70+
f"Could not find server name in URL: {repo_url}")
7071
else:
7172
# Third-party repo: use repo name as server name
7273
server_name = parts[4]
@@ -344,7 +345,8 @@ def _extract_with_llms(self, prompt: tuple[str, str]) -> Dict:
344345
try:
345346
response = self.client.converse(
346347
modelId=LLMModel.CLAUDE_3_7_SONNET,
347-
system=[{"text": "You are a helpful assistant for README analysis."}],
348+
system=[
349+
{"text": "You are a helpful assistant for README analysis."}],
348350
messages=[
349351
{
350352
"role": "user",
@@ -364,7 +366,8 @@ def _extract_with_llms(self, prompt: tuple[str, str]) -> Dict:
364366
content = response["output"]["message"]["content"]
365367

366368
# Find the first text item
367-
text_items = [item.get("text") for item in content if "text" in item]
369+
text_items = [item.get("text")
370+
for item in content if "text" in item]
368371
if text_items:
369372
try:
370373
# Look for JSON content within the text
@@ -377,18 +380,22 @@ def _extract_with_llms(self, prompt: tuple[str, str]) -> Dict:
377380
# If not, try to find JSON in the text (it might be surrounded by other text)
378381
import re
379382

380-
json_match = re.search(r"(\{.*\})", text_content, re.DOTALL)
383+
json_match = re.search(
384+
r"(\{.*\})", text_content, re.DOTALL)
381385
if json_match:
382386
return json.loads(json_match.group(1))
383387
else:
384-
logger.error(f"No JSON content found in response: {text_content[:100]}...")
388+
logger.error(
389+
f"No JSON content found in response: {text_content[:100]}...")
385390
return self._get_minimal_manifest()
386391
except (json.JSONDecodeError, ValueError) as e:
387-
logger.error(f"Failed to parse JSON from response: {e}")
392+
logger.error(
393+
f"Failed to parse JSON from response: {e}")
388394
else:
389395
logger.error("No text items found in response content")
390396
else:
391-
logger.error(f"Unexpected response structure: {response.keys()}")
397+
logger.error(
398+
f"Unexpected response structure: {response.keys()}")
392399

393400
# If we get here, something went wrong with the parsing
394401
return self._get_minimal_manifest()
@@ -437,7 +444,8 @@ def generate_manifest(self, repo_url: str, server_name: Optional[str] = None) ->
437444

438445
# If server info doesn't have a description, extract from README
439446
if not server_info["desc"]:
440-
server_info["desc"] = self._extract_description_from_readme(readme_content)
447+
server_info["desc"] = self._extract_description_from_readme(
448+
readme_content)
441449

442450
# Get prompt as tuple and extract manifest
443451
prompt = self._create_prompt(repo_url, readme_content)
@@ -457,19 +465,24 @@ def generate_manifest(self, repo_url: str, server_name: Optional[str] = None) ->
457465
manifest["description"] = server_info["desc"]
458466

459467
# Categorize the server
460-
sample_server = {"name": manifest.get("name", ""), "description": manifest.get("description", "")}
468+
sample_server = {"name": manifest.get(
469+
"name", ""), "description": manifest.get("description", "")}
461470

462-
categorized_servers = asyncio.run(self.categorize_servers([sample_server]))
471+
categorized_servers = asyncio.run(
472+
self.categorize_servers([sample_server]))
463473
if categorized_servers:
464-
manifest["categories"] = [categorized_servers[0].get("category", "Unknown")]
474+
manifest["categories"] = [
475+
categorized_servers[0].get("category", "Unknown")]
465476
manifest["tags"] = manifest.get("tags", [])
466477
logger.info(f"Server categorized as: {manifest['categories'][0]}")
467478

468479
# Sort installations by priority before running
469-
manifest["installations"] = self._filter_and_sort_installations(manifest.get("installations", {}))
480+
manifest["installations"] = self._filter_and_sort_installations(
481+
manifest.get("installations", {}))
470482
logger.info(f"Server installations: {manifest['installations']}")
471483
try:
472-
capabilities = asyncio.run(self._run_server_and_extract_capabilities(manifest))
484+
capabilities = asyncio.run(
485+
self._run_server_and_extract_capabilities(manifest))
473486
if capabilities:
474487
manifest.update(capabilities)
475488
except Exception as e:
@@ -483,16 +496,21 @@ async def _run_server_and_extract_capabilities(self, manifest: dict[str, Any]) -
483496
env_vars = {}
484497
if envs:
485498
for k, v in envs.items():
486-
env_vars[k] = manifest.get("arguments", {}).get(k, {}).get("example", v)
499+
env_vars[k] = manifest.get("arguments", {}).get(
500+
k, {}).get("example", v)
487501
await mcp_client.connect_to_server(installation["command"], installation["args"], env_vars)
488502
result = {}
489503
try:
490504
tools = await mcp_client.list_tools()
491-
result["tools"] = [json.loads(tool.model_dump_json()) for tool in tools.tools] # to avoid $schema field
505+
# to avoid $schema field
506+
result["tools"] = [json.loads(tool.model_dump_json())
507+
for tool in tools.tools]
492508
prompts = await mcp_client.list_prompts()
493-
result["prompts"] = [json.loads(prompt.model_dump_json()) for prompt in prompts.prompts]
509+
result["prompts"] = [json.loads(
510+
prompt.model_dump_json()) for prompt in prompts.prompts]
494511
resources = await mcp_client.list_resources()
495-
result["resources"] = [json.loads(resource.model_dump_json()) for resource in resources.resources]
512+
result["resources"] = [json.loads(
513+
resource.model_dump_json()) for resource in resources.resources]
496514
except Exception as e:
497515
traceback.print_exc()
498516
logger.error(f"Failed to list tools: {e}")
@@ -502,9 +520,12 @@ async def _run_server_and_extract_capabilities(self, manifest: dict[str, Any]) -
502520
return result
503521

504522
def _filter_and_sort_installations(self, installations: dict[str, dict[str, Any]]):
505-
priority = {"uvx": 0, "npm": 1, "python": 2, "docker": 3, "cli": 4, "custom": 5}
506-
filtered_installations = {k: v for k, v in installations.items() if k in priority}
507-
sorted_installations = sorted(filtered_installations.items(), key=lambda x: priority.get(x[0], 6))
523+
priority = {"uvx": 0, "npm": 1, "python": 2,
524+
"docker": 3, "cli": 4, "custom": 5}
525+
filtered_installations = {k: v for k,
526+
v in installations.items() if k in priority}
527+
sorted_installations = sorted(
528+
filtered_installations.items(), key=lambda x: priority.get(x[0], 6))
508529
return dict(sorted_installations)
509530

510531

@@ -540,7 +561,8 @@ def main(repo_url: str, is_official: bool = False):
540561
existing_manifest["name"] = new_name
541562
with open(new_filename, "w", encoding="utf-8") as file:
542563
json.dump(existing_manifest, file, indent=2)
543-
logger.info(f"Previous community manifest saved to {new_filename}")
564+
logger.info(
565+
f"Previous community manifest saved to {new_filename}")
544566

545567
with open(filename, "w", encoding="utf-8") as file:
546568
json.dump(manifest, file, indent=2)
@@ -558,9 +580,21 @@ def main(repo_url: str, is_official: bool = False):
558580
sys.exit(1)
559581

560582
repo_url = sys.argv[1].strip()
561-
is_official = bool(sys.argv[2]) if len(sys.argv) > 2 else False
583+
584+
# Check if the URL is a simple URL without protocol
562585
if not repo_url.startswith(("http://", "https://")):
563-
logger.error("Error: URL must start with http:// or https://")
586+
# Add https:// if it's a github.com URL without protocol
587+
if repo_url.startswith("github.com"):
588+
repo_url = "https://" + repo_url
589+
# Check if it's a full URL without protocol
590+
elif "github.com" in repo_url:
591+
repo_url = "https://" + repo_url
592+
593+
is_official = bool(sys.argv[2]) if len(sys.argv) > 2 else False
594+
595+
# Validate URL format
596+
if not "github.com" in repo_url:
597+
logger.error("Error: URL must be a GitHub URL")
564598
sys.exit(1)
565599

566600
logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")

0 commit comments

Comments
 (0)