Deluan bcc3643c81 chore: add debug logging to mcp-server
Added debug logging throughout the mcp-server components using the standard Go `log` package. All log messages are prefixed with `[MCP]` for easy identification.

This includes logging in: main function (startup, CLI execution, registration, serve loop), Tool handlers, Native and WASM fetcher implementations, Wikipedia, Wikidata, and DBpedia data fetching functions

Replaced previous `println` statements with `log.Println` or `log.Printf`.
2025-04-19 19:37:32 -04:00

290 lines
14 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net/url"
"os"
mcp_golang "github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/stdio"
)
type Content struct {
Title string `json:"title" jsonschema:"required,description=The title to submit"`
Description *string `json:"description" jsonschema:"description=The description to submit"`
}
type MyFunctionsArguments struct {
Submitter string `json:"submitter" jsonschema:"required,description=The name of the thing calling this tool (openai, google, claude, etc)"`
Content Content `json:"content" jsonschema:"required,description=The content of the message"`
}
type ArtistBiography struct {
ID string `json:"id" jsonschema:"required,description=The id of the artist"`
Name string `json:"name" jsonschema:"required,description=The name of the artist"`
MBID string `json:"mbid" jsonschema:"description=The mbid of the artist"`
}
type ArtistURLArgs struct {
ID string `json:"id" jsonschema:"required,description=The id of the artist"`
Name string `json:"name" jsonschema:"required,description=The name of the artist"`
MBID string `json:"mbid" jsonschema:"description=The mbid of the artist"`
}
func main() {
log.Println("[MCP] Starting mcp-server...")
done := make(chan struct{})
// Create the appropriate fetcher (native or WASM based on build tags)
log.Printf("[MCP] Debug: Creating fetcher...")
fetcher := NewFetcher()
log.Printf("[MCP] Debug: Fetcher created successfully.")
// --- Command Line Flag Handling ---
nameFlag := flag.String("name", "", "Artist name to query directly")
mbidFlag := flag.String("mbid", "", "Artist MBID to query directly")
flag.Parse()
if *nameFlag != "" || *mbidFlag != "" {
log.Printf("[MCP] Debug: Running tools directly via CLI flags (Name: '%s', MBID: '%s')", *nameFlag, *mbidFlag)
fmt.Println("--- Running Tools Directly ---")
// Call getArtistBiography
fmt.Printf("Calling get_artist_biography (Name: '%s', MBID: '%s')...\n", *nameFlag, *mbidFlag)
if *mbidFlag == "" && *nameFlag == "" {
fmt.Println(" Error: --mbid or --name is required for get_artist_biography")
} else {
// Use context.Background for CLI calls
log.Printf("[MCP] Debug: CLI calling getArtistBiography...")
bio, bioErr := getArtistBiography(fetcher, context.Background(), "cli", *nameFlag, *mbidFlag)
if bioErr != nil {
fmt.Printf(" Error: %v\n", bioErr)
log.Printf("[MCP] Error: CLI getArtistBiography failed: %v", bioErr)
} else {
fmt.Printf(" Result: %s\n", bio)
log.Printf("[MCP] Debug: CLI getArtistBiography succeeded.")
}
}
// Call getArtistURL
fmt.Printf("Calling get_artist_url (Name: '%s', MBID: '%s')...\n", *nameFlag, *mbidFlag)
if *mbidFlag == "" && *nameFlag == "" {
fmt.Println(" Error: --mbid or --name is required for get_artist_url")
} else {
log.Printf("[MCP] Debug: CLI calling getArtistURL...")
urlResult, urlErr := getArtistURL(fetcher, context.Background(), "cli", *nameFlag, *mbidFlag)
if urlErr != nil {
fmt.Printf(" Error: %v\n", urlErr)
log.Printf("[MCP] Error: CLI getArtistURL failed: %v", urlErr)
} else {
fmt.Printf(" Result: %s\n", urlResult)
log.Printf("[MCP] Debug: CLI getArtistURL succeeded.")
}
}
fmt.Println("-----------------------------")
log.Printf("[MCP] Debug: CLI execution finished.")
return // Exit after direct execution
}
// --- End Command Line Flag Handling ---
log.Printf("[MCP] Debug: Initializing MCP server...")
server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
log.Printf("[MCP] Debug: Registering tool 'hello'...")
err := server.RegisterTool("hello", "Say hello to a person", func(arguments MyFunctionsArguments) (*mcp_golang.ToolResponse, error) {
log.Printf("[MCP] Debug: Tool 'hello' called with args: %+v", arguments)
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("Hello, %server!", arguments.Submitter))), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register tool 'hello': %v", err)
}
log.Printf("[MCP] Debug: Registering tool 'get_artist_biography'...")
err = server.RegisterTool("get_artist_biography", "Get the biography of an artist", func(arguments ArtistBiography) (*mcp_golang.ToolResponse, error) {
log.Printf("[MCP] Debug: Tool 'get_artist_biography' called with args: %+v", arguments)
// Using background context in handlers as request context isn't passed through MCP library currently
bio, err := getArtistBiography(fetcher, context.Background(), arguments.ID, arguments.Name, arguments.MBID)
if err != nil {
log.Printf("[MCP] Error: getArtistBiography handler failed: %v", err)
return nil, fmt.Errorf("handler returned an error: %w", err) // Return structured error
}
log.Printf("[MCP] Debug: Tool 'get_artist_biography' succeeded.")
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(bio)), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register tool 'get_artist_biography': %v", err)
}
log.Printf("[MCP] Debug: Registering tool 'get_artist_url'...")
err = server.RegisterTool("get_artist_url", "Get the artist's specific Wikipedia URL via MBID, or a search URL using name as fallback", func(arguments ArtistURLArgs) (*mcp_golang.ToolResponse, error) {
log.Printf("[MCP] Debug: Tool 'get_artist_url' called with args: %+v", arguments)
urlResult, err := getArtistURL(fetcher, context.Background(), arguments.ID, arguments.Name, arguments.MBID)
if err != nil {
log.Printf("[MCP] Error: getArtistURL handler failed: %v", err)
return nil, fmt.Errorf("handler returned an error: %w", err)
}
log.Printf("[MCP] Debug: Tool 'get_artist_url' succeeded.")
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(urlResult)), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register tool 'get_artist_url': %v", err)
}
log.Printf("[MCP] Debug: Registering prompt 'prompt_test'...")
err = server.RegisterPrompt("prompt_test", "This is a test prompt", func(arguments Content) (*mcp_golang.PromptResponse, error) {
log.Printf("[MCP] Debug: Prompt 'prompt_test' called with args: %+v", arguments)
return mcp_golang.NewPromptResponse("description", mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(fmt.Sprintf("Hello, %server!", arguments.Title)), mcp_golang.RoleUser)), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register prompt 'prompt_test': %v", err)
}
log.Printf("[MCP] Debug: Registering resource 'test://resource'...")
err = server.RegisterResource("test://resource", "resource_test", "This is a test resource", "application/json", func() (*mcp_golang.ResourceResponse, error) {
log.Printf("[MCP] Debug: Resource 'test://resource' called")
return mcp_golang.NewResourceResponse(mcp_golang.NewTextEmbeddedResource("test://resource", "This is a test resource", "application/json")), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register resource 'test://resource': %v", err)
}
log.Printf("[MCP] Debug: Registering resource 'file://app_logs'...")
err = server.RegisterResource("file://app_logs", "app_logs", "The app logs", "text/plain", func() (*mcp_golang.ResourceResponse, error) {
log.Printf("[MCP] Debug: Resource 'file://app_logs' called")
return mcp_golang.NewResourceResponse(mcp_golang.NewTextEmbeddedResource("file://app_logs", "This is a test resource", "text/plain")), nil
})
if err != nil {
log.Fatalf("[MCP] Fatal: Failed to register resource 'file://app_logs': %v", err)
}
log.Println("[MCP] MCP server initialized and starting to serve...")
err = server.Serve()
if err != nil {
log.Fatalf("[MCP] Fatal: Server exited with error: %v", err)
}
log.Println("[MCP] Server exited cleanly.")
<-done // Keep running until interrupted (though server.Serve() is blocking)
}
func getArtistBiography(fetcher Fetcher, ctx context.Context, id, name, mbid string) (string, error) {
log.Printf("[MCP] Debug: getArtistBiography called (id: %s, name: %s, mbid: %s)", id, name, mbid)
if mbid == "" {
fmt.Fprintf(os.Stderr, "MBID not provided, attempting DBpedia lookup by name: %s\n", name)
log.Printf("[MCP] Debug: MBID not provided, attempting DBpedia lookup by name: %s", name)
} else {
// 1. Attempt Wikidata MBID lookup first
log.Printf("[MCP] Debug: Attempting Wikidata URL lookup for MBID: %s", mbid)
wikiURL, err := GetArtistWikipediaURL(fetcher, ctx, mbid)
if err == nil {
// 1a. Found Wikidata URL, now fetch from Wikipedia API
log.Printf("[MCP] Debug: Found Wikidata URL '%s', fetching bio from Wikipedia API...", wikiURL)
bio, errBio := GetBioFromWikipediaAPI(fetcher, ctx, wikiURL)
if errBio == nil {
log.Printf("[MCP] Debug: Successfully fetched bio from Wikipedia API for '%s'.", name)
return bio, nil // Success via Wikidata/Wikipedia!
} else {
// Failed to get bio even though URL was found
log.Printf("[MCP] Error: Found Wikipedia URL (%s) via MBID %s, but failed to fetch bio: %v", wikiURL, mbid, errBio)
fmt.Fprintf(os.Stderr, "Found Wikipedia URL (%s) via MBID %s, but failed to fetch bio: %v\n", wikiURL, mbid, errBio)
// Fall through to try DBpedia by name as a last resort?
// Let's fall through for now.
}
} else if !errors.Is(err, ErrNotFound) {
// Wikidata lookup failed for a reason other than not found (e.g., network)
log.Printf("[MCP] Error: Wikidata URL lookup failed for MBID %s (non-NotFound error): %v", mbid, err)
fmt.Fprintf(os.Stderr, "Wikidata URL lookup failed for MBID %s (non-NotFound error): %v\n", mbid, err)
// Don't proceed to DBpedia name lookup if Wikidata had a technical failure
return "", fmt.Errorf("Wikidata lookup failed: %w", err)
} else {
// Wikidata lookup returned ErrNotFound for MBID
log.Printf("[MCP] Debug: MBID %s not found on Wikidata, attempting DBpedia lookup by name: %s", mbid, name)
fmt.Fprintf(os.Stderr, "MBID %s not found on Wikidata, attempting DBpedia lookup by name: %s\n", mbid, name)
}
}
// 2. Attempt DBpedia lookup by name (if MBID was missing or failed with ErrNotFound)
if name == "" {
log.Printf("[MCP] Error: Cannot find artist bio: MBID lookup failed/missing, and no name provided.")
return "", fmt.Errorf("cannot find artist: MBID lookup failed or MBID not provided, and no name provided for DBpedia fallback")
}
log.Printf("[MCP] Debug: Attempting DBpedia bio lookup by name: %s", name)
dbpediaBio, errDb := GetArtistBioFromDBpedia(fetcher, ctx, name)
if errDb == nil {
log.Printf("[MCP] Debug: Successfully fetched bio from DBpedia for '%s'.", name)
return dbpediaBio, nil // Success via DBpedia!
}
// 3. If both Wikidata (MBID) and DBpedia (Name) failed
if errors.Is(errDb, ErrNotFound) {
log.Printf("[MCP] Error: Artist '%s' (MBID: %s) not found via Wikidata or DBpedia name lookup.", name, mbid)
return "", fmt.Errorf("artist '%s' (MBID: %s) not found via Wikidata MBID or DBpedia Name lookup", name, mbid)
}
// Return DBpedia's error if it wasn't ErrNotFound
log.Printf("[MCP] Error: DBpedia lookup failed for name '%s': %v", name, errDb)
return "", fmt.Errorf("DBpedia lookup failed for name '%s': %w", name, errDb)
}
// getArtistURL attempts to find the specific Wikipedia URL using MBID (via Wikidata),
// then by Name (via DBpedia), falling back to a search URL using name.
func getArtistURL(fetcher Fetcher, ctx context.Context, id, name, mbid string) (string, error) {
log.Printf("[MCP] Debug: getArtistURL called (id: %s, name: %s, mbid: %s)", id, name, mbid)
if mbid == "" {
fmt.Fprintf(os.Stderr, "getArtistURL: MBID not provided, attempting DBpedia lookup by name: %s\n", name)
log.Printf("[MCP] Debug: getArtistURL: MBID not provided, attempting DBpedia lookup by name: %s", name)
} else {
// Try to get the specific URL from Wikidata using MBID
log.Printf("[MCP] Debug: getArtistURL: Attempting Wikidata URL lookup for MBID: %s", mbid)
wikiURL, err := GetArtistWikipediaURL(fetcher, ctx, mbid)
if err == nil && wikiURL != "" {
log.Printf("[MCP] Debug: getArtistURL: Found specific URL '%s' via Wikidata MBID.", wikiURL)
return wikiURL, nil // Found specific URL via MBID
}
// Log error if Wikidata lookup failed for reasons other than not found
if err != nil && !errors.Is(err, ErrNotFound) {
log.Printf("[MCP] Error: getArtistURL: Wikidata URL lookup failed for MBID %s (non-NotFound error): %v", mbid, err)
fmt.Fprintf(os.Stderr, "getArtistURL: Wikidata URL lookup failed for MBID %s (non-NotFound error): %v\n", mbid, err)
// Fall through to try DBpedia if name is available
} else if errors.Is(err, ErrNotFound) {
log.Printf("[MCP] Debug: getArtistURL: MBID %s not found on Wikidata, attempting DBpedia lookup by name: %s", mbid, name)
fmt.Fprintf(os.Stderr, "getArtistURL: MBID %s not found on Wikidata, attempting DBpedia lookup by name: %s\n", mbid, name)
}
}
// Fallback 1: Try DBpedia lookup by name
if name != "" {
log.Printf("[MCP] Debug: getArtistURL: Attempting DBpedia URL lookup by name: %s", name)
dbpediaWikiURL, errDb := GetArtistWikipediaURLFromDBpedia(fetcher, ctx, name)
if errDb == nil && dbpediaWikiURL != "" {
log.Printf("[MCP] Debug: getArtistURL: Found specific URL '%s' via DBpedia Name lookup.", dbpediaWikiURL)
return dbpediaWikiURL, nil // Found specific URL via DBpedia Name lookup
}
// Log error if DBpedia lookup failed for reasons other than not found
if errDb != nil && !errors.Is(errDb, ErrNotFound) {
log.Printf("[MCP] Error: getArtistURL: DBpedia URL lookup failed for name '%s' (non-NotFound error): %v", name, errDb)
fmt.Fprintf(os.Stderr, "getArtistURL: DBpedia URL lookup failed for name '%s' (non-NotFound error): %v\n", name, errDb)
// Fall through to search URL fallback
} else if errors.Is(errDb, ErrNotFound) {
log.Printf("[MCP] Debug: getArtistURL: Name '%s' not found on DBpedia, attempting search fallback", name)
fmt.Fprintf(os.Stderr, "getArtistURL: Name '%s' not found on DBpedia, attempting search fallback\n", name)
}
}
// Fallback 2: Generate a search URL if name is provided
if name != "" {
searchURL := fmt.Sprintf("https://en.wikipedia.org/w/index.php?search=%s", url.QueryEscape(name))
log.Printf("[MCP] Debug: getArtistURL: Falling back to search URL: %s", searchURL)
fmt.Fprintf(os.Stderr, "getArtistURL: Falling back to search URL: %s\n", searchURL)
return searchURL, nil
}
// Final error: MBID lookup failed (or no MBID given) AND no name provided for fallback
log.Printf("[MCP] Error: getArtistURL: Cannot generate Wikipedia URL: Lookups failed and no name provided.")
return "", fmt.Errorf("cannot generate Wikipedia URL: Wikidata/DBpedia lookups failed and no artist name provided for search fallback")
}