Building a Custom Tool for the NetSuite AI Connector (So ChatGPT or Claude Can Act in NetSuite)
Building a Custom Tool for the NetSuite AI Connector (So ChatGPT or Claude Can Act in NetSuite)
An AI assistant that can read your NetSuite data is useful. One that can act in it, create a record, update a field, kick off a process, is a different thing entirely. NetSuite's Custom Tool script type, introduced in 2026.1, is how you let that happen on your terms: you expose a specific, bounded piece of NetSuite logic, and an external AI client such as ChatGPT or Claude can call it through natural language.
This is the build guide. It covers how a custom tool fits together with the AI Connector and MCP, the anatomy of the script and its schema, how to deploy it, and the constraints that matter. It is the custom-tool deep dive under the NetSuite AI development guide, and the counterpart to calling AI from your own scripts with N/llm. The two are opposites: N/llm puts a model inside your code; a custom tool lets a model outside NetSuite act on it.
How it fits together
The piece that connects an external AI client to your account is the AI Connector Service, which adopts the Model Context Protocol (MCP), the emerging standard for letting AI clients call into systems. A supported client (ChatGPT, Claude, or anything MCP-compatible) connects through the service and can call the tools your account exposes, all while staying inside NetSuite's security model and role-based permissions.
NetSuite ships some of those tools for you, in the MCP Standard Tools SuiteApp. The custom tool script type is how you add your own, for the operations specific to your account that the standard tools do not cover. Because an AI agent acting through MCP is, in effect, a new kind of integration consumer, it deserves the same care as any other integration, with the added wrinkle that this caller is non-deterministic.
Anatomy of a custom tool script
A custom tool script looks a little different from a normal SuiteScript. It is annotated @NScriptType CustomTool, and its entry-point functions are the tool methods, declared async. Each method receives an args object and returns an object with a result and an optional error.
/**
* @NApiVersion 2.1
* @NScriptType CustomTool
* @NModuleScope Public
*/
define(['N/search', 'N/record'], (search, record) => {
// Read: let an AI client look up an order's status.
const getOrderStatus = async (args) => {
try {
const fields = search.lookupFields({
type: search.Type.SALES_ORDER,
id: args.orderId,
columns: ['tranid', 'status']
});
return { result: { number: fields.tranid, status: fields.status[0].text } };
} catch (e) {
return { result: '', error: `Could not read order ${args.orderId}: ${e.message}` };
}
};
// Act: let an AI client append a note to an order.
const addOrderNote = async (args) => {
try {
const order = record.load({ type: record.Type.SALES_ORDER, id: args.orderId });
order.setValue({ fieldId: 'memo', value: args.note });
const savedId = order.save();
return { result: { savedOrderId: savedId } };
} catch (e) {
return { result: '', error: `Could not update order ${args.orderId}: ${e.message}` };
}
};
return { getOrderStatus, addOrderNote };
});The error handling matters here. The caller is a model interpreting a prompt, so it will pass things you did not expect. Returning a clear error string lets the AI client understand what went wrong and tell the user, rather than the tool failing opaquely.
The JSON tool schema
The script is only half of a tool. The other half is a JSON tool schema that tells the AI client what the tool is called, what it does, and what to pass it. The client reads the schema to decide when the tool applies, so the description is functional, not documentation you write afterwards.
{
"name": "getOrderStatus",
"description": "Look up the status and document number of a sales order by its internal ID.",
"inputSchema": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "The internal ID of the sales order." }
},
"required": ["orderId"]
},
"outputSchema": {
"type": "object",
"properties": {
"number": { "type": "string" },
"status": { "type": "string" }
}
}
}The mapping is direct: the schema name matches the method, inputSchema.properties describes the args the method reads, and outputSchema.properties describes the result it returns. A vague description ("order tool") makes the AI client guess; a precise one ("look up the status of a sales order by its internal ID") makes it call the right tool with the right argument. Treat the description as part of the interface. Oracle publishes worked examples in the MCP sample tools repository, which are the reference for the exact schema and entry-point contract.
Deploying the tool
You deploy custom tools only through the SuiteCloud Development Framework (SDF), either as a SuiteApp or an account customisation project. There is no UI upload path the way there is for a Suitelet or a User Event script, so this is SDF work from the start: the script file, the tool schema, and the matching SDF object, pushed from your project. From 2026.1 you can see a custom tool's execution logs on the Script Execution Logs page, which is where you will spend your debugging time when a tool behaves unexpectedly.
The constraints that will catch you out
Two things commonly trip people up on the first build.
Some modules are not available. Oracle states that the N/http, N/https, N/llm, and N/sftp modules are not supported in custom tool scripts. The N/llm exclusion is the one that surprises people, and it is the clearest signal of how this is meant to work: the language model lives in the AI client, not in your tool. Your tool does the NetSuite operation; the intelligence is on the other side of the connection. If you find yourself wanting N/llm inside a custom tool, the design has gone sideways.
Entry points are async. The tool methods are declared async, so write them that way and return the { result, error } shape rather than throwing.
Designing for the blast radius
The hard part of a custom tool is deciding what an AI agent should be allowed to do, given that the trigger is a natural-language prompt interpreted by a model. The code is the easy part. The permission model is the main control: a custom tool runs as a role, so the single most important decision is scoping that role to exactly what the tools need and nothing more.
Beyond that, a few habits keep this safe:
- Prefer bounded operations. "Add a note to this order" is safe. "Run this SuiteQL the model wrote" is not. Expose specific, well-defined actions, not open-ended ones.
- Validate the arguments. The model will pass malformed, missing, or nonsensical values. Check them before you act on a record.
- Log every call. You want a record of what was invoked, with what arguments, and what happened, for the same reasons you want it on any integration that can change data.
- Keep a human in the loop for anything consequential. Reading and low-risk writes can be autonomous. Posting a transaction or changing a price should not be.
This is the same discipline that keeps an integration from failing silently, applied to a caller that, by design, does not behave the same way twice.
Frequently asked questions
What can a custom tool actually do? It lets an external AI client retrieve data, trigger actions, or perform most SuiteScript-supported operations in your account, invoked through the NetSuite AI Connector Service from natural-language prompts. The tool runs under a role's permissions, so what it can do is bounded by that role.
How do I deploy a custom tool? Only through the SuiteCloud Development Framework (SDF), as a SuiteApp or an account customisation project. There is no upload-in-the-UI path. Each tool needs its script plus a JSON tool schema that describes its name, purpose, and parameters.
Which modules can I not use in a custom tool script? Oracle states that the N/http, N/https, N/llm, and N/sftp modules are not supported in custom tool scripts. The N/llm exclusion is deliberate: the language model runs in the AI client, and the tool just does the NetSuite work it is asked to do.
Is it safe to let an AI agent change records? It can be, if you design for it. The tool runs as a role, so scope that role tightly, prefer well-bounded operations over open-ended ones, validate the arguments the model passes, log every call, and keep a human in the loop for anything consequential. Treat the AI client as a non-deterministic caller, because it is.
If you want to expose NetSuite to an AI agent safely, with the tools scoped and the permissions right, that is one of the core services I offer. It starts with a short technical review of the account and what you want the agent to do.
Have a specific problem in mind?
A 30-minute technical review call to understand what's in your codebase and whether this is the right fit.
Book a technical review