suitecloud-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +52 -0
- package/dist/errors.js +16 -0
- package/dist/index.js +5 -0
- package/dist/project.js +40 -0
- package/dist/server.js +68 -0
- package/dist/setup-project.js +77 -0
- package/dist/tools.js +179 -0
- package/package.json +26 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export class CliNotFoundError extends Error {
|
|
3
|
+
constructor(binary) {
|
|
4
|
+
super(`Command \`${binary}\` was not found on PATH. Install the SuiteCloud CLI with ` +
|
|
5
|
+
'`npm install -g @oracle/suitecloud-cli` (requires a Java 17+ runtime). ' +
|
|
6
|
+
'See https://github.com/oracle/netsuite-suitecloud-sdk for details.');
|
|
7
|
+
this.name = 'CliNotFoundError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
// CSI sequences (colors, cursor movement) and OSC sequences (terminal titles).
|
|
11
|
+
const ANSI_PATTERN = /\u001b\[[0-9;?]*[A-Za-z]|\u001b\][^\u0007]*(\u0007|\u001b\\)?/g;
|
|
12
|
+
export function stripAnsi(text) {
|
|
13
|
+
return text.replace(ANSI_PATTERN, '');
|
|
14
|
+
}
|
|
15
|
+
export function cliBinary() {
|
|
16
|
+
return process.env.SUITECLOUD_MCP_BIN ?? 'suitecloud';
|
|
17
|
+
}
|
|
18
|
+
export function npmBinary() {
|
|
19
|
+
return process.env.SUITECLOUD_MCP_NPM_BIN ?? 'npm';
|
|
20
|
+
}
|
|
21
|
+
export function cliTimeoutMs() {
|
|
22
|
+
const parsed = Number(process.env.SUITECLOUD_MCP_TIMEOUT_MS);
|
|
23
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 600_000;
|
|
24
|
+
}
|
|
25
|
+
export function runProcess(command, args, cwd) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
// stdin is 'ignore': the CLI must never wait for interactive input.
|
|
28
|
+
const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
29
|
+
let output = '';
|
|
30
|
+
let timedOut = false;
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
timedOut = true;
|
|
33
|
+
child.kill('SIGKILL');
|
|
34
|
+
}, cliTimeoutMs());
|
|
35
|
+
child.stdout.on('data', (chunk) => (output += chunk.toString()));
|
|
36
|
+
child.stderr.on('data', (chunk) => (output += chunk.toString()));
|
|
37
|
+
child.on('error', (err) => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
if (err.code === 'ENOENT')
|
|
40
|
+
reject(new CliNotFoundError(command));
|
|
41
|
+
else
|
|
42
|
+
reject(err);
|
|
43
|
+
});
|
|
44
|
+
child.on('close', (code) => {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
resolve({ exitCode: code ?? 1, output: stripAnsi(output), timedOut });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function runCli(args, cwd) {
|
|
51
|
+
return runProcess(cliBinary(), args, cwd);
|
|
52
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const AUTH_PATTERNS = [
|
|
2
|
+
/account:setup/i,
|
|
3
|
+
/auth(entication)? ?id/i,
|
|
4
|
+
/token .*(expired|revoked|invalid)/i,
|
|
5
|
+
/not authenticated/i,
|
|
6
|
+
];
|
|
7
|
+
export function authGuidance(cliOutput) {
|
|
8
|
+
if (!AUTH_PATTERNS.some((pattern) => pattern.test(cliOutput)))
|
|
9
|
+
return null;
|
|
10
|
+
return ('Authentication is missing or expired. Run `suitecloud account:setup` in a terminal ' +
|
|
11
|
+
'inside the project directory (it is an interactive browser login and cannot run through ' +
|
|
12
|
+
'this MCP server). Use the `list_auth` tool to see which auth IDs are already configured.');
|
|
13
|
+
}
|
|
14
|
+
export const NO_PROJECT_MESSAGE = 'No SuiteCloud project found at or above the given path (looked for `suitecloud.config.js` ' +
|
|
15
|
+
'or `src/manifest.xml`). Pass `projectPath` pointing into an SDF project, or create one with ' +
|
|
16
|
+
'the `setup_project` tool.';
|
package/dist/index.js
ADDED
package/dist/project.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
export function findProjectRoot(startDir) {
|
|
4
|
+
let dir = resolve(startDir);
|
|
5
|
+
for (;;) {
|
|
6
|
+
if (existsSync(join(dir, 'suitecloud.config.js')) || existsSync(join(dir, 'src', 'manifest.xml'))) {
|
|
7
|
+
return dir;
|
|
8
|
+
}
|
|
9
|
+
const parent = dirname(dir);
|
|
10
|
+
if (parent === dir)
|
|
11
|
+
return null;
|
|
12
|
+
dir = parent;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function setDefaultAuthId(projectRoot, authId) {
|
|
16
|
+
const file = join(projectRoot, 'project.json');
|
|
17
|
+
let data = {};
|
|
18
|
+
if (existsSync(file)) {
|
|
19
|
+
try {
|
|
20
|
+
data = JSON.parse(readFileSync(file, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Corrupt file: rewrite it with just the auth ID.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
data.defaultAuthId = authId;
|
|
27
|
+
writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
|
|
28
|
+
}
|
|
29
|
+
export function readDefaultAuthId(projectRoot) {
|
|
30
|
+
const file = join(projectRoot, 'project.json');
|
|
31
|
+
if (!existsSync(file))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(readFileSync(file, 'utf8'));
|
|
35
|
+
return typeof data.defaultAuthId === 'string' ? data.defaultAuthId : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { CliNotFoundError, cliTimeoutMs, runCli } from './cli.js';
|
|
3
|
+
import { authGuidance, NO_PROJECT_MESSAGE } from './errors.js';
|
|
4
|
+
import { findProjectRoot, setDefaultAuthId } from './project.js';
|
|
5
|
+
import { setupProject, setupProjectInputSchema } from './setup-project.js';
|
|
6
|
+
import { cliTools } from './tools.js';
|
|
7
|
+
const VERSION = '0.1.0';
|
|
8
|
+
function ok(textContent) {
|
|
9
|
+
return { content: [{ type: 'text', text: textContent || '(no output)' }] };
|
|
10
|
+
}
|
|
11
|
+
function fail(textContent) {
|
|
12
|
+
return { content: [{ type: 'text', text: textContent }], isError: true };
|
|
13
|
+
}
|
|
14
|
+
async function handleCliTool(tool, params) {
|
|
15
|
+
const { projectPath, authId, ...rest } = params;
|
|
16
|
+
const startDir = typeof projectPath === 'string' ? projectPath : process.cwd();
|
|
17
|
+
let cwd = startDir;
|
|
18
|
+
if (tool.requiresProject) {
|
|
19
|
+
const root = findProjectRoot(startDir);
|
|
20
|
+
if (!root)
|
|
21
|
+
return fail(NO_PROJECT_MESSAGE);
|
|
22
|
+
if (typeof authId === 'string' && authId.length > 0)
|
|
23
|
+
setDefaultAuthId(root, authId);
|
|
24
|
+
cwd = root;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const result = await runCli(tool.buildArgs(rest), cwd);
|
|
28
|
+
if (result.timedOut) {
|
|
29
|
+
return fail(`The suitecloud CLI did not finish within ${cliTimeoutMs()} ms and was killed. ` +
|
|
30
|
+
'A deploy may still complete server-side — check the account before retrying. ' +
|
|
31
|
+
'Raise SUITECLOUD_MCP_TIMEOUT_MS to allow more time.\n\nPartial output:\n' +
|
|
32
|
+
result.output);
|
|
33
|
+
}
|
|
34
|
+
if (result.exitCode !== 0) {
|
|
35
|
+
const guidance = authGuidance(result.output);
|
|
36
|
+
return fail((guidance ? guidance + '\n\n' : '') + result.output);
|
|
37
|
+
}
|
|
38
|
+
return ok(result.output);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof CliNotFoundError)
|
|
42
|
+
return fail(error.message);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function createServer() {
|
|
47
|
+
const server = new McpServer({ name: 'suitecloud-mcp', version: VERSION });
|
|
48
|
+
for (const tool of cliTools) {
|
|
49
|
+
server.registerTool(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.inputSchema }, (async (args) => handleCliTool(tool, args ?? {})));
|
|
50
|
+
}
|
|
51
|
+
server.registerTool('setup_project', {
|
|
52
|
+
title: 'Create SDF project',
|
|
53
|
+
description: 'Create a new SuiteCloud SDF project (suitecloud project:create) and set up SuiteScript ' +
|
|
54
|
+
'autocompletion: installs @hitc/netsuite-types and writes tsconfig.json (language=ts) or ' +
|
|
55
|
+
'jsconfig.json (language=js). Purely local; nothing touches the NetSuite account.',
|
|
56
|
+
inputSchema: setupProjectInputSchema,
|
|
57
|
+
}, (async (args) => {
|
|
58
|
+
try {
|
|
59
|
+
return ok(await setupProject(args));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof Error)
|
|
63
|
+
return fail(error.message);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { npmBinary, runCli, runProcess } from './cli.js';
|
|
5
|
+
export const setupProjectInputSchema = {
|
|
6
|
+
parentPath: z.string().optional().describe('Directory to create the project in. Defaults to cwd.'),
|
|
7
|
+
projectName: z.string().describe('Name of the project folder to create.'),
|
|
8
|
+
type: z.enum(['ACP', 'SUITEAPP']).describe('ACP = account customization project.'),
|
|
9
|
+
language: z.enum(['ts', 'js']).describe('ts: tsconfig + typescript; js: jsconfig for JSDoc completions.'),
|
|
10
|
+
publisherId: z.string().optional().describe('SuiteApp only, e.g. com.example.'),
|
|
11
|
+
projectId: z.string().optional().describe('SuiteApp only.'),
|
|
12
|
+
projectVersion: z.string().optional().describe('SuiteApp only, e.g. 1.0.0.'),
|
|
13
|
+
};
|
|
14
|
+
const TSCONFIG = {
|
|
15
|
+
compilerOptions: {
|
|
16
|
+
module: 'amd',
|
|
17
|
+
target: 'es2019',
|
|
18
|
+
moduleResolution: 'node',
|
|
19
|
+
strict: true,
|
|
20
|
+
esModuleInterop: false,
|
|
21
|
+
baseUrl: 'node_modules/@hitc/netsuite-types',
|
|
22
|
+
paths: { N: ['N'], 'N/*': ['N/*'] },
|
|
23
|
+
outDir: 'src/FileCabinet/SuiteScripts',
|
|
24
|
+
},
|
|
25
|
+
include: ['src/TypeScript/**/*.ts'],
|
|
26
|
+
};
|
|
27
|
+
const JSCONFIG = {
|
|
28
|
+
compilerOptions: {
|
|
29
|
+
module: 'amd',
|
|
30
|
+
target: 'es2019',
|
|
31
|
+
moduleResolution: 'node',
|
|
32
|
+
baseUrl: 'node_modules/@hitc/netsuite-types',
|
|
33
|
+
paths: { N: ['N'], 'N/*': ['N/*'] },
|
|
34
|
+
},
|
|
35
|
+
include: ['src/FileCabinet/**/*.js'],
|
|
36
|
+
};
|
|
37
|
+
export async function setupProject(params) {
|
|
38
|
+
const parent = params.parentPath ?? process.cwd();
|
|
39
|
+
const createArgs = [
|
|
40
|
+
'project:create',
|
|
41
|
+
'--type',
|
|
42
|
+
params.type === 'ACP' ? 'ACCOUNTCUSTOMIZATION' : 'SUITEAPP',
|
|
43
|
+
'--projectname',
|
|
44
|
+
params.projectName,
|
|
45
|
+
];
|
|
46
|
+
if (params.type === 'SUITEAPP') {
|
|
47
|
+
if (!params.publisherId || !params.projectId || !params.projectVersion) {
|
|
48
|
+
throw new Error('SuiteApp projects require publisherId, projectId, and projectVersion.');
|
|
49
|
+
}
|
|
50
|
+
createArgs.push('--publisherid', params.publisherId, '--projectid', params.projectId, '--projectversion', params.projectVersion);
|
|
51
|
+
}
|
|
52
|
+
const created = await runCli(createArgs, parent);
|
|
53
|
+
if (created.exitCode !== 0) {
|
|
54
|
+
throw new Error(`suitecloud project:create failed:\n${created.output}`);
|
|
55
|
+
}
|
|
56
|
+
// The CLI names SuiteApp folders after publisherid.projectid; ACP folders after projectname.
|
|
57
|
+
const acpDir = join(parent, params.projectName);
|
|
58
|
+
const suiteAppDir = params.type === 'SUITEAPP' ? join(parent, `${params.publisherId}.${params.projectId}`) : null;
|
|
59
|
+
const projectDir = suiteAppDir && existsSync(suiteAppDir) ? suiteAppDir : acpDir;
|
|
60
|
+
if (!existsSync(projectDir)) {
|
|
61
|
+
throw new Error(`project:create reported success but the project folder was not found near ${acpDir}.`);
|
|
62
|
+
}
|
|
63
|
+
writeFileSync(join(projectDir, 'package.json'), JSON.stringify({ name: params.projectName, private: true, version: '0.0.0' }, null, 2) + '\n');
|
|
64
|
+
const npmPackages = ['@hitc/netsuite-types', ...(params.language === 'ts' ? ['typescript'] : [])];
|
|
65
|
+
const installed = await runProcess(npmBinary(), ['install', '--save-dev', ...npmPackages], projectDir);
|
|
66
|
+
if (installed.exitCode !== 0) {
|
|
67
|
+
throw new Error(`npm install failed:\n${installed.output}`);
|
|
68
|
+
}
|
|
69
|
+
const configName = params.language === 'ts' ? 'tsconfig.json' : 'jsconfig.json';
|
|
70
|
+
const config = params.language === 'ts' ? TSCONFIG : JSCONFIG;
|
|
71
|
+
writeFileSync(join(projectDir, configName), JSON.stringify(config, null, 2) + '\n');
|
|
72
|
+
return [
|
|
73
|
+
`Created ${params.type} project "${params.projectName}" at ${projectDir}.`,
|
|
74
|
+
`Installed ${npmPackages.join(', ')} and wrote ${configName} (SuiteScript N/* module completions).`,
|
|
75
|
+
'Next step: run `suitecloud account:setup` in a terminal inside the project to connect a NetSuite account.',
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const COMMON_INPUTS = {
|
|
3
|
+
projectPath: z
|
|
4
|
+
.string()
|
|
5
|
+
.optional()
|
|
6
|
+
.describe('Path inside the SDF project. Defaults to the current working directory.'),
|
|
7
|
+
authId: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('SuiteCloud authentication ID to use. Sets defaultAuthId in project.json before running ' +
|
|
11
|
+
'(the CLI has no per-command auth flag). Use list_auth to see available IDs.'),
|
|
12
|
+
};
|
|
13
|
+
const accountSpecificValues = z
|
|
14
|
+
.enum(['WARNING', 'ERROR'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('How to treat account-specific values in ACP projects. Default (CLI): ERROR.');
|
|
17
|
+
export const cliTools = [
|
|
18
|
+
{
|
|
19
|
+
name: 'deploy',
|
|
20
|
+
title: 'Deploy project',
|
|
21
|
+
description: 'Deploy the SDF project to the connected NetSuite account (suitecloud project:deploy). ' +
|
|
22
|
+
'THIS WRITES TO THE ACCOUNT. Unless the user has explicitly confirmed a real deploy, ' +
|
|
23
|
+
'run with dryRun=true first and show the preview. Ask before deploying to production auth IDs.',
|
|
24
|
+
requiresProject: true,
|
|
25
|
+
inputSchema: {
|
|
26
|
+
...COMMON_INPUTS,
|
|
27
|
+
dryRun: z.boolean().optional().describe('Preview the deploy without applying it (--dryrun).'),
|
|
28
|
+
accountSpecificValues,
|
|
29
|
+
applyInstallPrefs: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Apply hiding.xml/locking.xml/overwriting.xml settings (SuiteApps only).'),
|
|
33
|
+
},
|
|
34
|
+
buildArgs(p) {
|
|
35
|
+
const args = ['project:deploy'];
|
|
36
|
+
if (p.dryRun)
|
|
37
|
+
args.push('--dryrun');
|
|
38
|
+
if (p.accountSpecificValues)
|
|
39
|
+
args.push('--accountspecificvalues', String(p.accountSpecificValues));
|
|
40
|
+
if (p.applyInstallPrefs)
|
|
41
|
+
args.push('--applyinstallprefs');
|
|
42
|
+
return args;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'validate',
|
|
47
|
+
title: 'Validate project',
|
|
48
|
+
description: 'Validate the SDF project (suitecloud project:validate). Read-only. ' +
|
|
49
|
+
'Set server=true for a server-side validation against the account.',
|
|
50
|
+
requiresProject: true,
|
|
51
|
+
inputSchema: {
|
|
52
|
+
...COMMON_INPUTS,
|
|
53
|
+
server: z.boolean().optional().describe('Validate on the NetSuite server instead of locally.'),
|
|
54
|
+
accountSpecificValues,
|
|
55
|
+
},
|
|
56
|
+
buildArgs(p) {
|
|
57
|
+
const args = ['project:validate'];
|
|
58
|
+
if (p.server)
|
|
59
|
+
args.push('--server');
|
|
60
|
+
if (p.accountSpecificValues)
|
|
61
|
+
args.push('--accountspecificvalues', String(p.accountSpecificValues));
|
|
62
|
+
return args;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'import_objects',
|
|
67
|
+
title: 'Import objects',
|
|
68
|
+
description: 'Import custom objects from the NetSuite account into the project (suitecloud object:import). '
|
|
69
|
+
+ 'Overwrites local object files that already exist. Use list_objects first to discover types and script IDs.',
|
|
70
|
+
requiresProject: true,
|
|
71
|
+
inputSchema: {
|
|
72
|
+
...COMMON_INPUTS,
|
|
73
|
+
type: z.string().describe('SDF object type (e.g. customrecordtype, workflow) or "ALL".'),
|
|
74
|
+
scriptIds: z.array(z.string()).min(1).describe('Script IDs to import, or ["ALL"].'),
|
|
75
|
+
destinationFolder: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Folder inside /Objects to store the files. Default: /Objects.'),
|
|
79
|
+
excludeFiles: z.boolean().optional().describe('Do not import referenced SuiteScript files (ACP only).'),
|
|
80
|
+
appId: z.string().optional().describe('Application ID filter (SuiteApp objects).'),
|
|
81
|
+
},
|
|
82
|
+
buildArgs(p) {
|
|
83
|
+
const args = [
|
|
84
|
+
'object:import',
|
|
85
|
+
'--type',
|
|
86
|
+
String(p.type),
|
|
87
|
+
'--scriptid',
|
|
88
|
+
...p.scriptIds,
|
|
89
|
+
'--destinationfolder',
|
|
90
|
+
String(p.destinationFolder ?? '/Objects'),
|
|
91
|
+
];
|
|
92
|
+
if (p.excludeFiles)
|
|
93
|
+
args.push('--excludefiles');
|
|
94
|
+
if (p.appId)
|
|
95
|
+
args.push('--appid', String(p.appId));
|
|
96
|
+
return args;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'list_objects',
|
|
101
|
+
title: 'List account objects',
|
|
102
|
+
description: 'List custom objects deployed in the NetSuite account (suitecloud object:list). Read-only.',
|
|
103
|
+
requiresProject: true,
|
|
104
|
+
inputSchema: {
|
|
105
|
+
...COMMON_INPUTS,
|
|
106
|
+
types: z.array(z.string()).optional().describe('Filter by SDF object types.'),
|
|
107
|
+
scriptId: z.string().optional().describe('Filter by script ID substring.'),
|
|
108
|
+
appId: z.string().optional().describe('Application ID filter.'),
|
|
109
|
+
},
|
|
110
|
+
buildArgs(p) {
|
|
111
|
+
const args = ['object:list'];
|
|
112
|
+
if (Array.isArray(p.types) && p.types.length > 0)
|
|
113
|
+
args.push('--type', ...p.types);
|
|
114
|
+
if (p.scriptId)
|
|
115
|
+
args.push('--scriptid', String(p.scriptId));
|
|
116
|
+
if (p.appId)
|
|
117
|
+
args.push('--appid', String(p.appId));
|
|
118
|
+
return args;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'upload_files',
|
|
123
|
+
title: 'Upload files',
|
|
124
|
+
description: 'Upload files from the project FileCabinet folder to the NetSuite account (suitecloud file:upload). ' +
|
|
125
|
+
'THIS OVERWRITES the files in the account.',
|
|
126
|
+
requiresProject: true,
|
|
127
|
+
inputSchema: {
|
|
128
|
+
...COMMON_INPUTS,
|
|
129
|
+
paths: z
|
|
130
|
+
.array(z.string())
|
|
131
|
+
.min(1)
|
|
132
|
+
.describe('File Cabinet paths, e.g. ["/SuiteScripts/my_script.js"].'),
|
|
133
|
+
},
|
|
134
|
+
buildArgs(p) {
|
|
135
|
+
return ['file:upload', '--paths', ...p.paths];
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'import_files',
|
|
140
|
+
title: 'Import files',
|
|
141
|
+
description: 'Import File Cabinet files from the account into the project (suitecloud file:import; ACP only). ' +
|
|
142
|
+
'Overwrites local copies.',
|
|
143
|
+
requiresProject: true,
|
|
144
|
+
inputSchema: {
|
|
145
|
+
...COMMON_INPUTS,
|
|
146
|
+
paths: z.array(z.string()).min(1).describe('File Cabinet paths to import.'),
|
|
147
|
+
excludeProperties: z.boolean().optional().describe('Skip .attributes property folders.'),
|
|
148
|
+
},
|
|
149
|
+
buildArgs(p) {
|
|
150
|
+
const args = ['file:import', '--paths', ...p.paths];
|
|
151
|
+
if (p.excludeProperties)
|
|
152
|
+
args.push('--excludeproperties');
|
|
153
|
+
return args;
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'add_dependencies',
|
|
158
|
+
title: 'Add manifest dependencies',
|
|
159
|
+
description: 'Add missing dependencies to manifest.xml (suitecloud project:adddependencies). Modifies manifest.xml only.',
|
|
160
|
+
requiresProject: true,
|
|
161
|
+
inputSchema: { ...COMMON_INPUTS },
|
|
162
|
+
buildArgs() {
|
|
163
|
+
return ['project:adddependencies'];
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'list_auth',
|
|
168
|
+
title: 'List auth IDs',
|
|
169
|
+
description: 'List the SuiteCloud authentication IDs configured on this machine (suitecloud account:manageauth --list). ' +
|
|
170
|
+
'Read-only. To add one, the user must run `suitecloud account:setup` in a terminal.',
|
|
171
|
+
requiresProject: false,
|
|
172
|
+
inputSchema: {
|
|
173
|
+
projectPath: COMMON_INPUTS.projectPath,
|
|
174
|
+
},
|
|
175
|
+
buildArgs() {
|
|
176
|
+
return ['account:manageauth', '--list'];
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "suitecloud-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the NetSuite SuiteCloud (SDF) CLI: deploy, validate, import objects and files from any MCP client. Not affiliated with Oracle or NetSuite.",
|
|
5
|
+
"repository": "https://github.com/alegerber/zed-suitecloud-plugin",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": { "suitecloud-mcp": "dist/index.js" },
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["mcp", "netsuite", "suitecloud", "sdf", "suitescript"],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc && node -e \"require('node:fs').chmodSync('dist/index.js', 0o755)\"",
|
|
14
|
+
"prepare": "npm run build",
|
|
15
|
+
"test": "vitest run"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
|
+
"zod": "^4.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.6.0",
|
|
24
|
+
"vitest": "^3.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|