-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gmail is working for send_email, must run auth command to refresh token
- Loading branch information
Showing
7 changed files
with
347 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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[]; | ||
|
@@ -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> { | ||
|
@@ -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; | ||
|
||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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> { | ||
|
Oops, something went wrong.