UNPKG

you-mcp-server

Version:

MCP server for the You project - connects Claude to your personal data backend

255 lines 8.77 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 yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; /** * Default backend URL */ const DEFAULT_BACKEND_URL = 'https://you-backend-production.up.railway.app/ask'; /** * Parse command line arguments */ function parseArguments() { const argv = yargs(hideBin(process.argv)) .option('access-token', { alias: 't', type: 'string', description: 'Access token for backend authentication', demandOption: true }) .option('backend-url', { alias: 'u', type: 'string', description: 'Backend URL (optional)', default: DEFAULT_BACKEND_URL }) .help() .parseSync(); return { accessToken: argv['access-token'], backendUrl: argv['backend-url'] }; } /** * Make a request to the You backend with the question */ async function askBackend(question, accessToken, backendUrl) { try { const requestData = { question: question }; const response = await axios.post(backendUrl, requestData, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, timeout: 30000 // 30 seconds timeout }); return response.data.answer; } catch (error) { if (axios.isAxiosError(error)) { if (error.response) { // Server responded with an error code throw new Error(`Backend error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`); } else if (error.request) { // Request was sent but no response throw new Error('No response from backend - check network connectivity'); } else { // Error in request configuration throw new Error(`Configuration error: ${error.message}`); } } throw new Error(`Unknown error: ${error}`); } } /** * Add content to the You backend */ async function addToYou(content, title, accessToken, backendUrl) { try { const baseUrl = backendUrl.replace('/ask', ''); const addUrl = `${baseUrl}/documents`; const requestData = { content: content, title: title || 'AI Generated Content', document_type: 'ai_generated', added_by_ai: true }; const response = await axios.post(addUrl, requestData, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, timeout: 30000 // 30 seconds timeout }); return `Content successfully added to your You knowledge base. Document ID: ${response.data.id}`; } catch (error) { if (axios.isAxiosError(error)) { if (error.response) { // Server responded with an error code throw new Error(`Backend error (${error.response.status}): ${error.response.data?.message || error.response.statusText}`); } else if (error.request) { // Request was sent but no response throw new Error('No response from backend - check network connectivity'); } else { // Error in request configuration throw new Error(`Configuration error: ${error.message}`); } } throw new Error(`Unknown error: ${error}`); } } /** * Start the MCP server */ async function startMCPServer() { // Parse CLI arguments const args = parseArguments(); // Create MCP server const server = new Server({ name: 'you-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); /** * Declaration of the available tools * This includes ask-question and add-to-you tools */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'ask-question', description: 'Ask a question to the You backend and get a response based on your personal data', inputSchema: { type: 'object', properties: { question: { type: 'string', description: 'The question to ask the You backend' } }, required: ['question'] } }, { name: 'add-to-you', description: 'Add content to your You knowledge base. This will store and vectorize the content for future retrieval.', inputSchema: { type: 'object', properties: { content: { type: 'string', description: 'The content to add to your You knowledge base' }, title: { type: 'string', description: 'Optional title for the content' } }, required: ['content'] } } ] }; }); /** * Handler for tool calls * Processes Claude's requests and communicates with the backend */ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: toolArgs } = request.params; if (name === 'ask-question') { const { question } = toolArgs; if (!question || typeof question !== 'string') { throw new Error('The "question" parameter is required and must be a string'); } try { // Call to You backend const answer = await askBackend(question, args.accessToken, args.backendUrl); return { content: [ { type: 'text', text: answer } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error while communicating with the backend'}` } ], isError: true }; } } else if (name === 'add-to-you') { const { content, title } = toolArgs; if (!content || typeof content !== 'string') { throw new Error('The "content" parameter is required and must be a string'); } try { // Add content to You backend const result = await addToYou(content, title, args.accessToken, args.backendUrl); return { content: [ { type: 'text', text: result } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error while adding content to the backend'}` } ], isError: true }; } } else { throw new Error(`Unknown tool: ${name}`); } }); // Start stdio transport const transport = new StdioServerTransport(); await server.connect(transport); } /** * Global error handling */ process.on('uncaughtException', (error) => { process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { process.exit(1); }); /** * Main entry point */ startMCPServer().catch((error) => { process.exit(1); }); //# sourceMappingURL=index.js.map