@lovelybunch/api 1.0.69-alpha.2 → 1.0.69-alpha.3

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.
@@ -12,6 +12,9 @@ export declare class JobRunner {
12
12
  private ensureCliAvailable;
13
13
  private ensureLogPath;
14
14
  private loadAgentInstructions;
15
+ private loadMcpConfigs;
16
+ private writeMcpJson;
17
+ private cleanupMcpJson;
15
18
  private buildInstruction;
16
19
  run(job: ScheduledJob, runId: string): Promise<JobRunResult>;
17
20
  }
@@ -20,29 +20,37 @@ function resolveAgent(model) {
20
20
  }
21
21
  function buildCommand(agent, instruction, config) {
22
22
  const quotedInstruction = shellQuote(instruction);
23
- const mcpFlags = config.mcpServers && config.mcpServers.length > 0
24
- ? config.mcpServers.map(server => `--mcp ${shellQuote(server)}`).join(' ')
25
- : '';
23
+ let mainCommand = '';
26
24
  switch (agent) {
27
25
  case 'gemini': {
28
- const cmd = `gemini --yolo ${mcpFlags} -i ${quotedInstruction}`;
29
- return { command: 'gemini', shellCommand: cmd };
26
+ // For non-Claude agents, use the --mcp flag approach if supported
27
+ const mcpFlags = config.mcpServers && config.mcpServers.length > 0
28
+ ? config.mcpServers.map(server => `--mcp ${shellQuote(server)}`).join(' ')
29
+ : '';
30
+ mainCommand = `gemini --yolo ${mcpFlags} -i ${quotedInstruction}`;
31
+ break;
30
32
  }
31
33
  case 'codex': {
34
+ // For non-Claude agents, use the --mcp flag approach if supported
35
+ const mcpFlags = config.mcpServers && config.mcpServers.length > 0
36
+ ? config.mcpServers.map(server => `--mcp ${shellQuote(server)}`).join(' ')
37
+ : '';
32
38
  const baseCmd = `codex ${quotedInstruction} --dangerously-bypass-approvals-and-sandbox ${mcpFlags}`.trim();
33
39
  const needsPseudoTty = config.runningAsRoot && process.platform !== 'win32';
34
- const wrappedCmd = needsPseudoTty
40
+ mainCommand = needsPseudoTty
35
41
  ? `script -q -e -c ${shellQuote(baseCmd)} /dev/null`
36
42
  : baseCmd;
37
- return { command: 'codex', shellCommand: wrappedCmd };
43
+ break;
38
44
  }
39
45
  case 'claude':
40
46
  default: {
47
+ // Claude uses .mcp.json for MCP server configuration (no --mcp flag)
41
48
  const prefix = config.runningAsRoot ? 'IS_SANDBOX=1 ' : '';
42
- const cmd = `${prefix}claude ${quotedInstruction} --dangerously-skip-permissions ${mcpFlags}`.trim();
43
- return { command: 'claude', shellCommand: cmd };
49
+ mainCommand = `${prefix}claude ${quotedInstruction} --dangerously-skip-permissions`.trim();
50
+ break;
44
51
  }
45
52
  }
53
+ return { command: agent === 'claude' ? 'claude' : agent, shellCommand: mainCommand };
46
54
  }
47
55
  const CLI_AGENT_LABEL = {
48
56
  claude: 'Claude',
@@ -100,6 +108,52 @@ export class JobRunner {
100
108
  return null;
101
109
  }
102
110
  }
111
+ async loadMcpConfigs() {
112
+ try {
113
+ const projectRoot = await this.projectRootPromise;
114
+ const mcpConfigPath = path.join(projectRoot, '.nut', 'mcp', 'config.json');
115
+ const content = await fs.readFile(mcpConfigPath, 'utf-8');
116
+ const json = JSON.parse(content);
117
+ return json.mcpServers || {};
118
+ }
119
+ catch (error) {
120
+ if (error.code !== 'ENOENT') {
121
+ console.warn('Failed to load MCP config:', error);
122
+ }
123
+ return {};
124
+ }
125
+ }
126
+ async writeMcpJson(mcpServers, allMcpConfigs) {
127
+ const projectRoot = await this.projectRootPromise;
128
+ const mcpJsonPath = path.join(projectRoot, '.mcp.json');
129
+ // Filter to only include the requested servers that are enabled
130
+ const filteredConfigs = {};
131
+ for (const serverName of mcpServers) {
132
+ const config = allMcpConfigs[serverName];
133
+ if (config && config.enabled !== false) {
134
+ filteredConfigs[serverName] = config;
135
+ }
136
+ }
137
+ const mcpJson = {
138
+ mcpServers: filteredConfigs
139
+ };
140
+ await fs.writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), 'utf-8');
141
+ }
142
+ async cleanupMcpJson() {
143
+ try {
144
+ const projectRoot = await this.projectRootPromise;
145
+ const mcpJsonPath = path.join(projectRoot, '.mcp.json');
146
+ // Reset to empty mcpServers object
147
+ const emptyMcpJson = {
148
+ mcpServers: {}
149
+ };
150
+ await fs.writeFile(mcpJsonPath, JSON.stringify(emptyMcpJson, null, 2), 'utf-8');
151
+ }
152
+ catch (error) {
153
+ // Don't fail the job if cleanup fails
154
+ console.warn('Failed to cleanup .mcp.json:', error);
155
+ }
156
+ }
103
157
  async buildInstruction(job, agentLabel) {
104
158
  const scheduleDescription = job.schedule.type === 'cron'
105
159
  ? `Cron: ${job.schedule.expression}`
@@ -124,6 +178,12 @@ export class JobRunner {
124
178
  const agent = resolveAgent(job.model);
125
179
  const instruction = await this.buildInstruction(job, CLI_AGENT_LABEL[agent] || agent);
126
180
  const runningAsRoot = typeof process.getuid === 'function' && process.getuid() === 0;
181
+ // Write .mcp.json if MCP servers are specified (Claude reads this from project root)
182
+ const mcpJsonWritten = job.mcpServers && job.mcpServers.length > 0 && agent === 'claude';
183
+ if (mcpJsonWritten) {
184
+ const allMcpConfigs = await this.loadMcpConfigs();
185
+ await this.writeMcpJson(job.mcpServers, allMcpConfigs);
186
+ }
127
187
  const { shellCommand } = buildCommand(agent, instruction, {
128
188
  runningAsRoot,
129
189
  mcpServers: job.mcpServers
@@ -135,6 +195,9 @@ export class JobRunner {
135
195
  logStream.write(`[${new Date().toISOString()}] Starting job ${job.id} using ${agent} CLI\n`);
136
196
  logStream.write(`Instruction: ${instruction}\n`);
137
197
  logStream.write(`Command: ${shellCommand}\n`);
198
+ if (job.mcpServers && job.mcpServers.length > 0) {
199
+ logStream.write(`MCP Servers: ${job.mcpServers.join(', ')} (configured via .mcp.json)\n`);
200
+ }
138
201
  return new Promise((resolve) => {
139
202
  let cliMissingError = null;
140
203
  try {
@@ -147,6 +210,10 @@ export class JobRunner {
147
210
  const message = cliMissingError.message;
148
211
  logStream.write(`${message}\n`);
149
212
  logStream.end();
213
+ // Cleanup .mcp.json if it was written
214
+ if (mcpJsonWritten) {
215
+ this.cleanupMcpJson().catch(err => console.warn('Cleanup failed:', err));
216
+ }
150
217
  resolve({
151
218
  status: 'failed',
152
219
  error: message,
@@ -182,6 +249,10 @@ export class JobRunner {
182
249
  logStream.write(`${message}\n`);
183
250
  logStream.end();
184
251
  clearTimeout(abortTimeout);
252
+ // Cleanup .mcp.json if it was written
253
+ if (mcpJsonWritten) {
254
+ this.cleanupMcpJson().catch(err => console.warn('Cleanup failed:', err));
255
+ }
185
256
  resolve({
186
257
  status: 'failed',
187
258
  error: message,
@@ -195,6 +266,10 @@ export class JobRunner {
195
266
  logStream.write(`\n[${new Date().toISOString()}] Job ${job.id} completed with exit code ${code}\n`);
196
267
  logStream.end();
197
268
  clearTimeout(abortTimeout);
269
+ // Cleanup .mcp.json if it was written
270
+ if (mcpJsonWritten) {
271
+ this.cleanupMcpJson().catch(err => console.warn('Cleanup failed:', err));
272
+ }
198
273
  const summary = summaryChunks.join('');
199
274
  resolve({
200
275
  status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovelybunch/api",
3
- "version": "1.0.69-alpha.2",
3
+ "version": "1.0.69-alpha.3",
4
4
  "type": "module",
5
5
  "main": "dist/server-with-static.js",
6
6
  "exports": {
@@ -36,9 +36,9 @@
36
36
  "dependencies": {
37
37
  "@hono/node-server": "^1.13.7",
38
38
  "@hono/node-ws": "^1.0.6",
39
- "@lovelybunch/core": "^1.0.69-alpha.2",
40
- "@lovelybunch/mcp": "^1.0.69-alpha.2",
41
- "@lovelybunch/types": "^1.0.69-alpha.2",
39
+ "@lovelybunch/core": "^1.0.69-alpha.3",
40
+ "@lovelybunch/mcp": "^1.0.69-alpha.3",
41
+ "@lovelybunch/types": "^1.0.69-alpha.3",
42
42
  "arctic": "^1.9.2",
43
43
  "bcrypt": "^5.1.1",
44
44
  "cookie": "^0.6.0",