-
Notifications
You must be signed in to change notification settings - Fork 12
Add tools.json logging for MCP server tool discovery #903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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,160 @@ | ||
| // Package logger provides structured logging for the MCP Gateway. | ||
| // | ||
| // This file implements logging of MCP server tools to a JSON file (tools.json). | ||
| // It maintains a mapping of server IDs to their available tools with names and descriptions. | ||
| package logger | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
| "sync" | ||
| ) | ||
|
|
||
| // ToolInfo represents information about a single tool | ||
| type ToolInfo struct { | ||
| Name string `json:"name"` | ||
| Description string `json:"description"` | ||
| } | ||
|
|
||
| // ToolsData represents the structure of tools.json | ||
| type ToolsData struct { | ||
| // Map of serverID to array of tools | ||
| Servers map[string][]ToolInfo `json:"servers"` | ||
| } | ||
|
|
||
| // ToolsLogger manages logging of MCP server tools to a JSON file | ||
| type ToolsLogger struct { | ||
| logDir string | ||
| fileName string | ||
| data *ToolsData | ||
| mu sync.Mutex | ||
| useFallback bool | ||
| } | ||
|
|
||
| var ( | ||
| globalToolsLogger *ToolsLogger | ||
| globalToolsMu sync.RWMutex | ||
| ) | ||
|
|
||
| // InitToolsLogger initializes the global tools logger | ||
| // If the log directory doesn't exist and can't be created, falls back to no-op | ||
| func InitToolsLogger(logDir, fileName string) error { | ||
| logger, err := initLogger( | ||
| logDir, fileName, os.O_TRUNC, // Truncate existing file to start fresh | ||
| // Setup function: configure the logger after directory is ready | ||
| func(file *os.File, logDir, fileName string) (*ToolsLogger, error) { | ||
| // Close the file immediately - we'll write directly later | ||
| if file != nil { | ||
| file.Close() | ||
| } | ||
|
|
||
| tl := &ToolsLogger{ | ||
| logDir: logDir, | ||
| fileName: fileName, | ||
| data: &ToolsData{ | ||
| Servers: make(map[string][]ToolInfo), | ||
| }, | ||
| } | ||
| log.Printf("Tools logging to file: %s", filepath.Join(logDir, fileName)) | ||
| return tl, nil | ||
| }, | ||
| // Error handler: fallback to no-op on error | ||
| func(err error, logDir, fileName string) (*ToolsLogger, error) { | ||
| log.Printf("WARNING: Failed to initialize tools log file: %v", err) | ||
| log.Printf("WARNING: Tools logging disabled") | ||
| tl := &ToolsLogger{ | ||
| logDir: logDir, | ||
| fileName: fileName, | ||
| useFallback: true, | ||
| data: &ToolsData{ | ||
| Servers: make(map[string][]ToolInfo), | ||
| }, | ||
| } | ||
| return tl, nil | ||
| }, | ||
| ) | ||
|
|
||
| initGlobalToolsLogger(logger) | ||
| return err | ||
| } | ||
|
|
||
| // LogTools logs the tools for a specific server | ||
| func (tl *ToolsLogger) LogTools(serverID string, tools []ToolInfo) error { | ||
| tl.mu.Lock() | ||
| defer tl.mu.Unlock() | ||
|
|
||
| if tl.useFallback { | ||
| return nil // Silently skip if in fallback mode | ||
| } | ||
|
|
||
| // Update the data structure | ||
| tl.data.Servers[serverID] = tools | ||
|
|
||
| // Write the updated data to file | ||
| return tl.writeToFile() | ||
| } | ||
|
|
||
| // writeToFile writes the current tools data to the JSON file | ||
| // Caller must hold tl.mu lock | ||
| func (tl *ToolsLogger) writeToFile() error { | ||
| filePath := filepath.Join(tl.logDir, tl.fileName) | ||
|
|
||
| // Marshal to JSON with indentation for readability | ||
| jsonData, err := json.MarshalIndent(tl.data, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("failed to marshal tools data: %w", err) | ||
| } | ||
|
|
||
| // Write to file atomically using a temp file + rename | ||
| tempPath := filePath + ".tmp" | ||
| if err := os.WriteFile(tempPath, jsonData, 0644); err != nil { | ||
| return fmt.Errorf("failed to write temp file: %w", err) | ||
| } | ||
|
|
||
| if err := os.Rename(tempPath, filePath); err != nil { | ||
| // Clean up temp file on error | ||
| os.Remove(tempPath) | ||
| return fmt.Errorf("failed to rename temp file: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Close is a no-op for ToolsLogger (implements closableLogger interface) | ||
| func (tl *ToolsLogger) Close() error { | ||
| // No file handle to close since we write directly each time | ||
| return nil | ||
| } | ||
|
|
||
| // Global logging function that uses the global tools logger | ||
|
|
||
| // LogToolsForServer logs the tools for a specific server | ||
| func LogToolsForServer(serverID string, tools []ToolInfo) { | ||
| globalToolsMu.RLock() | ||
| defer globalToolsMu.RUnlock() | ||
|
|
||
| if globalToolsLogger != nil { | ||
| if err := globalToolsLogger.LogTools(serverID, tools); err != nil { | ||
| // Log errors using the standard logger to avoid recursion | ||
| log.Printf("WARNING: Failed to log tools for server %s: %v", serverID, err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // CloseToolsLogger closes the global tools logger | ||
| func CloseToolsLogger() error { | ||
| return closeGlobalToolsLogger() | ||
| } | ||
|
|
||
| // initGlobalToolsLogger initializes the global ToolsLogger using the generic helper. | ||
| func initGlobalToolsLogger(logger *ToolsLogger) { | ||
| initGlobalLogger(&globalToolsMu, &globalToolsLogger, logger) | ||
| } | ||
|
|
||
| // closeGlobalToolsLogger closes the global ToolsLogger using the generic helper. | ||
| func closeGlobalToolsLogger() error { | ||
| return closeGlobalLogger(&globalToolsMu, &globalToolsLogger) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InitToolsLogger truncates/creates tools.json via initLogger but the setup function closes the file and never writes an initial JSON payload. This leaves an empty (invalid JSON) tools.json until the first LogTools() call, and can permanently leave an invalid file if startup exits before tools are registered. Consider writing an initial
{ "servers": {} }to disk during initialization (or avoid truncating until the first successful write) so the file is always valid JSON.