Build a NetSuite Cowork plugin with SuiteQL and full record CRUD
June 01, 2026
Why a NetSuite plugin for Cowork
Most ERP plugins for Copilot ship a fixed list of tools — get customer, list invoices, maybe a saved search or two. The moment someone asks something off-script, the plugin shrugs and the user goes back to NetSuite.
The NetSuite plugin in the SharingIsCaring repository takes a different path. It pairs scenario skills for the questions people ask every day with a SuiteQL escape hatch and full record CRUD for everything else. The whole surface runs through an in-tenant MCP server, so credentials and data stay in your tenant.
What the plugin includes
This plugin ships:
- A
devPreviewCowork plugin manifest - Thirteen skills covering customer, sales, AR, vendor, SuiteQL, and record CRUD workflows
- An MCP server with a full read/write endpoint and a read-only federated endpoint
- Azure deployment assets driven by
azd up - Preflight, packaging, icon, and smoke-test scripts
Plugin folder:
- https://github.com/troystaylor/SharingIsCaring/tree/main/Cowork%20Plugins/NetSuite
Repository:
- https://github.com/troystaylor/SharingIsCaring
Current scope
The plugin covers the most common NetSuite reach-ins:
- SuiteQL queries against any NetSuite table
- Full CRUD on any record type (customer, vendor, salesorder, invoice, and more)
- Sublist line management on transactional records
- Record metadata discovery (record types and field schemas)
- Sales and finance scenario skills: customer briefings, open sales orders, AR aging, vendor lookup, recent transactions
That mix lets the plugin handle both targeted scenario prompts and open-ended ad hoc questions through SuiteQL.
Skills shipped in the plugin
The plugin ships thirteen skills, split into read and write modes.
Read skills:
- customer-briefing: Build a customer snapshot before account reviews
- open-sales-orders-review: Review open sales orders by customer or status
- ar-aging-snapshot: Pull open AR invoices and surface aging
- vendor-lookup: Find vendor records and contact info
- recent-transactions: List recent transactions for an entity
- run-suiteql-query: Execute an arbitrary SuiteQL query
- list-records: List and filter records of a given type
- get-record-details: Retrieve a single record by id
- get-record-metadata: Discover record types and field schemas
Write skills:
- create-record: Create a new record of any type
- update-record: Patch fields on an existing record
- delete-record: Permanently delete a record
- manage-sublist-lines: Add, update, or remove sublist lines
The scenario skills cover the questions people ask every day. The generic record skills cover everything else.
MCP tool surface and dual routes
The MCP server exposes two endpoints from one backend:
/mcp/full— all 16 tools (read and write). The plugin’smcpServerUrlpoints here./mcp/federated— 10 read-only tools, for federated and agent-to-agent scenarios where writes aren’t wanted.
Read tools (10):
run_suiteqllist_recordsget_recordlist_record_typesget_record_metadataget_sublistsearch_customerssearch_vendorsget_open_sales_ordersget_open_invoices
Write tools (6):
create_recordupdate_recorddelete_recordadd_sublist_lineupdate_sublist_linedelete_sublist_line
One backend, two routes. The full route powers the Cowork plugin. The federated route gives downstream agents a safe read-only NetSuite surface without giving them write tools.
Manifest schema choice
The plugin uses the devPreview Teams manifest schema (manifestVersion: "devPreview"), not v1.28. That’s deliberate.
In Cowork’s current runtime, only the devPreview path actually binds the MCP connector. A v1.28 manifest loads the skills but silently drops the connector, so the agent never invokes any tools. The devPreview path also requires packageName in reverse-DNS form and omits mcpToolDescription. Cowork discovers tools dynamically through MCP tools/list.
If you build from a v1.28 template and skills run but nothing reaches NetSuite, the manifest version is the first thing to check.
NetSuite prerequisites
Before deploying, set up NetSuite:
- Enable REST Web Services: Setup > Company > Enable Features > SuiteTalk.
- Create an OAuth 2.0 Integration Record: Setup > Integration > Manage Integrations > New.
- Enable OAuth 2.0
- Redirect URI:
https://teams.microsoft.com/api/platform/v1.0/oauthRedirect - Scope:
rest_webservices - Capture the Client ID and Client Secret (shown once)
- Get your Account ID: Setup > Company > Company Information. Replace any hyphen with an underscore (for example, sandbox
TSTDRV1234567_SB1).
Deploy to Azure
The deployment flow:
-
Provision the Azure infrastructure and MCP server:
cd "Cowork Plugins/NetSuite" azd upYou’ll be prompted for
NETSUITE_ACCOUNT_ID(for example,1234567orTSTDRV1234567_SB1). -
Bind the container app to ACR with its system identity (one-time, after first
azd up):az containerapp registry set ` -g <resource-group> -n <container-app-name> ` --server <acr-name>.azurecr.io --identity systemThe Bicep ships with
registries: []to avoid a first-deploy chicken-and-egg problem — the AcrPull role isn’t assigned yet when the container app first tries to bind. Runazd deployagain after binding. Subsequent deploys work without this step. - Register an OAuth client in the Teams Developer Portal pointing at your NetSuite Integration Record:
- Authorization endpoint:
https://<account>.app.netsuite.com/app/login/oauth2/authorize.nl - Token endpoint:
https://<account>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token - Scope:
rest_webservices
Capture the OAuth registration
referenceId. - Authorization endpoint:
- In
manifest.json, replace:id(``) with a fresh GUIDagentConnectors[0].toolSource.remoteMcpServer.mcpServerUrl(<YOUR-CONTAINER-APP-FQDN>) with the deployed Container App FQDNagentConnectors[0].toolSource.remoteMcpServer.authorization.referenceId(``) with the OAuth registration ID
-
Validate and package:
./preflight.ps1 ./package.ps1 -SkipIcons -
Smoke-test the deployed MCP endpoint:
./smoke.ps1 -Fqdn <container-app-fqdn>Expect
200 Healthyon/health/liveand/health/ready, plus atools/listresponse listing all 16 tools. - Upload
NetSuite.zipin the Microsoft 365 Admin Center, publish to test users, then connect in a fresh Cowork session.
For a value-mapping checklist see SETUP-CHECKLIST.md. For re-cutover after redeploys see CUTOVER-RUNBOOK.md.
Helper scripts
The plugin ships several helpers worth knowing about:
preflight.ps1: Validate manifest, skills, icons, and connector wiring before packagingpackage.ps1: BuildNetSuite.zipfrom manifest, skills, and iconsgenerate-icons.ps1: Download source PNG and produce a 192x192color.pngand 32x32outline.pngfix-outline.ps1: Rebuildoutline.pngas a pure-white silhouette (required by Microsoft 365 Admin Center)smoke.ps1: Post-deploy smoke test that hits/health/live,/health/ready,/status, and runs MCPinitializeplustools/listagainst/mcp/full
Those scripts make the upload-to-Cowork loop fast and predictable.
What to customize first
When you adapt this plugin for a real tenant, start here:
- SuiteQL query patterns and saved-search equivalents for your data model
- Field selection in customer and vendor reads to keep responses tight
- Required fields and lists on common record types for your write tools
- OAuth scopes pinned to the minimum your Integration Record needs
- Logging and correlation IDs so MCP calls are easy to trace end to end
Why this pattern is useful
This plugin is a strong example of mixing scenario skills with a generic CRUD and query layer.
You get:
- One MCP backend with two intent-shaped routes
- Skills for high-frequency finance and sales questions
- A SuiteQL escape hatch for anything the scenario skills don’t cover
- Full record CRUD without writing a new tool for every object type
- An OAuth and deployment flow that fits enterprise controls
Most other ERP integrations stop at read-only. This one gives you a complete two-way path while keeping data and credentials inside your tenant.