mcpField
A
asset-management

asset-management

github.com/serac-labsCloud & DevOps7782 installsUpdated 9w ago
Repository

About asset-management

A ServiceNow skill for tracking hardware and software assets throughout their lifecycle. It covers asset records, software license allocation, matching discovered configuration items, and inventory reporting, with ES5 examples.

Key features

  1. 01Hardware asset records
  2. 02Software license records and allocation
  3. 03Match CIs to assets
  4. 04Lifecycle status tracking
  5. 05Inventory summary reporting
  6. 06ES5 script examples

Use cases

  1. 01Track hardware through its lifecycle
  2. 02Allocate software licenses to users
  3. 03Match discovered CIs with assets
  4. 04Summarize inventory by status or location

MCP support

It documents MCP tools to query assets and licenses, search the CMDB for configuration items, test scripts, and find asset configurations.

Install

Copy the command and run it in a terminal.

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

After installing, type/asset-managementin Claude Code to confirm it loaded.

Details

From the project README

Asset Management for ServiceNow

Asset Management tracks hardware and software assets throughout their lifecycle.

Asset Architecture

Asset (alm_asset)
    ├── Hardware Asset (alm_hardware)
    │   └── Consumable (alm_consumable)
    └── License (alm_license)

CI (cmdb_ci) ←→ Asset (alm_asset)
    ↑
    One CI can have multiple assets over time

Key Tables

TablePurpose
alm_assetBase asset table
alm_hardwareHardware assets
alm_licenseSoftware licenses
alm_consumableConsumable assets
ast_contractAsset contracts
cmdb_ciConfiguration items

Hardware Assets (ES5)

Create Hardware Asset
// Create hardware asset (ES5 ONLY!)
var asset = new GlideRecord("alm_hardware")
asset.initialize()

// Basic info
asset.setValue("display_name", "Dell Latitude 5520")
asset.setValue("asset_tag", "ASSET-" + generateAssetTag())
asset.setValue("serial_number", "SN123456789")

// Model
asset.setValue("model", getModelSysId("Dell Latitude 5520"))
asset.setValue("model_category", getModelCategorySysId("Laptop"))

// Status and substatus
asset.setValue("install_status", 1) // Installed
asset.setValue("substatus", "in_use")

// Assignment
asset.setValue("assigned_to", userSysId)
asset.setValue("assigned", new GlideDateTime())
asset.setValue("location", locationSysId)
asset.setValue("department", departmentSysId)

// Financial
asset.setValue("cost", 1299.99)
asset.setValue("cost_center", costCenterSysId)

// Dates
asset.setValue("purchase_date", "2024-01-15")
asset.setValue("warranty_expiration", "2027-01-15")

// Link to CI if exists
asset.setValue("ci", cmdbCiSysId)

asset.insert()
Asset Lifecycle States
// Asset install_status values
var ASSET_STATUS = {
  INSTALLED: 1, // In use
  ON_ORDER: 2, // Ordered, not received
  IN_STOCK: 6, // In inventory
  IN_TRANSIT: 7, // Being shipped
  IN_MAINTENANCE: 8, // Under repair
  RETIRED: 9, // End of life
  DISPOSED: 10, // Disposed of
}

// Transition asset status (ES5 ONLY!)
function transitionAssetStatus(assetSysId, newStatus, notes) {
  var asset = new GlideRecord("alm_hardware")
  if (!asset.get(assetSysId)) {
    return { success: false, message: "Asset not found" }
  }

  var currentStatus = parseInt(asset.getValue("install_status"), 10)

  // Validate transition
  var validTransitions = {
    2: [6, 7], // On Order -> In Stock, In Transit
    7: [6, 1], // In Transit -> In Stock, Installed
    6: [1, 7, 9], // In Stock -> Installed, In Transit, Retired
    1: [8, 6, 9], // Installed -> In Maintenance, In Stock, Retired
    8: [1, 6, 9], // In Maintenance -> Installed, In Stock, Retired
    9: [10], // Retired -> Disposed
  }

  if (!validTransitions[currentStatus] || validTransitions[currentStatus].indexOf(newStatus) === -1) {
    return {
      success: false,
      message: "Invalid transition from " + currentStatus + " to " + newStatus,
    }
  }

  // Update status
  asset.setValue("install_status", newStatus)

  // Handle specific transitions
  if (newStatus === 1) {
    asset.setValue("installed", new GlideDateTime())
  } else if (newStatus === 9) {
    asset.setValue("retired", new GlideDateTime())
    asset.setValue("assigned_to", "")
  } else if (newStatus === 10) {
    asset.setValue("disposed", new GlideDateTime())
  }

  // Add work note
  if (notes) {
    asset.work_notes = notes
  }

  asset.update()

  return { success: true, asset_tag: asset.getValue("asset_tag") }
}

Software Licenses (ES5)

Create License Record
// Create software license (ES5 ONLY!)
var license = new GlideRecord("alm_license")
license.initialize()

// Basic info
license.setValue("display_name", "Microsoft Office 365 E3")
license.setValue("product", getProductSysId("Microsoft Office 365"))
license.setValue("license_type", "per_user") // per_user, per_device, site, enterprise

// Quantities
license.setValue("rights", 500) // Total licenses purchased
license.setValue("used", 0) // Will be calculated
license.setValue("remaining", 500) // Will be calculated

// Dates
license.setValue("start_date", "2024-01-01")
license.setValue("end_date", "2024-12-31")

// Cost
license.setValue("cost", 25000.0)
license.setValue("cost_per_unit", 50.0)

// Vendor
license.setValue("vendor", vendorSysId)
license.setValue("contract", contractSysId)

license.insert()
License Allocation
// Allocate license to user (ES5 ONLY!)
function allocateLicense(licenseSysId, userSysId) {
  var license = new GlideRecord("alm_license")
  if (!license.get(licenseSysId)) {
    return { success: false, message: "License not found" }
  }

  // Check availability
  var remaining = parseInt(license.getValue("remaining"), 10)
  if (remaining <= 0) {
    return { success: false, message: "No licenses available" }
  }

  // Check if user already has this license
  var existing = new GlideRecord("alm_entitlement_user")
  existing.addQuery("licensed_by", licenseSysId)
  existing.addQuery("user", userSysId)
  existing.query()

  if (existing.hasNext()) {
    return { success: false, message: "User already has this license" }
  }

  // Create entitlement
  var entitlement = new GlideRecord("alm_entitlement_user")
  entitlement.initialize()
  entitlement.setValue("licensed_by", licenseSysId)
  entitlement.setValue("user", userSysId)
  entitlement.setValue("allocated", new GlideDateTime())
  entitlement.insert()

  // Update license counts
  updateLicenseCounts(licenseSysId)

  return {
    success: true,
    message: "License allocated",
    entitlement: entitlement.getUniqueValue(),
  }
}

function updateLicenseCounts(licenseSysId) {
  var license = new GlideRecord("alm_license")
  if (!license.get(licenseSysId)) return

  // Count allocations
  var ga = new GlideAggregate("alm_entitlement_user")
  ga.addQuery("licensed_by", licenseSysId)
  ga.addAggregate("COUNT")
  ga.query()

  var used = 0
  if (ga.next()) {
    used = parseInt(ga.getAggregate("COUNT"), 10)
  }

  var rights = parseInt(license.getValue("rights"), 10)

  license.setValue("used", used)
  license.setValue("remaining", rights - used)
  license.update()
}

Asset Discovery Integration (ES5)

Match Discovered CI to Asset
// Match discovered CI to existing asset (ES5 ONLY!)
function matchCIToAsset(ciSysId) {
  var ci = new GlideRecord("cmdb_ci_computer")
  if (!ci.get(ciSysId)) {
    return null
  }

  var serialNumber = ci.getValue("serial_number")
  var assetTag = ci.getValue("asset_tag")

  // Try to find matching asset
  var asset = new GlideRecord("alm_hardware")

  // Match by serial number first
  if (serialNumber) {
    asset.addQuery("serial_number", serialNumber)
    asset.query()
    if (asset.next()) {
      return linkAssetToCI(asset, ci)
    }
  }

  // Match by asset tag
  if (assetTag) {
    asset = new GlideRecord("alm_hardware")
    asset.addQuery("asset_tag", assetTag)
    asset.query()
    if (asset.next()) {
      return linkAssetToCI(asset, ci)
    }
  }

  // No match - create new asset
  return createAssetFromCI(ci)
}

function linkAssetToCI(asset, ci) {
  asset.setValue("ci", ci.getUniqueValue())
  asset.update()

  ci.setValue("asset", asset.getUniqueValue())
  ci.update()

  return asset.getUniqueValue()
}

function createAssetFromCI(ci) {
  var asset = new GlideRecord("alm_hardware")
  asset.initialize()
  asset.setValue("display_name", ci.getDisplayValue())
  asset.setValue("serial_number", ci.getValue("serial_number"))
  asset.setValue("asset_tag", ci.getValue("asset_tag"))
  asset.setValue("model", ci.getValue("model_id"))
  asset.setValue("ci", ci.getUniqueValue())
  asset.setValue("install_status", 1)

  var assetSysId = asset.insert()

  ci.setValue("asset", assetSysId)
  ci.update()

  return assetSysId
}

Asset Reports (ES5)

Asset Inventory Summary
// Get asset inventory summary (ES5 ONLY!)
function getAssetInventorySummary() {
  var summary = {
    by_status: {},
    by_category: {},
    by_location: {},
    total_value: 0,
  }

  // By status
  var ga = new GlideAggregate("alm_hardware")
  ga.addAggregate("COUNT")
  ga.addAggregate("SUM", "cost")
  ga.groupBy("install_status")
  ga.query()

  while (ga.next()) {
    var status = ga.install_status.getDisplayValue()
    summary.by_status[status] = {
      count: parseInt(ga.getAggregate("COUNT"), 10),
      value: parseFloat(ga.getAggregate("SUM", "cost")) || 0,
    }
    summary.total_value += summary.by_status[status].value
  }

  // By model category
  ga = new GlideAggregate("alm_hardware")
  ga.addAggregate("COUNT")
  ga.groupBy("model_category")
  ga.query()

  while (ga.next()) {
    var category = ga.model_category.getDisplayValue() || "Uncategorized"
    summary.by_category[category] = parseInt(ga.getAggregate("COUNT"), 10)
  }

  // By location
  ga = new GlideAggregate("alm_hardware")
  ga.addQuery("install_status", 1) // Only installed
  ga.addAggregate("COUNT")
  ga.groupBy("location")
  ga.query()

  while (ga.next()) {
    var location = ga.location.getDisplayValue() || "Unknown"
    summary.by_location[location] = parseInt(ga.getAggregate("COUNT"), 10)
  }

  return summary
}

MCP Tool Integration

Available Tools
ToolPurpose
snow_query_tableQuery assets and licenses
snow_cmdb_searchSearch CMDB for CIs
snow_execute_scriptTest asset scripts
snow_artifact_manageFind asset configurations (action: find)
Example Workflow
// 1. Query hardware assets
await snow_query_table({
  table: "alm_hardware",
  query: "install_status=1",
  fields: "asset_tag,display_name,assigned_to,location,model",
})

// 2. Check license compliance
await snow_execute_script({
  script: `
        var license = new GlideRecord('alm_license');
        license.addQuery('remainingRELATIVELT@integer@0');
        license.query();
        while (license.next()) {
            gs.info('Over-allocated: ' + license.display_name);
        }
    `,
})

// 3. Find assets nearing warranty expiration
await snow_query_table({
  table: "alm_hardware",
  query: "warranty_expirationBETWEENjavascript:gs.beginningOfToday()@javascript:gs.daysAgoEnd(-30)",
  fields: "asset_tag,display_name,warranty_expiration,assigned_to",
})

Best Practices

  1. Asset Tags - Unique, scannable identifiers
  2. Lifecycle Tracking - Track all state changes
  3. CI Linking - Connect assets to CMDB
  4. License Compliance - Monitor allocation vs rights
  5. Warranty Tracking - Alert before expiration
  6. Financial Accuracy - Maintain cost data
  7. Regular Audits - Verify physical inventory
  8. ES5 Only - No modern JavaScript syntax

Comments

Markdown supported