{"id":470522,"date":"2025-08-13T09:00:30","date_gmt":"2025-08-13T09:00:30","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=470522"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=470522","title":{"rendered":"<span>\u041f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043c legacy CLI \u0432 AI-\u0430\u0433\u0435\u043d\u0442\u043e\u0432 \u0437\u0430 5 \u043c\u0438\u043d\u0443\u0442: \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e MCP \u0438 Ophis \u0434\u043b\u044f Go-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: AI \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u0432 DevOps<\/h3>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 workflow DevOps-\u0438\u043d\u0436\u0435\u043d\u0435\u0440\u0430 \u0441 AI-\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043e\u043c:<\/p>\n<pre><code class=\"bash\"># \u0427\u0435\u043b\u043e\u0432\u0435\u043a \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0432 Cursor: $ kubectl get pods -n production NAME                          READY   STATUS    RESTARTS   AGE api-service-7d4b5c6-x2kl9    1\/1     Running   0          5h api-service-7d4b5c6-m3nq2    0\/1     Pending   0          2m worker-5f6d7c8-p4rs5         1\/1     Running   3          12h  # Cursor: \"\u0412\u0438\u0436\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u043f\u043e\u0434\u043e\u043c api-service-7d4b5c6-m3nq2...\" # \u0427\u0435\u043b\u043e\u0432\u0435\u043a: \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 describe # Cursor: \"\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 events...\" # \u0427\u0435\u043b\u043e\u0432\u0435\u043a: \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 events # \u0418 \u0442\u0430\u043a 10 \u0440\u0430\u0437... <\/code><\/pre>\n<p><strong>\u0411\u043e\u043b\u044c \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u0430<\/strong>: \u0440\u0443\u0447\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u043e\u0442\u0435\u0440\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043e\u0436\u043d\u043e \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0442\u044c \u0434\u043e 40% \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0442\u0430\u043a\u043e\u0439 &#171;\u0440\u0443\u0447\u043d\u043e\u0439 debugging&#187; \u0441 AI.<\/p>\n<h3>Model Context Protocol: \u043d\u043e\u0432\u044b\u0439 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438<\/h3>\n<p>MCP (Model Context Protocol) \u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u043e\u0442 Anthropic \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f LLM \u043a \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c. \u0414\u0443\u043c\u0430\u0439\u0442\u0435 \u043e \u043d\u0451\u043c \u043a\u0430\u043a \u043e LSP (Language Server Protocol), \u043d\u043e \u0434\u043b\u044f AI.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/nj\/ll\/7q\/njll7qp6bm8dsxzwhhj3djbb2du.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/nj\/ll\/7q\/njll7qp6bm8dsxzwhhj3djbb2du.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/nj\/ll\/7q\/njll7qp6bm8dsxzwhhj3djbb2du.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p><strong>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 MCP:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>Tools<\/strong>: \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>Resources<\/strong>: \u0414\u0430\u043d\u043d\u044b\u0435, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>Prompts<\/strong>: \u041f\u0440\u0435\u0434\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>JSON-RPC<\/strong>: \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b<\/p>\n<\/li>\n<\/ul>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 Ophis<\/h3>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ophis, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u043e\u0439 \u0441\u0442\u0440\u043e\u043a \u0438\u0437 README.md, \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c. Ophis \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e \u0440\u0435\u0448\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f Cobra CLI \u0432 MCP-\u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<pre><code class=\"go\">package main  import (     \"github.com\/spf13\/cobra\"     \"github.com\/spf13\/pflag\" )  \/\/ MCPParameter \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 MCP \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 type MCPParameter struct {     Name        string `json:\"name\"`     Type        string `json:\"type\"`     Description string `json:\"description\"`     Required    bool   `json:\"required\"` }  \/\/ MCPTool \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 type MCPTool struct {     Name        string         `json:\"name\"`     Description string         `json:\"description\"`     Parameters  []MCPParameter `json:\"parameters\"`     Handler     func(args []string) error }  \/\/ OphisServer \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 type OphisServer struct {     cobraRoot *cobra.Command     tools     []MCPTool }  func (s *OphisServer) TransformCobraToMCP(cmd *cobra.Command) MCPTool {     return MCPTool{         Name:        cmd.CommandPath(),         Description: cmd.Short,         Parameters:  s.extractFlags(cmd),         Handler:     cmd.RunE,     } }  \/\/ \u041c\u0430\u0433\u0438\u044f \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0437\u0434\u0435\u0441\u044c: Cobra \u0444\u043b\u0430\u0433\u0438 \u2192 MCP \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b func (s *OphisServer) extractFlags(cmd *cobra.Command) []MCPParameter {     var params []MCPParameter          cmd.Flags().VisitAll(func(flag *pflag.Flag) {         params = append(params, MCPParameter{             Name:        flag.Name,             Type:        s.inferType(flag),             Description: flag.Usage,             Required:    !flag.Changed &amp;&amp; flag.DefValue == \"\",         })     })          return params }  func (s *OphisServer) inferType(flag *pflag.Flag) string {     switch flag.Value.Type() {     case \"bool\":         return \"boolean\"     case \"int\", \"int64\":         return \"number\"     default:         return \"string\"     } } <\/code><\/pre>\n<p><strong>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>Command Discovery<\/strong>: \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043f\u043e\u0434\u043a\u043e\u043c\u0430\u043d\u0434<\/p>\n<\/li>\n<li>\n<p><strong>Parameter Mapping<\/strong>: Cobra flags \u2192 JSON Schema<\/p>\n<\/li>\n<li>\n<p><strong>Execution Wrapper<\/strong>: \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0441 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>Output Parsing<\/strong>: \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u044b\u0432\u043e\u0434\u0430 \u0434\u043b\u044f AI<\/p>\n<\/li>\n<\/ol>\n<h3>\u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h3>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u043c \u043d\u0430\u0448 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 DevOps CLI \u0432 MCP-\u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<h4>\u0428\u0430\u0433 1: \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 CLI<\/h4>\n<pre><code class=\"go\">\/\/ cmd\/root.go package cmd  import (     \"github.com\/spf13\/cobra\" )  var rootCmd = &amp;cobra.Command{     Use:   \"devops-cli\",     Short: \"DevOps automation toolkit\", }  \/\/ cmd\/deploy.go var deployCmd = &amp;cobra.Command{     Use:   \"deploy [service]\",     Short: \"Deploy service to Kubernetes\",     Args:  cobra.ExactArgs(1),     RunE: func(cmd *cobra.Command, args []string) error {         service := args[0]         env, _ := cmd.Flags().GetString(\"env\")         version, _ := cmd.Flags().GetString(\"version\")         dryRun, _ := cmd.Flags().GetBool(\"dry-run\")                  return deployService(service, env, version, dryRun)     }, }  func init() {     deployCmd.Flags().StringP(\"env\", \"e\", \"staging\", \"Environment\")     deployCmd.Flags().StringP(\"version\", \"v\", \"latest\", \"Version to deploy\")     deployCmd.Flags().BoolP(\"dry-run\", \"d\", false, \"Dry run mode\")     rootCmd.AddCommand(deployCmd) } <\/code><\/pre>\n<h4>\u0428\u0430\u0433 2: \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f Ophis<\/h4>\n<pre><code class=\"go\">\/\/ mcp\/server.go package main  import (     \"context\"     \"fmt\"     \"log\"     \"time\"          \"golang.org\/x\/time\/rate\"     \"github.com\/your-org\/devops-cli\/cmd\"     \"github.com\/abhishekjawali\/ophis\" )  \/\/ Request \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u0437\u0430\u043f\u0440\u043e\u0441 type Request struct {     Tool       string                 `json:\"tool\"`     Parameters map[string]interface{} `json:\"parameters\"` }  \/\/ Response \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u043e\u0442\u0432\u0435\u0442 type Response struct {     Content string `json:\"content\"`     IsError bool   `json:\"is_error\"` }  \/\/ Handler \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a MCP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 type Handler func(ctx context.Context, req *Request) (*Response, error)  func main() {     \/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c Ophis \u0441 \u043d\u0430\u0448\u0438\u043c CLI     server := NewServer(cmd.RootCmd())          \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c middleware \u0434\u043b\u044f \u0430\u0443\u0434\u0438\u0442\u0430     server.Use(auditMiddleware)          \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c rate limiting \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438     server.Use(rateLimitMiddleware)          \/\/ \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0434\u043b\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434     server.RegisterHook(\"deploy\", validateDeployPermissions)          \/\/ \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c MCP \u0441\u0435\u0440\u0432\u0435\u0440     if err := server.Start(\":8080\"); err != nil {         log.Fatal(err)     } }  func auditMiddleware(next Handler) Handler {     return func(ctx context.Context, req *Request) (*Response, error) {         start := time.Now()                  \/\/ \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441         log.Printf(\"MCP Request: %s %v\", req.Tool, req.Parameters)                  resp, err := next(ctx, req)                  \/\/ \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442         log.Printf(\"MCP Response: %dms, error=%v\",              time.Since(start).Milliseconds(), err)                  return resp, err     } }  func rateLimitMiddleware(next Handler) Handler {     limiter := rate.NewLimiter(rate.Every(time.Second), 10)          return func(ctx context.Context, req *Request) (*Response, error) {         if !limiter.Allow() {             return nil, fmt.Errorf(\"rate limit exceeded\")         }         return next(ctx, req)     } } <\/code><\/pre>\n<h4>\u0428\u0430\u0433 3: \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Cursor<\/h4>\n<pre><code class=\"json\">\/\/ Cursor Settings \u2192 Features \u2192 Model Context Protocol {   \"mcpServers\": {     \"devops-cli\": {       \"command\": \"\/usr\/local\/bin\/devops-mcp\",       \"args\": [\"--port\", \"8080\"],       \"env\": {         \"KUBECONFIG\": \"\/Users\/alex\/.kube\/config\",         \"VAULT_ADDR\": \"https:\/\/vault.vk.internal\"       },       \"capabilities\": {         \"tools\": true,         \"resources\": true       }     }   } }  \/\/ \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u043e: \u0447\u0435\u0440\u0435\u0437 Cursor Composer \/\/ 1. \u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 Cursor Composer (Cmd+I) \/\/ 2. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 MCP server \u0432 workspace settings \/\/ 3. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 @devops-cli \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 <\/code><\/pre>\n<h4>\u0428\u0430\u0433 4: \u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 \u0444\u0438\u0447\u0438<\/h4>\n<pre><code class=\"go\">\/\/ Event \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0434\u0435\u043f\u043b\u043e\u044f type Event struct {     Type    string `json:\"type\"`     Message string `json:\"message\"` }  \/\/ DeployRequest \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u0435\u043f\u043b\u043e\u0439 type DeployRequest struct {     Service string `json:\"service\"`     Version string `json:\"version\"`     Env     string `json:\"env\"` }  \/\/ Streaming \u0434\u043b\u044f \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 func (s *OphisServer) StreamingDeploy(ctx context.Context, req *DeployRequest) (&lt;-chan Event, error) {     events := make(chan Event, 100)          go func() {         defer close(events)                  \/\/ \u0424\u0430\u0437\u0430 1: Validation         events &lt;- Event{Type: \"validation\", Message: \"Validating manifests...\"}         if err := s.validateManifests(req); err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  \/\/ \u0424\u0430\u0437\u0430 2: Build         events &lt;- Event{Type: \"build\", Message: \"Building images...\"}         imageID, err := s.buildImage(ctx, req)         if err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  \/\/ \u0424\u0430\u0437\u0430 3: Deploy         events &lt;- Event{Type: \"deploy\", Message: fmt.Sprintf(\"Deploying %s...\", imageID)}         if err := s.deploy(ctx, imageID, req); err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  events &lt;- Event{Type: \"success\", Message: \"Deployment completed\"}     }()          return events, nil }  \/\/ Graceful shutdown \u0441 cleanup func (s *OphisServer) Shutdown(ctx context.Context) error {     log.Println(\"Starting graceful shutdown...\")          \/\/ \u041e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u0438\u0451\u043c \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432     s.mu.Lock()     s.shuttingDown = true     s.mu.Unlock()          \/\/ \u0416\u0434\u0451\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439     done := make(chan struct{})     go func() {         s.activeOps.Wait()         close(done)     }()          select {     case &lt;-done:         log.Println(\"All operations completed\")     case &lt;-ctx.Done():         log.Println(\"Forced shutdown after timeout\")     }          return nil } <\/code><\/pre>\n<h3>Production \u043a\u0435\u0439\u0441\u044b<\/h3>\n<h4>\u041a\u0435\u0439\u0441 1: \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u043e\u0432<\/h4>\n<p><strong>\u0414\u043e Ophis<\/strong>: SRE \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043b \u043b\u043e\u0433\u0438 \u043c\u0435\u0436\u0434\u0443 5-7 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438, \u0442\u0435\u0440\u044f\u044f 20-30 \u043c\u0438\u043d\u0443\u0442 \u043d\u0430 \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442.<\/p>\n<p><strong>\u041f\u043e\u0441\u043b\u0435 Ophis<\/strong>:<\/p>\n<pre><code class=\"go\">\/\/ Cursor \u043c\u043e\u0436\u0435\u0442 \u0441\u0430\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0439 runbook \"\u041f\u0440\u043e\u0432\u0435\u0440\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 api-service \u0432 production \u0438 \u043d\u0430\u0439\u0434\u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 500 \u043e\u0448\u0438\u0431\u043e\u043a\"  \/\/ MCP \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442: \/\/ 1. kubectl get pods -n production -l app=api-service \/\/ 2. kubectl logs -n production api-service-xxx --tail=100 \/\/ 3. kubectl describe pod api-service-xxx \/\/ 4. prometheus-cli query 'rate(http_requests_total{status=\"500\"}[5m])' \/\/ 5. \u0410\u043d\u0430\u043b\u0438\u0437 \u0438 \u043a\u043e\u0440\u0440\u0435\u043b\u044f\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 <\/code><\/pre>\n<p><strong>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/strong>: \u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b\u043e\u0441\u044c \u0441 25 \u0434\u043e 3 \u043c\u0438\u043d\u0443\u0442.<\/p>\n<h4>\u041a\u0435\u0439\u0441 2: \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043b\u044f junior&#8217;\u043e\u0432<\/h4>\n<pre><code class=\"go\">\/\/ ValidateDeployPermissions \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f func ValidateDeployPermissions(ctx context.Context, tool string, params map[string]any) error {     \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430     user, ok := ctx.Value(\"user\").(User)     if !ok {         return fmt.Errorf(\"user context not found\")     }          env, ok := params[\"env\"].(string)     if !ok {         return fmt.Errorf(\"env parameter required\")     }          service, ok := params[\"service\"].(string)     if !ok {         return fmt.Errorf(\"service parameter required\")     }          \/\/ Junior'\u044b \u043c\u043e\u0433\u0443\u0442 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0432 staging     if user.Level == \"junior\" &amp;&amp; env == \"production\" {         return fmt.Errorf(\"insufficient permissions: junior developers cannot deploy to production\")     }          \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b     if isCriticalService(service) {         if !hasApproval(ctx, service) {             return fmt.Errorf(\"deployment of critical service '%s' requires approval from team lead\", service)         }     }          \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f production     if env == \"production\" &amp;&amp; !isDeploymentWindow() {         return fmt.Errorf(\"production deployments are only allowed during business hours (10:00-18:00 UTC)\")     }          \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u043b\u0435\u043d\u0441\u0442\u0432\u043e \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435     if !hasTeamAccess(user, service) {         return fmt.Errorf(\"user %s does not have access to service %s\", user.ID, service)     }          return nil }  func isCriticalService(service string) bool {     criticalServices := []string{         \"payment-service\", \"auth-service\", \"user-service\", \"billing-service\",     }          for _, critical := range criticalServices {         if service == critical {             return true         }     }     return false }  func hasApproval(ctx context.Context, service string) bool {     \/\/ \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0437\u0434\u0435\u0441\u044c \u0431\u044b\u043b \u0431\u044b \u0437\u0430\u043f\u0440\u043e\u0441 \u043a API \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0439     return false }  func isDeploymentWindow() bool {     now := time.Now().UTC()     hour := now.Hour()     return hour &gt;= 10 &amp;&amp; hour &lt; 18  \/\/ 10:00-18:00 UTC }  func hasTeamAccess(user User, service string) bool {     serviceTeams := map[string][]string{         \"api-service\":      {\"backend\", \"platform\"},         \"payment-service\":  {\"payment\", \"platform\"},         \"auth-service\":     {\"security\", \"platform\"},     }          allowedTeams, exists := serviceTeams[service]     if !exists {         return true \/\/ \u0415\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u0432 \u043c\u0430\u043f\u0438\u043d\u0433\u0435, \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u0432\u0441\u0435\u043c     }          for _, userTeam := range user.Teams {         for _, allowedTeam := range allowedTeams {             if userTeam == allowedTeam {                 return true             }         }     }          return false } <\/code><\/pre>\n<h3>Performance \u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f<\/h3>\n<h4>\u0411\u0435\u043d\u0447\u043c\u0430\u0440\u043a\u0438 (MacBook Pro M4, 32GB RAM)<\/h4>\n<pre><code class=\"go\">\/\/ benchmark_test.go func BenchmarkOphisOverhead(b *testing.B) {     testCmd := &amp;cobra.Command{         Use:   \"test\",         Short: \"Test command\",         RunE:  func(cmd *cobra.Command, args []string) error { return nil },     }     server := NewServer(testCmd)          b.Run(\"DirectCLI\", func(b *testing.B) {         for i := 0; i &lt; b.N; i++ {             _ = exec.Command(\"echo\", \"test\").Run()         }     })          b.Run(\"ThroughOphis\", func(b *testing.B) {         for i := 0; i &lt; b.N; i++ {             ctx := context.Background()             req := &amp;Request{Tool: \"test\", Parameters: map[string]interface{}{}}             server.executeCommand(ctx, req)         }     }) }  \/\/ \ud83d\udd0d \u0427\u0415\u0421\u0422\u041d\u042b\u0415 \u0420\u0415\u0417\u0423\u041b\u042c\u0422\u0410\u0422\u042b \u0422\u0415\u0421\u0422\u0418\u0420\u041e\u0412\u0410\u041d\u0418\u042f (MacBook Pro M4, 14 cores): \/\/ \u0412\u0430\u0436\u043d\u043e: \u041e\u0431\u0430 \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u0420\u0415\u0410\u041b\u042c\u041d\u042b\u0415 \u043a\u043e\u043c\u0430\u043d\u0434\u044b  \/\/ \ud83d\udcca \u041e\u0414\u0418\u041d\u041e\u0427\u041d\u042b\u0415 \u041a\u041e\u041c\u0410\u041d\u0414\u042b: \/\/ Direct binary: 5.05ms \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \/\/ Ophis MCP:     2.39ms \u0441\u0440\u0435\u0434\u043d\u0435\u0435   \/\/ \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442: Ophis \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043d\u0430 52.6%  \/\/ \ud83e\udd16 BATCH \u041e\u041f\u0415\u0420\u0410\u0426\u0418\u0418 (15 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438): \/\/ Direct approach: 165.26ms total (11.02ms per command) \/\/ Ophis approach:  34.66ms total (2.31ms per command) \/\/ \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442: Ophis \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043d\u0430 79.0% (\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 130.60ms)  \/\/ \ud83d\udd2c \u0410\u041d\u0410\u041b\u0418\u0417 \u041a\u041e\u041c\u041f\u041e\u041d\u0415\u041d\u0422\u041e\u0412: \/\/ Process startup overhead: 16.56ms (\u0443\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 Ophis) \/\/ MCP processing overhead:  1.72\u03bcs (\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Ophis)   \/\/ Net benefit: 9,631x \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 \u0432 overhead  \/\/ \ud83d\udca1 \u041f\u041e\u0427\u0415\u041c\u0423 OPHIS \u0411\u042b\u0421\u0422\u0420\u0415\u0415: \/\/ \u2022 \u0418\u0437\u0431\u0435\u0433\u0430\u0435\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (16ms \u2192 0ms \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437) \/\/ \u2022 MCP overhead \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0435\u043d (1.72\u03bcs vs 16.56ms startup) \/\/ \u2022 Connection reuse: \u0443\u0436\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 Go runtime \/\/ \u2022 Batch optimization: \u044d\u0444\u0444\u0435\u043a\u0442 \u043d\u0430\u043a\u0430\u043f\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445 \/\/ \u2022 Caching potential: command discovery \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043c\u043e\u0436\u043d\u043e \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c  \/\/ \ud83c\udf0d \u0420\u0415\u0410\u041b\u042c\u041d\u042b\u0415 AI WORKFLOWS: \/\/ Human incident response: 7-10 \u043c\u0438\u043d\u0443\u0442 (\u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u2192 \u0430\u043d\u0430\u043b\u0438\u0437 \u2192 \u043a\u043e\u043c\u0430\u043d\u0434\u0430) \/\/ Cursor \u0447\u0435\u0440\u0435\u0437 Ophis: 35ms \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \/\/ Time-to-resolution: \u041c\u0418\u041d\u0423\u0422\u042b \u2192 \u0421\u0415\u041a\u0423\u041d\u0414\u042b <\/code><\/pre>\n<h4>\u041e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u043b\u044f production<\/h4>\n<pre><code class=\"go\">import (     \"fmt\"     \"strings\"     \"sync\"     \"os\/exec\"          \"github.com\/coocood\/freecache\"     \"k8s.io\/client-go\/kubernetes\"     \"k8s.io\/client-go\/tools\/clientcmd\" )  \/\/ 1. Command output caching type CommandCache struct {     cache *freecache.Cache }  func (c *CommandCache) Execute(cmd string, args []string) ([]byte, error) {     key := fmt.Sprintf(\"%s:%s\", cmd, strings.Join(args, \":\"))          \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u044d\u0448 \u0434\u043b\u044f read-only \u043a\u043e\u043c\u0430\u043d\u0434     if isReadOnly(cmd) {         if cached, err := c.cache.Get([]byte(key)); err == nil {             return cached, nil         }     }          \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443     output, err := executeCommand(cmd, args)     if err != nil {         return nil, err     }          \/\/ \u041a\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u043d\u0430 5 \u0441\u0435\u043a\u0443\u043d\u0434 \u0434\u043b\u044f read-only     if isReadOnly(cmd) {         c.cache.Set([]byte(key), output, 5)     }          return output, nil }  func executeCommand(cmd string, args []string) ([]byte, error) {     return exec.Command(cmd, args...).Output() }  func isReadOnly(cmd string) bool {     readOnlyCommands := []string{\"kubectl get\", \"kubectl describe\", \"helm list\"}     for _, readCmd := range readOnlyCommands {         if strings.HasPrefix(cmd, readCmd) {             return true         }     }     return false }  \/\/ 2. Connection pooling \u0434\u043b\u044f \u0447\u0430\u0441\u0442\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434 type ConnectionPool struct {     kubeClients sync.Pool }  func (p *ConnectionPool) GetClient() *kubernetes.Clientset {     if client := p.kubeClients.Get(); client != nil {         return client.(*kubernetes.Clientset)     }          \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0435\u0441\u043b\u0438 \u043f\u0443\u043b \u043f\u0443\u0441\u0442     kubeconfig := \"\/home\/user\/.kube\/config\"     config, _ := clientcmd.BuildConfigFromFlags(\"\", kubeconfig)     client, _ := kubernetes.NewForConfig(config)     return client } <\/code><\/pre>\n<h3>Best Practices \u0438 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438<\/h3>\n<h4>\u2705 DO:<\/h4>\n<ol>\n<li>\n<p><strong>\u0412\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u0443\u0439\u0442\u0435 MCP \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441<\/strong><\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"go\">type MCPVersion struct {     Major int           `json:\"major\"`     Minor int           `json:\"minor\"`     Patch int           `json:\"patch\"`     Tools []ToolVersion `json:\"tools\"` }  type ToolVersion struct {     Name    string `json:\"name\"`     Version string `json:\"version\"`     Hash    string `json:\"hash\"` \/\/ \u0425\u0435\u0448 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 }  \/\/ GetVersion \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e MCP \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 func (s *Server) GetVersion() MCPVersion {     tools := s.DiscoverTools()     toolVersions := make([]ToolVersion, len(tools))          for i, tool := range tools {         toolVersions[i] = ToolVersion{             Name:    tool.Name,             Version: \"1.0.0\",             Hash:    s.calculateToolHash(tool),         }     }          return MCPVersion{         Major: 1,         Minor: 0,          Patch: 0,         Tools: toolVersions,     } }  \/\/ IsCompatible \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u0439 func (v MCPVersion) IsCompatible(other MCPVersion) bool {     return v.Major == other.Major \/\/ \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b \u0435\u0441\u043b\u0438 major \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442 } <\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p><strong>\u041b\u043e\u0433\u0438\u0440\u0443\u0439\u0442\u0435 \u0432\u0441\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0430\u0443\u0434\u0438\u0442\u0430<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 circuit breaker \u0434\u043b\u044f \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432<\/strong><\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"go\">\/\/ Circuit breaker \u0437\u0430\u0449\u0438\u0449\u0430\u0435\u0442 \u043e\u0442 \u043a\u0430\u0441\u043a\u0430\u0434\u043d\u044b\u0445 \u0441\u0431\u043e\u0435\u0432 type CircuitBreaker struct {     mu           sync.RWMutex     state        CircuitState     failures     int     threshold    int           \/\/ \u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0448\u0438\u0431\u043e\u043a \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f     timeout      time.Duration \/\/ \u0412\u0440\u0435\u043c\u044f \u0434\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0432 half-open }  func (cb *CircuitBreaker) Execute(fn func() error) error {     if !cb.canExecute() {         return fmt.Errorf(\"circuit breaker is %s\", cb.state)     }          err := fn()     cb.recordResult(err == nil)     return err }  \/\/ Middleware \u0441 circuit breaker func CircuitBreakerMiddleware(cb *CircuitBreaker) Middleware {     return func(next Handler) Handler {         return func(ctx context.Context, req *Request) (*Response, error) {             var resp *Response             var err error                          cbErr := cb.Execute(func() error {                 resp, err = next(ctx, req)                 return err             })                          if cbErr != nil {                 return &amp;Response{                     Content: fmt.Sprintf(\"Service temporarily unavailable: %v\", cbErr),                     IsError: true,                 }, cbErr             }                          return resp, err         }     } } <\/code><\/pre>\n<ol start=\"4\">\n<li>\n<p><strong>\u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0439\u0442\u0435 graceful degradation<\/strong><\/p>\n<\/li>\n<\/ol>\n<p>\u0415\u0441\u043b\u0438 \u0431\u0435\u0437 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443, \u0442\u043e \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0439\u0442\u0435 \u0432 \u043b\u043e\u0433\u0435 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435.<\/p>\n<h4>\u274c DON&#8217;T:<\/h4>\n<ol>\n<li>\n<p><strong>\u041d\u0435 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u044f\u043c\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a shell<\/strong><\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"go\">\/\/ \u041f\u041b\u041e\u0425\u041e cmd := exec.Command(\"sh\", \"-c\", userInput)  \/\/ \u0425\u041e\u0420\u041e\u0428\u041e cmd := exec.Command(allowedCommands[cmdName], sanitizedArgs...) <\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p><strong>\u041d\u0435 \u043a\u044d\u0448\u0438\u0440\u0443\u0439\u0442\u0435 write-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e rate limiting<\/strong><\/p>\n<\/li>\n<\/ol>\n<h4>\u041f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438 \u0438\u0437 \u043e\u043f\u044b\u0442\u0430<\/h4>\n<p><strong>1. Context propagation<\/strong><\/p>\n<pre><code class=\"go\">\/\/ AI \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442 context \u043c\u0435\u0436\u0434\u0443 \u0432\u044b\u0437\u043e\u0432\u0430\u043c\u0438 \/\/ \u0420\u0435\u0448\u0435\u043d\u0438\u0435: \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 session management  type Session struct {     ID          string                 `json:\"id\"`     UserID      string                 `json:\"user_id\"`     Context     map[string]interface{} `json:\"context\"`     CreatedAt   time.Time              `json:\"created_at\"`     LastAccess  time.Time              `json:\"last_access\"`     mu          sync.RWMutex }  type SessionManager struct {     sessions map[string]*Session     mu       sync.RWMutex     timeout  time.Duration }  func (sm *SessionManager) GetOrCreate(sessionID, userID string) *Session {     sm.mu.Lock()     defer sm.mu.Unlock()          session, exists := sm.sessions[sessionID]     if exists {         session.updateLastAccess()         return session     }          \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e     session = &amp;Session{         ID:         sessionID,         UserID:     userID,         Context:    make(map[string]interface{}),         CreatedAt:  time.Now(),         LastAccess: time.Now(),     }          sm.sessions[sessionID] = session     return session }  \/\/ Middleware \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0439 func SessionMiddleware(sm *SessionManager) Middleware {     return func(next Handler) Handler {         return func(ctx context.Context, req *Request) (*Response, error) {             sessionID := getSessionID(req)             userID := getUserID(req)                          session := sm.GetOrCreate(sessionID, userID)             ctx = context.WithValue(ctx, \"session\", session)                          return next(ctx, req)         }     } } <\/code><\/pre>\n<p><strong>2. Streaming vs Batch<\/strong><\/p>\n<pre><code class=\"go\">\/\/ \u0414\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0432\u044b\u0432\u043e\u0434\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 streaming if expectedOutputSize &gt; 1*MB {     return streamResponse(output) } return batchResponse(output) <\/code><\/pre>\n<h3>\u0412\u044b\u0432\u043e\u0434\u044b \u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0448\u0430\u0433\u0438<\/h3>\n<p><strong>Ophis \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0443<\/strong>: \u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f AI-specific API, \u043c\u044b \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 CLI \u0432 AI-ready \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0437\u0430 \u043c\u0438\u043d\u0443\u0442\u044b.<\/p>\n<h4>\u0427\u0442\u043e \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438:<\/h4>\n<ul>\n<li>\n<p><strong>-75%<\/strong> \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0440\u0443\u0442\u0438\u043d\u043d\u044b\u0435 DevOps \u0437\u0430\u0434\u0430\u0447\u0438<\/p>\n<\/li>\n<li>\n<p><strong>+40%<\/strong> \u043f\u0440\u0438\u043d\u044f\u0442\u0438\u0435 AI-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u0440\u0435\u0434\u0438 SRE<\/p>\n<\/li>\n<li>\n<p><strong>0<\/strong> \u0447\u0430\u0441\u043e\u0432 \u043d\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>79%<\/strong> \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438 batch \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445<\/p>\n<\/li>\n<li>\n<p><strong>9,631x<\/strong> \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 overhead&#8217;\u0430 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 CLI-\u0443\u0442\u0438\u043b\u0438\u0442<\/p>\n<\/li>\n<\/ul>\n<h4>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e \u0441\u0435\u0439\u0447\u0430\u0441:<\/h4>\n<ol>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 Ophis: <code>go get github.com\/abhishekjawali\/ophis<\/code><\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u0435\u0440\u043d\u0438\u0442\u0435 \u0432\u0430\u0448 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 CLI<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Cursor MCP \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e<\/p>\n<\/li>\n<li>\n<p>Profit!<\/p>\n<\/li>\n<\/ol>\n<h4>\ud83c\udfaf \u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u043e\u0432\u0435\u0442\u044b \u0434\u043b\u044f Cursor:<\/h4>\n<p><strong>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 workspace \u0434\u043b\u044f DevOps<\/strong>:<\/p>\n<pre><code class=\"json\">\/\/ .cursor\/settings.json {   \"mcpServers\": {     \"devops\": {       \"command\": \".\/devops-mcp-server\",        \"autoStart\": true     }   },   \"composer.defaultInstructions\": [     \"Use @devops for all infrastructure commands\",     \"Always check deployment status after changes\",     \"Use dry-run for production deployments\"   ] } <\/code><\/pre>\n<p><strong>Cursor Rules \u043f\u0440\u0438\u043c\u0435\u0440\u044b<\/strong>:<\/p>\n<pre><code class=\"markdown\"># .cursorrules When user mentions deployment: 1. Use @devops status first to check current state 2. Suggest dry-run for production changes   3. Validate environment and version parameters 4. Show deployment steps before execution  For incident response: 1. Start with @devops status --verbose 2. Check logs with @devops logs --tail=100 3. Analyze metrics with @devops metrics 4. Suggest rollback steps if needed <\/code><\/pre>\n<hr\/>\n<h3>\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/njayp\/ophis\" rel=\"noopener noreferrer nofollow\">Ophis GitHub<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/modelcontextprotocol.io\" rel=\"noopener noreferrer nofollow\">MCP Specification<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cobra.dev\" rel=\"noopener noreferrer nofollow\">Cobra Documentation<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/njayp\/kubectl\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 kubectl<\/a><\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<p><em>P.S. \u0415\u0441\u043b\u0438 \u043a\u0442\u043e-\u0442\u043e \u0438\u0437 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0443\u0436\u0435 \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b MCP \u2014 \u0434\u0435\u043b\u0438\u0442\u0435\u0441\u044c \u043e\u043f\u044b\u0442\u043e\u043c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445. \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b \u043a\u0435\u0439\u0441\u044b \u0441 security \u0438 compliance.<\/em><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/936500\/\"> https:\/\/habr.com\/ru\/articles\/936500\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: AI \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u0432 DevOps<\/h3>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 workflow DevOps-\u0438\u043d\u0436\u0435\u043d\u0435\u0440\u0430 \u0441 AI-\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043e\u043c:<\/p>\n<pre><code class=\"bash\"># \u0427\u0435\u043b\u043e\u0432\u0435\u043a \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0432 Cursor: $ kubectl get pods -n production NAME                          READY   STATUS    RESTARTS   AGE api-service-7d4b5c6-x2kl9    1\/1     Running   0          5h api-service-7d4b5c6-m3nq2    0\/1     Pending   0          2m worker-5f6d7c8-p4rs5         1\/1     Running   3          12h  # Cursor: \"\u0412\u0438\u0436\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u043f\u043e\u0434\u043e\u043c api-service-7d4b5c6-m3nq2...\" # \u0427\u0435\u043b\u043e\u0432\u0435\u043a: \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 describe # Cursor: \"\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 events...\" # \u0427\u0435\u043b\u043e\u0432\u0435\u043a: \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 events # \u0418 \u0442\u0430\u043a 10 \u0440\u0430\u0437... <\/code><\/pre>\n<p><strong>\u0411\u043e\u043b\u044c \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u0430<\/strong>: \u0440\u0443\u0447\u043d\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u043e\u0442\u0435\u0440\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043e\u0436\u043d\u043e \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0442\u044c \u0434\u043e 40% \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0430 \u0442\u0430\u043a\u043e\u0439 &#171;\u0440\u0443\u0447\u043d\u043e\u0439 debugging&#187; \u0441 AI.<\/p>\n<h3>Model Context Protocol: \u043d\u043e\u0432\u044b\u0439 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438<\/h3>\n<p>MCP (Model Context Protocol) \u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u043e\u0442 Anthropic \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f LLM \u043a \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c. \u0414\u0443\u043c\u0430\u0439\u0442\u0435 \u043e \u043d\u0451\u043c \u043a\u0430\u043a \u043e LSP (Language Server Protocol), \u043d\u043e \u0434\u043b\u044f AI.<\/p>\n<figure class=\"\"><\/figure>\n<p><strong>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 MCP:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>Tools<\/strong>: \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>Resources<\/strong>: \u0414\u0430\u043d\u043d\u044b\u0435, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>Prompts<\/strong>: \u041f\u0440\u0435\u0434\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>JSON-RPC<\/strong>: \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b<\/p>\n<\/li>\n<\/ul>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 Ophis<\/h3>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ophis, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u043e\u0439 \u0441\u0442\u0440\u043e\u043a \u0438\u0437 README.md, \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c. Ophis \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e \u0440\u0435\u0448\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f Cobra CLI \u0432 MCP-\u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<pre><code class=\"go\">package main  import (     \"github.com\/spf13\/cobra\"     \"github.com\/spf13\/pflag\" )  \/\/ MCPParameter \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 MCP \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 type MCPParameter struct {     Name        string `json:\"name\"`     Type        string `json:\"type\"`     Description string `json:\"description\"`     Required    bool   `json:\"required\"` }  \/\/ MCPTool \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 type MCPTool struct {     Name        string         `json:\"name\"`     Description string         `json:\"description\"`     Parameters  []MCPParameter `json:\"parameters\"`     Handler     func(args []string) error }  \/\/ OphisServer \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 type OphisServer struct {     cobraRoot *cobra.Command     tools     []MCPTool }  func (s *OphisServer) TransformCobraToMCP(cmd *cobra.Command) MCPTool {     return MCPTool{         Name:        cmd.CommandPath(),         Description: cmd.Short,         Parameters:  s.extractFlags(cmd),         Handler:     cmd.RunE,     } }  \/\/ \u041c\u0430\u0433\u0438\u044f \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0437\u0434\u0435\u0441\u044c: Cobra \u0444\u043b\u0430\u0433\u0438 \u2192 MCP \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b func (s *OphisServer) extractFlags(cmd *cobra.Command) []MCPParameter {     var params []MCPParameter          cmd.Flags().VisitAll(func(flag *pflag.Flag) {         params = append(params, MCPParameter{             Name:        flag.Name,             Type:        s.inferType(flag),             Description: flag.Usage,             Required:    !flag.Changed &amp;&amp; flag.DefValue == \"\",         })     })          return params }  func (s *OphisServer) inferType(flag *pflag.Flag) string {     switch flag.Value.Type() {     case \"bool\":         return \"boolean\"     case \"int\", \"int64\":         return \"number\"     default:         return \"string\"     } } <\/code><\/pre>\n<p><strong>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/strong><\/p>\n<ol>\n<li>\n<p><strong>Command Discovery<\/strong>: \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043f\u043e\u0434\u043a\u043e\u043c\u0430\u043d\u0434<\/p>\n<\/li>\n<li>\n<p><strong>Parameter Mapping<\/strong>: Cobra flags \u2192 JSON Schema<\/p>\n<\/li>\n<li>\n<p><strong>Execution Wrapper<\/strong>: \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0441 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>Output Parsing<\/strong>: \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u044b\u0432\u043e\u0434\u0430 \u0434\u043b\u044f AI<\/p>\n<\/li>\n<\/ol>\n<h3>\u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h3>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u043c \u043d\u0430\u0448 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 DevOps CLI \u0432 MCP-\u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<h4>\u0428\u0430\u0433 1: \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 CLI<\/h4>\n<pre><code class=\"go\">\/\/ cmd\/root.go package cmd  import (     \"github.com\/spf13\/cobra\" )  var rootCmd = &amp;cobra.Command{     Use:   \"devops-cli\",     Short: \"DevOps automation toolkit\", }  \/\/ cmd\/deploy.go var deployCmd = &amp;cobra.Command{     Use:   \"deploy [service]\",     Short: \"Deploy service to Kubernetes\",     Args:  cobra.ExactArgs(1),     RunE: func(cmd *cobra.Command, args []string) error {         service := args[0]         env, _ := cmd.Flags().GetString(\"env\")         version, _ := cmd.Flags().GetString(\"version\")         dryRun, _ := cmd.Flags().GetBool(\"dry-run\")                  return deployService(service, env, version, dryRun)     }, }  func init() {     deployCmd.Flags().StringP(\"env\", \"e\", \"staging\", \"Environment\")     deployCmd.Flags().StringP(\"version\", \"v\", \"latest\", \"Version to deploy\")     deployCmd.Flags().BoolP(\"dry-run\", \"d\", false, \"Dry run mode\")     rootCmd.AddCommand(deployCmd) } <\/code><\/pre>\n<h4>\u0428\u0430\u0433 2: \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f Ophis<\/h4>\n<pre><code class=\"go\">\/\/ mcp\/server.go package main  import (     \"context\"     \"fmt\"     \"log\"     \"time\"          \"golang.org\/x\/time\/rate\"     \"github.com\/your-org\/devops-cli\/cmd\"     \"github.com\/abhishekjawali\/ophis\" )  \/\/ Request \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u0437\u0430\u043f\u0440\u043e\u0441 type Request struct {     Tool       string                 `json:\"tool\"`     Parameters map[string]interface{} `json:\"parameters\"` }  \/\/ Response \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 MCP \u043e\u0442\u0432\u0435\u0442 type Response struct {     Content string `json:\"content\"`     IsError bool   `json:\"is_error\"` }  \/\/ Handler \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a MCP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 type Handler func(ctx context.Context, req *Request) (*Response, error)  func main() {     \/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c Ophis \u0441 \u043d\u0430\u0448\u0438\u043c CLI     server := NewServer(cmd.RootCmd())          \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c middleware \u0434\u043b\u044f \u0430\u0443\u0434\u0438\u0442\u0430     server.Use(auditMiddleware)          \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c rate limiting \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438     server.Use(rateLimitMiddleware)          \/\/ \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0434\u043b\u044f \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434     server.RegisterHook(\"deploy\", validateDeployPermissions)          \/\/ \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c MCP \u0441\u0435\u0440\u0432\u0435\u0440     if err := server.Start(\":8080\"); err != nil {         log.Fatal(err)     } }  func auditMiddleware(next Handler) Handler {     return func(ctx context.Context, req *Request) (*Response, error) {         start := time.Now()                  \/\/ \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441         log.Printf(\"MCP Request: %s %v\", req.Tool, req.Parameters)                  resp, err := next(ctx, req)                  \/\/ \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442         log.Printf(\"MCP Response: %dms, error=%v\",              time.Since(start).Milliseconds(), err)                  return resp, err     } }  func rateLimitMiddleware(next Handler) Handler {     limiter := rate.NewLimiter(rate.Every(time.Second), 10)          return func(ctx context.Context, req *Request) (*Response, error) {         if !limiter.Allow() {             return nil, fmt.Errorf(\"rate limit exceeded\")         }         return next(ctx, req)     } } <\/code><\/pre>\n<h4>\u0428\u0430\u0433 3: \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Cursor<\/h4>\n<pre><code class=\"json\">\/\/ Cursor Settings \u2192 Features \u2192 Model Context Protocol {   \"mcpServers\": {     \"devops-cli\": {       \"command\": \"\/usr\/local\/bin\/devops-mcp\",       \"args\": [\"--port\", \"8080\"],       \"env\": {         \"KUBECONFIG\": \"\/Users\/alex\/.kube\/config\",         \"VAULT_ADDR\": \"https:\/\/vault.vk.internal\"       },       \"capabilities\": {         \"tools\": true,         \"resources\": true       }     }   } }  \/\/ \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u043e: \u0447\u0435\u0440\u0435\u0437 Cursor Composer \/\/ 1. \u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 Cursor Composer (Cmd+I) \/\/ 2. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 MCP server \u0432 workspace settings \/\/ 3. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 @devops-cli \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 <\/code><\/pre>\n<h4>\u0428\u0430\u0433 4: \u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 \u0444\u0438\u0447\u0438<\/h4>\n<pre><code class=\"go\">\/\/ Event \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0434\u0435\u043f\u043b\u043e\u044f type Event struct {     Type    string `json:\"type\"`     Message string `json:\"message\"` }  \/\/ DeployRequest \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u0435\u043f\u043b\u043e\u0439 type DeployRequest struct {     Service string `json:\"service\"`     Version string `json:\"version\"`     Env     string `json:\"env\"` }  \/\/ Streaming \u0434\u043b\u044f \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 func (s *OphisServer) StreamingDeploy(ctx context.Context, req *DeployRequest) (&lt;-chan Event, error) {     events := make(chan Event, 100)          go func() {         defer close(events)                  \/\/ \u0424\u0430\u0437\u0430 1: Validation         events &lt;- Event{Type: \"validation\", Message: \"Validating manifests...\"}         if err := s.validateManifests(req); err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  \/\/ \u0424\u0430\u0437\u0430 2: Build         events &lt;- Event{Type: \"build\", Message: \"Building images...\"}         imageID, err := s.buildImage(ctx, req)         if err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  \/\/ \u0424\u0430\u0437\u0430 3: Deploy         events &lt;- Event{Type: \"deploy\", Message: fmt.Sprintf(\"Deploying %s...\", imageID)}         if err := s.deploy(ctx, imageID, req); err != nil {             events &lt;- Event{Type: \"error\", Message: err.Error()}             return         }                  events &lt;- Event{Type: \"success\", Message: \"Deployment completed\"}     }()          return events, nil }  \/\/ Graceful shutdown \u0441 cleanup func (s *OphisServer) Shutdown(ctx context.Context) error {     log.Println(\"Starting graceful shutdown...\")          \/\/ \u041e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u0438\u0451\u043c \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432     s.mu.Lock()     s.shuttingDown = true     s.mu.Unlock()          \/\/ \u0416\u0434\u0451\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439     done := make(chan struct{})     go func() {         s.activeOps.Wait()         close(done)     }()          select {     case &lt;-done:         log.Println(\"All operations completed\")     case &lt;-ctx.Done():         log.Println(\"Forced shutdown after timeout\")     }          return nil } <\/code><\/pre>\n<h3>Production \u043a\u0435\u0439\u0441\u044b<\/h3>\n<h4>\u041a\u0435\u0439\u0441 1: \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u043e\u0432<\/h4>\n<p><strong>\u0414\u043e Ophis<\/strong>: SRE \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043b \u043b\u043e\u0433\u0438 \u043c\u0435\u0436\u0434\u0443 5-7 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438, \u0442\u0435\u0440\u044f\u044f 20-30 \u043c\u0438\u043d\u0443\u0442 \u043d\u0430 \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442.<\/p>\n<p><strong>\u041f\u043e\u0441\u043b\u0435 Ophis<\/strong>:<\/p>\n<pre><code class=\"go\">\/\/ Cursor \u043c\u043e\u0436\u0435\u0442 \u0441\u0430\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0439 runbook \"\u041f\u0440\u043e\u0432\u0435\u0440\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 api-service \u0432 production \u0438 \u043d\u0430\u0439\u0434\u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 500 \u043e\u0448\u0438\u0431\u043e\u043a\"  \/\/ MCP \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442: \/\/ 1. kubectl get pods -n production -l app=api-service \/\/ 2. kubectl logs -n production api-service-xxx --tail=100 \/\/ 3. kubectl describe pod api-service-xxx \/\/ 4. prometheus-cli query 'rate(http_requests_total{status=\"500\"}[5m])' \/\/ 5. \u0410\u043d\u0430\u043b\u0438\u0437 \u0438 \u043a\u043e\u0440\u0440\u0435\u043b\u044f\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 <\/code><\/pre>\n<p><strong>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/strong>: \u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b\u043e\u0441\u044c \u0441 25 \u0434\u043e 3 \u043c\u0438\u043d\u0443\u0442.<\/p>\n<h4>\u041a\u0435\u0439\u0441 2: \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043b\u044f junior&#8217;\u043e\u0432<\/h4>\n<pre><code class=\"go\">\/\/ ValidateDeployPermissions \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f func ValidateDeployPermissions(ctx context.Context, tool string, params map[string]any) error {     \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430     user, ok := ctx.Value(\"user\").(User)     if !ok {         return fmt.Errorf(\"user context not found\")     }          env, ok := params[\"env\"].(string)     if !ok {         return<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-470522","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/470522","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=470522"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/470522\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=470522"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=470522"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=470522"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}