Skip to main content
Glama
RITHVIKILLANDULA

NetSuite AI Connector

NetSuite AI Connector — Governed MCP ERP Bridge

Source: https://github.com/RITHVIKILLANDULA/netsuite-ai-connector Live demo: https://rithvikillandula.github.io/My-Portfolio/demos/netsuite/

I built a working model of the pattern behind Oracle NetSuite's AI Connector Service. It's an MCP bridge that lets an AI client (Claude, ChatGPT) query live ERP data through governed tools — SuiteQL, saved searches, and records. The AI connects as a finance role, inherits that role's permissions on every call, and every call is audit-logged. I treat the AI as a governed user, not as a way around the controls.

The thesis I wanted to prove is simple: an agent answers from whatever data layer you give it. Put the agent behind a reconciled, governed finance data layer and its answers become trustworthy by construction. The wrong-grain or not-permitted answer gets closed off at the source, not in the prompt.

Run it

make demo    # spawns the MCP server as 4 roles, runs identical requests, prints the matrix + audit log
make test    # 11 governance tests
make serve ROLE=AR    # run the MCP server for one role over stdio

Zero dependencies. The server, the governance layer, and the MCP stdio transport are all standard library.

Related MCP server: netsuite-mcp

What the demo shows

I run the same request across four connected roles and the results differ:

Request

CFO

Controller

AR

AP

SELECT bank_account, tax_id FROM customer

both visible

bank NULL

both NULL

DENY (no customer grant)

SELECT COUNT(*) FROM bill

110

110

DENY

58 (subsidiary-scoped)

SELECT DISTINCT subsidiary_id FROM invoice

1,2,3

1,2,3

1,2 only

DENY

DELETE FROM invoice …

DENY

DENY

DENY

DENY (read-only)

SELECT 1; DROP TABLE customer

DENY

DENY

DENY

DENY (single stmt)

SELECT … FROM main.invoice (base bypass)

140

140

DENY (row-scoped)

DENY

saved search trial_balance

DENY

DENY

Every one of those decisions gets written to erp/audit.db.

How the governance works

I enforce at the database engine, with a SQLite authorizer bound to the connected role (server/governance.py). It holds no matter what SuiteQL the AI writes. There is no query, and no prompt, that talks its way past it.

  • Table allowlist — a read of any relation the role isn't granted is denied.

  • Read-only. Every write, DDL, PRAGMA, or ATTACH action is denied. Only a single SELECT/WITH statement gets through.

  • Column masking. Sensitive columns (tax_id, bank_account) read back as NULL per role, via SQLITE_IGNORE.

  • Row-level scoping. Row-filtered relations are reachable only through per-role TEMP VIEWs that embed the subsidiary / document-type WHERE clause. Direct access to the base table is denied, so a role cannot widen its own row scope even by qualifying the table name.

I declare the roles as pure policy in server/roles.py so an auditor can read exactly what CFO / Controller / AR / AP can and cannot touch:

CFO         all subsidiaries · AR + AP · sensitive fields visible
Controller  all subsidiaries · AR + AP · bank_account masked
AR          subs 1-2 · customers + invoices + AR GL · tax_id & bank_account masked
AP          subs 1-2 · vendors + bills + AP GL     · tax_id & bank_account masked

The governed tools (MCP tools/list)

Tool

Purpose

whoami

the connected role and the permissions it inherits

describe_schema

role-scoped catalog: relations, columns (masked flagged), row filters

suiteql_query

run a read-only SuiteQL SELECT under the role's permissions

saved_search_run

invoke a pre-vetted saved search by id (params always bound)

record_get

fetch one record, masked and row-scoped

list_saved_searches

saved searches available to the role

Saved searches (server/saved_searches.py) are the safer counterpart to raw SuiteQL. An analyst authors and reviews the SQL once. The AI can only invoke it by id with bound parameters, never string-formatted, and it still runs through the role's filters and masks.

Wire it to a real MCP client

The server speaks JSON-RPC 2.0 over stdio (initializetools/listtools/call). See mcp_client_config.example.json. Each entry connects the AI as a fixed role:

{ "command": "python3", "args": ["-m", "server.mcp_server", "--role", "AR"],
  "cwd": "/absolute/path/to/netsuite-ai-connector" }

Layout

server/erp_seed.py        synthetic NetSuite-shaped ERP (subs, AR, AP, COA, GL)
server/roles.py           CFO / Controller / AR / AP policy (pure declaration)
server/governance.py      authorizer-based enforcement (the core guarantee)
server/saved_searches.py  pre-vetted, parameter-bound saved searches
server/audit.py           append-only audit log of every call
server/tools.py           governed tool surface + audited dispatch
server/mcp_server.py      MCP JSON-RPC stdio server
client/demo_client.py     spawns the server per role, runs the governance matrix
tests/                    11 enforcement tests (pytest or stdlib runner)

Why this is the interesting half of the portfolio

My other projects, Foundry and Forecast, build a reconciled, governed finance data layer. This one shows what that layer is worth once an agent is pointed at it. The reconciliation and lineage stop being back-office plumbing and become the thing that makes every AI answer trustworthy.

Resume line: Built a governed MCP connector that lets AI clients query live ERP data through role-scoped tools (SuiteQL, saved searches, records), where every call inherits the connected role's permissions and is audit-logged.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RITHVIKILLANDULA/netsuite-ai-connector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server