Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ServiceNow Custom API Example

Barebones example showing how AI tools can call a custom ServiceNow API instead of connecting directly to the Table API.

The ServiceNow side is only two files:

  • a Script Include with the tools/actions
  • a Scripted REST API resource that calls that Script Include

MCP path:

AI tool → MCP server → Scripted REST API resource → Script Include tools → ServiceNow

SDK/app path:

AI or app workflow → Connector.fetch(...) → Scripted REST API resource → Script Include tools → ServiceNow

The client interface is not the point.

The custom API is the boundary for what AI can read, write, execute, log, and expose.

What is in this repo

.
├── src/index.ts
├── servicenow/01-copy-into-servicenow-script-include-Custom_ServiceNow_API.js
├── servicenow/02-copy-into-servicenow-scripted-rest-resource-custom_api_POST.js
├── .env.example
├── package.json
└── README.md

What this exposes

MCP tools:

  • sn_query — query tables through the custom API
  • sn_get — get one record by sys_id
  • sn_schema — inspect a table schema
  • sn_script — server-side script execution, enabled by default

sn_script is included by default.

That tool is powerful because it gives the AI a controlled path to ServiceNow server-side JavaScript and GlideRecord.

That is also why it is dangerous. Use it only in a controlled dev/admin context. The real access is whatever your integration user and Script Include allow.

Setup

1. Install locally

npm install
npm run build

2. Add the ServiceNow Script Include

This repo includes the Script Include code here:

servicenow/01-copy-into-servicenow-script-include-Custom_ServiceNow_API.js

Create it in ServiceNow:

  1. Open a dev or sub-prod ServiceNow instance.
  2. Go to System Definition > Script Includes.
  3. Click New.
  4. Set:
    • Name: Custom_ServiceNow_API
    • Active: checked
    • Client callable: unchecked
    • Accessible from: This application scope only unless you intentionally need cross-scope access
  5. Replace the generated script with the full contents of:
servicenow/01-copy-into-servicenow-script-include-Custom_ServiceNow_API.js
  1. Save or submit.

No ServiceNow properties are required for the demo.

The defaults are in the Script Include:

  • sn_script enabled
  • max query limit 500
  • all tables allowed by default, limited by the integration user's roles and ACLs

To restrict tables, uncomment the per-table block in initialize():

// this.allowedTables = this._csv([
//     'incident',
//     'problem',
//     'change_request',
//     'sys_script',
//     'sys_script_include',
//     'sys_db_object',
//     'sys_dictionary'
// ].join(','));

If you want read-only discovery, set this.enableScript = false in the Script Include and set SN_ENABLE_SCRIPT=false in the MCP config.

Start in dev or sub-prod.

3. Add the Scripted REST API

This repo includes the REST resource code here:

servicenow/02-copy-into-servicenow-scripted-rest-resource-custom_api_POST.js

Create the API in ServiceNow:

  1. Go to System Web Services > Scripted REST APIs.
  2. Click New.
  3. Set:
    • Name: ServiceNow Custom API
    • API ID: custom_api
    • Active: checked
  4. Save or submit.

ServiceNow will expose the API under this shape:

/api/<your_namespace>/custom_api

For a scoped app, <your_namespace> is usually your app scope, for example:

/api/x_your_scope/custom_api

Now create the POST resource:

  1. Open the ServiceNow Custom API Scripted REST API record.
  2. In the Resources related list, click New.
  3. Set:
    • Name: POST
    • HTTP method: POST
    • Relative path: /
    • Requires authentication: checked
  4. Paste the full contents of this repo file into the resource script:
servicenow/02-copy-into-servicenow-scripted-rest-resource-custom_api_POST.js
  1. Save or submit.

If your ServiceNow version does not allow / as the resource path, use /run instead. Then your endpoint becomes:

/api/<your_namespace>/custom_api/run

Use that full URL as SN_API_URL.

The MCP server sends JSON like:

{
  "action": "query",
  "table": "incident",
  "query": "active=true",
  "limit": 5
}

The Scripted REST API calls the Script Include: Custom_ServiceNow_API.

Quick test with basic auth:

curl -u "$SN_USER:$SN_PASS" \
  -H "Content-Type: application/json" \
  -X POST "$SN_API_URL" \
  -d '{"action":"query","table":"incident","query":"active=true","limit":1}'

Expected response shape:

{
  "success": true,
  "result": {
    "table": "incident",
    "count": 1,
    "records": [
      {
        "sys_id": "...",
        "number": "INC0010001"
      }
    ]
  }
}

4. Create an integration user

Use a dedicated integration user.

Do not use your personal admin account.

Keep its roles narrow. Your custom API is not a replacement for ServiceNow ACLs. It is an extra control layer.

5. Configure local env

Copy .env.example into your shell, launcher, or MCP client config.

Basic auth:

export SN_API_URL="https://YOUR_INSTANCE.service-now.com/api/x_your_scope/custom_api"
export SN_USER="<integration_user>"
export SN_PASS="<password>"
export SN_ENABLE_SCRIPT="true"

Bearer token:

export SN_API_URL="https://YOUR_INSTANCE.service-now.com/api/x_your_scope/custom_api"
export SN_BEARER_TOKEN="<token>"
export SN_ENABLE_SCRIPT="true"

6. Run the MCP server

node dist/index.js

For an MCP client, point the client at:

node /absolute/path/to/servicenow-custom-api-example/dist/index.js

Example MCP config shape:

{
  "mcpServers": {
    "servicenow-custom-api": {
      "command": "node",
      "args": ["/absolute/path/to/servicenow-custom-api-example/dist/index.js"],
      "env": {
        "SN_API_URL": "https://YOUR_INSTANCE.service-now.com/api/x_your_scope/custom_api",
        "SN_USER": "<integration_user>",
        "SN_PASS": "<password>",
        "SN_ENABLE_SCRIPT": "true"
      }
    }
  }
}

Do not commit real credentials.

Calling the same API without MCP

MCP is just one interface.

The same custom API can be called from other controlled paths, including SDK / Fluent-style code using Connector.fetch(...).

Shape:

await Connector.fetch({
  method: 'POST',
  url: '/api/x_your_scope/custom_api',
  body: {
    action: 'query',
    table: 'incident',
    query: 'active=true',
    limit: 5
  }
});

Exact syntax depends on where you are running it. The point is not the client. The point is the custom API boundary.

Disabling the script tool

Script execution is on by default in this example. To disable it:

Local MCP env:

export SN_ENABLE_SCRIPT="false"

ServiceNow Script Include:

this.enableScript = false;

Example script:

var gr = new GlideRecord('incident');
gr.addActiveQuery();
gr.setLimit(5);
gr.query();

var out = [];
while (gr.next()) {
  out.push({
    number: gr.getValue('number'),
    short_description: gr.getValue('short_description')
  });
}
out;

Warning: server-side script execution can read, write, or delete data depending on the API user's roles and your Script Include. Treat it as privileged access.

What to add before serious use

This repo is an example, not a production security product.

Before using the pattern seriously, add:

  • tighter table allowlists
  • field allowlists
  • explicit write gates
  • audit logging
  • request IDs
  • rate limits
  • environment checks
  • no production writeback by default
  • approvals for dangerous actions
  • separate controls for script execution

Takeaway

You do not need to give an AI agent broad Table API access just because you want it to work with ServiceNow.

Use MCP, Connector.fetch, or another client interface if you want.

But put your own custom API in the middle when the organization needs control over data, access, and governance.

About

Barebones ServiceNow custom API example showing how AI tools can call a controlled Scripted REST API instead of connecting directly to the Table API.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages