Skip to content

feat(command): Add info command and simplify search output #73

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10,717 changes: 10,717 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/get_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

dotenv.load_dotenv()
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)


Expand Down
56 changes: 28 additions & 28 deletions scripts/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def status_message(message: str) -> None:
def load_schema() -> Dict[str, Any]:
"""Load the JSON schema for validation"""
try:
with open(SCHEMA_PATH, 'r') as f:
with open(SCHEMA_PATH, "r") as f:
return json.load(f)
except json.JSONDecodeError as e:
error_exit(f"Invalid JSON in schema file: {e}")
Expand All @@ -42,7 +42,7 @@ def load_schema() -> Dict[str, Any]:
def load_manifest(manifest_path: Path) -> Dict[str, Any]:
"""Load and parse a manifest file with schema validation"""
try:
with open(manifest_path, 'r') as f:
with open(manifest_path, "r") as f:
manifest = json.load(f)

# Get the schema
Expand Down Expand Up @@ -70,7 +70,7 @@ def find_server_manifests(servers_dir: Path) -> List[Path]:
error_exit(f"Servers directory not found: {servers_dir}")

server_files = []
for file_path in servers_dir.glob('*.json'):
for file_path in servers_dir.glob("*.json"):
if file_path.is_file():
server_files.append(file_path)

Expand All @@ -86,39 +86,39 @@ def extract_github_repos(server_manifests: List[Path]) -> Dict[str, str]:
manifest = load_manifest(manifest_path)

# Check if manifest has GitHub repository URL
if 'repository' in manifest:
repo_url = manifest['repository']
if "repository" in manifest:
repo_url = manifest["repository"]

# Handle both string and dictionary repository formats
if isinstance(repo_url, str) and repo_url.startswith('https://github.com/'):
if isinstance(repo_url, str) and repo_url.startswith("https://github.com/"):
github_repos[server_name] = repo_url
elif (isinstance(repo_url, dict) and 'url' in repo_url and
isinstance(repo_url['url'], str) and
repo_url['url'].startswith('https://github.com/')):
github_repos[server_name] = repo_url['url']
elif (isinstance(repo_url, dict) and "url" in repo_url and
isinstance(repo_url["url"], str) and
repo_url["url"].startswith("https://github.com/")):
github_repos[server_name] = repo_url["url"]

return github_repos


def fetch_github_stars_batch(repo_urls: List[str]) -> Dict[str, int]:
"""Fetch GitHub stars for multiple repositories using GraphQL API"""
# Get GitHub token from environment variable
github_token = os.environ.get('GITHUB_TOKEN')
github_token = os.environ.get("GITHUB_TOKEN")

# Prepare headers
headers = {
'Content-Type': 'application/json',
"Content-Type": "application/json",
}

# Add authorization if token is provided
if github_token:
headers['Authorization'] = f"Bearer {github_token}"
headers["Authorization"] = f"Bearer {github_token}"

# Extract owner and repo from URLs
repos = []
for url in repo_urls:
if url.startswith('https://github.com/'):
parts = url.replace('https://github.com/', '').split('/')
if url.startswith("https://github.com/"):
parts = url.replace("https://github.com/", "").split("/")
if len(parts) >= 2:
owner, repo = parts[0], parts[1]
repos.append((owner, repo))
Expand Down Expand Up @@ -147,9 +147,9 @@ def fetch_github_stars_batch(repo_urls: List[str]) -> Dict[str, int]:
variables[f"repo{i}"] = repo

# Join the query parts with proper line length
variable_defs = ', '.join(f'$owner{i}: String!, $repo{i}: String!'
variable_defs = ", ".join(f"$owner{i}: String!, $repo{i}: String!"
for i in range(len(batch)))
query_body = ' '.join(query_parts)
query_body = " ".join(query_parts)

query = f"""query ({variable_defs}) {{
{query_body}
Expand All @@ -160,7 +160,7 @@ def fetch_github_stars_batch(repo_urls: List[str]) -> Dict[str, int]:
response = requests.post(
GITHUB_API_URL,
headers=headers,
json={'query': query, 'variables': variables}
json={"query": query, "variables": variables}
)

# Check for errors
Expand All @@ -179,20 +179,20 @@ def fetch_github_stars_batch(repo_urls: List[str]) -> Dict[str, int]:
data = response.json()

# Check for GraphQL errors
if 'errors' in data:
if "errors" in data:
print(f"⚠️ GraphQL errors: {data['errors']}")
continue

# Extract star counts
for i, (owner, repo) in enumerate(batch):
repo_key = f"repo{i}"
if repo_key in data['data'] and data['data'][repo_key]:
url = data['data'][repo_key]['url']
star_count = data['data'][repo_key]['stargazerCount']
if repo_key in data["data"] and data["data"][repo_key]:
url = data["data"][repo_key]["url"]
star_count = data["data"][repo_key]["stargazerCount"]
stars[url] = star_count
if url.startswith('https://github.com/'):
if url.startswith("https://github.com/"):
returned_parts = url.replace(
'https://github.com/', '').split('/')
"https://github.com/", "").split("/")
if len(returned_parts) >= 2:
returned_owner, returned_repo = returned_parts[0], returned_parts[1]
if owner != returned_owner:
Expand Down Expand Up @@ -243,13 +243,13 @@ def generate_servers_json(server_manifests: List[Path], output_path: Path) -> Di

# Use the entire manifest as is, preserving all fields
# Ensure the name field at minimum is present
if 'name' not in manifest:
manifest['name'] = server_name
if "name" not in manifest:
manifest["name"] = server_name

servers_data[server_name] = manifest

# Write servers.json
with open(output_path, 'w') as f:
with open(output_path, "w") as f:
json.dump(servers_data, f, indent=2)

return servers_data
Expand All @@ -260,7 +260,7 @@ def generate_stars_json(stars: Dict[str, int], output_path: Path) -> None:
status_message("Generating stars.json...")

# Write stars.json
with open(output_path, 'w') as f:
with open(output_path, "w") as f:
json.dump(stars, f, indent=2)


Expand Down
29 changes: 15 additions & 14 deletions scripts/validate_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Third-party imports
import jsonschema


def error_exit(message: str) -> None:
"""Print error message and exit with error code"""
print(f"❌ {message}")
Expand All @@ -17,9 +18,9 @@ def load_schema(schema_path: Path) -> Dict:
"""Load and parse the schema file"""
if not schema_path.exists():
error_exit(f"Schema file not found: {schema_path}")

try:
with open(schema_path, 'r') as f:
with open(schema_path, "r") as f:
return json.load(f)
except json.JSONDecodeError as e:
error_exit(f"Invalid schema JSON: {e}")
Expand All @@ -29,13 +30,13 @@ def load_schema(schema_path: Path) -> Dict:
def validate_manifest(manifest_path: Path, schema: Dict) -> Tuple[bool, str]:
"""Validate a single manifest file against the schema"""
try:
with open(manifest_path, 'r') as f:
with open(manifest_path, "r") as f:
manifest = json.load(f)
except json.JSONDecodeError as e:
return False, f"Invalid JSON: {e}"
except Exception as e:
return False, f"Error reading file: {e}"

try:
jsonschema.validate(manifest, schema)
return True, ""
Expand All @@ -48,12 +49,12 @@ def find_server_files(servers_dir: Path) -> List[Path]:
"""Find all server JSON files in the servers directory"""
if not servers_dir.exists() or not servers_dir.is_dir():
error_exit(f"Servers directory not found: {servers_dir}")

server_files = []
for file_path in servers_dir.glob('*.json'):
for file_path in servers_dir.glob("*.json"):
if file_path.is_file():
server_files.append(file_path)

return server_files

def main() -> int:
Expand All @@ -63,32 +64,32 @@ def main() -> int:
repo_root = script_dir.parent
schema_path = repo_root / "mcp-registry" / "schema" / "server-schema.json"
servers_dir = repo_root / "mcp-registry" / "servers"

# Load the schema
schema = load_schema(schema_path)

# Find all server JSON files
server_files = find_server_files(servers_dir)

if not server_files:
print("No server files found to validate.")
return 0

print(f"Found {len(server_files)} server files to validate.")

# Validate each server file
any_errors = False
for server_path in server_files:
server_name = server_path.stem
valid, error_msg = validate_manifest(server_path, schema)

if valid:
print(f"✓ {server_name}: Valid")
else:
print(f"✗ {server_name}: Invalid")
print(f" - {error_msg}")
any_errors = True

return 1 if any_errors else 0

if __name__ == "__main__":
Expand Down
3 changes: 3 additions & 0 deletions src/mcpm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
add,
client,
config,
info,
inspector,
list,
pop,
Expand Down Expand Up @@ -147,6 +148,7 @@ def main(ctx, help_flag, version):

commands_table.add_row("[yellow]server[/]")
commands_table.add_row(" [cyan]search[/]", "Search available MCP servers.")
commands_table.add_row(" [cyan]info[/]", "Show detailed information about a specific MCP server.")
commands_table.add_row(" [cyan]add[/]", "Add an MCP server directly to a client.")
commands_table.add_row(" [cyan]cp[/]", "Copy a server from one client/profile to another.")
commands_table.add_row(" [cyan]mv[/]", "Move a server from one client/profile to another.")
Expand Down Expand Up @@ -175,6 +177,7 @@ def main(ctx, help_flag, version):

# Register commands
main.add_command(search.search)
main.add_command(info.info)
main.add_command(remove.remove, name="rm")
main.add_command(add.add)
main.add_command(list.list, name="ls")
Expand Down
Loading