@@ -12,7 +12,9 @@ import (
1212
1313 "github.com/github/github-mcp-server/internal/githubv4mock"
1414 "github.com/github/github-mcp-server/internal/toolsnaps"
15+ "github.com/github/github-mcp-server/pkg/inventory"
1516 "github.com/github/github-mcp-server/pkg/raw"
17+ "github.com/github/github-mcp-server/pkg/scopes"
1618 "github.com/github/github-mcp-server/pkg/translations"
1719 "github.com/github/github-mcp-server/pkg/utils"
1820 "github.com/google/go-github/v89/github"
@@ -2984,6 +2986,171 @@ func Test_PushFiles(t *testing.T) {
29842986 }
29852987}
29862988
2989+ func Test_DeleteRepository (t * testing.T ) {
2990+ serverTool := DeleteRepository (translations .NullTranslationHelper )
2991+ tool := serverTool .Tool
2992+ require .NoError (t , toolsnaps .Test (tool .Name , tool ))
2993+
2994+ schema , ok := tool .InputSchema .(* jsonschema.Schema )
2995+ require .True (t , ok , "InputSchema should be *jsonschema.Schema" )
2996+ assert .Equal (t , "delete_repository" , tool .Name )
2997+ assert .NotEmpty (t , tool .Description )
2998+ assert .ElementsMatch (t , []string {"owner" , "repo" }, schema .Required )
2999+ require .NotNil (t , tool .Annotations )
3000+ require .NotNil (t , tool .Annotations .DestructiveHint )
3001+ assert .True (t , * tool .Annotations .DestructiveHint )
3002+ assert .Equal (t , inventory .ProtocolVersionMultiRoundTrip , serverTool .MinimumProtocolVersion )
3003+ assert .Equal (t , []string {string (scopes .DeleteRepo )}, serverTool .RequiredScopes )
3004+
3005+ t .Run ("requests exact repository name through elicitation" , func (t * testing.T ) {
3006+ result := invokeDeleteRepository (t , serverTool , NewMockedHTTPClient (), nil )
3007+
3008+ require .False (t , result .IsError )
3009+ require .Len (t , result .InputRequests , 1 )
3010+ inputRequest , ok := result .InputRequests [deleteRepositoryConfirmationID ].(* mcp.ElicitParams )
3011+ require .True (t , ok )
3012+ assert .Equal (t , "form" , inputRequest .Mode )
3013+ assert .Contains (t , inputRequest .Message , `"owner/repo"` )
3014+
3015+ requestedSchema , ok := inputRequest .RequestedSchema .(* jsonschema.Schema )
3016+ require .True (t , ok )
3017+ assert .ElementsMatch (t , []string {deleteRepositoryConfirmationField }, requestedSchema .Required )
3018+ assert .Contains (t , requestedSchema .Properties , deleteRepositoryConfirmationField )
3019+ })
3020+
3021+ t .Run ("deletes after exact confirmation" , func (t * testing.T ) {
3022+ client := NewMockedHTTPClient (
3023+ WithRequestMatchHandler (
3024+ DeleteReposByOwnerByRepo ,
3025+ mockResponse (t , http .StatusNoContent , nil ),
3026+ ),
3027+ )
3028+ result := invokeDeleteRepository (t , serverTool , client , & mcp.ElicitResult {
3029+ Action : "accept" ,
3030+ Content : map [string ]any {
3031+ deleteRepositoryConfirmationField : "owner/repo" ,
3032+ },
3033+ })
3034+
3035+ require .False (t , result .IsError )
3036+ assert .Contains (t , getTextResult (t , result ).Text , "owner/repo was deleted" )
3037+ })
3038+
3039+ t .Run ("completes multi-round-trip elicitation before deleting" , func (t * testing.T ) {
3040+ httpClient := NewMockedHTTPClient (
3041+ WithRequestMatchHandler (
3042+ DeleteReposByOwnerByRepo ,
3043+ mockResponse (t , http .StatusNoContent , nil ),
3044+ ),
3045+ )
3046+ deps := BaseDeps {Client : mustNewGHClient (t , httpClient )}
3047+
3048+ inv , err := inventory .NewBuilder ().
3049+ SetTools ([]inventory.ServerTool {serverTool }).
3050+ WithToolsets ([]string {"all" }).
3051+ Build ()
3052+ require .NoError (t , err )
3053+
3054+ server := mcp .NewServer (& mcp.Implementation {Name : "test-server" , Version : "v0.0.1" }, nil )
3055+ server .AddReceivingMiddleware (func (next mcp.MethodHandler ) mcp.MethodHandler {
3056+ return func (ctx context.Context , method string , request mcp.Request ) (mcp.Result , error ) {
3057+ return next (ContextWithDeps (ctx , deps ), method , request )
3058+ }
3059+ })
3060+ inv .RegisterTools (context .Background (), server , deps )
3061+
3062+ serverTransport , clientTransport := mcp .NewInMemoryTransports ()
3063+ serverSession , err := server .Connect (context .Background (), serverTransport , nil )
3064+ require .NoError (t , err )
3065+ t .Cleanup (func () { _ = serverSession .Close () })
3066+
3067+ client := mcp .NewClient (& mcp.Implementation {Name : "test-client" , Version : "v0.0.1" }, & mcp.ClientOptions {
3068+ ElicitationHandler : func (_ context.Context , _ * mcp.ElicitRequest ) (* mcp.ElicitResult , error ) {
3069+ return & mcp.ElicitResult {
3070+ Action : "accept" ,
3071+ Content : map [string ]any {
3072+ deleteRepositoryConfirmationField : "owner/repo" ,
3073+ },
3074+ }, nil
3075+ },
3076+ })
3077+ clientSession , err := client .Connect (context .Background (), clientTransport , nil )
3078+ require .NoError (t , err )
3079+ t .Cleanup (func () { _ = clientSession .Close () })
3080+
3081+ result , err := clientSession .CallTool (context .Background (), & mcp.CallToolParams {
3082+ Name : "delete_repository" ,
3083+ Arguments : map [string ]any {
3084+ "owner" : "owner" ,
3085+ "repo" : "repo" ,
3086+ },
3087+ })
3088+ require .NoError (t , err )
3089+ require .False (t , result .IsError )
3090+ assert .Contains (t , getTextResult (t , result ).Text , "owner/repo was deleted" )
3091+ })
3092+
3093+ t .Run ("refuses mismatched confirmation" , func (t * testing.T ) {
3094+ result := invokeDeleteRepository (t , serverTool , NewMockedHTTPClient (), & mcp.ElicitResult {
3095+ Action : "accept" ,
3096+ Content : map [string ]any {
3097+ deleteRepositoryConfirmationField : "owner/another-repo" ,
3098+ },
3099+ })
3100+
3101+ require .True (t , result .IsError )
3102+ assert .Contains (t , getErrorResult (t , result ).Text , "did not match" )
3103+ })
3104+
3105+ t .Run ("refuses declined confirmation" , func (t * testing.T ) {
3106+ result := invokeDeleteRepository (t , serverTool , NewMockedHTTPClient (), & mcp.ElicitResult {
3107+ Action : "decline" ,
3108+ })
3109+
3110+ require .True (t , result .IsError )
3111+ assert .Contains (t , getErrorResult (t , result ).Text , "was not confirmed" )
3112+ })
3113+
3114+ t .Run ("returns GitHub API errors" , func (t * testing.T ) {
3115+ client := NewMockedHTTPClient (
3116+ WithRequestMatchHandler (
3117+ DeleteReposByOwnerByRepo ,
3118+ mockResponse (t , http .StatusForbidden , map [string ]any {"message" : "Requires admin permissions" }),
3119+ ),
3120+ )
3121+ result := invokeDeleteRepository (t , serverTool , client , & mcp.ElicitResult {
3122+ Action : "accept" ,
3123+ Content : map [string ]any {
3124+ deleteRepositoryConfirmationField : "owner/repo" ,
3125+ },
3126+ })
3127+
3128+ require .True (t , result .IsError )
3129+ assert .Contains (t , getErrorResult (t , result ).Text , "failed to delete repository" )
3130+ })
3131+ }
3132+
3133+ func invokeDeleteRepository (t * testing.T , tool inventory.ServerTool , httpClient * http.Client , confirmation * mcp.ElicitResult ) * mcp.CallToolResult {
3134+ t .Helper ()
3135+
3136+ deps := BaseDeps {Client : mustNewGHClient (t , httpClient )}
3137+ handler := tool .Handler (deps )
3138+ request := createMCPRequest (map [string ]any {
3139+ "owner" : "owner" ,
3140+ "repo" : "repo" ,
3141+ })
3142+ if confirmation != nil {
3143+ request .Params .InputResponses = mcp.InputResponseMap {
3144+ deleteRepositoryConfirmationID : confirmation ,
3145+ }
3146+ }
3147+
3148+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
3149+ require .NoError (t , err )
3150+ require .NotNil (t , result )
3151+ return result
3152+ }
3153+
29873154func Test_ListBranches (t * testing.T ) {
29883155 // Verify tool definition once
29893156 serverTool := ListBranches (translations .NullTranslationHelper )
0 commit comments