@google/clasp 3.0.4-alpha → 3.0.6-alpha
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/README.md +20 -1
- package/build/src/commands/program.js +11 -1
- package/build/src/commands/pull.js +38 -2
- package/build/src/commands/start-mcp.js +12 -0
- package/build/src/core/clasp.js +4 -2
- package/build/src/core/files.js +4 -1
- package/build/src/mcp/server.js +329 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ If your organization restricts authorization for third-party apps, you may eithe
|
|
|
189
189
|
* Request your admin allow-list clasp's client id `1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com`
|
|
190
190
|
* Set up an internal-only GCP project for clasp as described in the previous section.
|
|
191
191
|
|
|
192
|
-
### Service accounts
|
|
192
|
+
### Service accounts (EXPERIMENTAL/NOT WORKING)
|
|
193
193
|
|
|
194
194
|
Use the `--adc` option on any command to read credentials from the environment using Google Cloud's [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) mechanism.
|
|
195
195
|
|
|
@@ -394,6 +394,8 @@ Updates local files with Apps Script project.
|
|
|
394
394
|
#### Options
|
|
395
395
|
|
|
396
396
|
- `--versionNumber <number>`: The version number of the project to retrieve.
|
|
397
|
+
- `--deleteUnusedFiles`: Deletes local files that would have been pushed that were not returned by the server. Prompts for confirmation
|
|
398
|
+
- `--force`: Used with `--deleteUnusedFiles` to automatically confirm. Use with caution.
|
|
397
399
|
|
|
398
400
|
#### Examples
|
|
399
401
|
|
|
@@ -541,6 +543,23 @@ Lists your most recent Apps Script projects.
|
|
|
541
543
|
|
|
542
544
|
- `clasp list-scripts`: Prints `helloworld1 – xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...`
|
|
543
545
|
|
|
546
|
+
### MCP (EXPERIMENTAL)
|
|
547
|
+
|
|
548
|
+
Runs clasp in MCP (model context protocol) mode for use with coding agents. Configure clasp as a local tool using STDIO transport. While running in MCP mode clasp uses the same credentials as
|
|
549
|
+
normal when used as a CLI. Run `clasp login` ahead of time to authorize.
|
|
550
|
+
|
|
551
|
+
When used in MCP mode clasp does not need to be started from the project directory. The project directoy is specified in the tool calls. Switching projects does not require a restart of the MCP server, while switching credentials does.
|
|
552
|
+
|
|
553
|
+
This feature is experimental and currently offers a limited subset of tools for agents. Feedback is welcome.
|
|
554
|
+
|
|
555
|
+
#### Options
|
|
556
|
+
|
|
557
|
+
N/A
|
|
558
|
+
|
|
559
|
+
#### Examples
|
|
560
|
+
|
|
561
|
+
- `clasp mcp`
|
|
562
|
+
|
|
544
563
|
## Advanced Commands
|
|
545
564
|
|
|
546
565
|
> **NOTE**: These commands require Project ID/credentials setup ([see below](#projectid-optional)).
|
|
@@ -18,23 +18,30 @@ import { command as openContainerCommand } from './open-container.js';
|
|
|
18
18
|
import { command as openAuthCommand } from './open-credentials.js';
|
|
19
19
|
import { command as openLogsCommand } from './open-logs.js';
|
|
20
20
|
import { command as openScriptCommand } from './open-script.js';
|
|
21
|
+
import { command as openWebappCommand } from './open-webapp.js';
|
|
21
22
|
import { command as pullCommand } from './pull.js';
|
|
22
23
|
import { command as pushCommand } from './push.js';
|
|
23
24
|
import { command as runCommand } from './run-function.js';
|
|
24
25
|
import { command as setupLogsCommand } from './setup-logs.js';
|
|
25
26
|
import { command as authStatusCommand } from './show-authorized-user.js';
|
|
26
27
|
import { command as filesStatusCommand } from './show-file-status.js';
|
|
28
|
+
import { command as mcpCommand } from './start-mcp.js';
|
|
27
29
|
import { command as tailLogsCommand } from './tail-logs.js';
|
|
30
|
+
import { command as updateDeploymentCommand } from './update-deployment.js';
|
|
28
31
|
import { dirname } from 'path';
|
|
29
32
|
import { fileURLToPath } from 'url';
|
|
30
33
|
import { readPackageUpSync } from 'read-package-up';
|
|
31
34
|
import { initAuth } from '../auth/auth.js';
|
|
32
35
|
import { initClaspInstance } from '../core/clasp.js';
|
|
33
36
|
import { intl } from '../intl.js';
|
|
34
|
-
export function
|
|
37
|
+
export function getVersion() {
|
|
35
38
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
39
|
const manifest = readPackageUpSync({ cwd: __dirname });
|
|
37
40
|
const version = manifest ? manifest.packageJson.version : 'unknown';
|
|
41
|
+
return version;
|
|
42
|
+
}
|
|
43
|
+
export function makeProgram(exitOveride) {
|
|
44
|
+
const version = getVersion();
|
|
38
45
|
const program = new Command();
|
|
39
46
|
program.exitOverride(exitOveride);
|
|
40
47
|
program.storeOptionsAsProperties(false);
|
|
@@ -77,6 +84,7 @@ export function makeProgram(exitOveride) {
|
|
|
77
84
|
createDeploymentCommand,
|
|
78
85
|
deleteDeploymentCOmand,
|
|
79
86
|
listDeploymentsCommand,
|
|
87
|
+
updateDeploymentCommand,
|
|
80
88
|
disableApiCommand,
|
|
81
89
|
enableApiCommand,
|
|
82
90
|
listApisCommand,
|
|
@@ -88,10 +96,12 @@ export function makeProgram(exitOveride) {
|
|
|
88
96
|
tailLogsCommand,
|
|
89
97
|
openScriptCommand,
|
|
90
98
|
openContainerCommand,
|
|
99
|
+
openWebappCommand,
|
|
91
100
|
runCommand,
|
|
92
101
|
listCommand,
|
|
93
102
|
createVersionCommand,
|
|
94
103
|
listVersionsCommand,
|
|
104
|
+
mcpCommand,
|
|
95
105
|
];
|
|
96
106
|
for (const cmd of commandsToAdd) {
|
|
97
107
|
program.addCommand(cmd);
|
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
2
4
|
import { intl } from '../intl.js';
|
|
3
|
-
import { withSpinner } from './utils.js';
|
|
5
|
+
import { isInteractive, withSpinner } from './utils.js';
|
|
4
6
|
export const command = new Command('pull')
|
|
5
7
|
.description('Fetch a remote project')
|
|
6
8
|
.option('--versionNumber <version>', 'The version number of the project to retrieve.')
|
|
9
|
+
.option('-d, --deleteUnusedFiles ', 'Delete local files that are not in the remote project. Use with caution.')
|
|
10
|
+
.option('-f, --force', 'Forcibly delete local files that are not in the remote project without prompting.')
|
|
7
11
|
.action(async function (options) {
|
|
8
12
|
const clasp = this.opts().clasp;
|
|
9
13
|
const versionNumber = options.versionNumber;
|
|
10
|
-
const
|
|
14
|
+
const forceDelete = options.force;
|
|
15
|
+
let spinnerMsg = intl.formatMessage({ id: "dh7Bw6", defaultMessage: [{ type: 0, value: "Checking local files..." }] });
|
|
16
|
+
const localFiles = await clasp.files.collectLocalFiles();
|
|
17
|
+
spinnerMsg = intl.formatMessage({ id: "jilcJH", defaultMessage: [{ type: 0, value: "Pulling files..." }] });
|
|
11
18
|
const files = await withSpinner(spinnerMsg, async () => {
|
|
12
19
|
return await clasp.files.pull(versionNumber);
|
|
13
20
|
});
|
|
21
|
+
if (options.deleteUnusedFiles) {
|
|
22
|
+
const filesToDelete = localFiles.filter(f => !files.find(p => p.localPath === f.localPath));
|
|
23
|
+
await deleteLocalFiles(filesToDelete, forceDelete);
|
|
24
|
+
}
|
|
14
25
|
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
15
26
|
const successMessage = intl.formatMessage({ id: "4mRAfN", defaultMessage: [{ type: 0, value: "Pulled " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
|
|
16
27
|
count: files.length,
|
|
17
28
|
});
|
|
18
29
|
console.log(successMessage);
|
|
19
30
|
});
|
|
31
|
+
async function deleteLocalFiles(filesToDelete, forceDelete = false) {
|
|
32
|
+
if (!filesToDelete || filesToDelete.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const skipConfirmation = forceDelete;
|
|
36
|
+
if (!isInteractive() && !forceDelete) {
|
|
37
|
+
const msg = intl.formatMessage({ id: "zLuvSg", defaultMessage: [{ type: 0, value: "You are not in an interactive terminal and --force not used. Skipping file deletion." }] });
|
|
38
|
+
console.warn(msg);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const file of filesToDelete) {
|
|
42
|
+
if (!skipConfirmation) {
|
|
43
|
+
const confirm = await inquirer.prompt({
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'deleteFile',
|
|
46
|
+
message: intl.formatMessage({ id: "lVx/lI", defaultMessage: [{ type: 0, value: "Delete " }, { type: 1, value: "file" }, { type: 0, value: "?" }] }, { file: file.localPath }),
|
|
47
|
+
});
|
|
48
|
+
if (!confirm.deleteFile) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await fs.unlink(file.localPath);
|
|
53
|
+
console.log(intl.formatMessage({ id: "Nx315v", defaultMessage: [{ type: 0, value: "Deleted " }, { type: 1, value: "file" }] }, { file: file.localPath }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { buildMcpServer } from '../mcp/server.js';
|
|
4
|
+
export const command = new Command('start-mcp-server')
|
|
5
|
+
.alias('mcp')
|
|
6
|
+
.description('Starts an MCP server for interacting with apps script.')
|
|
7
|
+
.action(async function () {
|
|
8
|
+
const auth = this.opts().auth;
|
|
9
|
+
const server = buildMcpServer(auth);
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
});
|
package/build/src/core/clasp.js
CHANGED
|
@@ -63,11 +63,13 @@ export class Clasp {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
export async function initClaspInstance(options) {
|
|
66
|
+
var _a;
|
|
66
67
|
debug('Initializing clasp instance');
|
|
67
68
|
const projectRoot = await findProjectRootdDir(options.configFile);
|
|
68
69
|
if (!projectRoot) {
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const dir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd();
|
|
71
|
+
debug(`No project found, defaulting to ${dir}`);
|
|
72
|
+
const rootDir = path.resolve(dir);
|
|
71
73
|
const configFilePath = path.resolve(rootDir, '.clasp.json');
|
|
72
74
|
const ignoreFile = await findIgnoreFile(rootDir, options.ignoreFile);
|
|
73
75
|
const ignoreRules = await loadIgnoreFileOrDefaults(ignoreFile);
|
package/build/src/core/files.js
CHANGED
|
@@ -371,7 +371,10 @@ export class Files {
|
|
|
371
371
|
debug('Skipping empty file.');
|
|
372
372
|
return;
|
|
373
373
|
}
|
|
374
|
-
|
|
374
|
+
const localDirname = path.dirname(file.localPath);
|
|
375
|
+
if (localDirname !== '.') {
|
|
376
|
+
await fs.mkdir(localDirname, { recursive: true });
|
|
377
|
+
}
|
|
375
378
|
await fs.writeFile(file.localPath, file.source);
|
|
376
379
|
};
|
|
377
380
|
return await pMap(files, mapper);
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { getDefaultProjectName } from '../commands/create-script.js';
|
|
6
|
+
import { getVersion } from '../commands/program.js';
|
|
7
|
+
import { initClaspInstance } from '../core/clasp.js';
|
|
8
|
+
export function buildMcpServer(auth) {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: 'Clasp',
|
|
11
|
+
version: getVersion(),
|
|
12
|
+
});
|
|
13
|
+
server.tool('push_files', 'Pushes the local Apps Script project to the remote server.', {
|
|
14
|
+
projectDir: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('The local directory of the Apps Script project to push. Must contain a .clasp.json file containing the project info.'),
|
|
17
|
+
}, {
|
|
18
|
+
title: 'Push project files to Apps Script',
|
|
19
|
+
openWorldHint: false,
|
|
20
|
+
destructiveHint: true,
|
|
21
|
+
idempotentHint: false,
|
|
22
|
+
readOnlyHint: false,
|
|
23
|
+
}, async ({ projectDir }) => {
|
|
24
|
+
if (!projectDir) {
|
|
25
|
+
return {
|
|
26
|
+
isError: true,
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: 'Project directory is required.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const clasp = await initClaspInstance({
|
|
36
|
+
credentials: auth.credentials,
|
|
37
|
+
configFile: projectDir,
|
|
38
|
+
rootDir: projectDir,
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
const files = await clasp.files.push();
|
|
42
|
+
const fileList = files.map(file => ({
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
45
|
+
}));
|
|
46
|
+
return {
|
|
47
|
+
status: 'success',
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `Pushed project in ${projectDir} to remote server successfully.`,
|
|
52
|
+
},
|
|
53
|
+
...fileList,
|
|
54
|
+
],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
scriptId: clasp.project.scriptId,
|
|
57
|
+
projectDir: projectDir,
|
|
58
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
isError: true,
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: `Error pushing project: ${err.message}`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
server.tool('pull_files', 'Pulls files from Apps Script project to local file system.', {
|
|
75
|
+
projectDir: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe('The local directory of the Apps Script project to update. Must contain a .clasp.json file containing the project info.'),
|
|
78
|
+
}, {
|
|
79
|
+
title: 'Pull project files from Apps Script',
|
|
80
|
+
openWorldHint: false,
|
|
81
|
+
destructiveHint: true,
|
|
82
|
+
idempotentHint: false,
|
|
83
|
+
readOnlyHint: false,
|
|
84
|
+
}, async ({ projectDir }) => {
|
|
85
|
+
if (!projectDir) {
|
|
86
|
+
return {
|
|
87
|
+
isError: true,
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: 'Project directory is required.',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const clasp = await initClaspInstance({
|
|
97
|
+
credentials: auth.credentials,
|
|
98
|
+
configFile: projectDir,
|
|
99
|
+
rootDir: projectDir,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
const files = await clasp.files.pull();
|
|
103
|
+
const fileList = files.map(file => ({
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
106
|
+
}));
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `Pushed project in ${projectDir} to remote server successfully.`,
|
|
112
|
+
},
|
|
113
|
+
...fileList,
|
|
114
|
+
],
|
|
115
|
+
structuredContent: {
|
|
116
|
+
scriptId: clasp.project.scriptId,
|
|
117
|
+
projectDir: projectDir,
|
|
118
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
isError: true,
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: `Error pushing project: ${err.message}`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
server.tool('create_project', 'Create a new apps script project.', {
|
|
135
|
+
projectDir: z.string().describe('The local directory where the Apps Script project will be created.'),
|
|
136
|
+
sourceDir: z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.describe('Local directory relative to projectDir where the Apps Script source files are located. If not specified, files are placed in the project directory.'),
|
|
140
|
+
projectName: z
|
|
141
|
+
.string()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe('Name of the project. If not provided, the project name will be infered from the directory.'),
|
|
144
|
+
}, {
|
|
145
|
+
title: 'Create Apps Script project',
|
|
146
|
+
openWorldHint: false,
|
|
147
|
+
destructiveHint: true,
|
|
148
|
+
idempotentHint: false,
|
|
149
|
+
readOnlyHint: false,
|
|
150
|
+
}, async ({ projectDir, sourceDir, projectName }) => {
|
|
151
|
+
if (!projectDir) {
|
|
152
|
+
return {
|
|
153
|
+
isError: true,
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: 'Project directory is required.',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
await mkdir(projectDir, { recursive: true });
|
|
163
|
+
if (!projectName) {
|
|
164
|
+
projectName = getDefaultProjectName(projectDir);
|
|
165
|
+
}
|
|
166
|
+
const clasp = await initClaspInstance({
|
|
167
|
+
credentials: auth.credentials,
|
|
168
|
+
configFile: projectDir,
|
|
169
|
+
rootDir: projectDir,
|
|
170
|
+
});
|
|
171
|
+
clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.');
|
|
172
|
+
try {
|
|
173
|
+
const id = await clasp.project.createScript(projectName);
|
|
174
|
+
const files = await clasp.files.pull();
|
|
175
|
+
await clasp.project.updateSettings();
|
|
176
|
+
const fileList = files.map(file => ({
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
179
|
+
}));
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: `Created project ${id} in ${projectDir} successfully.`,
|
|
185
|
+
},
|
|
186
|
+
...fileList,
|
|
187
|
+
],
|
|
188
|
+
structuredContent: {
|
|
189
|
+
scriptId: id,
|
|
190
|
+
projectDir: projectDir,
|
|
191
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
return {
|
|
197
|
+
isError: true,
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: `Error pushing project: ${err.message}`,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
server.tool('clone_project', 'Clones and pulls an existing Apps Script project to a local directory.', {
|
|
208
|
+
projectDir: z.string().describe('The local directory where the Apps Script project will be created.'),
|
|
209
|
+
sourceDir: z
|
|
210
|
+
.string()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe('Local directory relative to projectDir where the Apps Script source files are located. If not specified, files are placed in the project directory.'),
|
|
213
|
+
scriptId: z.string().optional().describe('ID of the Apps Script project to clone.'),
|
|
214
|
+
}, {
|
|
215
|
+
title: 'Create Apps Script project',
|
|
216
|
+
openWorldHint: false,
|
|
217
|
+
destructiveHint: true,
|
|
218
|
+
idempotentHint: false,
|
|
219
|
+
readOnlyHint: false,
|
|
220
|
+
}, async ({ projectDir, sourceDir, scriptId }) => {
|
|
221
|
+
if (!projectDir) {
|
|
222
|
+
return {
|
|
223
|
+
isError: true,
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
text: 'Project directory is required.',
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
await mkdir(projectDir, { recursive: true });
|
|
233
|
+
if (!scriptId) {
|
|
234
|
+
return {
|
|
235
|
+
isError: true,
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: 'Script ID is required.',
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const clasp = await initClaspInstance({
|
|
245
|
+
credentials: auth.credentials,
|
|
246
|
+
configFile: projectDir,
|
|
247
|
+
rootDir: projectDir,
|
|
248
|
+
});
|
|
249
|
+
clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.').withScriptId(scriptId);
|
|
250
|
+
try {
|
|
251
|
+
const files = await clasp.files.pull();
|
|
252
|
+
clasp.project.updateSettings();
|
|
253
|
+
const fileList = files.map(file => ({
|
|
254
|
+
type: 'text',
|
|
255
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
256
|
+
}));
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: 'text',
|
|
261
|
+
text: `Cloned project ${scriptId} in ${projectDir} successfully.`,
|
|
262
|
+
},
|
|
263
|
+
...fileList,
|
|
264
|
+
],
|
|
265
|
+
structuredContent: {
|
|
266
|
+
scriptId: scriptId,
|
|
267
|
+
projectDir: projectDir,
|
|
268
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return {
|
|
274
|
+
isError: true,
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: 'text',
|
|
278
|
+
text: `Error pushing project: ${err.message}`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
server.tool('list_projects', 'List Apps Script projects', {}, {
|
|
285
|
+
title: 'List Apps Script projects',
|
|
286
|
+
openWorldHint: false,
|
|
287
|
+
destructiveHint: true,
|
|
288
|
+
idempotentHint: false,
|
|
289
|
+
readOnlyHint: false,
|
|
290
|
+
}, async () => {
|
|
291
|
+
const clasp = await initClaspInstance({
|
|
292
|
+
credentials: auth.credentials,
|
|
293
|
+
});
|
|
294
|
+
try {
|
|
295
|
+
const scripts = await clasp.project.listScripts();
|
|
296
|
+
const scriptList = scripts.results.map(script => ({
|
|
297
|
+
type: 'text',
|
|
298
|
+
text: `${script.name} (${script.id})`,
|
|
299
|
+
}));
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: `Found ${scripts.results.length} Apps Script projects (script ID in parentheses):`,
|
|
305
|
+
},
|
|
306
|
+
...scriptList,
|
|
307
|
+
],
|
|
308
|
+
structuredContent: {
|
|
309
|
+
scripts: scripts.results.map(script => ({
|
|
310
|
+
scriptId: script.id,
|
|
311
|
+
name: script.name,
|
|
312
|
+
})),
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
return {
|
|
318
|
+
isError: true,
|
|
319
|
+
content: [
|
|
320
|
+
{
|
|
321
|
+
type: 'text',
|
|
322
|
+
text: `Error listing projects: ${err.message}`,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return server;
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google/clasp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6-alpha",
|
|
4
4
|
"description": "Develop Apps Script Projects locally",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./build/src/index.js",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"inquirer-autocomplete-standalone": "^0.8.1",
|
|
73
73
|
"loud-rejection": "^2.2.0",
|
|
74
74
|
"micromatch": "^4.0.8",
|
|
75
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
75
76
|
"normalize-path": "^3.0.0",
|
|
76
77
|
"open": "^10.1.2",
|
|
77
78
|
"ora": "^8.1.1",
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
"read-package-up": "^11.0.0",
|
|
81
82
|
"server-destroy": "^1.0.1",
|
|
82
83
|
"split-lines": "^3.0.0",
|
|
83
|
-
"strip-bom": "^5.0.0"
|
|
84
|
+
"strip-bom": "^5.0.0",
|
|
85
|
+
"zod": "^3.25.36"
|
|
84
86
|
},
|
|
85
87
|
"devDependencies": {
|
|
86
88
|
"@biomejs/biome": "^1.9.4",
|