server.js•8.33 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import * as cheerio from 'cheerio';
class ProudNetMCPServer {
constructor() {
this.server = new Server(
{
name: 'proudnet-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
}
setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_proudnet_docs',
description: 'Search ProudNet documentation for specific topics',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query for ProudNet documentation',
},
},
required: ['query'],
},
},
{
name: 'get_proudnet_page',
description: 'Get content from a specific ProudNet documentation page',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the documentation page (e.g., /guides/getting-started)',
},
},
required: ['path'],
},
},
{
name: 'list_proudnet_sections',
description: 'List main sections of ProudNet documentation',
inputSchema: {
type: 'object',
properties: {},
},
},
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'search_proudnet_docs':
return await this.searchDocs(args.query);
case 'get_proudnet_page':
return await this.getPage(args.path);
case 'list_proudnet_sections':
return await this.listSections();
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
};
}
});
}
async searchDocs(query) {
try {
const urls = [
'https://docs.proudnet.com/proudnet',
'https://guide.nettention.com',
'https://help.nettention.com'
];
const results = [];
for (const url of urls) {
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Search through all links and headings
$('a, h1, h2, h3, h4').each((_, elem) => {
const text = $(elem).text().toLowerCase();
const href = $(elem).attr('href') || $(elem).find('a').attr('href');
if (text.includes(query.toLowerCase())) {
let fullUrl = null;
if (href) {
if (href.startsWith('http')) {
fullUrl = href;
} else if (href.startsWith('/')) {
const baseUrl = new URL(url);
fullUrl = `${baseUrl.origin}${href}`;
} else {
fullUrl = `${url}/${href}`;
}
}
results.push({
title: $(elem).text().trim(),
url: fullUrl,
source: url,
type: elem.name,
});
}
});
} catch (siteError) {
console.error(`Failed to search ${url}: ${siteError.message}`);
}
}
return {
content: [
{
type: 'text',
text: results.length > 0
? `Found ${results.length} results for "${query}":\n\n${results.map(r =>
`- ${r.title}${r.url ? ` (${r.url})` : ''} [from ${r.source}]`
).join('\n')}`
: `No results found for "${query}"`,
},
],
};
} catch (error) {
throw new Error(`Failed to search docs: ${error.message}`);
}
}
async getPage(path) {
try {
let url;
if (path.startsWith('http')) {
url = path;
} else {
// Default to docs.proudnet.com if no domain is specified
if (path.includes('guide.nettention.com') || path.includes('help.nettention.com')) {
url = path.startsWith('/') ? `https:/${path}` : `https://${path}`;
} else {
url = `https://docs.proudnet.com${path.startsWith('/') ? path : '/' + path}`;
}
}
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Remove script and style elements
$('script, style').remove();
// Extract main content
const content = $('.content, main, article, [role="main"]').first();
const text = content.length > 0
? content.text().trim()
: $('body').text().trim();
// Extract code examples
const codeBlocks = [];
$('pre code, .highlight').each((_, elem) => {
codeBlocks.push($(elem).text().trim());
});
return {
content: [
{
type: 'text',
text: `Content from ${url}:\n\n${text.substring(0, 3000)}${text.length > 3000 ? '...' : ''}${
codeBlocks.length > 0
? '\n\nCode Examples:\n' + codeBlocks.slice(0, 3).join('\n---\n')
: ''
}`,
},
],
};
} catch (error) {
throw new Error(`Failed to get page: ${error.message}`);
}
}
async listSections() {
try {
const urls = [
'https://docs.proudnet.com/proudnet',
'https://guide.nettention.com',
'https://help.nettention.com'
];
const allSections = [];
for (const url of urls) {
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
const sections = [];
// Find navigation or main sections
$('.nav-link, .sidebar a, nav a, [class*="menu"] a').each((_, elem) => {
const text = $(elem).text().trim();
const href = $(elem).attr('href');
if (text && href && !sections.find(s => s.title === text)) {
let fullPath = href;
if (!href.startsWith('http')) {
if (href.startsWith('/')) {
const baseUrl = new URL(url);
fullPath = `${baseUrl.origin}${href}`;
} else {
fullPath = `${url}/${href}`;
}
}
sections.push({
title: text,
path: fullPath,
source: url,
});
}
});
allSections.push({
site: url,
sections: sections.slice(0, 10)
});
} catch (siteError) {
console.error(`Failed to get sections from ${url}: ${siteError.message}`);
}
}
const formattedSections = allSections.map(site =>
`From ${site.site}:\n${site.sections.map(s =>
`- ${s.title}: ${s.path}`
).join('\n')}`
).join('\n\n');
return {
content: [
{
type: 'text',
text: `Documentation Sections:\n\n${formattedSections}`,
},
],
};
} catch (error) {
throw new Error(`Failed to list sections: ${error.message}`);
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('ProudNet MCP Server running on stdio');
}
}
const server = new ProudNetMCPServer();
server.run().catch(console.error);