mcpField
I
incident-management

incident-management

github.com/serac-labsクラウド・DevOps7763 回インストール9 週間前に更新
リポジトリ

incident-management について

ServiceNow の管理者や開発者向けのインシデント管理ガイドです。ライフサイクル、割り当て、エスカレーション、解決、指標の ES5 例と、関連操作用の MCP ツールを紹介します。

主な機能

  1. 01ライフサイクルと関連テーブルの説明
  2. 02インシデントの作成と優先度設定
  3. 03自動割り当てと再割り当て追跡
  4. 04重大インシデント、エスカレーション、解決への対応
  5. 05平均解決時間の算出

活用シーン

  1. 01インシデントレコードの作成と分類
  2. 02割り当てと再割り当ての追跡
  3. 03応答待ちの高優先度インシデントのエスカレーション
  4. 04インシデント解決指標の集計

MCP 対応

インシデントレコードの照会、クエリ実行、スクリプトのテスト、ビジネスルール作成に使う MCP ツールを掲載しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serac-labs/serac --skill incident-management

インストール後、Claude Code で/incident-managementを入力して読み込まれたか確認します。

詳細

ServiceNowのインシデント管理

インシデント管理では、ビジネスへの影響を最小限に抑えながら、サービスの正常な運用をできる限り迅速に回復します。

インシデントのライフサイクル

New (1)
    ↓
In Progress (2)
    ↓ ← On Hold (3)
Resolved (6)
    ↓
Closed (7)

Cancelled (8) ← Can occur from New/In Progress

主なテーブル

テーブル用途
incidentインシデントレコード
incident_taskインシデントのサブタスク
incident_alert関連するアラート
problem関連する問題

インシデントの作成(ES5)

基本的なインシデントの作成
// Create incident (ES5 ONLY!)
var incident = new GlideRecord("incident")
incident.initialize()

// Required fields
incident.setValue("caller_id", callerSysId)
incident.setValue("short_description", "Email not working")
incident.setValue("description", "User cannot send or receive emails since this morning")

// Classification
incident.setValue("category", "software")
incident.setValue("subcategory", "email")
incident.setValue("impact", 2)
incident.setValue("urgency", 2)
// Priority is calculated automatically from impact/urgency

// Assignment
incident.setValue("assignment_group", getGroupSysId("Email Support"))

// Optional: affected CI
incident.setValue("cmdb_ci", emailServerSysId)

var incidentSysId = incident.insert()
gs.info("Created incident: " + incident.getValue("number"))
優先度マトリックス
// Priority calculation (ES5 ONLY!)
// Priority = Impact x Urgency matrix
var priorityMatrix = {
  "1-1": 1, // High Impact + High Urgency = Critical
  "1-2": 2, // High Impact + Medium Urgency = High
  "1-3": 3, // High Impact + Low Urgency = Moderate
  "2-1": 2, // Medium Impact + High Urgency = High
  "2-2": 3, // Medium Impact + Medium Urgency = Moderate
  "2-3": 4, // Medium Impact + Low Urgency = Low
  "3-1": 3, // Low Impact + High Urgency = Moderate
  "3-2": 4, // Low Impact + Medium Urgency = Low
  "3-3": 5, // Low Impact + Low Urgency = Planning
}

function calculatePriority(impact, urgency) {
  var key = impact + "-" + urgency
  return priorityMatrix[key] || 4
}

インシデントの割り当て(ES5)

自動割り当てルール
// Assignment rule script (ES5 ONLY!)
// Business Rule: before, insert, incident

;(function executeRule(current, previous) {
  // Skip if already assigned
  if (current.assignment_group) {
    return
  }

  var group = determineAssignmentGroup(current)
  if (group) {
    current.assignment_group = group

    // Optionally assign to on-call
    var onCallUser = getOnCallUser(group)
    if (onCallUser) {
      current.assigned_to = onCallUser
    }
  }
})(current, previous)

function determineAssignmentGroup(incident) {
  var category = incident.getValue("category")
  var subcategory = incident.getValue("subcategory")

  // Category-based assignment
  var mapping = {
    network: "Network Support",
    hardware: "Desktop Support",
    "software-email": "Email Support",
    "software-erp": "ERP Support",
    database: "Database Admins",
  }

  var key = category
  if (subcategory) {
    key = category + "-" + subcategory
  }

  var groupName = mapping[key] || mapping[category] || "Service Desk"

  var group = new GlideRecord("sys_user_group")
  if (group.get("name", groupName)) {
    return group.getUniqueValue()
  }

  return null
}
追跡機能付き再割り当て
// Track reassignments (ES5 ONLY!)
// Business Rule: before, update, incident

;(function executeRule(current, previous) {
  // Check if assignment group changed
  if (current.assignment_group.changes()) {
    // Increment reassignment count
    var count = parseInt(current.getValue("reassignment_count"), 10) || 0
    current.reassignment_count = count + 1

    // Add work note
    current.work_notes =
      "Reassigned from " +
      previous.assignment_group.getDisplayValue() +
      " to " +
      current.assignment_group.getDisplayValue()

    // Alert if excessive reassignments
    if (count >= 3) {
      gs.eventQueue("incident.excessive.reassignments", current, count.toString(), "")
    }
  }
})(current, previous)

重大インシデント管理(ES5)

重大インシデントの宣言
// Declare major incident (ES5 ONLY!)
function declareMajorIncident(incidentSysId, reason) {
  var incident = new GlideRecord("incident")
  if (!incident.get(incidentSysId)) {
    return false
  }

  // Set major incident flag
  incident.setValue("major_incident_state", "confirmed")
  incident.setValue("priority", 1)

  // Set major incident fields
  incident.setValue("u_major_incident_start", new GlideDateTime())
  incident.setValue("u_major_incident_reason", reason)

  // Assign to Major Incident team
  var miTeam = new GlideRecord("sys_user_group")
  if (miTeam.get("name", "Major Incident Team")) {
    incident.setValue("assignment_group", miTeam.getUniqueValue())
  }

  // Add work note
  incident.work_notes = "MAJOR INCIDENT DECLARED\nReason: " + reason

  incident.update()

  // Trigger notifications
  gs.eventQueue("incident.major.declared", incident, "", "")

  // Create bridge call record
  createBridgeCall(incident)

  return true
}

function createBridgeCall(incident) {
  var bridge = new GlideRecord("u_bridge_call")
  bridge.initialize()
  bridge.setValue("u_incident", incident.getUniqueValue())
  bridge.setValue("u_dial_in", "1-800-555-0123")
  bridge.setValue("u_pin", generatePin())
  bridge.setValue("u_start_time", new GlideDateTime())
  bridge.insert()
}
重大インシデントに関するコミュニケーション
// Send major incident update (ES5 ONLY!)
function sendMajorIncidentUpdate(incidentSysId, updateText, recipientGroups) {
  var incident = new GlideRecord("incident")
  if (!incident.get(incidentSysId)) {
    return
  }

  // Add to work notes
  incident.work_notes = "MAJOR INCIDENT UPDATE:\n" + updateText
  incident.update()

  // Build recipient list
  var recipients = []
  for (var i = 0; i < recipientGroups.length; i++) {
    var members = getGroupMembers(recipientGroups[i])
    recipients = recipients.concat(members)
  }

  // Send notification
  var eventParams = JSON.stringify({
    update: updateText,
    recipients: recipients,
  })
  gs.eventQueue("incident.major.update", incident, eventParams, "")
}

インシデントのエスカレーション(ES5)

時間ベースのエスカレーション
// Escalation script for scheduled job (ES5 ONLY!)
;(function executeScheduledJob() {
  var LOG_PREFIX = "[IncidentEscalation] "

  // Find incidents needing escalation
  var now = new GlideDateTime()

  // P1 not acknowledged in 15 minutes
  escalateUnacknowledged("1", 15)

  // P2 not acknowledged in 30 minutes
  escalateUnacknowledged("2", 30)

  // P1 not resolved in 4 hours
  escalateUnresolved("1", 240)

  // P2 not resolved in 8 hours
  escalateUnresolved("2", 480)

  function escalateUnacknowledged(priority, minutes) {
    var threshold = new GlideDateTime()
    threshold.addSeconds(-minutes * 60)

    var gr = new GlideRecord("incident")
    gr.addQuery("priority", priority)
    gr.addQuery("state", 1) // New
    gr.addQuery("sys_created_on", "<", threshold)
    gr.addNullQuery("assigned_to")
    gr.query()

    while (gr.next()) {
      gs.info(LOG_PREFIX + "Escalating unacknowledged P" + priority + ": " + gr.number)
      gr.escalation = 1
      gr.work_notes = "Auto-escalated: Not acknowledged within " + minutes + " minutes"
      gr.update()
      gs.eventQueue("incident.escalation.unacknowledged", gr, priority, "")
    }
  }

  function escalateUnresolved(priority, minutes) {
    var threshold = new GlideDateTime()
    threshold.addSeconds(-minutes * 60)

    var gr = new GlideRecord("incident")
    gr.addQuery("priority", priority)
    gr.addQuery("state", "IN", "1,2") // New or In Progress
    gr.addQuery("sys_created_on", "<", threshold)
    gr.query()

    while (gr.next()) {
      var currentEsc = parseInt(gr.getValue("escalation"), 10) || 0
      if (currentEsc < 3) {
        gr.escalation = currentEsc + 1
        gr.work_notes = "Auto-escalated: Not resolved within target time"
        gr.update()
        notifyNextLevel(gr)
      }
    }
  }

  function notifyNextLevel(incident) {
    var group = incident.assignment_group.getRefRecord()
    if (group.manager) {
      gs.eventQueue("incident.escalation.manager", incident, group.manager, "")
    }
  }
})()

インシデントの解決(ES5)

インシデントの解決
// Resolve incident with validation (ES5 ONLY!)
function resolveIncident(incidentSysId, resolution) {
  var incident = new GlideRecord("incident")
  if (!incident.get(incidentSysId)) {
    return { success: false, message: "Incident not found" }
  }

  // Validate state transition
  var currentState = incident.getValue("state")
  if (currentState === "6" || currentState === "7") {
    return { success: false, message: "Incident already resolved/closed" }
  }

  // Validate resolution fields
  if (!resolution.code) {
    return { success: false, message: "Resolution code is required" }
  }
  if (!resolution.notes) {
    return { success: false, message: "Resolution notes are required" }
  }

  // Update incident
  incident.setValue("state", 6) // Resolved
  incident.setValue("resolution_code", resolution.code)
  incident.setValue("close_notes", resolution.notes)
  incident.setValue("resolved_at", new GlideDateTime())
  incident.setValue("resolved_by", gs.getUserID())

  // Link to problem/known error if provided
  if (resolution.problem) {
    incident.setValue("problem_id", resolution.problem)
  }
  if (resolution.knowledge) {
    incident.setValue("u_resolution_article", resolution.knowledge)
  }

  incident.update()

  // Notify caller
  gs.eventQueue("incident.resolved", incident, "", "")

  return {
    success: true,
    message: "Incident resolved",
    number: incident.getValue("number"),
  }
}

インシデントのメトリクス(ES5)

MTTRの計算
// Calculate Mean Time to Resolve (ES5 ONLY!)
function calculateMTTR(startDate, endDate, filters) {
  var ga = new GlideAggregate("incident")
  ga.addQuery("resolved_at", ">=", startDate)
  ga.addQuery("resolved_at", "<=", endDate)
  ga.addQuery("state", "IN", "6,7")

  // Apply optional filters
  if (filters.priority) {
    ga.addQuery("priority", filters.priority)
  }
  if (filters.category) {
    ga.addQuery("category", filters.category)
  }

  ga.addAggregate("AVG", "calendar_duration")
  ga.addAggregate("COUNT")
  ga.query()

  if (ga.next()) {
    var avgDuration = ga.getAggregate("AVG", "calendar_duration")
    var count = ga.getAggregate("COUNT")

    // Convert to hours
    var durationObj = new GlideDuration(avgDuration)
    var hours = durationObj.getNumericValue() / 3600000

    return {
      mttr_hours: Math.round(hours * 100) / 100,
      incident_count: parseInt(count, 10),
    }
  }

  return { mttr_hours: 0, incident_count: 0 }
}

MCPツールの統合

利用可能なツール
ツール用途
snow_record_manage (action='query')インシデントレコードの検索
snow_query_table高度なインシデント検索
snow_execute_scriptインシデントスクリプトのテスト
snow_create_business_ruleインシデント自動化の作成
ワークフローの例
// 1. Query open P1 incidents
await snow_record_manage({
  action: "query",
  table: "incident",
  query: "priority=1^active=true",
  fields: "number,short_description,assigned_to,opened_at",
})

// 2. Check incident metrics
await snow_execute_script({
  script: `
        var stats = calculateMTTR(gs.beginningOfThisMonth(), gs.endOfThisMonth(), {});
        gs.info('MTTR: ' + stats.mttr_hours + ' hours');
    `,
})

// 3. Find unassigned incidents
await snow_query_table({
  table: "incident",
  query: "active=true^assigned_toISEMPTY",
  fields: "number,short_description,priority,assignment_group",
})

ベストプラクティス

  1. 明確な分類 - 適切なカテゴリ/サブカテゴリ
  2. 影響の評価 - 影響度/緊急度を正確に評価
  3. 割り当てルール - ルーティングを自動化
  4. エスカレーション経路 - 手順を明確に定義
  5. コミュニケーション - 定期的に最新情報を共有
  6. 解決コード - 一貫した分類
  7. ナレッジの紐付け - KB記事へのリンク
  8. ES5のみ - モダンなJavaScript構文は使用しない

コメント