Files
eSIM-Tools/scripts/generate-agent-metadata.js
Abner 9941f66ee7 feat: 完善 OAuth Discovery、MCP Server Card 和 WebMCP 支持
- 添加 /.well-known/openid-configuration (RFC 8414) OAuth 发现元数据
- 更新 MCP Server Card 为 SEP-2127 格式(顶级 name/version/description)
- 新增 WebMCP 支持,通过 navigator.modelContext 暴露站点工具
2026-05-11 13:01:14 +08:00

304 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 生成 AI Agent 友好元数据文件
* - robots.txt含 AI 爬虫规则 + Content-Signal
* - sitemap.xml
* - .well-known/api-catalog
* - .well-known/openid-configuration
* - .well-known/oauth-protected-resource
* - .well-known/mcp/server-card.json
* - .well-known/agent-skills/index.json
*/
const fs = require('fs');
const path = require('path');
const BuildLogger = require('./logger.js');
const projectRoot = path.join(__dirname, '..');
const distDir = path.join(projectRoot, 'dist');
const SITE_URL = 'https://esim.cosr.eu.org';
// 规范 URL 列表
const CANONICAL_URLS = [
{ loc: '/', priority: '1.0', changefreq: 'daily' },
{ loc: '/giffgaff', priority: '0.9', changefreq: 'weekly' },
{ loc: '/simyo', priority: '0.9', changefreq: 'weekly' },
];
/**
* 生成 robots.txt
* 包含标准爬虫规则、AI 爬虫规则、Content-Signal 声明
*/
function generateRobotsTxt() {
return `# eSIM-Tools robots.txt
# https://www.rfc-editor.org/rfc/rfc9309
User-agent: *
Allow: /
Disallow: /api/
Disallow: /bff/
Disallow: /src/
Disallow: /.netlify/
Disallow: /coverage/
# AI 爬虫规则
User-agent: GPTBot
Allow: /
Disallow: /api/
Disallow: /bff/
User-agent: OAI-SearchBot
Allow: /
Disallow: /api/
Disallow: /bff/
User-agent: Claude-Web
Allow: /
Disallow: /api/
Disallow: /bff/
User-agent: ClaudeBot
Allow: /
Disallow: /api/
Disallow: /bff/
User-agent: Google-Extended
Allow: /
Disallow: /api/
Disallow: /bff/
User-agent: Bingbot
Allow: /
Disallow: /api/
Disallow: /bff/
# 内容信号声明 (Content-Signal)
# https://contentsignals.org/
Content-Signal: ai-train=no, search=yes, ai-input=no
# 站点地图
Sitemap: ${SITE_URL}/sitemap.xml
`;
}
/**
* 生成 sitemap.xml
*/
function generateSitemap() {
const urls = CANONICAL_URLS.map(({ loc, priority, changefreq }) => `
<url>
<loc>${SITE_URL}${loc}</loc>
<changefreq>${changefreq}</changefreq>
<priority>${priority}</priority>
</url>`).join('');
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`;
}
/**
* 生成 API Catalog (RFC 9727)
*/
function generateApiCatalog() {
return JSON.stringify({
linkset: [
{
anchor: `${SITE_URL}/bff/`,
'service-desc': {
href: `${SITE_URL}/docs/api`,
title: 'eSIM Tools API Documentation'
},
'service-doc': {
href: `${SITE_URL}/README.md`,
title: 'Project README'
},
status: {
href: '/.netlify/functions/health',
title: 'Health Check'
}
}
]
}, null, 2);
}
/**
* 生成 OAuth Protected Resource Metadata (RFC 9728)
*/
function generateOAuthProtectedResource() {
return JSON.stringify({
resource: `${SITE_URL}`,
authorization_servers: [
'https://id.giffgaff.com'
],
scopes_supported: [
'openid',
'profile'
],
bearer_methods_supported: [
'header'
],
resource_documentation: `${SITE_URL}/docs/api`
}, null, 2);
}
/**
* 生成 MCP Server Card (SEP-2127)
*/
function generateMcpServerCard() {
return JSON.stringify({
$schema: 'https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json',
name: 'esim-tools/server',
version: '2.0.0',
title: 'eSIM Tools MCP Server',
description: 'eSIM management tools for Giffgaff and Simyo users - OAuth authentication, MFA verification, and eSIM activation',
websiteUrl: SITE_URL,
repository: 'https://github.com/Silentely/eSIM-Tools',
remotes: [
{
type: 'http',
url: `${SITE_URL}/bff/`
}
],
capabilities: {
tools: [
{
name: 'giffgaff-oauth',
description: 'Giffgaff OAuth token exchange and authentication'
},
{
name: 'simyo-esim-activate',
description: 'Simyo eSIM activation and management'
}
]
}
}, null, 2);
}
/**
* 生成 OAuth Authorization Server Metadata (RFC 8414)
* Giffgaff 使用外部 OpenID Connect 提供商
*/
function generateOAuthDiscovery() {
return JSON.stringify({
issuer: 'https://id.giffgaff.com',
authorization_endpoint: 'https://id.giffgaff.com/authorize',
token_endpoint: 'https://id.giffgaff.com/token',
jwks_uri: 'https://id.giffgaff.com/.well-known/jwks.json',
revocation_endpoint: 'https://id.giffgaff.com/revoke',
scopes_supported: [
'openid',
'profile',
'email'
],
response_types_supported: [
'code'
],
grant_types_supported: [
'authorization_code',
'refresh_token'
],
token_endpoint_auth_methods_supported: [
'client_secret_basic',
'client_secret_post'
],
code_challenge_methods_supported: [
'S256'
],
service_documentation: `${SITE_URL}/docs/api`
}, null, 2);
}
/**
* 生成 Agent Skills Discovery Index
*/
function generateAgentSkillsIndex() {
return JSON.stringify({
$schema: 'https://agentskills.io/schema/v0.2.0',
skills: [
{
name: 'giffgaff-esim',
type: 'action',
description: 'Manage Giffgaff eSIM: OAuth authentication, MFA verification, and eSIM activation via GraphQL',
url: `${SITE_URL}/.well-known/agent-skills/giffgaff-esim`,
sha256: 'placeholder'
},
{
name: 'simyo-esim',
type: 'action',
description: 'Manage Simyo eSIM: login, device swap, and eSIM activation',
url: `${SITE_URL}/.well-known/agent-skills/simyo-esim`,
sha256: 'placeholder'
}
]
}, null, 2);
}
/**
* 确保目录存在
*/
async function ensureDir(dir) {
await fs.promises.mkdir(dir, { recursive: true });
}
/**
* 写入文件(带日志)
*/
async function writeFile(filePath, content) {
const relativePath = path.relative(distDir, filePath);
await ensureDir(path.dirname(filePath));
await fs.promises.writeFile(filePath, content, 'utf-8');
BuildLogger.log(`${relativePath}`);
}
/**
* 主函数
*/
async function main() {
BuildLogger.log('🤖 生成 AI Agent 元数据文件...');
// robots.txt
await writeFile(path.join(distDir, 'robots.txt'), generateRobotsTxt());
// sitemap.xml
await writeFile(path.join(distDir, 'sitemap.xml'), generateSitemap());
// .well-known/api-catalog
await writeFile(
path.join(distDir, '.well-known', 'api-catalog'),
generateApiCatalog()
);
// .well-known/oauth-protected-resource
await writeFile(
path.join(distDir, '.well-known', 'oauth-protected-resource'),
generateOAuthProtectedResource()
);
// .well-known/openid-configuration (OAuth Discovery)
await writeFile(
path.join(distDir, '.well-known', 'openid-configuration'),
generateOAuthDiscovery()
);
// .well-known/mcp/server-card.json
await writeFile(
path.join(distDir, '.well-known', 'mcp', 'server-card.json'),
generateMcpServerCard()
);
// .well-known/agent-skills/index.json
await writeFile(
path.join(distDir, '.well-known', 'agent-skills', 'index.json'),
generateAgentSkillsIndex()
);
BuildLogger.success(' AI Agent 元数据文件生成完成');
}
main().catch(err => {
console.error('生成 Agent 元数据失败:', err);
process.exitCode = 1;
});