Connecting Claude to NetSuite with the AI Connector Service (MCP), plus your own custom tools
- Published on
- -12 mins read
- Authors
- Name
- Andy Nur (Andy)
- @andynur
NetSuite now ships its own MCP server. Oracle calls it the NetSuite AI Connector Service. Any AI client that speaks the Model Context Protocol can connect to it, sign in as a NetSuite user, and call tools: read a record, run a saved search, run SuiteQL, or call a tool you write yourself in SuiteScript.
For integration engineers this changes two things. First, "can you pull this report for me?" requests can go straight to the AI assistant, under the user's own role. Second, a custom tool becomes a new integration surface, next to RESTlets and the REST API.
This post covers the setup that actually works in a real account, and the parts that trip people up.
In this article:
- How a request travels
- Step 1: Enable features and install the SuiteApp
- Step 2: Create an MCP role
- Step 3: Connect Claude or Claude Code
- What the standard tools can do
- Step 4: Write a custom tool
- Limits and governance
Prerequisites
- A NetSuite account where you can install SuiteApps. Start in a sandbox.
- An MCP client that supports remote MCP over streamable HTTP, protocol version 2025-06-18, and OAuth 2.0 authorization code with PKCE. Claude (Pro, Max, Team, Enterprise), Claude Code, ChatGPT in developer mode, and Codex all work.
- For custom tools: the SuiteCloud CLI and a SuiteCloud project.
How a request travels
The AI never gets a database connection. It only sees the tools NetSuite exposes, and every tool runs with the permissions of the role you signed in with.
One question, end to end
Step 1 / 81. OAuth 2.0 + PKCE
On first connect the client opens a NetSuite login. You sign in, pick a non-Administrator role, and approve. The client stores the access and refresh tokens.
Step 1: Enable features and install the SuiteApp
- In Setup > Company > Enable Features > SuiteCloud, check Server SuiteScript, REST Web Services, and OAuth 2.0.
- Install the MCP Standard Tools SuiteApp from the SuiteApp Marketplace. It gives you the built-in record, saved search, report, and SuiteQL tools.
Step 2: Create an MCP role
The AI Connector Service does not accept the Administrator role. That is a good thing: create a role that matches what you want the assistant to do.
Minimum permissions on the Setup subtab:
- MCP Server Connection
- Log in using OAuth 2.0 Access Tokens
- REST Web Services, needed by most standard tools
Then add record and report permissions. A good first role is read-only: View on customers, transactions, and items, plus Perform Search. Give write access later, per record type, once you trust the workflow.
One role per use case
A finance role with report access and a support role with case access are easier to reason about than one big “AI” role. The user picks the role during the OAuth consent, so people only get what their job already allows.
Step 3: Connect Claude or Claude Code
You need one URL. Use the /all endpoint so the client sees standard and custom tools. Without /all at the end, the connection shows as disconnected.
Account ID → endpoints
Type your account ID the way it shows in the browser address bar or in Company Information.
1234567_SB11234567-sb1https://1234567-sb1.suitetalk.api.netsuite.com/services/mcp/v1/allhttps://1234567-sb1.suitetalk.api.netsuite.com/services/mcp/v1/suiteapp/com.netsuite.mcpstandardtoolsclaude mcp add --transport http netsuite https://1234567-sb1.suitetalk.api.netsuite.com/services/mcp/v1/allClaude (web or desktop): open Settings > Connectors. Pick NetSuite from the directory if your plan shows it, or add a custom connector with the /all URL. Then complete the NetSuite login in the pop-up.
Claude Code: add it as a remote HTTP server, then run /mcp inside Claude Code to sign in:
claude mcp add --transport http netsuite \ https://1234567-sb1.suitetalk.api.netsuite.com/services/mcp/v1/allData leaves NetSuite
NetSuite enforces role permissions, but it cannot control what an external client does with data after a tool returns it. Check your AI provider's data retention settings, and do not connect a role that can read payroll or bank details unless that is the point.
What the standard tools can do
The MCP Standard Tools SuiteApp groups its tools into four families. Only the record tools can write. Pick a family and a tool to see what it is good for and what the role needs:
MCP Standard Tools explorer
ns_runCustomSuiteQL
SuiteQL tool
Runs a read-only SuiteQL query the AI writes for you.
Try asking
“Top 10 customers by open AR balance, with days overdue.”
Role needs
REST Web Services + view access to every joined table
Tool names reflect the SuiteApp at the time of writing. Oracle adds tools between releases, so check Customization > Scripting > Custom Tools in your account for the current list.
Step 4: Write a custom tool
The standard tools are generic. A custom tool encodes your business logic: the correct way to compute available-to-promise, the saved definition of an "at-risk" customer, or a lookup that needs three joins your users always get wrong.
A custom tool has three parts:
- A SuiteScript 2.1 script with
@NScriptType CustomTool. Every entry point isasyncand returns JSON. - A JSON-RPC schema that describes each tool to the model.
- A
toolsetSDF object that ties them together and sets the permissions.
Use toolset, not tool
The older tool SDF object is deprecated. Since the September 2026 minor release you cannot deploy
custom tools with tool XML definitions anymore. Use toolset.
Here is a tool that returns a customer's open invoices with days overdue:
/** * @NApiVersion 2.1 * @NModuleScope Public * @NScriptType CustomTool */define(['N/query'], (query) => { return { getOpenInvoices: async function (args) { try { const customerId = Number(args.customerId) // Clamp to an integer so it is safe to inline in the query const limit = Math.max(1, Math.min(Math.floor(Number(args.limit) || 20), 100))
const rows = query .runSuiteQL({ query: ` SELECT t.id, t.tranid, t.trandate, t.duedate, t.foreignamountremaining AS amount_due, BUILTIN.DF(t.currency) AS currency, GREATEST(TRUNC(SYSDATE) - t.duedate, 0) AS days_overdue FROM transaction t WHERE t.type = 'CustInvc' AND t.entity = ? AND t.foreignamountremaining > 0 ORDER BY t.duedate FETCH FIRST ${limit} ROWS ONLY`, params: [customerId], }) .asMappedResults()
return { result: rows } } catch (e) { return { result: [], error: `getOpenInvoices failed: ${e.message}` } } }, }})The schema is what the model reads. Write the description for a model that has never seen your account:
{ "tools": [ { "name": "getOpenInvoices", "description": "Lists unpaid customer invoices for one customer, oldest due date first, with the amount still due and days overdue. Use it for questions about what a customer owes or which invoices are late. Needs the customer internal ID.", "inputSchema": { "type": "object", "properties": { "customerId": { "type": "number", "description": "Customer internal ID" }, "limit": { "type": "number", "description": "Max rows, default 20, max 100" } }, "required": ["customerId"] }, "annotations": { "title": "Open invoices for a customer", "readOnlyHint": true, "idempotentHint": true, "openWorldHint": false } } ]}And the SDF object that registers both files with the AI Connector:
<toolset scriptid="custtoolset_ar_tools"> <name>AR Tools</name> <scriptfile>[/SuiteApps/com.example.artools/tools/ar_tools.js]</scriptfile> <rpcschema>[/SuiteApps/com.example.artools/tools/ar_tools_schema.json]</rpcschema> <exposetoaiconnector>T</exposetoaiconnector> <permissions> <permission> <permkey>TRAN_CUSTINVC</permkey> <permlevel>VIEW</permlevel> </permission> </permissions></toolset>Deploy with suitecloud project:deploy. After deployment the toolset shows up under Customization > Scripting > Custom Tools, and on the /all endpoint. A SuiteApp project also gets its own endpoint at /services/mcp/v1/suiteapp/<applicationid>.
A few rules that save debugging time:
- No external calls.
N/http,N/https,N/llm, andN/sftpare not supported in custom tool scripts. - Every schema method must exist in the script, with the exact same name, or deployment fails.
- Return errors as data. Catch exceptions and return an
errorfield. The model can read it and retry with better arguments, instead of the call failing silently. - Keep results small. Clients handle a few thousand rows at most. Add a
limitargument and a hard cap, as above. - Set
readOnlyHinthonestly. Clients use it to decide when to ask the user before calling a tool.
MCP Apps
A custom tool can also return an interactive UI inside the chat. Add a _meta.ui.resourceUri
property to the tool in the schema, and bundle a self-contained HTML file in the SuiteCloud
project.
Limits and governance
- Concurrency is shared. MCP calls use the same account concurrency pool as your other integrations. One question can trigger several tool calls. If you hit
429 Too Many Requests, set a per-integration limit in Integration Governance so the AI cannot starve your order sync. See handling NetSuite concurrency limits for the client side. - Reports are limited. Report tools support only date and subsidiary filters. Accounting period filters are not supported.
- Logs show calls, not prompts. The execution log on the integration record lists tool calls. Keep the prompt history in your AI client if you need an audit trail.
- SuiteQL tools are read-only. All writes go through the record tools or your own custom tools.
Conclusion
The AI Connector Service is a thin, well-designed layer: OAuth 2.0 with PKCE, a non-Administrator role, and tools that run with that role's permissions. The standard tools cover ad hoc questions. Custom tools are where an integration engineer adds real value, because they turn tribal knowledge into a function the model can call safely.
Start with a read-only role in a sandbox, connect Claude Code, and write one custom tool for the question your finance team asks every week.