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

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,
@@ -176,6 +176,7 @@ export class JobStore {
176
176
  tags: data.tags ?? [],
177
177
  contextPaths: data.contextPaths ?? [],
178
178
  agentId: data.agentId,
179
+ agentIds: data.agentIds,
179
180
  mcpServers: data.mcpServers,
180
181
  };
181
182
  }
@@ -209,6 +210,7 @@ export class JobStore {
209
210
  tags: job.tags ?? [],
210
211
  contextPaths: job.contextPaths ?? [],
211
212
  agentId: job.agentId,
213
+ agentIds: job.agentIds,
212
214
  mcpServers: job.mcpServers,
213
215
  };
214
216
  }
@@ -48,6 +48,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
48
48
  tags?: string[];
49
49
  contextPaths?: string[];
50
50
  agentId?: string;
51
+ agentIds?: string[];
51
52
  mcpServers?: string[];
52
53
  };
53
54
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
@@ -111,6 +112,7 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
111
112
  tags?: string[];
112
113
  contextPaths?: string[];
113
114
  agentId?: string;
115
+ agentIds?: string[];
114
116
  mcpServers?: string[];
115
117
  };
116
118
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
@@ -85,6 +85,9 @@ export async function PATCH(c) {
85
85
  agentId: body.agentId !== undefined
86
86
  ? (typeof body.agentId === 'string' && body.agentId ? body.agentId : undefined)
87
87
  : existing.agentId,
88
+ agentIds: body.agentIds !== undefined
89
+ ? (Array.isArray(body.agentIds) ? body.agentIds.filter((s) => typeof s === 'string') : undefined)
90
+ : existing.agentIds,
88
91
  mcpServers: body.mcpServers !== undefined
89
92
  ? (Array.isArray(body.mcpServers) ? body.mcpServers.filter((s) => typeof s === 'string') : undefined)
90
93
  : existing.mcpServers,
@@ -44,6 +44,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
44
44
  tags?: string[];
45
45
  contextPaths?: string[];
46
46
  agentId?: string;
47
+ agentIds?: string[];
47
48
  mcpServers?: string[];
48
49
  }[];
49
50
  }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
@@ -101,6 +102,7 @@ export declare function POST(c: Context): Promise<(Response & import("hono").Typ
101
102
  tags?: string[];
102
103
  contextPaths?: string[];
103
104
  agentId?: string;
105
+ agentIds?: string[];
104
106
  mcpServers?: string[];
105
107
  };
106
108
  }, 201, "json">) | (Response & import("hono").TypedResponse<{
@@ -123,6 +123,7 @@ export async function POST(c) {
123
123
  tags: Array.isArray(body.tags) ? body.tags : [],
124
124
  contextPaths: Array.isArray(body.contextPaths) ? body.contextPaths : [],
125
125
  agentId: body.agentId && typeof body.agentId === 'string' ? body.agentId : undefined,
126
+ agentIds: Array.isArray(body.agentIds) ? body.agentIds.filter((s) => typeof s === 'string') : undefined,
126
127
  mcpServers: Array.isArray(body.mcpServers) ? body.mcpServers.filter((s) => typeof s === 'string') : undefined,
127
128
  };
128
129
  await store.saveJob(job);
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.4",
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.4",
40
+ "@lovelybunch/mcp": "^1.0.69-alpha.4",
41
+ "@lovelybunch/types": "^1.0.69-alpha.4",
42
42
  "arctic": "^1.9.2",
43
43
  "bcrypt": "^5.1.1",
44
44
  "cookie": "^0.6.0",