mcpField
S
script-include-patterns

script-include-patterns

github.com/serac-labs开发7758 次安装9 周前更新
代码仓库

关于 script-include-patterns

面向 ServiceNow 开发者的 Script Include 编写技能,帮助构建可复用的服务端代码。它提供各类 Script Include、继承和 GlideAjax 的 ES5 模式,并介绍相关实践。

主要功能

  1. 01介绍标准和客户端可调用的 Script Include 模式
  2. 02提供继承与作用域 Script Include 的 ES5 示例
  3. 03提供 GlideAjax 客户端调用示例
  4. 04介绍命名、职责划分和错误处理实践
  5. 05通过 MCP 工具创建、查找、更新和测试 Script Include

适用场景

  1. 01创建可复用的服务端工具类
  2. 02通过 GlideAjax 向客户端脚本开放 Script Include
  3. 03查找并修改现有 Script Include
  4. 04使用 MCP 工具测试 Script Include

MCP 相关

README 提到用于创建、查找、更新和测试 Script Include 的 MCP 工具,但未说明提供这些工具的 MCP 服务或客户端。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/serac-labs/serac --skill script-include-patterns

安装后在 Claude Code 中输入/script-include-patterns确认已加载。

详细介绍

ServiceNow 脚本包含项模式

脚本包含项是可在任何服务端脚本中调用的可复用服务端 JavaScript 库。

脚本包含项类型

类型用途可从客户端调用
标准服务端实用工具否
可从客户端调用从客户端调用 GlideAjax是
按需加载延迟加载否
AbstractAjaxProcessor客户端与服务端通信是

标准脚本包含项(ES5)

// Basic utility class
var IncidentUtils = Class.create()
IncidentUtils.prototype = {
  initialize: function () {
    this.LOG_PREFIX = "[IncidentUtils] "
  },

  /**
   * Get incident by number
   * @param {string} number - Incident number (INC0010001)
   * @returns {GlideRecord|null} - Incident record or null
   */
  getByNumber: function (number) {
    var gr = new GlideRecord("incident")
    gr.addQuery("number", number)
    gr.query()
    if (gr.next()) {
      return gr
    }
    return null
  },

  /**
   * Calculate priority based on impact and urgency
   * @param {number} impact - Impact value (1-3)
   * @param {number} urgency - Urgency value (1-3)
   * @returns {number} - Calculated priority (1-5)
   */
  calculatePriority: function (impact, urgency) {
    var matrix = {
      "1-1": 1,
      "1-2": 2,
      "1-3": 3,
      "2-1": 2,
      "2-2": 3,
      "2-3": 4,
      "3-1": 3,
      "3-2": 4,
      "3-3": 5,
    }
    var key = impact + "-" + urgency
    return matrix[key] || 4
  },

  /**
   * Get open incidents for user
   * @param {string} userSysId - User sys_id
   * @returns {Array} - Array of incident objects
   */
  getOpenIncidentsForUser: function (userSysId) {
    var incidents = []
    var gr = new GlideRecord("incident")
    gr.addQuery("caller_id", userSysId)
    gr.addQuery("active", true)
    gr.orderByDesc("opened_at")
    gr.query()

    while (gr.next()) {
      incidents.push({
        sys_id: gr.getUniqueValue(),
        number: gr.getValue("number"),
        short_description: gr.getValue("short_description"),
        state: gr.state.getDisplayValue(),
        priority: gr.priority.getDisplayValue(),
      })
    }
    return incidents
  },

  type: "IncidentUtils",
}

可从客户端调用的脚本包含项(ES5)

// Extends AbstractAjaxProcessor for GlideAjax calls
var IncidentAjax = Class.create()
IncidentAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  /**
   * Get incident details - callable from client
   * Client calls: new GlideAjax('IncidentAjax').addParam('sysparm_name', 'getIncidentDetails')
   */
  getIncidentDetails: function () {
    var incidentId = this.getParameter("sysparm_incident_id")
    var result = {}

    var gr = new GlideRecord("incident")
    if (gr.get(incidentId)) {
      result.success = true
      result.number = gr.getValue("number")
      result.short_description = gr.getValue("short_description")
      result.state = gr.state.getDisplayValue()
      result.priority = gr.priority.getDisplayValue()
      result.assigned_to = gr.assigned_to.getDisplayValue()
      result.assignment_group = gr.assignment_group.getDisplayValue()
    } else {
      result.success = false
      result.message = "Incident not found"
    }

    return JSON.stringify(result)
  },

  /**
   * Search incidents by keyword
   */
  searchIncidents: function () {
    var keyword = this.getParameter("sysparm_keyword")
    var limit = parseInt(this.getParameter("sysparm_limit"), 10) || 10
    var incidents = []

    var gr = new GlideRecord("incident")
    gr.addQuery("short_description", "CONTAINS", keyword)
    gr.addOrCondition("description", "CONTAINS", keyword)
    gr.addQuery("active", true)
    gr.setLimit(limit)
    gr.orderByDesc("opened_at")
    gr.query()

    while (gr.next()) {
      incidents.push({
        sys_id: gr.getUniqueValue(),
        number: gr.getValue("number"),
        short_description: gr.getValue("short_description"),
      })
    }

    return JSON.stringify(incidents)
  },

  /**
   * Check if user can update incident
   */
  canUserUpdate: function () {
    var incidentId = this.getParameter("sysparm_incident_id")
    var userId = gs.getUserID()

    var gr = new GlideRecord("incident")
    if (gr.get(incidentId)) {
      // Check if user is assigned or in assignment group
      var canUpdate =
        gr.getValue("assigned_to") === userId || this._isUserInGroup(userId, gr.getValue("assignment_group"))
      return JSON.stringify({ canUpdate: canUpdate })
    }

    return JSON.stringify({ canUpdate: false })
  },

  _isUserInGroup: function (userId, groupId) {
    var member = new GlideRecord("sys_user_grmember")
    member.addQuery("user", userId)
    member.addQuery("group", groupId)
    member.query()
    return member.hasNext()
  },

  type: "IncidentAjax",
})

客户端 GlideAjax 调用(ES5)

// Client script calling Script Include
function getIncidentDetails(incidentSysId, callback) {
  var ga = new GlideAjax("IncidentAjax")
  ga.addParam("sysparm_name", "getIncidentDetails")
  ga.addParam("sysparm_incident_id", incidentSysId)
  ga.getXMLAnswer(function (answer) {
    var result = JSON.parse(answer)
    callback(result)
  })
}

// Usage in client script
getIncidentDetails(g_form.getUniqueValue(), function (incident) {
  if (incident.success) {
    g_form.addInfoMessage("Incident: " + incident.number)
  } else {
    g_form.addErrorMessage(incident.message)
  }
})

继承模式(ES5)

// Base class
var TaskUtils = Class.create()
TaskUtils.prototype = {
  initialize: function (tableName) {
    this.tableName = tableName || "task"
  },

  getByState: function (state) {
    var records = []
    var gr = new GlideRecord(this.tableName)
    gr.addQuery("state", state)
    gr.query()
    while (gr.next()) {
      records.push(this._toObject(gr))
    }
    return records
  },

  _toObject: function (gr) {
    return {
      sys_id: gr.getUniqueValue(),
      number: gr.getValue("number"),
      short_description: gr.getValue("short_description"),
      state: gr.getValue("state"),
    }
  },

  type: "TaskUtils",
}

// Derived class
var IncidentUtilsExtended = Class.create()
IncidentUtilsExtended.prototype = Object.extendsObject(TaskUtils, {
  initialize: function () {
    TaskUtils.prototype.initialize.call(this, "incident")
  },

  getP1Incidents: function () {
    var incidents = []
    var gr = new GlideRecord("incident")
    gr.addQuery("priority", 1)
    gr.addQuery("active", true)
    gr.query()
    while (gr.next()) {
      var obj = this._toObject(gr)
      obj.caller = gr.caller_id.getDisplayValue()
      incidents.push(obj)
    }
    return incidents
  },

  type: "IncidentUtilsExtended",
})

作用域脚本包含项(ES5)

// In scoped application: x_myapp
var MyAppUtils = Class.create()
MyAppUtils.prototype = {
  initialize: function () {
    this.APP_SCOPE = "x_myapp"
  },

  /**
   * Get application property
   * @param {string} name - Property name (without scope prefix)
   */
  getAppProperty: function (name) {
    return gs.getProperty(this.APP_SCOPE + "." + name)
  },

  /**
   * Log with application prefix
   */
  log: function (message, source) {
    gs.info("[" + this.APP_SCOPE + "][" + (source || "MyAppUtils") + "] " + message)
  },

  /**
   * Access cross-scope table safely
   */
  getGlobalUser: function (userId) {
    var gr = new GlideRecord("sys_user")
    if (gr.get(userId)) {
      return {
        name: gr.getValue("name"),
        email: gr.getValue("email"),
      }
    }
    return null
  },

  type: "MyAppUtils",
}

MCP 工具集成

可用的脚本包含项工具
工具用途
snow_create_script_include创建新的脚本包含项
snow_artifact_manage (action='find')查找现有脚本包含项
snow_artifact_manage (action='update')修改脚本包含项代码
snow_execute_script测试脚本包含项
示例工作流
// 1. Create Script Include
await snow_create_script_include({
  name: "IncidentUtils",
  script: "/* Script Include code */",
  client_callable: false,
  description: "Incident utility functions",
})

// 2. Test the Script Include
await snow_execute_script({
  script: `
        var utils = new IncidentUtils();
        var incident = utils.getByNumber('INC0010001');
        gs.info('Found: ' + (incident ? incident.number : 'null'));
    `,
})

最佳实践

  1. 单一职责 - 一个类,一个用途
  2. 有意义的名称 - 使用 IncidentUtils,而不是 Utils
  3. 记录方法 - 添加 JSDoc 注释
  4. 错误处理 - 使用带日志记录的 try-catch
  5. 私有方法 - 以下划线作为前缀
  6. 无副作用 - 初始化不应修改数据
  7. 可测试 - 编写可进行单元测试的方法
  8. 仅使用 ES5 - 不使用 const、let、箭头函数和模板字面量

评论

支持 Markdown