Skip to content

Commit

Permalink
gmail is working for send_email, must run auth command to refresh token
Browse files Browse the repository at this point in the history
  • Loading branch information
patruff committed Dec 15, 2024
1 parent 550b997 commit 0eb67b6
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 105 deletions.
8 changes: 7 additions & 1 deletion bridge_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"env": {
"REPLICATE_API_TOKEN": "${REPLICATE_API_TOKEN}"
}
},
"gmail-drive": {
"command": "C:\\Program Files\\nodejs\\node.exe",
"args": [
"C:\\Users\\patru\\AppData\\Roaming\\npm\\node_modules\\@patruff\\server-gmail-drive\\dist\\index.js"
]
}
},
"llm": {
Expand All @@ -48,5 +54,5 @@
"temperature": 0.7,
"maxTokens": 1000
},
"systemPrompt": "You are a helpful assistant that can use various tools to help answer questions. You have access to multiple MCPs including filesystem operations, GitHub interactions, Brave search, and Flux for image generation. When using these tools, make sure to respect their specific requirements and limitations."
"systemPrompt": "You are a helpful assistant that can use various tools to help answer questions. You have access to multiple MCPs including filesystem operations, GitHub interactions, Brave search, Gmail, Google Drive, and Flux for image generation. When using these tools, make sure to respect their specific requirements and limitations."
}
20 changes: 20 additions & 0 deletions server-gmail-drive/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@patruff/server-gmail-drive",
"version": "0.1.0",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "ts-node src/index.ts",
"auth": "ts-node src/index.ts auth"
},
"dependencies": {
"@google-cloud/local-auth": "^2.1.0",
"@modelcontextprotocol/sdk": "^0.1.0",
"googleapis": "^105.0.0"
},
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
}
}
147 changes: 101 additions & 46 deletions src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { MCPClient } from './mcp-client';
import { LLMClient } from './llm-client';
import { logger } from './logger';
import { BridgeConfig, Tool } from './types';
import path from 'path';
import fs from 'fs/promises';

export interface MCPLLMBridge {
tools: any[];
Expand All @@ -20,29 +18,101 @@ export class MCPLLMBridge implements MCPLLMBridge {
public llmClient: LLMClient;
private toolNameMapping: Map<string, string> = new Map();
public tools: any[] = [];
private baseAllowedPath: string;

constructor(private bridgeConfig: BridgeConfig) {
this.config = bridgeConfig;
this.mcpClient = new MCPClient(bridgeConfig.mcpServer);
this.llmClient = new LLMClient(bridgeConfig.llmConfig);
this.baseAllowedPath = bridgeConfig.mcpServer.allowedDirectory || 'C:/Users/patru/anthropicFun';
}

private getToolInstructions(): string {
return `You are a helpful assistant that can create files in the ${this.baseAllowedPath} directory.
To create a file, respond with ONLY a JSON object in this format:
private detectToolFromPrompt(prompt: string): string | null {
const emailKeywords = ['email', 'send', 'mail', 'message'];
const driveKeywords = ['drive', 'folder', 'file', 'upload'];
const searchKeywords = ['find', 'search', 'locate', 'list'];

prompt = prompt.toLowerCase();

if (emailKeywords.some(keyword => prompt.includes(keyword)) &&
prompt.includes('@')) {
return 'send_email';
}

if (searchKeywords.some(keyword => prompt.includes(keyword))) {
if (emailKeywords.some(keyword => prompt.includes(keyword))) {
return 'search_email';
}
if (driveKeywords.some(keyword => prompt.includes(keyword))) {
return 'search_drive';
}
}

if (driveKeywords.some(keyword => prompt.includes(keyword))) {
if (prompt.includes('folder') || prompt.includes('directory')) {
return 'create_folder';
}
if (prompt.includes('upload') || prompt.includes('create file')) {
return 'upload_file';
}
return 'search_drive';
}

return null;
}

private getToolInstructions(detectedTool: string): string {
const baseInstructions = `You are a helpful assistant that can interact with Gmail and Google Drive.
Always respond with ONLY a JSON object in the correct format for the tool being used.
Do not add any other text outside the JSON.
`;

const toolFormats = {
search_email: `When searching emails, format:
{
"tool_name": "write_file",
"tool_args": {
"path": "filename.txt",
"content": "file content here"
},
"thoughts": "Creating the requested file"
}
"name": "search_email",
"arguments": {
"query": "search query"
}
}`,

search_drive: `When searching Drive files, format:
{
"name": "search_drive",
"arguments": {
"query": "search query"
}
}`,

create_folder: `When creating folders in Drive, format:
{
"name": "create_folder",
"arguments": {
"name": "folder name"
}
}`,

send_email: `When sending emails, format:
{
"name": "send_email",
"arguments": {
"to": "[email protected]",
"subject": "email subject",
"body": "email content"
}
}`,

upload_file: `When uploading files to Drive, format:
{
"name": "upload_file",
"arguments": {
"name": "filename",
"content": "file content",
"mimeType": "text/plain"
}
}`
};

The path should be just the filename - I will automatically put it in the correct directory.
Do not add any other text outside the JSON.`;
return baseInstructions + toolFormats[detectedTool as keyof typeof toolFormats];
}

async initialize(): Promise<boolean> {
Expand All @@ -54,11 +124,7 @@ Do not add any other text outside the JSON.`;
const mcpTools = await this.mcpClient.getAvailableTools();
logger.info(`Received ${mcpTools.length} tools from MCP server`);

// Filter to only include write_file tool
const filteredTools = mcpTools.filter(tool => tool.name === 'write_file');
logger.info(`Filtered to ${filteredTools.length} filesystem tools`);

const convertedTools = this.convertMCPToolsToOpenAIFormat(filteredTools);
const convertedTools = this.convertMCPToolsToOpenAIFormat(mcpTools);
this.tools = convertedTools;
this.llmClient.tools = convertedTools;

Expand All @@ -74,29 +140,26 @@ Do not add any other text outside the JSON.`;
type: 'function',
function: {
name: tool.name,
description: `Create a file in ${this.baseAllowedPath}`,
description: tool.description || `Use the ${tool.name} tool`,
parameters: {
type: "object",
properties: {
path: {
type: "string",
description: "Just the filename (e.g. test.txt)"
},
content: {
type: "string",
description: "Content to write to the file"
}
},
required: ["path", "content"]
properties: tool.inputSchema?.properties || {},
required: tool.inputSchema?.required || []
}
}
}));
}

async processMessage(message: string): Promise<string> {
try {
// Override system prompt with minimal instructions
this.llmClient.systemPrompt = this.getToolInstructions();
const detectedTool = this.detectToolFromPrompt(message);
logger.info(`Detected tool: ${detectedTool}`);

if (detectedTool) {
this.llmClient.systemPrompt = this.getToolInstructions(detectedTool);
} else {
this.llmClient.systemPrompt = this.config.systemPrompt || null;
}

logger.info('Sending message to LLM...');
let response = await this.llmClient.invokeWithPrompt(message);
Expand All @@ -105,13 +168,6 @@ Do not add any other text outside the JSON.`;
while (response.isToolCall && response.toolCalls?.length) {
logger.info(`Processing ${response.toolCalls.length} tool calls`);

for (const call of response.toolCalls) {
const args = JSON.parse(call.function.arguments);
// Ensure path is relative to allowed directory
args.path = path.join(this.baseAllowedPath, args.path);
call.function.arguments = JSON.stringify(args);
}

const toolResponses = await this.handleToolCalls(response.toolCalls);
logger.info('Tool calls completed, sending results back to LLM');
response = await this.llmClient.invoke(toolResponses);
Expand Down Expand Up @@ -167,10 +223,9 @@ Do not add any other text outside the JSON.`;
}

async setTools(tools: any[]): Promise<void> {
// Only accept write_file tool
this.tools = tools.filter(t => t.function.name === 'write_file');
this.llmClient.tools = this.tools;
logger.debug('Updated tools:', this.tools.map(t => t.function.name));
this.tools = tools;
this.llmClient.tools = tools;
logger.debug('Updated tools:', tools.map(t => t.function.name));
}

async close(): Promise<void> {
Expand Down
Loading

0 comments on commit 0eb67b6

Please sign in to comment.