196 lines
6.7 KiB
Go
196 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/maximhq/bifrost/core/schemas"
|
|
)
|
|
|
|
// Plugin configuration
|
|
type PluginConfig struct {
|
|
BlockedTools []string `json:"blocked_tools"` // List of tool names to block
|
|
EnableAudit bool `json:"enable_audit"` // Enable audit trail logging
|
|
EnableLogging bool `json:"enable_logging"` // Enable detailed logging
|
|
TransformErrors bool `json:"transform_errors"` // Transform 404 errors to friendly messages
|
|
CustomErrorMessage string `json:"custom_error_message"` // Custom error message for blocked tools
|
|
}
|
|
|
|
var (
|
|
// Default configuration
|
|
pluginConfig = &PluginConfig{
|
|
BlockedTools: []string{"dangerous_tool"},
|
|
EnableAudit: true,
|
|
EnableLogging: true,
|
|
TransformErrors: true,
|
|
CustomErrorMessage: "Tool is not allowed by security policy",
|
|
}
|
|
)
|
|
|
|
// Init is called when the plugin is loaded (optional)
|
|
func Init(config any) error {
|
|
fmt.Println("[MCP-Only Plugin] Init called")
|
|
|
|
// Parse configuration
|
|
if configMap, ok := config.(map[string]interface{}); ok {
|
|
if blockedTools, ok := configMap["blocked_tools"].([]interface{}); ok {
|
|
pluginConfig.BlockedTools = []string{}
|
|
for _, tool := range blockedTools {
|
|
if toolName, ok := tool.(string); ok {
|
|
pluginConfig.BlockedTools = append(pluginConfig.BlockedTools, toolName)
|
|
}
|
|
}
|
|
fmt.Printf("[MCP-Only Plugin] Blocked tools: %v\n", pluginConfig.BlockedTools)
|
|
}
|
|
|
|
if enableAudit, ok := configMap["enable_audit"].(bool); ok {
|
|
pluginConfig.EnableAudit = enableAudit
|
|
fmt.Printf("[MCP-Only Plugin] Audit trail: %v\n", pluginConfig.EnableAudit)
|
|
}
|
|
|
|
if enableLogging, ok := configMap["enable_logging"].(bool); ok {
|
|
pluginConfig.EnableLogging = enableLogging
|
|
fmt.Printf("[MCP-Only Plugin] Logging enabled: %v\n", pluginConfig.EnableLogging)
|
|
}
|
|
|
|
if transformErrors, ok := configMap["transform_errors"].(bool); ok {
|
|
pluginConfig.TransformErrors = transformErrors
|
|
fmt.Printf("[MCP-Only Plugin] Error transformation: %v\n", pluginConfig.TransformErrors)
|
|
}
|
|
|
|
if customMsg, ok := configMap["custom_error_message"].(string); ok {
|
|
pluginConfig.CustomErrorMessage = customMsg
|
|
}
|
|
}
|
|
|
|
fmt.Printf("[MCP-Only Plugin] Configuration loaded: %+v\n", pluginConfig)
|
|
return nil
|
|
}
|
|
|
|
// GetName returns the name of the plugin (required)
|
|
// This is the system identifier - not editable by users
|
|
// Users can set a custom display_name in the config for the UI
|
|
func GetName() string {
|
|
return "mcp-only"
|
|
}
|
|
|
|
// PreMCPHook is called before MCP tool/resource calls are executed
|
|
// This example demonstrates request validation and governance
|
|
func PreMCPHook(ctx *schemas.BifrostContext, req *schemas.BifrostMCPRequest) (*schemas.BifrostMCPRequest, *schemas.MCPPluginShortCircuit, error) {
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Println("[MCP-Only Plugin] PreMCPHook called")
|
|
fmt.Printf("[MCP-Only Plugin] Request type: %v\n", req.RequestType)
|
|
}
|
|
|
|
// Example: Governance - check tool calls (configurable)
|
|
if req.ChatAssistantMessageToolCall != nil {
|
|
toolName := ""
|
|
if req.ChatAssistantMessageToolCall.Function.Name != nil {
|
|
toolName = *req.ChatAssistantMessageToolCall.Function.Name
|
|
}
|
|
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Printf("[MCP-Only Plugin] Tool call: %s\n", toolName)
|
|
}
|
|
|
|
// Check if tool is in blocked list
|
|
for _, blockedTool := range pluginConfig.BlockedTools {
|
|
if toolName == blockedTool {
|
|
fmt.Printf("[MCP-Only Plugin] Blocked tool call: %s\n", toolName)
|
|
// Return a short-circuit response to prevent the call
|
|
errorMsg := fmt.Sprintf("%s: %s", pluginConfig.CustomErrorMessage, toolName)
|
|
// Get the tool call ID to link the response back to the original call
|
|
toolCallID := req.ChatAssistantMessageToolCall.ID
|
|
return req, &schemas.MCPPluginShortCircuit{
|
|
Response: &schemas.BifrostMCPResponse{
|
|
// Chat API format - tool result message
|
|
ChatMessage: &schemas.ChatMessage{
|
|
Role: schemas.ChatMessageRoleTool,
|
|
ChatToolMessage: &schemas.ChatToolMessage{
|
|
ToolCallID: toolCallID,
|
|
},
|
|
Content: &schemas.ChatMessageContent{
|
|
ContentStr: &errorMsg,
|
|
},
|
|
},
|
|
// Responses API format - function_call_output
|
|
ResponsesMessage: &schemas.ResponsesMessage{
|
|
Type: schemas.Ptr(schemas.ResponsesMessageTypeFunctionCallOutput),
|
|
ResponsesToolMessage: &schemas.ResponsesToolMessage{
|
|
CallID: toolCallID,
|
|
Output: &schemas.ResponsesToolMessageOutputStruct{
|
|
ResponsesToolCallOutputStr: &errorMsg,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example: Add audit trail to context (configurable)
|
|
if pluginConfig.EnableAudit {
|
|
auditMsg := fmt.Sprintf("MCP request processed at %v", ctx.Value(schemas.BifrostContextKey("request_id")))
|
|
ctx.SetValue(schemas.BifrostContextKey("mcp-audit-trail"), auditMsg)
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Printf("[MCP-Only Plugin] Audit: %s\n", auditMsg)
|
|
}
|
|
}
|
|
|
|
// Return modified request, no short-circuit, no error
|
|
return req, nil, nil
|
|
}
|
|
|
|
// PostMCPHook is called after MCP tool/resource calls complete
|
|
// This example demonstrates response logging and error handling
|
|
func PostMCPHook(ctx *schemas.BifrostContext, resp *schemas.BifrostMCPResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostMCPResponse, *schemas.BifrostError, error) {
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Println("[MCP-Only Plugin] PostMCPHook called")
|
|
}
|
|
|
|
// Retrieve audit trail from context (if enabled)
|
|
if pluginConfig.EnableAudit {
|
|
auditTrail := ctx.Value(schemas.BifrostContextKey("mcp-audit-trail"))
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Printf("[MCP-Only Plugin] Audit trail: %v\n", auditTrail)
|
|
}
|
|
}
|
|
|
|
// Example: Log the response (configurable)
|
|
if pluginConfig.EnableLogging && resp != nil {
|
|
if resp.ChatMessage != nil {
|
|
fmt.Printf("[MCP-Only Plugin] Chat message response received\n")
|
|
}
|
|
if resp.ResponsesMessage != nil {
|
|
fmt.Printf("[MCP-Only Plugin] Responses message received\n")
|
|
}
|
|
}
|
|
|
|
// Example: Log errors if present
|
|
if bifrostErr != nil && bifrostErr.Error != nil {
|
|
fmt.Printf("[MCP-Only Plugin] Error occurred: %v\n", bifrostErr.Error.Message)
|
|
}
|
|
|
|
// Example: Transform error responses (configurable)
|
|
if pluginConfig.TransformErrors && bifrostErr != nil && bifrostErr.StatusCode != nil && *bifrostErr.StatusCode == 404 {
|
|
// Convert 404 to a more user-friendly error
|
|
if bifrostErr.Error != nil {
|
|
bifrostErr.Error.Message = "The requested MCP resource was not found. Please check your request."
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Println("[MCP-Only Plugin] Error message transformed")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return modified response and error
|
|
return resp, bifrostErr, nil
|
|
}
|
|
|
|
// Cleanup is called when the plugin is unloaded (required)
|
|
func Cleanup() error {
|
|
if pluginConfig.EnableLogging {
|
|
fmt.Println("[MCP-Only Plugin] Cleanup called")
|
|
}
|
|
return nil
|
|
}
|