---
title: "ServiceNow"
description: "The verified plugin ai.oz0/servicenow - a ServiceNow instance's incidents, requested items and tasks on its Table API, incident changes as events, and ServiceNow's own MCP server as an option."
canonical_url: "https://docs.oz0.ai/build/plugins/verified/servicenow"
---
# ServiceNow

> The verified plugin ai.oz0/servicenow - a ServiceNow instance's incidents, requested items and tasks on its Table API, incident changes as events, and ServiceNow's own MCP server as an option.

For: tenant administrators and plugin users.

`ai.oz0/servicenow` connects to a ServiceNow instance through its Table API, which every instance provides. Agents can find and read incidents, requested items and tasks, and open, update, comment on or resolve incidents.

The suggested policy asks a person before each change; an admin can change that policy with a reason. `add_comment` always asks because its additional comments are visible to the caller and emailed by the instance (`requires_approval`). Incident changes can start routines as events. Instances with Now Assist can also enable ServiceNow's MCP server.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Tool
    </td>
    
    <td>
      <code>
        servicenow
      </code>
      
       (Table API), and <code>
        servicenow_assist
      </code>
      
       (ServiceNow's own MCP server, off by default)
    </td>
  </tr>
  
  <tr>
    <td>
      Agent
    </td>
    
    <td>
      <code>
        servicenow
      </code>
      
      , a delegate other plugins hand their ServiceNow work to
    </td>
  </tr>
  
  <tr>
    <td>
      Event source
    </td>
    
    <td>
      <code>
        servicenow
      </code>
      
      : incident changes from a business rule on your instance
    </td>
  </tr>
  
  <tr>
    <td>
      Connections
    </td>
    
    <td>
      <code>
        servicenow
      </code>
      
       and <code>
        servicenow-assist
      </code>
      
      , each an OAuth app of your own in the instance
    </td>
  </tr>
  
  <tr>
    <td>
      Reaches
    </td>
    
    <td>
      Your instance's host, and nothing else
    </td>
  </tr>
</tbody>
</table>

"Verified" means the project tested, pinned and secured this plugin. It does not mean that ServiceNow endorses it.

## What it needs

- A ServiceNow instance, and an admin there who can create an OAuth app (the `admin` or `oauth_admin` role).
- A ServiceNow user for agents to act as. Every call is made as the user who signs in when you connect, so the
instance's own roles and access controls decide what agents can read and change. Give that user only what agents
should do, such as `itil`.
- A tenant admin in Orchestrator Zero to install the plugin and connect it.

## Install it

```bash
oz0 plugin install ai.oz0/servicenow
```

Installing runs the agent's eval as the [quality gate](https://docs.oz0.ai/improve/evals), on your tenant's own models, so it makes
one model call.

## Connect it to your instance

The plugin uses an OAuth app you create in your own instance. Orchestrator Zero never registers a client for you, and
it keeps no ServiceNow credential in the plugin.

1. Give the connection your instance's host:```bash
oz0 connection set servicenow --param instance=acme.service-now.com
oz0 connection start servicenow
```

<br />

`connection start` prints a link to the edge's page for the connection. Open it: the first step shows the redirect
URL to give the app.
2. In the instance, create the app. On current releases: Machine Identity Console > Inbound integrations >
New integration > OAuth; Authorization code grant. On older ones: System OAuth > **Application
Registry > New > Create an OAuth API endpoint for external clients**. Set:
  - the redirect URL from the edge's page;
  - a confidential client, with a client secret: the instance gives refresh tokens only to such a client;
  - the scope `useraccount` (the default);
  - the access token's lifespan as your policy says (1,800 seconds by default): the edge renews it before it ends.
3. On the edge's page, paste the app's client ID and client secret. The secret is sealed on the edges and sent only to
the instance you gave; it is never shown again, and never reaches a node.
4. Sign in to the instance as the user agents act as, and allow the app. The edge exchanges the code, with PKCE, and
the connection shows connected:```bash
oz0 connection show servicenow
```

The host is the one the connection was connected for. If you change `instance` later, calls get no token until you
connect again, and the client secret is not sent to the new host until you paste it again for that host.

The instance's tokens are the user's: when that user leaves or loses a role, agents lose it too. A refresh token lasts
100 days by default on an instance; when it ends or someone revokes it, the connection shows needs reconnecting
and the tenant's admins are told. Run `oz0 connection start servicenow` again.

## The functions

<table>
<thead>
  <tr>
    <th>
      Function
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Approval
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        servicenow.search_incidents
      </code>
    </td>
    
    <td>
      Incidents by state, priority, group, assignee, caller, words in the short description, or update time
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.get_incident
      </code>
    </td>
    
    <td>
      One incident by number or sys_id, with its comments and work notes
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.search_request_items
      </code>
    </td>
    
    <td>
      Requested items (<code>
        sc_req_item
      </code>
      
      )
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.get_request_item
      </code>
    </td>
    
    <td>
      One requested item, with its stage
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.search_tasks
      </code>
    </td>
    
    <td>
      Tasks of every kind (the <code>
        task
      </code>
      
       table)
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.get_task
      </code>
    </td>
    
    <td>
      One task by number, whichever its table
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.query_table
      </code>
    </td>
    
    <td>
      Rows of an allowed table, with typed conditions; reads only
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.create_incident
      </code>
    </td>
    
    <td>
      A new incident
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.update_incident
      </code>
    </td>
    
    <td>
      Its state (new, in progress, on hold), urgency, impact, group, assignee, short description or category
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.add_comment
      </code>
    </td>
    
    <td>
      A comment the caller sees
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.add_work_note
      </code>
    </td>
    
    <td>
      A work note only fulfillers see
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        servicenow.resolve_incident
      </code>
    </td>
    
    <td>
      Resolves it with a resolution code and notes
    </td>
    
    <td>
      Yes
    </td>
  </tr>
</tbody>
</table>

Every function says what it does with MCP's annotations, and every one answers in a declared shape, so cards and
[contracts](https://docs.oz0.ai/build/parts/contracts-and-limits) can use the result. What a record holds (a description, a comment) is
outside text, and jobs that read it are tainted.

Every write waits for a person. The default deciders are the tenant's operators, never the person who started the
job; your [tool policy](https://docs.oz0.ai/govern/tool-policy) can name your own team instead. A description, a comment, a work note and
resolution notes start with the tenant's [AI label](https://docs.oz0.ai/govern/ai-labels), such as `[AI agent] `.

`create_incident` is safe to repeat: it keeps the call's idempotency key in the incident's `correlation_id`, and a call
made again with the same key answers the first incident instead of making a second one. `update_incident` and
`resolve_incident` are safe to repeat too: the same change made again changes nothing more. These three say so, so the
platform may make them again after a lost node. A comment or a work note made twice is two entries, so the platform
makes those once.

An instance's rate limit (429) is not made and not tried again by the plugin: the agent reads an error that says it is
retryable and how long the instance asks to wait (its `Retry-After`), and the platform's one retry layer decides.

### Queries are built, never written by a model

ServiceNow's encoded queries (`sysparm_query`) join their terms with `^`, and the instance runs a value that starts with
`javascript:` as script. So no function takes a query: they take typed filters, and `query_table` takes conditions of a
field, an operator (`equals`, `contains`, `starts_with`, `in`, `at_least`, `is_empty` and the like) and a value. The
plugin refuses a value with `^`, a line break or a leading `javascript:`, and a field that dot-walks into another table,
before anything reaches the instance.

Every read function reads only the tables the setting `SERVICENOW_TABLES` lists, `incident,sc_req_item,task,sys_user`
unless you change it: `query_table` any of them, and the typed functions their own table (`incident`, `sc_req_item` or
`task`), which they refuse when it is not listed. `task` is the table every task-like table extends, so listing it lets
`search_tasks`, `get_task` and `query_table` read change requests, problems, catalog tasks and any other table built on
it, such as HR or security cases, as far as the connected user may. Leave `task` out to stop that:

```bash
oz0 setting set SERVICENOW_TABLES incident,sc_req_item,task,sys_user,cmdb_ci
```

The setting holds reads only. The writes change incidents whatever it lists, since each waits for a person. The
instance's own access controls apply on top, as the connected user's.

## Events: incident changes

The `servicenow` event source takes incident changes at `https://<edge>/events/<tenant>/servicenow`. Each delivery
carries a token you choose in the header `X-Oz0-Event-Token`, which the edge compares with the tenant secret
`SERVICENOW_EVENTS_TOKEN` in constant time before anything reads the delivery.

Make a token and set it:

```bash
openssl rand -hex 32 | oz0 secret set SERVICENOW_EVENTS_TOKEN --signing --host edge.example.com:7443
```

Then, in the instance, keep the same token where only admins can read it, such as a system property
`oz0.events_token`, and add a business rule on the Incident table: When async, on Insert and
Update, with this script:

```javascript
(function executeRule(current, previous) {
  var r = new sn_ws.RESTMessageV2();
  r.setEndpoint('https://edge.example.com/events/acme/servicenow');
  r.setHttpMethod('post');
  r.setRequestHeader('Content-Type', 'application/json');
  r.setRequestHeader('X-Oz0-Event-Token', gs.getProperty('oz0.events_token'));
  r.setRequestHeader('X-Oz0-Delivery', current.getUniqueValue() + ':' + current.getValue('sys_mod_count'));
  r.setRequestBody(JSON.stringify({
    table: 'incident',
    operation: current.operation(),
    sys_id: current.getUniqueValue(),
    number: current.getValue('number'),
    state: current.getValue('state'),
    priority: current.getValue('priority'),
    short_description: current.getValue('short_description'),
    assignment_group: current.assignment_group.getDisplayValue(),
    sys_updated_on: current.getValue('sys_updated_on'),
    sys_mod_count: current.getValue('sys_mod_count')
  }));
  r.executeAsync();
})(current, previous);
```

Each delivery becomes one event of type `incident`, with the action `created`, `updated`, `resolved`, `closed` or
`canceled`, the incident's number as its subject, and its number, state, priority and group as attributes that
routines filter on:

```yaml
triggers:
  - name: changed
    event:
      source: servicenow
      types: [incident]
      filter: event.action == 'created' && event.attributes.priority <= 2
```

The delivery's ID is the record's sys_id and update count, so a delivery the instance sends again is taken once.

The plugin's tools run in [sandbox tier 1](https://docs.oz0.ai/concepts/security) on nodes that have it, where the network goes only
through the egress proxy. Your instance is yours, so its host is on no plugin's list: add it to the tenant's list so
that its calls reach it:

```bash
oz0 tenant egress acme <the hosts listed now> acme.service-now.com
```

## ServiceNow's own MCP server, where you have Now Assist

ServiceNow's MCP Server Console serves Now Assist skills over MCP. It needs a Now Assist licence, and each call spends
one of your assists. The plugin can call its Quickstart Server
(`https://<instance>/sncapps/mcp-server/mcp/sn_mcp_server_default`, which looks up and summarises incidents and cases)
as the tool `servicenow_assist`. It is off until you turn it on:

1. In the instance's MCP Server Console, create an OAuth inbound integration for Orchestrator Zero (scope
`mcp_server`, the redirect URL from the edge's page), and connect it:```bash
oz0 connection set servicenow-assist --param instance=acme.service-now.com
oz0 connection start servicenow-assist
```
2. Once connected, the server lists its tools. The plugin pins what it expects. If `oz0 plugin list` shows them
waiting for approval, as a change to what a plugin lists waits, approve them with
`oz0 plugin approve-tools servicenow`. A server that later lists itself otherwise waits again.
3. Turn it on:```bash
oz0 setting set SERVICENOW_NOW_ASSIST true
```

Until then, the plugin's own hook refuses every call of `servicenow_assist`, and none spends an assist. Once on, every
call still waits for a person, and each spends one assist, which the job's usage shows as `now-assist, 1 assist` next
to its model calls, so reports count it. Budgets count US dollars, and ServiceNow publishes no price per assist, so an
assist is reported at $0 and counts against no budget: the approval of each call is what limits them. The plugin's agent does not call the server; a plugin that wants
it names its functions itself.

## Settings

<table>
<thead>
  <tr>
    <th>
      Setting
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      What it does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        SERVICENOW_TABLES
      </code>
    </td>
    
    <td>
      <code>
        incident,sc_req_item,task,sys_user
      </code>
    </td>
    
    <td>
      The tables the read functions may read
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SERVICENOW_NOW_ASSIST
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Turns on ServiceNow's own MCP server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SERVICENOW_CA_PEM
      </code>
    </td>
    
    <td>
      empty
    </td>
    
    <td>
      A CA certificate (PEM) your instance's certificate chains to, for an instance behind your own CA
    </td>
  </tr>
</tbody>
</table>

## Where it connects

Every call goes to your instance's host and nowhere else, over TLS that is checked. The plugin checks the host with the
[egress rules](https://docs.oz0.ai/concepts/security) first: it reaches public addresses, and an instance on your own network only when the
tenant's egress list names it. It connects to the address it checked. The edge talks to the same host when it connects
and renews the token.

## Limits

- Tested against a fake instance, not a live one. The project tests the plugin against a fake shaped from
ServiceNow's documentation of the Table API, OAuth and the MCP Server Console. Check it on a test instance of yours
before you rely on it.
- One user's access. Every call is the connected user's. There is no per-person access through this plugin.
- No client credentials grant. The connection signs in with the authorization code grant. A machine-to-machine
client (client credentials) is not offered yet.
- Customised instances. The plugin sends a base instance's incident states (1 New, 2 In Progress, 3 On Hold,
6 Resolved) and urgency and impact values (1 to 3). An instance with other values needs `query_table` for reads,
and its writes may be refused by its data policies.
- Comments and work notes are not idempotent. A repeated call adds a second entry. `create_incident`,
`update_incident` and `resolve_incident` are safe to repeat.
- ServiceNow's own MCP server. Only the Quickstart Server at its documented path; another server of the console
needs its own plugin. ServiceNow documents what its tools do, not their exact listing, so the pinned snapshot comes
from the fake: if the live server lists itself otherwise, its functions wait for approval and the job says so. Its
results are text only. Each call may spend one of your vendor's credits (an assist), which the plugin reports
because the server reports nothing.
- Assists are counted, not priced. They show on the job's usage and in reports as units, at $0, so no dollar
budget or quota stops them. Your contract decides what an assist costs; until a tenant can set a price per unit,
each call's approval is the limit.
- `task` spans every task table. With `task` in `SERVICENOW_TABLES`, the task functions and `query_table` read
every table that extends it (changes, problems, HR or security cases) as far as the connected user may.
- Events. Only incidents, through the business rule above. Deletions are not sent.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
