diff --git a/description_test.go b/description_test.go index 7f69500..02c87ab 100644 --- a/description_test.go +++ b/description_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" ) func TestGenerateAIFriendlyDescription_WithJsonSchema(t *testing.T) { diff --git a/error.go b/error.go index 330fd9f..18f235c 100644 --- a/error.go +++ b/error.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" ) // generateAI400ErrorResponse creates a comprehensive, AI-optimized error response for 400 HTTP errors diff --git a/go.mod b/go.mod index ad145be..bd2050a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ toolchain go1.24.6 require ( github.com/getkin/kin-openapi v0.132.0 - github.com/modelcontextprotocol/go-sdk v0.2.0 + github.com/google/jsonschema-go v0.2.0 + github.com/modelcontextprotocol/go-sdk v0.3.0 go.yaml.in/yaml/v3 v3.0.4 ) diff --git a/go.sum b/go.sum index 2fc0c42..02de284 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.2.0 h1:Uh19091iHC56//WOsAd1oRg6yy1P9BpSvpjOL6RcjLQ= +github.com/google/jsonschema-go v0.2.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -18,8 +20,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/modelcontextprotocol/go-sdk v0.2.0 h1:PESNYOmyM1c369tRkzXLY5hHrazj8x9CY1Xu0fLCryM= -github.com/modelcontextprotocol/go-sdk v0.2.0/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E= +github.com/modelcontextprotocol/go-sdk v0.3.0 h1:/1XC6+PpdKfE4CuFJz8/goo0An31bu8n8G8d3BkeJoY= +github.com/modelcontextprotocol/go-sdk v0.3.0/go.mod h1:71VUZVa8LL6WARvSgLJ7DMpDWSeomT4uBv8g97mGBvo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= diff --git a/openapi2mcp.go b/openapi2mcp.go index ee0c942..0db04a5 100644 --- a/openapi2mcp.go +++ b/openapi2mcp.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/getkin/kin-openapi/openapi3" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" ) // OpenAPIOperation describes a single OpenAPI operation to be mapped to an MCP tool. diff --git a/postprocess_test.go b/postprocess_test.go index 5b356f8..20a61c6 100644 --- a/postprocess_test.go +++ b/postprocess_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/getkin/kin-openapi/openapi3" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" ) func TestPostProcessSchema_Integration(t *testing.T) { @@ -29,7 +29,7 @@ func TestPostProcessSchema_Integration(t *testing.T) { // Build the initial schema originalSchema := BuildInputSchema(params, nil) - + // Verify original schema doesn't have the custom description if originalSchema.Description != "" { t.Errorf("Original schema should not have description, got: %s", originalSchema.Description) @@ -48,11 +48,11 @@ func TestPostProcessSchema_Integration(t *testing.T) { if processedSchema.Type != "object" { t.Errorf("Expected type 'object', got %q", processedSchema.Type) } - + if processedSchema.Properties == nil { t.Error("Properties should be preserved") } - + if _, ok := processedSchema.Properties["testParam"]; !ok { t.Error("testParam property should be preserved") } @@ -64,8 +64,7 @@ func TestPostProcessSchema_Integration(t *testing.T) { func TestPostProcessSchema_TypesIntegrity(t *testing.T) { // Test that the function signature change maintains type safety - var postProcessor func(string, jsonschema.Schema) jsonschema.Schema - postProcessor = func(toolName string, schema jsonschema.Schema) jsonschema.Schema { + postProcessor := func(toolName string, schema jsonschema.Schema) jsonschema.Schema { // This demonstrates the function signature is correct return schema } @@ -83,9 +82,9 @@ func TestPostProcessSchema_TypesIntegrity(t *testing.T) { testSchema := jsonschema.Schema{ Type: "object", } - + result := opts.PostProcessSchema("test", testSchema) if result.Type != "object" { t.Error("Function should return the schema") } -} \ No newline at end of file +} diff --git a/register.go b/register.go index 77b3b3e..d9a8f76 100644 --- a/register.go +++ b/register.go @@ -11,7 +11,7 @@ import ( "time" "github.com/getkin/kin-openapi/openapi3" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -189,58 +189,6 @@ func generateAIFriendlyDescription(op OpenAPIOperation, inputSchema jsonschema.S return desc.String() } -// generateExampleValue creates appropriate example values based on the parameter schema -func generateExampleValue(prop map[string]any) any { - typeStr, _ := prop["type"].(string) - - // Check for enum values first - if enum, ok := prop["enum"].([]any); ok && len(enum) > 0 { - return enum[0] - } - - // Check for example values in schema - if example, ok := prop["example"]; ok { - return example - } - - // Generate based on type - switch typeStr { - case "string": - if format, ok := prop["format"].(string); ok { - switch format { - case "email": - return "user@example.com" - case "uri", "url": - return "https://example.com" - case "date": - return "2024-01-01" - case "date-time": - return "2024-01-01T00:00:00Z" - case "uuid": - return "123e4567-e89b-12d3-a456-426614174000" - default: - return "example_string" - } - } - return "example_string" - case "number": - return 123.45 - case "integer": - return 123 - case "boolean": - return true - case "array": - if items, ok := prop["items"].(map[string]any); ok { - return []any{generateExampleValue(items)} - } - return []any{"item1", "item2"} - case "object": - return map[string]any{"key": "value"} - default: - return nil - } -} - // generateExampleValueFromSchema creates appropriate example values based on the jsonschema.Schema func generateExampleValueFromSchema(prop *jsonschema.Schema) any { if prop == nil { @@ -492,7 +440,6 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena tool := &mcp.Tool{ Name: "externalDocs", Description: "Show the OpenAPI external documentation URL and description.", - InputSchema: &jsonschema.Schema{Type: "object", Properties: map[string]*jsonschema.Schema{}}, } if opts != nil && opts.Version != "" { @@ -501,7 +448,7 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena } } - mcp.AddTool(server, tool, func(ctx context.Context, session *mcp.ServerSession, params *mcp.CallToolParams) (*mcp.CallToolResult, error) { + mcp.AddTool(server, tool, func(_ context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { info := "External documentation URL: " + doc.ExternalDocs.URL if doc.ExternalDocs.Description != "" { info += "\nDescription: " + doc.ExternalDocs.Description @@ -512,7 +459,7 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena Text: info, }, }, - }, nil + }, nil, nil }) toolNames = append(toolNames, "externalDocs") } @@ -522,7 +469,6 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena tool := &mcp.Tool{ Name: "info", Description: "Show API metadata: title, version, description, and terms of service.", - InputSchema: &jsonschema.Schema{Type: "object", Properties: map[string]*jsonschema.Schema{}}, } if opts != nil && opts.Version != "" { @@ -531,7 +477,7 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena } } - mcp.AddTool(server, tool, func(ctx context.Context, session *mcp.ServerSession, params *mcp.CallToolParams) (*mcp.CallToolResult, error) { + mcp.AddTool(server, tool, func(_ context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { var sb strings.Builder if doc.Info.Title != "" { sb.WriteString("Title: " + doc.Info.Title + "\n") @@ -551,7 +497,7 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena Text: strings.TrimSpace(sb.String()), }, }, - }, nil + }, nil, nil }) toolNames = append(toolNames, "info") } @@ -584,7 +530,7 @@ func RegisterOpenAPITools(server *mcp.Server, ops []OpenAPIOperation, doc *opena MIMEType: "application/json", } - server.AddResource(×tampResource, func(ctx context.Context, session *mcp.ServerSession, params *mcp.ReadResourceParams) (*mcp.ReadResourceResult, error) { + server.AddResource(×tampResource, func(ctx context.Context, req *mcp.ServerRequest[*mcp.ReadResourceParams]) (*mcp.ReadResourceResult, error) { now := time.Now().Unix() content := fmt.Sprintf(`{"unix_timestamp": %d, "iso8601": "%s", "timezone": "%s"}`, now, diff --git a/schema.go b/schema.go index 6506704..331f8ec 100644 --- a/schema.go +++ b/schema.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/getkin/kin-openapi/openapi3" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" ) // escapeParameterName converts parameter names with brackets to MCP-compatible names. @@ -31,15 +31,6 @@ func escapeParameterName(name string) string { return escaped } -// unescapeParameterName converts escaped parameter names back to their original form. -// This maintains a mapping from escaped names to original names for parameter lookup. -func unescapeParameterName(escaped string, originalNames map[string]string) string { - if original, exists := originalNames[escaped]; exists { - return original - } - return escaped // Return as-is if not found in mapping -} - // buildParameterNameMapping creates a mapping from escaped parameter names to original names. // This is used to reverse the escaping when looking up parameter values. func buildParameterNameMapping(params openapi3.Parameters) map[string]string { diff --git a/server.go b/server.go index 31ba9f9..170b658 100644 --- a/server.go +++ b/server.go @@ -5,84 +5,12 @@ import ( "context" "fmt" "net/http" - "os" "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/modelcontextprotocol/go-sdk/mcp" ) -// authContextFunc extracts authentication headers from HTTP requests and sets them -// as environment variables for the duration of each request. This allows API keys -// and other authentication to be provided via HTTP headers when using HTTP mode. -func authContextFunc(ctx context.Context, r *http.Request) context.Context { - // Save original environment values to restore them later - origAPIKey := os.Getenv("API_KEY") - origBearerToken := os.Getenv("BEARER_TOKEN") - origBasicAuth := os.Getenv("BASIC_AUTH") - - // Extract authentication from HTTP headers - if apiKey := r.Header.Get("X-API-Key"); apiKey != "" { - os.Setenv("API_KEY", apiKey) - } else if apiKey := r.Header.Get("Api-Key"); apiKey != "" { - os.Setenv("API_KEY", apiKey) - } - - if bearerToken := r.Header.Get("Authorization"); bearerToken != "" { - if len(bearerToken) > 7 && bearerToken[:7] == "Bearer " { - os.Setenv("BEARER_TOKEN", bearerToken[7:]) - } else if len(bearerToken) > 6 && bearerToken[:6] == "Basic " { - os.Setenv("BASIC_AUTH", bearerToken[6:]) - } - } - - // Create a context that restores the original environment when done - return &authContext{ - Context: ctx, - origAPIKey: origAPIKey, - origBearerToken: origBearerToken, - origBasicAuth: origBasicAuth, - } -} - -// authContext wraps a context and restores original environment variables when done -type authContext struct { - context.Context - origAPIKey string - origBearerToken string - origBasicAuth string -} - -// Done restores the original environment variables when the context is done -func (c *authContext) Done() <-chan struct{} { - done := c.Context.Done() - if done != nil { - go func() { - <-done - c.restoreEnv() - }() - } - return done -} - -func (c *authContext) restoreEnv() { - if c.origAPIKey != "" { - os.Setenv("API_KEY", c.origAPIKey) - } else { - os.Unsetenv("API_KEY") - } - if c.origBearerToken != "" { - os.Setenv("BEARER_TOKEN", c.origBearerToken) - } else { - os.Unsetenv("BEARER_TOKEN") - } - if c.origBasicAuth != "" { - os.Setenv("BASIC_AUTH", c.origBasicAuth) - } else { - os.Unsetenv("BASIC_AUTH") - } -} - // NewServer creates a new MCP server, registers all OpenAPI tools, and returns the server. // Equivalent to calling RegisterOpenAPITools with all operations from the spec. // Example usage for NewServer: @@ -118,8 +46,8 @@ func NewServerWithOps(name, version string, doc *openapi3.T, ops []OpenAPIOperat // // openapi2mcp.ServeStdio(srv) func ServeStdio(server *mcp.Server) error { - transport := mcp.NewStdioTransport() - _, err := server.Connect(context.Background(), transport) + transport := new(mcp.StdioTransport) + _, err := server.Connect(context.Background(), transport, nil) return err } diff --git a/tool.go b/tool.go index 5ef5ff0..bc25569 100644 --- a/tool.go +++ b/tool.go @@ -14,7 +14,7 @@ import ( "strings" "github.com/getkin/kin-openapi/openapi3" - "github.com/modelcontextprotocol/go-sdk/jsonschema" + "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -30,19 +30,8 @@ func toolHandler( baseURLs []string, confirmDangerousActions bool, requestHandler func(req *http.Request) (*http.Response, error), -) func(ctx context.Context, session *mcp.ServerSession, params *mcp.CallToolParams) (*mcp.CallToolResult, error) { - return func(ctx context.Context, session *mcp.ServerSession, params *mcp.CallToolParams) (*mcp.CallToolResult, error) { - var args map[string]any - if params.Arguments == nil { - args = map[string]any{} - } else { - var ok bool - args, ok = params.Arguments.(map[string]any) - if !ok { - args = map[string]any{} - } - } - +) func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + return func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Build parameter name mapping for escaped parameter names paramNameMapping := buildParameterNameMapping(op.Parameters) @@ -90,7 +79,7 @@ func toolHandler( baseURL := baseURLs[rand.Intn(len(baseURLs))] fullURL, err := url.JoinPath(baseURL, path) if err != nil { - return nil, err + return nil, nil, err } if len(query) > 0 { fullURL += "?" + query.Encode() @@ -122,7 +111,7 @@ func toolHandler( method := strings.ToUpper(op.Method) httpReq, err := http.NewRequestWithContext(ctx, method, fullURL, bytes.NewReader(body)) if err != nil { - return nil, err + return nil, nil, err } if len(body) > 0 && requestContentType != "" { httpReq.Header.Set("Content-Type", requestContentType) @@ -205,7 +194,7 @@ func toolHandler( resp, err := requestHandler(httpReq) if err != nil { - return nil, err + return nil, nil, err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) @@ -278,7 +267,7 @@ func toolHandler( }, }, IsError: true, - }, nil + }, nil, nil } // Create a simple text error message @@ -298,7 +287,7 @@ func toolHandler( }, }, IsError: true, - }, nil + }, nil, nil } // Handle binary/file responses for success @@ -329,7 +318,7 @@ func toolHandler( Text: string(resultJSON), }, }, - }, nil + }, nil, nil } // Always format the response as: HTTP \nStatus: \nResponse:\n @@ -341,7 +330,7 @@ func toolHandler( Text: respText, }, }, - }, nil + }, nil, nil } if confirmDangerousActions && (method == "PUT" || method == "POST" || method == "DELETE") { @@ -353,7 +342,7 @@ func toolHandler( Text: confirmText, }, }, - }, nil + }, nil, nil } } @@ -363,7 +352,7 @@ func toolHandler( Text: respText, }, }, - }, nil + }, nil, nil } }