Skip to main content
Glama

Payman AI Documentation MCP Server

index.ts17 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import fetch from "node-fetch"; const server = new McpServer({ name: "payman-mcp", version: "1.0.0", }); function log(message: string, ...args: any[]): void { console.error(`[${new Date().toISOString()}] ${message}`, ...args); } const documentCache = new Map<string, { content: string; timestamp: number }>(); const CACHE_TTL = 3600000; async function fetchDocMarkdown(path: string): Promise<string> { const now = Date.now(); const cachedDoc = documentCache.get(path); if (cachedDoc && now - cachedDoc.timestamp < CACHE_TTL) { log(`Using cached content for: ${path}`); return cachedDoc.content; } try { const url = `https://docs.paymanai.com${path}.md`; log(`Fetching: ${url}`); const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch: ${response.status}`); } const content = await response.text(); documentCache.set(path, { content, timestamp: now }); return content; } catch (error) { log(`Error fetching documentation: ${error}`); return `Documentation content not available for path: ${path}.md\nError: ${ error instanceof Error ? error.message : String(error) }`; } } const docTopics = [ "quickstart", "playground", "setup-and-installation", "create-payees", "send-payments", "create-payee", "search-payees", "check-balances", "bill-payment-agent", "api-reference", "api-keys", "error-handling", ] as const; const pathMap: Record<string, string> = { quickstart: "/overview/quickstart", playground: "/overview/playground", "setup-and-installation": "/sdks/setup-and-installation", "create-payees": "/sdks/create-payees", "send-payments": "/sdks/send-payments", "create-payee": "/sdks/create-payee", "search-payees": "/sdks/search-payees", "check-balances": "/sdks/check-balances", "bill-payment-agent": "/guides/bill-payment-agent", "api-reference": "/api-reference/introduction", "api-keys": "/api-reference/get-api-key", "error-handling": "/api-reference/error-handling", }; const topicMetadata: Record< string, { title: string; relatedTopics: string[]; } > = { quickstart: { title: "Quickstart Guide", relatedTopics: ["setup-and-installation", "api-keys"], }, playground: { title: "API Playground", relatedTopics: ["api-reference", "api-keys"], }, "setup-and-installation": { title: "Setup and Installation", relatedTopics: ["api-keys", "quickstart"], }, "create-payees": { title: "Create Payees", relatedTopics: ["create-payee", "search-payees"], }, "send-payments": { title: "Send Payments", relatedTopics: ["check-balances", "create-payees"], }, "create-payee": { title: "Create Payee", relatedTopics: ["create-payees", "search-payees"], }, "search-payees": { title: "Search Payees", relatedTopics: ["create-payee", "create-payees"], }, "check-balances": { title: "Check Balances", relatedTopics: ["send-payments"], }, "bill-payment-agent": { title: "Bill Payment Agent", relatedTopics: ["send-payments"], }, "api-reference": { title: "API Reference", relatedTopics: ["error-handling", "api-keys"], }, "api-keys": { title: "API Keys", relatedTopics: ["api-reference", "setup-and-installation"], }, "error-handling": { title: "Error Handling", relatedTopics: ["api-reference"], }, }; server.tool( "get-documentation", "Get PaymanAI documentation on a specific topic", { topic: z .enum(docTopics) .describe("The documentation topic to retrieve"), }, async ({ topic }) => { const path = pathMap[topic]; log(`Getting doc for topic: ${topic}, path: ${path}`); const docContent = await fetchDocMarkdown(path); const relatedTopics = topicMetadata[topic].relatedTopics; const relatedTopicsText = relatedTopics.length > 0 ? `\n\n## Related Topics\n\n${relatedTopics .map( (t) => `- ${topicMetadata[t].title} (use get-documentation with topic "${t}")` ) .join("\n")}` : ""; return { content: [ { type: "text", text: docContent + relatedTopicsText, }, ], }; } ); server.tool( "search-documentation", "Search through PaymanAI documentation", { query: z.string().describe("Search term"), }, async ({ query }) => { log(`Searching for: "${query}"`); const docsToSearch = Object.entries(pathMap).map(([topic, path]) => ({ topic, path, title: topicMetadata[topic].title, })); const searchPromises = docsToSearch.map(async (doc) => { const content = await fetchDocMarkdown(doc.path); const queryLower = query.toLowerCase(); if (content.toLowerCase().includes(queryLower)) { const sections = content.split(/^#+\s+/m); let bestSection = ""; let bestContext = ""; for (const section of sections) { if (section.toLowerCase().includes(queryLower)) { const lines = section.split("\n"); const sectionTitle = lines[0] || ""; const sectionContent = lines.slice(1).join("\n"); const index = sectionContent .toLowerCase() .indexOf(queryLower); const start = Math.max(0, index - 150); const end = Math.min( sectionContent.length, index + queryLower.length + 150 ); const excerpt = (start > 0 ? "..." : "") + sectionContent .substring(start, end) .replace(/\n+/g, " ") + (end < sectionContent.length ? "..." : ""); bestSection = sectionTitle; bestContext = excerpt; break; } } return { title: doc.title, topic: doc.topic, section: bestSection, excerpt: bestContext || content.substring(0, 200) + "...", }; } return null; }); const searchResults = (await Promise.all(searchPromises)).filter( Boolean ); if (searchResults.length === 0) { const possibleTopics = Object.entries(topicMetadata) .filter( ([topic, meta]) => topic.includes(query.toLowerCase()) || meta.title.toLowerCase().includes(query.toLowerCase()) ) .map(([topic, meta]) => ({ topic, title: meta.title, })); let suggestionText = ""; if (possibleTopics.length > 0) { suggestionText = "\n\nYou might be interested in these topics:\n\n" + possibleTopics .map( (s) => `- ${s.title} (use get-documentation with topic "${s.topic}")` ) .join("\n"); } return { content: [ { type: "text", text: `No results found for "${query}". Try a different search term.${suggestionText}`, }, ], }; } const formattedResults = searchResults .map((r) => { if (!r) return ""; const sectionHeading = r.section ? `### ${r.section}\n\n` : ""; return `## ${r.title}\n\n${sectionHeading}${r.excerpt}\n\n*For full documentation, use the get-documentation tool with topic "${r.topic}".*`; }) .join("\n\n---\n\n"); return { content: [ { type: "text", text: `# Search Results for "${query}"\n\n${formattedResults}`, }, ], }; } ); server.tool( "get-code-examples", "Get Node.js or Python code examples for PaymanAI integration", { feature: z .string() .describe( "The feature or functionality you need code examples for" ), language: z .enum(["nodejs", "python"]) .default("nodejs") .describe("Programming language (nodejs or python)"), }, async ({ feature, language }) => { log(`Getting ${language} code example for: "${feature}"`); const potentialTopics = Object.entries(pathMap) .filter( ([topic]) => topic.toLowerCase().includes(feature.toLowerCase()) || topicMetadata[topic].title .toLowerCase() .includes(feature.toLowerCase()) ) .map(([topic]) => topic); const topicsToSearch = potentialTopics.length > 0 ? potentialTopics : Object.keys(pathMap); const examplesPromises = topicsToSearch.map(async (topic) => { const path = pathMap[topic]; const content = await fetchDocMarkdown(path); const codeBlockRegex = language === "nodejs" ? /```(?:javascript|typescript|js|nodejs|node)([\s\S]*?)```/g : /```(?:python|py)([\s\S]*?)```/g; const matches = [...content.matchAll(codeBlockRegex)]; const relevantBlocks = matches .map((match) => match[1].trim()) .filter( (code) => code.toLowerCase().includes(feature.toLowerCase()) || content .substring( Math.max(0, content.indexOf(code) - 300), content.indexOf(code) ) .toLowerCase() .includes(feature.toLowerCase()) ); if (relevantBlocks.length === 0) return null; return { topic, title: topicMetadata[topic].title, examples: relevantBlocks, }; }); const allExamples = (await Promise.all(examplesPromises)).filter( Boolean ); if (allExamples.length === 0) { return { content: [ { type: "text", text: `No ${language} code examples found for "${feature}". Try searching for a different feature or check the full documentation using get-documentation.`, }, ], }; } let responseText = `# ${language.toUpperCase()} Code Examples for "${feature}"\n\n`; allExamples.forEach((topicExamples) => { if (!topicExamples) return; responseText += `## From ${topicExamples.title}\n\n`; topicExamples.examples.forEach((code, index) => { responseText += `### Example ${index + 1}\n\n`; responseText += `\`\`\`${ language === "nodejs" ? "javascript" : "python" }\n${code}\n\`\`\`\n\n`; }); responseText += `*For more context, check the full documentation: use get-documentation with topic "${topicExamples.topic}".*\n\n---\n\n`; }); return { content: [ { type: "text", text: responseText, }, ], }; } ); server.tool( "solve-problem", "Get help with common PaymanAI integration issues", { problem: z.string().describe("Describe the issue you're experiencing"), sdk: z .enum(["nodejs", "python"]) .optional() .describe("Which SDK you're using (nodejs or python)"), }, async ({ problem, sdk }) => { log(`Solving problem: "${problem}" for SDK: ${sdk || "any"}`); const problemCategories = [ { category: "Authentication", keywords: [ "api key", "auth", "authentication", "unauthorized", "401", ], topics: ["api-keys", "error-handling"], }, { category: "Payments", keywords: [ "payment", "send payment", "transaction", "failed payment", ], topics: ["send-payments", "error-handling"], }, { category: "Payees", keywords: ["payee", "recipient", "create payee", "add payee"], topics: ["create-payee", "create-payees"], }, { category: "Setup", keywords: [ "install", "setup", "configuration", "sdk", "initialize", ], topics: ["setup-and-installation", "quickstart"], }, { category: "Error Handling", keywords: ["error", "exception", "crash", "failed"], topics: ["error-handling", "api-reference"], }, ]; const problemLower = problem.toLowerCase(); const matchingCategories = problemCategories.filter((cat) => cat.keywords.some((keyword) => problemLower.includes(keyword)) ); const topicsToConsult = matchingCategories.length > 0 ? matchingCategories.flatMap((cat) => cat.topics) : ["error-handling", "api-reference", "quickstart"]; const uniqueTopics = [...new Set(topicsToConsult)]; let solutionText = `# Solution for: "${problem}"\n\n`; if (matchingCategories.length > 0) { solutionText += `This appears to be a ${matchingCategories .map((c) => c.category) .join("/")} related issue.\n\n`; } if (sdk) { solutionText += `## ${sdk.toUpperCase()} SDK Specific Guidance\n\n`; solutionText += `When working with the ${sdk} SDK, make sure to:\n\n`; if (sdk === "nodejs") { solutionText += `- Check you're using the latest version: \`npm view @paymanai/sdk version\`\n`; solutionText += `- Update if needed: \`npm install @paymanai/sdk@latest\`\n`; solutionText += `- Verify your environment variables are set correctly\n`; solutionText += `- Use try/catch blocks to properly handle API errors\n\n`; } else { solutionText += `- Check you're using the latest version: \`pip show paymanai\`\n`; solutionText += `- Update if needed: \`pip install --upgrade paymanai\`\n`; solutionText += `- Handle exceptions properly with try/except blocks\n`; solutionText += `- Ensure your Python version is compatible (3.7+)\n\n`; } } solutionText += `## Troubleshooting Steps\n\n`; solutionText += `1. **Check your API credentials** - Verify your API key is valid and correctly formatted\n`; solutionText += `2. **Look for specific error codes** - Error codes provide detailed information about what went wrong\n`; solutionText += `3. **Check your request format** - Ensure all required parameters are included and properly formatted\n`; solutionText += `4. **Review rate limits** - Make sure you're not exceeding API rate limits\n\n`; solutionText += `## Relevant Documentation\n\n`; uniqueTopics.forEach((topic) => { solutionText += `- ${topicMetadata[topic].title}: use get-documentation with topic "${topic}"\n`; }); return { content: [ { type: "text", text: solutionText, }, ], }; } ); server.tool( "get-sdk-help", "Get help with Node.js or Python SDK usage", { sdk: z .enum(["nodejs", "python"]) .describe("Which SDK you need help with"), feature: z .string() .describe("Which SDK feature or class you need help with"), }, async ({ sdk, feature }) => { log(`Getting help for ${sdk} SDK feature: ${feature}`); const sdkTopics = [ "setup-and-installation", "create-payees", "send-payments", "create-payee", "search-payees", "check-balances", ]; const helpPromises = sdkTopics.map(async (topic) => { const content = await fetchDocMarkdown(pathMap[topic]); const sdkIdentifier = sdk === "nodejs" ? ["node", "nodejs", "javascript", "js"] : ["python", "py"]; const relevance = sdkIdentifier.some( (id) => content.toLowerCase().includes(id) && content.toLowerCase().includes(feature.toLowerCase()) && Math.abs( content.toLowerCase().indexOf(id) - content.toLowerCase().indexOf(feature.toLowerCase()) ) < 500 ); if (!relevance) return null; const lines = content.split("\n"); const featureIndex = lines.findIndex((line) => line.toLowerCase().includes(feature.toLowerCase()) ); if (featureIndex === -1) return null; let headingIndex = featureIndex; while (headingIndex > 0 && !lines[headingIndex].startsWith("#")) { headingIndex--; } const sectionHeading = lines[headingIndex]; const sectionContent = lines .slice(headingIndex, featureIndex + 20) .join("\n"); return { topic, heading: sectionHeading, content: sectionContent, relevance: (content.toLowerCase().includes(sdk) ? 2 : 1) + (sectionContent .toLowerCase() .includes(feature.toLowerCase()) ? 3 : 0), }; }); const helpResults = (await Promise.all(helpPromises)).filter(Boolean); helpResults.sort((a, b) => (b?.relevance || 0) - (a?.relevance || 0)); if (helpResults.length === 0) { return { content: [ { type: "text", text: `No specific help found for ${feature} in the ${sdk} SDK. Try checking the full SDK documentation with get-documentation or using more generic terms.`, }, ], }; } let helpText = `# ${sdk.toUpperCase()} SDK Help: ${feature}\n\n`; helpResults.forEach((result) => { if (!result) return; helpText += `## From ${topicMetadata[result.topic].title}\n\n`; helpText += `${result.content}\n\n`; helpText += `*For complete documentation, use get-documentation with topic "${result.topic}"*\n\n---\n\n`; }); helpText += `## Additional Resources\n\n`; helpText += `- Setup and Installation: use get-documentation with topic "setup-and-installation"\n`; helpText += `- Error Handling: use get-documentation with topic "error-handling"\n`; helpText += `- For code examples, try the get-code-examples tool with feature="${feature}" and language="${sdk}"\n`; return { content: [ { type: "text", text: helpText, }, ], }; } ); server.resource("payman-overview", "Overview of PaymanAI", async (request) => { const content = await fetchDocMarkdown("/overview/quickstart"); return { contents: [ { text: content, uri: "overview.md", mimeType: "text/markdown", }, ], }; }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); log("PaymanAI MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Vanshika-Rana/payman-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server