@pluggedin/pluggedin-mcp-proxy
Version:
Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes
988 lines (962 loc) • 44.1 kB
JavaScript
import axios from "axios";
import { getPluggedinMCPApiKey, getPluggedinMCPApiBaseUrl, sanitizeName, isDebugEnabled } from "../utils.js";
import { logMcpActivity, createExecutionTimer } from "../notification-logger.js";
import { debugError } from "../debug-log.js";
import { getApiKeySetupMessage } from "./static-handlers-helpers.js";
import { DiscoverToolsInputSchema, RagQueryInputSchema, SendNotificationInputSchema, ListNotificationsInputSchema, MarkNotificationReadInputSchema, DeleteNotificationInputSchema, CreateDocumentInputSchema, ListDocumentsInputSchema, SearchDocumentsInputSchema, GetDocumentInputSchema, UpdateDocumentInputSchema } from '../schemas/index.js';
import { getMcpServers } from "../fetch-pluggedinmcp.js";
import { setupStaticTool, discoverToolsStaticTool, ragQueryStaticTool, sendNotificationStaticTool, listNotificationsStaticTool, markNotificationReadStaticTool, deleteNotificationStaticTool, createDocumentStaticTool, listDocumentsStaticTool, searchDocumentsStaticTool, getDocumentStaticTool, updateDocumentStaticTool } from '../tools/static-tools.js';
/**
* Handles execution of static tools that are built into the Plugged.in MCP proxy.
* These tools provide core functionality like discovery, RAG queries, notifications, and document management.
*/
export class StaticToolHandlers {
toolToServerMap;
instructionToServerMap;
constructor(toolToServerMap, instructionToServerMap) {
this.toolToServerMap = toolToServerMap;
this.instructionToServerMap = instructionToServerMap;
}
async handleSetup(args) {
const topic = args?.topic || 'getting_started';
let content = '';
switch (topic) {
case 'getting_started':
content = `# Welcome to Plugged.in MCP! 🚀
## What is Plugged.in?
Plugged.in is a unified interface for Model Context Protocol (MCP) servers, allowing you to:
- Connect multiple MCP servers through a single proxy
- Manage AI-generated documents
- Use RAG capabilities for semantic search
- Send notifications and track activities
## Getting Started
1. **Get your API key**: Visit https://plugged.in/settings to create an account and get your API key
2. **Configure your environment**: Set the PLUGGEDIN_API_KEY environment variable
3. **Discover tools**: Run \`pluggedin_discover_tools\` to see available MCP servers
4. **Start using tools**: Access any tool from connected servers
## Available Commands
- \`pluggedin_setup\` - This help system (no API key required)
- \`pluggedin_discover_tools\` - List available MCP servers
- Other tools require an API key - see descriptions for details
For more help, try: \`pluggedin_setup\` with topic: "api_key", "configuration", or "troubleshooting"`;
break;
case 'api_key':
content = `# Setting up your Plugged.in API Key 🔑
## Getting an API Key
1. Visit https://plugged.in
2. Sign up or log in to your account
3. Navigate to Settings → API Keys
4. Create a new API key
5. Copy the key (starts with \`pg_in_\`)
## Configuring the API Key
Set the environment variable before running your MCP client:
### macOS/Linux:
\`\`\`bash
export PLUGGEDIN_API_KEY="pg_in_your_key_here"
export PLUGGEDIN_API_BASE_URL="https://plugged.in" # Optional, defaults to this
\`\`\`
### Windows:
\`\`\`cmd
set PLUGGEDIN_API_KEY=pg_in_your_key_here
set PLUGGEDIN_API_BASE_URL=https://plugged.in
\`\`\`
### In your application:
Add to your \`.env\` file or configuration.
## Verifying Setup
Run \`pluggedin_discover_tools\` - if configured correctly, you'll see your connected MCP servers.`;
break;
case 'configuration':
content = `# Plugged.in Configuration Guide ⚙️
## Environment Variables
- **PLUGGEDIN_API_KEY** (required): Your API key from https://plugged.in/settings
- **PLUGGEDIN_API_BASE_URL** (optional): API endpoint (defaults to https://plugged.in)
- **PLUGGEDIN_DEBUG** (optional): Set to "true" for verbose logging
## MCP Server Configuration
1. Log in to https://plugged.in
2. Navigate to MCP Servers
3. Add your MCP servers with their connection details
4. Servers are automatically available through the proxy
## Docker Configuration
If using Docker, pass environment variables:
\`\`\`bash
docker run -e PLUGGEDIN_API_KEY="your_key" pluggedin-mcp
\`\`\`
## Testing Configuration
- \`pluggedin_discover_tools\` - Lists connected servers
- \`pluggedin_rag_query\` - Tests RAG functionality
- \`pluggedin_list_documents\` - Tests document access`;
break;
case 'troubleshooting':
content = `# Troubleshooting Guide 🔧
## Common Issues
### "API Key not configured"
- Check if PLUGGEDIN_API_KEY environment variable is set
- Verify the key starts with \`pg_in_\`
- Ensure no extra spaces or quotes in the key
### No servers found with discover_tools
- Verify your API key is valid
- Check if you have MCP servers configured at https://plugged.in
- Try with \`force_refresh: true\` parameter
### Connection timeouts
- Check your internet connection
- Verify PLUGGEDIN_API_BASE_URL if using custom endpoint
- Check if behind a firewall or proxy
### Tools not working
- Most tools require an API key (check tool descriptions)
- Ensure your account has appropriate permissions
- Check server logs for detailed error messages
## Debug Mode
Enable debug logging:
\`\`\`bash
export PLUGGEDIN_DEBUG=true
\`\`\`
## Getting Help
- Documentation: https://plugged.in/docs
- Support: support@plugged.in
- GitHub: https://github.com/pluggedin/mcp-proxy
## Platform-Specific Notes
### Claude Desktop
Add to your Claude Desktop config:
\`\`\`json
{
"mcpServers": {
"pluggedin": {
"command": "npx",
"args": ["@pluggedin/mcp-proxy"],
"env": {
"PLUGGEDIN_API_KEY": "pg_in_your_key_here"
}
}
}
}
\`\`\`
### VS Code / Cursor
Set environment variables in your terminal before launching the editor.
### Common Error Codes
- 401: Invalid API key
- 403: Permission denied (check account status)
- 429: Rate limit exceeded
- 500: Server error (try again later)`;
break;
}
return {
content: [{ type: "text", text: content }],
isError: false,
};
}
async handleDiscoverTools(args) {
debugError(`[CallTool Handler] Executing static tool: ${discoverToolsStaticTool.name}`);
const validatedArgs = DiscoverToolsInputSchema.parse(args ?? {});
const timer = createExecutionTimer();
try {
// Log discovery attempt
logMcpActivity({
action: 'tool_call',
serverName: 'Pluggedin Discovery',
serverUuid: 'pluggedin_discovery',
itemName: discoverToolsStaticTool.name,
success: true,
executionTime: 0, // Will update after
}).catch(() => { }); // Ignore notification errors
// Wipe stored servers and instructions maps
Object.keys(this.toolToServerMap).forEach(key => delete this.toolToServerMap[key]);
Object.keys(this.instructionToServerMap).forEach(key => delete this.instructionToServerMap[key]);
const { data, success, error } = await getMcpServers();
if (!success || !data || !Array.isArray(data)) {
const errorMsg = `Could not fetch MCP servers: ${error}. Please ensure your Pluggedin API key and URL are correctly configured.`;
throw new Error(errorMsg);
}
let dataContent = '# Available MCP Servers\n\n';
data.forEach((server) => {
dataContent += `## ${server.name} (${server.uuid})\n`;
// Process and register tools
if (server.capabilities?.tools?.length > 0) {
dataContent += `### Tools (${server.capabilities.tools.length}):\n`;
server.capabilities.tools.forEach((tool) => {
const prefixedName = sanitizeName(`${server.name}_${tool.name}`);
this.toolToServerMap[prefixedName] = { originalName: tool.name, serverUuid: server.uuid };
dataContent += `- **${prefixedName}**: ${tool.description}\n`;
});
dataContent += '\n';
}
// Process and register custom instructions
if (server.customInstructions?.length > 0) {
dataContent += `### Instructions (${server.customInstructions.length}):\n`;
server.customInstructions.forEach((instruction) => {
const name = instruction.name || `instruction_${Math.random().toString(36).substring(7)}`;
this.instructionToServerMap[name] = server.uuid;
dataContent += `- **${name}**: ${instruction.instruction}\n`;
});
dataContent += '\n';
}
});
if (isDebugEnabled()) {
dataContent += '\n## Static Tools\n';
dataContent += '1. **pluggedin_discover_tools** - Triggers discovery of tools for configured MCP servers\n';
dataContent += '2. **pluggedin_rag_query** - Performs a RAG query against documents\n';
dataContent += '3. **pluggedin_send_notification** - Send custom notifications\n';
dataContent += '4. **pluggedin_list_notifications** - List notifications with filters\n';
dataContent += '5. **pluggedin_mark_notification_read** - Mark a notification as read\n';
dataContent += '6. **pluggedin_delete_notification** - Delete a notification\n';
dataContent += '7. **pluggedin_create_document** - Create and save AI-generated documents to the user\'s library\n';
dataContent += '8. **pluggedin_list_documents** - List documents with filtering options\n';
dataContent += '9. **pluggedin_search_documents** - Search documents semantically\n';
dataContent += '10. **pluggedin_get_document** - Retrieve a specific document by ID\n';
dataContent += '11. **pluggedin_update_document** - Update or append to an existing document\n';
}
// Update activity log with success
logMcpActivity({
action: 'tool_call',
serverName: 'Pluggedin Discovery',
serverUuid: 'pluggedin_discovery',
itemName: discoverToolsStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
return {
content: [{ type: "text", text: dataContent }],
isError: false,
};
}
catch (toolError) {
// Log discovery failure
logMcpActivity({
action: 'tool_call',
serverName: 'Pluggedin Discovery',
serverUuid: 'pluggedin_discovery',
itemName: discoverToolsStaticTool.name,
success: false,
errorMessage: toolError instanceof Error ? toolError.message : String(toolError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
throw toolError;
}
}
async handleRagQuery(args) {
debugError(`[CallTool Handler] Executing static tool: ${ragQueryStaticTool.name}`);
const validatedArgs = RagQueryInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_rag_query")
}],
isError: false
};
}
const ragApiUrl = `${baseUrl}/api/rag-query`;
const timer = createExecutionTimer();
try {
const response = await axios.post(ragApiUrl, { query: validatedArgs.query }, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// Log successful RAG query
logMcpActivity({
action: 'tool_call',
serverName: 'Pluggedin RAG',
serverUuid: 'pluggedin_rag',
itemName: ragQueryStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const ragResponse = response.data.response || "No response received from RAG service.";
return {
content: [{ type: "text", text: ragResponse }],
isError: false,
};
}
catch (apiError) {
// Log failed RAG query
logMcpActivity({
action: 'tool_call',
serverName: 'Pluggedin RAG',
serverUuid: 'pluggedin_rag',
itemName: ragQueryStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
let errorMsg = "Failed to perform RAG query";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 404) {
errorMsg = "RAG service not available. Please ensure RAG is enabled in your Pluggedin configuration.";
}
else if (apiError.response?.status === 400) {
errorMsg = "Invalid query provided to RAG service.";
}
else if (apiError.response?.status) {
errorMsg = `RAG service error (${apiError.response.status})`;
}
}
throw new Error(errorMsg);
}
}
async handleSendNotification(args) {
debugError(`[CallTool Handler] Executing static tool: ${sendNotificationStaticTool.name}`);
const validatedArgs = SendNotificationInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_send_notification")
}],
isError: false
};
}
const notificationApiUrl = `${baseUrl}/api/notifications`;
const timer = createExecutionTimer();
try {
const response = await axios.post(notificationApiUrl, validatedArgs, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// Log successful notification send
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: sendNotificationStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const notificationId = response.data.notificationId;
const emailSent = response.data.emailSent || false;
let responseText = `Notification sent successfully! (ID: ${notificationId})`;
if (emailSent) {
responseText += '\nEmail notification was also sent.';
}
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed notification send
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: sendNotificationStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
let errorMsg = "Failed to send notification";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 400) {
errorMsg = "Invalid notification data provided";
}
else if (apiError.response?.status === 413) {
errorMsg = "Notification message too large";
}
else if (apiError.response?.status) {
errorMsg = `Notification service error (${apiError.response.status})`;
}
}
throw new Error(errorMsg);
}
}
async handleListNotifications(args) {
debugError(`[CallTool Handler] Executing static tool: ${listNotificationsStaticTool.name}`);
const validatedArgs = ListNotificationsInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_list_notifications")
}],
isError: false
};
}
// Build query parameters
const queryParams = new URLSearchParams();
queryParams.append('limit', validatedArgs.limit.toString());
queryParams.append('unreadOnly', validatedArgs.unreadOnly.toString());
if (validatedArgs.severity) {
queryParams.append('severity', validatedArgs.severity);
}
const notificationApiUrl = `${baseUrl}/api/notifications?${queryParams.toString()}`;
const timer = createExecutionTimer();
try {
const response = await axios.get(notificationApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
// Log successful notification list
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: listNotificationsStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const notifications = response.data.notifications || [];
let responseText = `Found ${notifications.length} notification(s)\n\n`;
notifications.forEach((notif, index) => {
responseText += `${index + 1}. **${notif.title}**\n`;
responseText += ` ID: ${notif.id}\n`;
responseText += ` Status: ${notif.read_at ? 'Read' : 'Unread'}\n`;
responseText += ` Severity: ${notif.severity}\n`;
responseText += ` Created: ${new Date(notif.created_at).toLocaleString()}\n`;
responseText += ` Message: ${notif.message}\n`;
if (notif.link) {
responseText += ` Link: ${notif.link}\n`;
}
responseText += '\n';
});
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed notification list
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: listNotificationsStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
throw new Error("Failed to list notifications");
}
}
async handleMarkNotificationRead(args) {
debugError(`[CallTool Handler] Executing static tool: ${markNotificationReadStaticTool.name}`);
const validatedArgs = MarkNotificationReadInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_mark_notification_read")
}],
isError: false
};
}
const notificationApiUrl = `${baseUrl}/api/notifications/${validatedArgs.notificationId}/read`;
const timer = createExecutionTimer();
try {
await axios.patch(notificationApiUrl, {}, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
// Log successful mark as read
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: markNotificationReadStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
return {
content: [{ type: "text", text: "Notification marked as read successfully!" }],
isError: false,
};
}
catch (apiError) {
// Log failed mark as read
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: markNotificationReadStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
let errorMsg = "Failed to mark notification as read";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 404) {
errorMsg = "Notification not found";
}
}
throw new Error(errorMsg);
}
}
async handleDeleteNotification(args) {
debugError(`[CallTool Handler] Executing static tool: ${deleteNotificationStaticTool.name}`);
const validatedArgs = DeleteNotificationInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_delete_notification")
}],
isError: false
};
}
const notificationApiUrl = `${baseUrl}/api/notifications/${validatedArgs.notificationId}`;
const timer = createExecutionTimer();
try {
await axios.delete(notificationApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
// Log successful deletion
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: deleteNotificationStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
return {
content: [{ type: "text", text: "Notification deleted successfully!" }],
isError: false,
};
}
catch (apiError) {
// Log failed deletion
logMcpActivity({
action: 'tool_call',
serverName: 'Notification System',
serverUuid: 'pluggedin_notifications',
itemName: deleteNotificationStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
let errorMsg = "Failed to delete notification";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 404) {
errorMsg = "Notification not found";
}
}
throw new Error(errorMsg);
}
}
async handleCreateDocument(args) {
debugError(`[CallTool Handler] Executing static tool: ${createDocumentStaticTool.name}`);
const validatedArgs = CreateDocumentInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_create_document")
}],
isError: false
};
}
const documentApiUrl = `${baseUrl}/api/documents/ai`;
const timer = createExecutionTimer();
try {
const response = await axios.post(documentApiUrl, validatedArgs, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// Log successful creation
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: createDocumentStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const responseText = `Document created successfully!\nID: ${response.data.documentId}\nTitle: ${validatedArgs.title}\nURL: ${response.data.url}`;
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed creation
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: createDocumentStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
// Handle specific error cases
let errorMsg = "Failed to create document";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 400) {
errorMsg = "Invalid document data provided";
}
else if (apiError.response?.status === 413) {
errorMsg = "Document content too large (max 10MB)";
}
else if (apiError.response?.status) {
errorMsg = `Document service error (${apiError.response.status})`;
}
}
throw new Error(errorMsg);
}
}
async handleListDocuments(args) {
debugError(`[CallTool Handler] Executing static tool: ${listDocumentsStaticTool.name}`);
const validatedArgs = ListDocumentsInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_list_documents")
}],
isError: false
};
}
// Build query parameters
const queryParams = new URLSearchParams();
if (validatedArgs.filters?.source)
queryParams.append('source', validatedArgs.filters.source);
if (validatedArgs.filters?.modelName)
queryParams.append('modelName', validatedArgs.filters.modelName);
if (validatedArgs.filters?.modelProvider)
queryParams.append('modelProvider', validatedArgs.filters.modelProvider);
if (validatedArgs.filters?.dateFrom)
queryParams.append('dateFrom', validatedArgs.filters.dateFrom);
if (validatedArgs.filters?.dateTo)
queryParams.append('dateTo', validatedArgs.filters.dateTo);
if (validatedArgs.filters?.tags) {
validatedArgs.filters.tags.forEach(tag => queryParams.append('tags', tag));
}
if (validatedArgs.filters?.category)
queryParams.append('category', validatedArgs.filters.category);
if (validatedArgs.filters?.searchQuery)
queryParams.append('searchQuery', validatedArgs.filters.searchQuery);
queryParams.append('sort', validatedArgs.sort);
queryParams.append('limit', validatedArgs.limit.toString());
queryParams.append('offset', validatedArgs.offset.toString());
const documentApiUrl = `${baseUrl}/api/documents?${queryParams.toString()}`;
const timer = createExecutionTimer();
try {
const response = await axios.get(documentApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
// Log successful list
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: listDocumentsStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const documents = response.data.documents || [];
const total = response.data.total || 0;
let responseText = `Found ${total} document(s) (showing ${documents.length})\n\n`;
documents.forEach((doc, index) => {
responseText += `${index + 1}. **${doc.title}**\n`;
responseText += ` ID: ${doc.id}\n`;
responseText += ` Created: ${new Date(doc.createdAt).toLocaleDateString()}\n`;
responseText += ` Source: ${doc.source}`;
if (doc.source === 'ai_generated' && doc.aiMetadata?.model) {
responseText += ` (${doc.aiMetadata.model.name})`;
}
responseText += `\n`;
if (doc.tags && doc.tags.length > 0) {
responseText += ` Tags: ${doc.tags.join(', ')}\n`;
}
if (doc.description) {
responseText += ` Description: ${doc.description}\n`;
}
responseText += '\n';
});
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed list
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: listDocumentsStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
throw new Error("Failed to list documents");
}
}
async handleSearchDocuments(args) {
debugError(`[CallTool Handler] Executing static tool: ${searchDocumentsStaticTool.name}`);
const validatedArgs = SearchDocumentsInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_search_documents")
}],
isError: false
};
}
const documentApiUrl = `${baseUrl}/api/documents/search`;
const timer = createExecutionTimer();
try {
const response = await axios.post(documentApiUrl, validatedArgs, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// Log successful search
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: searchDocumentsStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const results = response.data.results || [];
let responseText = `Search results for "${validatedArgs.query}" (${results.length} found):\n\n`;
results.forEach((result, index) => {
responseText += `${index + 1}. **${result.title}**\n`;
responseText += ` ID: ${result.id}\n`;
responseText += ` Relevance: ${(result.relevanceScore * 100).toFixed(1)}%\n`;
responseText += ` Snippet: ${result.snippet}\n`;
responseText += ` Source: ${result.source}`;
if (result.source === 'ai_generated' && result.aiMetadata?.model) {
responseText += ` (${result.aiMetadata.model.name})`;
}
responseText += `\n\n`;
});
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed search
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: searchDocumentsStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
throw new Error("Failed to search documents");
}
}
async handleGetDocument(args) {
debugError(`[CallTool Handler] Executing static tool: ${getDocumentStaticTool.name}`);
const validatedArgs = GetDocumentInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_get_document")
}],
isError: false
};
}
const queryParams = new URLSearchParams();
queryParams.append('includeContent', validatedArgs.includeContent.toString());
queryParams.append('includeVersions', validatedArgs.includeVersions.toString());
const documentApiUrl = `${baseUrl}/api/documents/${validatedArgs.documentId}?${queryParams.toString()}`;
const timer = createExecutionTimer();
try {
const response = await axios.get(documentApiUrl, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
// Log successful retrieval
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: getDocumentStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
// Format response
const doc = response.data;
let responseText = `**${doc.title}**\n\n`;
responseText += `ID: ${doc.id}\n`;
responseText += `Created: ${new Date(doc.createdAt).toLocaleDateString()}\n`;
responseText += `Source: ${doc.source}`;
if (doc.source === 'ai_generated' && doc.aiMetadata?.model) {
responseText += ` (${doc.aiMetadata.model.name})`;
}
responseText += `\n`;
responseText += `Version: ${doc.version}\n`;
if (doc.tags && doc.tags.length > 0) {
responseText += `Tags: ${doc.tags.join(', ')}\n`;
}
responseText += `\n`;
if (doc.description) {
responseText += `Description: ${doc.description}\n\n`;
}
if (validatedArgs.includeContent && doc.content) {
responseText += `--- Content ---\n${doc.content}\n`;
}
if (validatedArgs.includeVersions && doc.versions && doc.versions.length > 0) {
responseText += `\n--- Version History ---\n`;
doc.versions.forEach((version) => {
responseText += `v${version.versionNumber} - ${new Date(version.createdAt).toLocaleDateString()}`;
if (version.createdByModel) {
responseText += ` by ${version.createdByModel.name}`;
}
if (version.changeSummary) {
responseText += ` - ${version.changeSummary}`;
}
responseText += `\n`;
});
}
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed retrieval
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: getDocumentStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
// Handle specific error cases
let errorMsg = "Failed to retrieve document";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 404) {
errorMsg = "Document not found or not accessible";
}
}
throw new Error(errorMsg);
}
}
async handleUpdateDocument(args) {
debugError(`[CallTool Handler] Executing static tool: ${updateDocumentStaticTool.name}`);
const validatedArgs = UpdateDocumentInputSchema.parse(args ?? {});
const apiKey = getPluggedinMCPApiKey();
const baseUrl = getPluggedinMCPApiBaseUrl();
if (!apiKey || !baseUrl) {
return {
content: [{
type: "text",
text: getApiKeySetupMessage("pluggedin_update_document")
}],
isError: false
};
}
const documentApiUrl = `${baseUrl}/api/documents/${validatedArgs.documentId}`;
const timer = createExecutionTimer();
try {
const response = await axios.patch(documentApiUrl, validatedArgs, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
// Log successful update
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: updateDocumentStaticTool.name,
success: true,
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
const responseText = `Document updated successfully!\nID: ${validatedArgs.documentId}\nOperation: ${validatedArgs.operation}\nNew version: ${response.data.version}`;
return {
content: [{ type: "text", text: responseText }],
isError: false,
};
}
catch (apiError) {
// Log failed update
logMcpActivity({
action: 'tool_call',
serverName: 'Document System',
serverUuid: 'pluggedin_documents',
itemName: updateDocumentStaticTool.name,
success: false,
errorMessage: apiError instanceof Error ? apiError.message : String(apiError),
executionTime: timer.stop(),
}).catch(() => { }); // Ignore notification errors
// Handle specific error cases
let errorMsg = "Failed to update document";
if (axios.isAxiosError(apiError)) {
if (apiError.response?.status === 404) {
errorMsg = "Document not found or not accessible";
}
else if (apiError.response?.status === 400) {
errorMsg = "Invalid update data provided";
}
else if (apiError.response?.status === 501) {
errorMsg = apiError.response.data?.details || "Document updates are not supported at this time";
}
}
throw new Error(errorMsg);
}
}
// Main handler method
async handleStaticTool(toolName, args) {
switch (toolName) {
case setupStaticTool.name:
return this.handleSetup(args);
case discoverToolsStaticTool.name:
return this.handleDiscoverTools(args);
case ragQueryStaticTool.name:
return this.handleRagQuery(args);
case sendNotificationStaticTool.name:
return this.handleSendNotification(args);
case listNotificationsStaticTool.name:
return this.handleListNotifications(args);
case markNotificationReadStaticTool.name:
return this.handleMarkNotificationRead(args);
case deleteNotificationStaticTool.name:
return this.handleDeleteNotification(args);
case createDocumentStaticTool.name:
return this.handleCreateDocument(args);
case listDocumentsStaticTool.name:
return this.handleListDocuments(args);
case searchDocumentsStaticTool.name:
return this.handleSearchDocuments(args);
case getDocumentStaticTool.name:
return this.handleGetDocument(args);
case updateDocumentStaticTool.name:
return this.handleUpdateDocument(args);
default:
return null; // Not a static tool
}
}
}