ServiceNow
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.
| Tool | servicenow (Table API), and servicenow_assist (ServiceNow's own MCP server, off by default) |
| Agent | servicenow, a delegate other plugins hand their ServiceNow work to |
| Event source | servicenow: incident changes from a business rule on your instance |
| Connections | servicenow and servicenow-assist, each an OAuth app of your own in the instance |
| Reaches | Your instance's host, and nothing else |
"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
adminoroauth_adminrole). - 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
oz0 plugin install ai.oz0/servicenow
Installing runs the agent's eval as the quality gate, 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.
- Give the connection your instance's host:
oz0 connection set servicenow --param instance=acme.service-now.com oz0 connection start servicenowconnection startprints a link to the edge's page for the connection. Open it: the first step shows the redirect URL to give the app. - 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.
- 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.
- 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:
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
| Function | What it does | Approval |
|---|---|---|
servicenow.search_incidents | Incidents by state, priority, group, assignee, caller, words in the short description, or update time | No |
servicenow.get_incident | One incident by number or sys_id, with its comments and work notes | No |
servicenow.search_request_items | Requested items (sc_req_item) | No |
servicenow.get_request_item | One requested item, with its stage | No |
servicenow.search_tasks | Tasks of every kind (the task table) | No |
servicenow.get_task | One task by number, whichever its table | No |
servicenow.query_table | Rows of an allowed table, with typed conditions; reads only | No |
servicenow.create_incident | A new incident | Yes |
servicenow.update_incident | Its state (new, in progress, on hold), urgency, impact, group, assignee, short description or category | Yes |
servicenow.add_comment | A comment the caller sees | Yes |
servicenow.add_work_note | A work note only fulfillers see | Yes |
servicenow.resolve_incident | Resolves it with a resolution code and notes | Yes |
Every function says what it does with MCP's annotations, and every one answers in a declared shape, so cards and contracts 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 can name your own team instead. A description, a comment, a work note and
resolution notes start with the tenant's AI label, 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:
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:
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:
(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:
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 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:
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:
- 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:oz0 connection set servicenow-assist --param instance=acme.service-now.com oz0 connection start servicenow-assist - Once connected, the server lists its tools. The plugin pins what it expects. If
oz0 plugin listshows them waiting for approval, as a change to what a plugin lists waits, approve them withoz0 plugin approve-tools servicenow. A server that later lists itself otherwise waits again. - Turn it on:
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
| Setting | Default | What it does |
|---|---|---|
SERVICENOW_TABLES | incident,sc_req_item,task,sys_user | The tables the read functions may read |
SERVICENOW_NOW_ASSIST | false | Turns on ServiceNow's own MCP server |
SERVICENOW_CA_PEM | empty | A CA certificate (PEM) your instance's certificate chains to, for an instance behind your own CA |
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 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_tablefor 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_incidentandresolve_incidentare 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.
taskspans every task table. WithtaskinSERVICENOW_TABLES, the task functions andquery_tableread 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.
Atlassian Data Center
The verified connector ai.oz0/atlassian-dc - Jira and Confluence Data Center from Atlassian's own OpenAPI documents, on your own application links, every write asking a person by default, Jira's webhooks as events.
GitLab
The verified connector ai.oz0/gitlab - GitLab's issues, merge requests, pipelines and repositories through GitLab's own MCP server, on GitLab.com or your own instance, on your own OAuth application, with GitLab's webhooks as events.