Skip to content

Commit f65a3e9

Browse files
authored
Merge pull request #5 from camel-ai/2-feature-request-ci-cd
test ci/cd
2 parents cb75dce + 77003a9 commit f65a3e9

File tree

4 files changed

+132
-10
lines changed

4 files changed

+132
-10
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const requiredFields = ['name', 'key', 'description', 'command', 'homepage'];
5+
6+
function validateJsonFile(filePath, requiredFieldsList) {
7+
console.log(`Validating ${filePath}...`);
8+
9+
try {
10+
const fileContent = fs.readFileSync(filePath, 'utf8');
11+
const jsonData = JSON.parse(fileContent);
12+
13+
if (!Array.isArray(jsonData)) {
14+
console.error(`Error: ${filePath} should contain an array of server objects`);
15+
process.exit(1);
16+
}
17+
18+
let hasErrors = false;
19+
20+
jsonData.forEach((server, index) => {
21+
const missingFields = [];
22+
23+
requiredFieldsList.forEach(field => {
24+
if (!server.hasOwnProperty(field) || server[field] === null || server[field] === undefined || server[field] === '') {
25+
missingFields.push(field);
26+
}
27+
});
28+
29+
if (missingFields.length > 0) {
30+
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
31+
console.error(` Missing required fields: ${missingFields.join(', ')}`);
32+
hasErrors = true;
33+
}
34+
35+
if (server.hasOwnProperty('args') && !Array.isArray(server.args)) {
36+
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
37+
console.error(` Field 'args' must be an array`);
38+
hasErrors = true;
39+
}
40+
41+
if (server.hasOwnProperty('env') && (typeof server.env !== 'object' || Array.isArray(server.env) || server.env === null)) {
42+
console.error(`Error in ${filePath}, server at index ${index} (${server.name || 'unnamed'}):`);
43+
console.error(` Field 'env' must be an object`);
44+
hasErrors = true;
45+
}
46+
});
47+
48+
if (hasErrors) {
49+
process.exit(1);
50+
} else {
51+
console.log(`${filePath} is valid!`);
52+
}
53+
} catch (error) {
54+
console.error(`Error processing ${filePath}: ${error.message}`);
55+
process.exit(1);
56+
}
57+
}
58+
59+
const rootDir = process.cwd();
60+
const serversDir = path.join(rootDir, 'public', 'servers');
61+
62+
if (!fs.existsSync(serversDir)) {
63+
console.error(`Error: Directory ${serversDir} does not exist`);
64+
process.exit(1);
65+
}
66+
67+
let jsonFiles = [];
68+
69+
function findJsonFiles(dir) {
70+
const files = fs.readdirSync(dir);
71+
72+
files.forEach(file => {
73+
const filePath = path.join(dir, file);
74+
const stat = fs.statSync(filePath);
75+
76+
if (stat.isDirectory()) {
77+
findJsonFiles(filePath);
78+
} else if (file.endsWith('.json')) {
79+
jsonFiles.push(filePath);
80+
}
81+
});
82+
}
83+
84+
findJsonFiles(serversDir);
85+
86+
if (jsonFiles.length === 0) {
87+
console.warn(`Warning: No JSON files found in ${serversDir}`);
88+
process.exit(0);
89+
}
90+
91+
let hasErrors = false;
92+
93+
jsonFiles.forEach(jsonFile => {
94+
try {
95+
validateJsonFile(jsonFile, requiredFields);
96+
} catch (error) {
97+
hasErrors = true;
98+
}
99+
});
100+
101+
if (hasErrors) {
102+
process.exit(1);
103+
} else {
104+
console.log('All validations completed successfully!');
105+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Validate Server JSON Files
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'public/servers/**/*.json'
7+
8+
jobs:
9+
validate-json:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v3
14+
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v3
17+
with:
18+
node-version: '20'
19+
cache: 'npm'
20+
21+
- name: Install dependencies
22+
run: npm ci
23+
24+
- name: Validate Server JSON Files
25+
run: node .github/scripts/validate-server-json.js

app/metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const homeMetadata: Metadata = {
88
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.",
99
images: [
1010
{
11-
url: "/og-image.jpg", // Make sure to add this image to your public folder
11+
url: "/og-image.jpg",
1212
width: 1200,
1313
height: 630,
1414
alt: "CamelAI MCP Hub - Official MCP Servers & Integrations",

app/page.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ import { Check, Copy, ShieldCheck, Grid2X2 } from "lucide-react";
1818
import { motion, AnimatePresence } from "framer-motion";
1919
import { Header } from "@/components/header";
2020

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

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

5048
function Modal({ server, onClose }: ModalProps) {
51-
// Create a config object that matches Claude client format
5249
const serverConfig = {
5350
command: server.command,
5451
...(server.args && { args: server.args }),
5552
...(server.env && { env: server.env })
5653
};
5754

58-
// Create the final config object using the server's key as property name
5955
const configObject = {
6056
mcpServers: {
6157
[server.key.toLowerCase()]: serverConfig
6258
}
6359
};
6460

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

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

71-
// Function to copy config to clipboard
7265
const copyToClipboard = () => {
7366
navigator.clipboard.writeText(formattedConfig)
7467
.then(() => {
7568
setCopied(true);
76-
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
69+
setTimeout(() => setCopied(false), 2000)
7770
})
7871
.catch(err => {
7972
console.error('Copy failed:', err);
@@ -174,7 +167,6 @@ export default function Home() {
174167
setIsModalOpen(false);
175168
};
176169

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

0 commit comments

Comments
 (0)