Skip to content

test ci/cd #5

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
merged 5 commits into from
May 8, 2025
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
105 changes: 105 additions & 0 deletions .github/scripts/validate-server-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const fs = require('fs');
const path = require('path');

const requiredFields = ['name', 'key', 'description', 'command', 'homepage'];

function validateJsonFile(filePath, requiredFieldsList) {
console.log(`Validating ${filePath}...`);

try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const jsonData = JSON.parse(fileContent);

if (!Array.isArray(jsonData)) {
console.error(`Error: ${filePath} should contain an array of server objects`);
process.exit(1);
}

let hasErrors = false;

jsonData.forEach((server, index) => {
const missingFields = [];

requiredFieldsList.forEach(field => {
if (!server.hasOwnProperty(field) || server[field] === null || server[field] === undefined || server[field] === '') {
missingFields.push(field);
}
});

if (missingFields.length > 0) {
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
console.error(` Missing required fields: ${missingFields.join(', ')}`);
hasErrors = true;
}

if (server.hasOwnProperty('args') && !Array.isArray(server.args)) {
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
console.error(` Field 'args' must be an array`);
hasErrors = true;
}

if (server.hasOwnProperty('env') && (typeof server.env !== 'object' || Array.isArray(server.env) || server.env === null)) {
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
console.error(` Field 'env' must be an object`);
hasErrors = true;
}
});

if (hasErrors) {
process.exit(1);
} else {
console.log(`${filePath} is valid!`);
}
} catch (error) {
console.error(`Error processing ${filePath}: ${error.message}`);
process.exit(1);
}
}

const rootDir = process.cwd();
const serversDir = path.join(rootDir, 'public', 'servers');

if (!fs.existsSync(serversDir)) {
console.error(`Error: Directory ${serversDir} does not exist`);
process.exit(1);
}

let jsonFiles = [];

function findJsonFiles(dir) {
const files = fs.readdirSync(dir);

files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);

if (stat.isDirectory()) {
findJsonFiles(filePath);
} else if (file.endsWith('.json')) {
jsonFiles.push(filePath);
}
});
}

findJsonFiles(serversDir);

if (jsonFiles.length === 0) {
console.warn(`Warning: No JSON files found in ${serversDir}`);
process.exit(0);
}

let hasErrors = false;

jsonFiles.forEach(jsonFile => {
try {
validateJsonFile(jsonFile, requiredFields);
} catch (error) {
hasErrors = true;
}
});

if (hasErrors) {
process.exit(1);
} else {
console.log('All validations completed successfully!');
}
25 changes: 25 additions & 0 deletions .github/workflows/validate-server-json.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Validate Server JSON Files

on:
pull_request:
paths:
- 'public/servers/**/*.json'

jobs:
validate-json:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Validate Server JSON Files
run: node .github/scripts/validate-server-json.js
2 changes: 1 addition & 1 deletion app/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const homeMetadata: Metadata = {
description: "Explore CamelAI's MCP Hub: your directory of official MCP (Model Context Protocol) servers and integrations designed to supercharge AI agents and multi-agent workflows.",
images: [
{
url: "/og-image.jpg", // Make sure to add this image to your public folder
url: "/og-image.jpg",
width: 1200,
height: 630,
alt: "CamelAI MCP Hub - Official MCP Servers & Integrations",
Expand Down
10 changes: 1 addition & 9 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ import { Check, Copy, ShieldCheck, Grid2X2 } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { Header } from "@/components/header";

// 给每个服务器添加来源标记
const serversWithSource = [
...anthropicServers.map(server => ({ ...server, source: 'anthropic' as const })),
...officialServers.map(server => ({ ...server, source: 'official' as const }))
];

// 按名称字母顺序排序
const allServers = serversWithSource.sort((a, b) =>
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
);
Expand All @@ -48,32 +46,27 @@ interface ModalProps {
}

function Modal({ server, onClose }: ModalProps) {
// Create a config object that matches Claude client format
const serverConfig = {
command: server.command,
...(server.args && { args: server.args }),
...(server.env && { env: server.env })
};

// Create the final config object using the server's key as property name
const configObject = {
mcpServers: {
[server.key.toLowerCase()]: serverConfig
}
};

// Convert the config object to a formatted JSON string
const formattedConfig = JSON.stringify(configObject, null, 2);

// State to track if config has been copied
const [copied, setCopied] = useState(false);

// Function to copy config to clipboard
const copyToClipboard = () => {
navigator.clipboard.writeText(formattedConfig)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
setTimeout(() => setCopied(false), 2000)
})
.catch(err => {
console.error('Copy failed:', err);
Expand Down Expand Up @@ -174,7 +167,6 @@ export default function Home() {
setIsModalOpen(false);
};

// 根据筛选条件过滤服务器列表
const filteredServers = allServers.filter(server => {
if (filter === 'all') return true;
return server.source === filter;
Expand Down