Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 85 additions & 7 deletions router/pkg/schemaloader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// Operation represents a GraphQL operation with its AST document and schema information
type Operation struct {
Name string
ToolName string
FilePath string
Document ast.Document
OperationString string
Expand Down Expand Up @@ -46,8 +47,7 @@ func NewOperationLoader(logger *zap.Logger, schemaDoc *ast.Document) *OperationL
func (l *OperationLoader) LoadOperationsFromDirectory(dirPath string) ([]Operation, error) {
var operations []Operation

// Create an operation validator
validator := astvalidation.DefaultOperationValidator()
validator := newMCPOperationValidator()

// Walk through the directory and process GraphQL files
err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error {
Expand Down Expand Up @@ -92,7 +92,10 @@ func (l *OperationLoader) LoadOperationsFromDirectory(dirPath string) ([]Operati
return nil
}

// Validate operation against schema
opDescription := extractOperationDescription(&opDoc)
toolName := extractMCPToolName(&opDoc)
stripMCPDirective(&opDoc)

validationReport := operationreport.Report{}
validationState := validator.Validate(&opDoc, l.SchemaDocument, &validationReport)
if validationState == astvalidation.Invalid {
Expand All @@ -116,12 +119,10 @@ func (l *OperationLoader) LoadOperationsFromDirectory(dirPath string) ([]Operati
}
}

// Extract description from operation definition
opDescription := extractOperationDescription(&opDoc)

// Add to our list of operations
operations = append(operations, Operation{
Name: opName,
ToolName: toolName,
FilePath: path,
Document: opDoc,
OperationString: operationString,
Expand Down Expand Up @@ -186,7 +187,9 @@ func getOperationNameAndType(doc *ast.Document) (string, string, error) {
return "", "", fmt.Errorf("no operation found in document")
}

// extractOperationDescription extracts the description string from an operation definition
// extractOperationDescription returns the description text of the first operation definition
// in the document, trimmed of surrounding whitespace. If the document has no operation
// description or no operation definition, an empty string is returned.
func extractOperationDescription(doc *ast.Document) string {
for _, ref := range doc.RootNodes {
if ref.Kind == ast.NodeKindOperationDefinition {
Expand All @@ -200,3 +203,78 @@ func extractOperationDescription(doc *ast.Document) string {
}
return ""
}

var mcpDirectiveName = []byte("mcpTool")
var mcpNameArgument = []byte("name")

// subscription/root-field restrictions.
func newMCPOperationValidator() *astvalidation.OperationValidator {
return astvalidation.NewOperationValidator([]astvalidation.Rule{
astvalidation.AllVariablesUsed(),
astvalidation.AllVariableUsesDefined(),
astvalidation.DocumentContainsExecutableOperation(),
astvalidation.OperationNameUniqueness(),
astvalidation.LoneAnonymousOperation(),
astvalidation.SubscriptionSingleRootField(),
astvalidation.FieldSelections(),
astvalidation.FieldSelectionMerging(),
astvalidation.KnownArguments(),
astvalidation.Values(),
astvalidation.ArgumentUniqueness(),
astvalidation.RequiredArguments(),
astvalidation.Fragments(),
astvalidation.DirectivesAreInValidLocations(),
astvalidation.DirectivesAreUniquePerLocation(),
astvalidation.VariableUniqueness(),
astvalidation.VariablesAreInputTypes(),
})
}

// extractMCPToolName extracts the MCP tool name from the first operation definition's
// mcpTool directive, if present.
// It returns the value of the directive's "name" argument when that argument is a string;
// otherwise it returns an empty string.
func extractMCPToolName(doc *ast.Document) string {
for _, ref := range doc.RootNodes {
if ref.Kind == ast.NodeKindOperationDefinition {
opDef := doc.OperationDefinitions[ref.Ref]
if !opDef.HasDirectives {
return ""
}

directiveRef, exists := doc.DirectiveWithNameBytes(opDef.Directives.Refs, mcpDirectiveName)
if !exists {
return ""
}

value, argExists := doc.DirectiveArgumentValueByName(directiveRef, mcpNameArgument)
if !argExists {
return ""
}

if value.Kind == ast.ValueKindString {
return doc.StringValueContentString(value.Ref)
}

return ""
}
}
return ""
}

// stripMCPDirective removes the `mcpTool` directive from the first operation definition in doc.
// It mutates the document in-place: if the directive is present it is removed and the operation's
// HasDirectives flag is updated; if no operation or no such directive exists the document is left unchanged.
func stripMCPDirective(doc *ast.Document) {
for _, ref := range doc.RootNodes {
if ref.Kind == ast.NodeKindOperationDefinition {
opDef := &doc.OperationDefinitions[ref.Ref]
if !opDef.HasDirectives {
return
}
opDef.Directives.RemoveDirectiveByName(doc, "mcpTool")
opDef.HasDirectives = len(opDef.Directives.Refs) > 0
return
}
}
}
Loading