@daffodiltech/mcp-docs
Version:
MCP server exposing Daffodil's knowledge base tools (read_advisor_faqs, read_nonprofit_faqs, etc.)
235 lines (228 loc) • 8.08 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
// ../../packages/common/src/polyfills/index.ts
async function getFetch() {
if (typeof globalThis !== "undefined" && globalThis.fetch) {
return globalThis.fetch;
}
const { default: fetch } = await import('node-fetch');
return fetch;
}
// ../../packages/common/src/ai/tools/web-page-reader.ts
function createWebPageReaderTool(config) {
const { url, name, description, title: configuredTitle, maxContentLength = 1e5 } = config;
return {
name: name || "read_web_page",
description: description || `Read and extract content from ${url}`,
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "Optional query to focus on specific content within the page"
}
},
required: []
},
execute: async (args) => {
try {
const { query } = args;
const fetchFn = await getFetch();
const response = await fetchFn(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const html = await response.text();
if (!html || html.length === 0) {
throw new Error("Empty response received from server");
}
let pageTitle = configuredTitle;
if (!configuredTitle) {
const titlePatterns = [
/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["']/i,
/<title[^>]*>([^<]+)<\/title>/i,
/<h1[^>]*>([^<]+)<\/h1>/i
];
for (const pattern of titlePatterns) {
const match = html.match(pattern);
if (match && match[1] && match[1].trim()) {
pageTitle = match[1].trim();
break;
}
}
if (!pageTitle) {
try {
const urlObj = new URL(url);
pageTitle = urlObj.hostname.replace("www.", "");
} catch {
pageTitle = "Webpage";
}
}
}
let cleanedContent = html;
const wasTruncated = cleanedContent.length > maxContentLength;
if (wasTruncated) {
console.warn(`[WebPageReader] Content truncated for ${url}. Original: ${cleanedContent.length} chars, Limit: ${maxContentLength} chars, Truncated: ${cleanedContent.length - maxContentLength} chars`);
cleanedContent = cleanedContent.substring(0, maxContentLength) + "...";
}
if (!cleanedContent || cleanedContent.length < 500) {
return `Unable to extract meaningful content from ${url}. The page may require JavaScript or have access restrictions.`;
}
let result = `Content from ${url}:
Title: ${pageTitle}
${cleanedContent}`;
if (query) {
result += `
Note: User was looking for information about: ${query}`;
}
return result;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
if (errorMessage.includes("fetch") || errorMessage.includes("network")) {
return `Unable to access ${url}. The page may be temporarily unavailable or require authentication.`;
} else if (errorMessage.includes("timeout")) {
return `Timeout while trying to fetch content from ${url}. Please try again later.`;
} else {
return `Error reading content from ${url}: ${errorMessage}. The page may have access restrictions or require JavaScript.`;
}
}
}
};
}
// ../../packages/common/src/ai/tools/web-page-reader-defaults.ts
var daffodilKnowledgeBaseConfigs = [
// Public Daffodil documentation
{
url: "https://www.getdaffodil.com/terms",
name: "read_daffodil_terms",
description: "Read Daffodil terms and conditions"
},
{
url: "https://www.getdaffodil.com/privacy",
name: "read_daffodil_privacy",
description: "Read Daffodil privacy policy"
},
{
url: "https://www.getdaffodil.com/member-agreement",
name: "read_member_agreement",
description: "Read Daffodil member agreement and DAF policies"
},
// Notion.site documentation (with explicit titles for better extraction)
{
url: "https://daffodil-charitable.notion.site/daffodil-charitable-granting-and-fees",
name: "read_granting_fees",
description: "Read information about Daffodil granting policies and fee structure",
title: "Daffodil Charitable Granting & Fees"
},
{
url: "https://daffodil-charitable.notion.site/faqs-for-advisors",
name: "read_advisor_faqs",
description: "Read frequently asked questions for financial advisors",
title: "FAQs for Advisors"
},
{
url: "https://daffodil-charitable.notion.site/faqs-for-nonprofits",
name: "read_nonprofit_faqs",
description: "Read frequently asked questions for nonprofit organizations",
title: "FAQs for Nonprofits"
},
{
url: "https://daffodil-charitable.notion.site/faqs-for-giving",
name: "read_giving_faqs",
description: "Read frequently asked questions about charitable giving and donor-advised funds",
title: "FAQs for Giving"
},
{
url: "https://daffodil-charitable.notion.site/daffodil-impact-enablement-model",
name: "read_impact_model",
description: "Read about Daffodil impact enablement model and methodology",
title: "Daffodil Impact Enablement Model"
}
];
// src/index.ts
var packageVersion = "1.0.3";
var server = new Server(
{
name: "@daffodiltech/mcp-docs",
version: packageVersion
},
{
capabilities: {
tools: {}
}
}
);
var tools = daffodilKnowledgeBaseConfigs.map((config) => createWebPageReaderTool(config));
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: tools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.parameters
}))
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = tools.find((t) => t.name === name);
if (!tool) {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
try {
const result = await tool.execute(args || {});
return {
content: [
{
type: "text",
text: typeof result === "string" ? result : JSON.stringify(result)
}
]
};
} catch (error) {
console.error("Tool execution error:", error);
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
);
}
});
async function main() {
try {
console.error("Starting Daffodil MCP server...");
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Daffodil MCP server running on stdio");
process.on("SIGINT", () => {
console.error("Received SIGINT, shutting down gracefully...");
process.exit(0);
});
process.on("SIGTERM", () => {
console.error("Received SIGTERM, shutting down gracefully...");
process.exit(0);
});
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
process.exit(1);
});
} catch (error) {
console.error("Failed to start MCP server:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});