package mcp import ( "context" "github.com/mark3labs/mcp-go/mcp" ) // registerBootstrapResources surfaces the bootstrap-state tools as // MCP resources. Every session reads these at startup; resources are // the right shape because they take no arguments, are URI-addressable, // and can push `notifications/resources/updated` after each graph // re-warm so agents stop polling. // // The corresponding tools (`graph_stats`, `index_health`, // `workspace_info`, `list_repos`, `get_active_project`) stay // registered. Client support for resources is patchier than tools — // keeping the tool form alive avoids regressing those clients. func (s *Server) registerBootstrapResources() { s.mcpServer.AddResource( mcp.NewResource( "gortex://index-health", "Index Health", mcp.WithResourceDescription("Health score, parse failures, stale files, language coverage. Read at session start to confirm the index is current. Same payload as the `index_health` tool."), mcp.WithMIMEType("application/json"), ), s.handleResourceIndexHealth, ) s.mcpServer.AddResource( mcp.NewResource( "gortex://workspace", "Workspace Info", mcp.WithResourceDescription("Bind mode, root directory, marker contents, the auto-discovered member set, and any unknown marker keys. Same payload as the `workspace_info` tool."), mcp.WithMIMEType("application/json"), ), s.handleResourceWorkspace, ) s.mcpServer.AddResource( mcp.NewResource( "gortex://repos", "Workspace Repos", mcp.WithResourceDescription("Every project in the active workspace — the legal `repo` values for any scope: repo or scope: fan-out tool call. Same payload as the `list_repos` tool."), mcp.WithMIMEType("application/json"), ), s.handleResourceRepos, ) s.mcpServer.AddResource( mcp.NewResource( "gortex://active-project", "Active Project", mcp.WithResourceDescription("Current active project name and its repo list. Same payload as the `get_active_project` tool."), mcp.WithMIMEType("application/json"), ), s.handleResourceActiveProject, ) } func (s *Server) handleResourceIndexHealth(_ context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { payload := s.buildIndexHealthPayload() if payload == nil { payload = map[string]any{ "error": "no indexer available", } } return jsonResource(req.Params.URI, payload) } func (s *Server) handleResourceWorkspace(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { return jsonResource(req.Params.URI, s.buildWorkspaceInfoPayload(ctx)) } func (s *Server) handleResourceRepos(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { return jsonResource(req.Params.URI, s.buildListReposPayload(ctx)) } func (s *Server) handleResourceActiveProject(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { return jsonResource(req.Params.URI, s.buildActiveProjectPayload(ctx)) } // bootstrapResourceURIs lists the resources whose payloads change when // the graph is re-warmed. The resource broadcaster pushes a // `notifications/resources/updated` for each on re-warm completion. func bootstrapResourceURIs() []string { return []string{ "gortex://stats", "gortex://index-health", "gortex://workspace", "gortex://repos", "gortex://active-project", } }