you-mcp-server
Version:
MCP server for the You project - connects Claude to your personal data backend
255 lines • 8.77 kB
JavaScript
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