replicas-mcp 0.1.7 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ import { z } from 'zod';
4
4
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { parseAgentEvents, type AgentProvider, type ReadReplicaHistoryResponse } from '@replicas/shared';
6
6
 
7
- const API_URL = process.env.REPLICAS_API_URL || 'https://api.replicas.dev';
7
+ const API_URL = process.env.REPLICAS_API_URL || 'https://api.tryreplicas.com';
8
8
  const API_KEY = process.env.REPLICAS_API_KEY;
9
9
 
10
10
  if (!API_KEY) {
@@ -57,11 +57,15 @@ type RegisterToolFn = (
57
57
 
58
58
  const server = new McpServer({
59
59
  name: 'replicas-mcp',
60
- version: '0.1.1',
60
+ version: '0.2.1',
61
61
  });
62
62
 
63
63
  const registerTool = server.registerTool.bind(server) as RegisterToolFn;
64
64
 
65
+ // ---------------------------------------------------------------------------
66
+ // Replica CRUD
67
+ // ---------------------------------------------------------------------------
68
+
65
69
  registerTool(
66
70
  'create_replica',
67
71
  {
@@ -69,8 +73,11 @@ registerTool(
69
73
  inputSchema: {
70
74
  name: z.string().regex(/^\S+$/, 'Name cannot contain spaces'),
71
75
  message: z.string(),
72
- repositories: z.array(z.string()).min(1),
73
- coding_agent: z.enum(['claude', 'codex']).optional(),
76
+ repository_ids: z.array(z.string()).min(1).optional().describe('Repository IDs to include. Mutually exclusive with repository_set_id.'),
77
+ repository_set_id: z.string().optional().describe('Repository set ID to use. Mutually exclusive with repository_ids.'),
78
+ coding_agent: z.enum(['claude', 'codex']).optional().describe('Coding agent to use (default: claude)'),
79
+ model: z.string().optional().describe('Model to use for the coding agent'),
80
+ lifecycle_policy: z.enum(['default', 'delete_when_done', 'delete_after_inactivity']).optional().describe('Lifecycle policy for the workspace'),
74
81
  },
75
82
  },
76
83
  async (args) => {
@@ -78,9 +85,12 @@ registerTool(
78
85
  const body: Record<string, unknown> = {
79
86
  name: args.name,
80
87
  message: args.message,
81
- repositories: args.repositories,
82
88
  };
89
+ if (args.repository_ids) body.repository_ids = args.repository_ids;
90
+ if (args.repository_set_id) body.repository_set_id = args.repository_set_id;
83
91
  if (args.coding_agent) body.coding_agent = args.coding_agent;
92
+ if (args.model) body.model = args.model;
93
+ if (args.lifecycle_policy) body.lifecycle_policy = args.lifecycle_policy;
84
94
  return result(await api('POST', '/v1/replica', body));
85
95
  } catch (e) {
86
96
  return result(e instanceof Error ? e.message : String(e), true);
@@ -91,10 +101,13 @@ registerTool(
91
101
  registerTool(
92
102
  'list_replicas',
93
103
  {
94
- description: 'List all replicas',
104
+ description: 'List all replicas with pagination and optional filters',
95
105
  inputSchema: {
96
106
  page: z.number().optional(),
97
107
  limit: z.number().optional(),
108
+ user_id: z.string().optional().describe('Filter by creator user ID'),
109
+ source: z.enum(['api', 'dashboard', 'slack', 'linear', 'github', 'automation']).optional().describe('Filter by creation source'),
110
+ status: z.enum(['active', 'sleeping', 'preparing']).optional().describe('Filter by workspace status'),
98
111
  },
99
112
  },
100
113
  async (args) => {
@@ -102,6 +115,9 @@ registerTool(
102
115
  const params = new URLSearchParams();
103
116
  if (args.page) params.set('page', String(args.page));
104
117
  if (args.limit) params.set('limit', String(args.limit));
118
+ if (args.user_id) params.set('user_id', args.user_id as string);
119
+ if (args.source) params.set('source', args.source as string);
120
+ if (args.status) params.set('status', args.status as string);
105
121
  const q = params.toString();
106
122
  return result(await api('GET', `/v1/replica${q ? '?' + q : ''}`));
107
123
  } catch (e) {
@@ -113,14 +129,18 @@ registerTool(
113
129
  registerTool(
114
130
  'get_replica',
115
131
  {
116
- description: 'Get replica by ID',
132
+ description: 'Get replica details by ID, including current status, chats, and repository statuses',
117
133
  inputSchema: {
118
134
  id: z.string(),
135
+ include: z.array(z.enum(['environment', 'diffs'])).optional().describe('Extra data to include in response'),
119
136
  },
120
137
  },
121
138
  async (args) => {
122
139
  try {
123
- return result(await api('GET', `/v1/replica/${args.id}`));
140
+ const params = new URLSearchParams();
141
+ if (args.include) params.set('include', (args.include as string[]).join(','));
142
+ const q = params.toString();
143
+ return result(await api('GET', `/v1/replica/${args.id}${q ? '?' + q : ''}`));
124
144
  } catch (e) {
125
145
  return result(e instanceof Error ? e.message : String(e), true);
126
146
  }
@@ -130,18 +150,24 @@ registerTool(
130
150
  registerTool(
131
151
  'send_replica_message',
132
152
  {
133
- description: 'Send message to a replica',
153
+ description: 'Send a message to an existing replica. Automatically handles waking sleeping replicas.',
134
154
  inputSchema: {
135
155
  id: z.string(),
136
156
  message: z.string(),
137
- coding_agent: z.enum(['claude', 'codex']).optional(),
157
+ coding_agent: z.enum(['claude', 'codex']).optional().describe('Coding agent to use'),
158
+ chat_id: z.string().optional().describe('Chat ID to send the message to (uses default chat if omitted)'),
159
+ model: z.string().optional().describe('Model to use'),
160
+ plan_mode: z.boolean().optional().describe('Whether to use plan mode'),
138
161
  },
139
162
  },
140
163
  async (args) => {
141
164
  try {
142
165
  const body: Record<string, unknown> = { message: args.message };
143
166
  if (args.coding_agent) body.coding_agent = args.coding_agent;
144
- return result(await api('POST', `/v1/replica/${args.id}/send`, body));
167
+ if (args.chat_id) body.chat_id = args.chat_id;
168
+ if (args.model) body.model = args.model;
169
+ if (args.plan_mode !== undefined) body.plan_mode = args.plan_mode;
170
+ return result(await api('POST', `/v1/replica/${args.id}/messages`, body));
145
171
  } catch (e) {
146
172
  return result(e instanceof Error ? e.message : String(e), true);
147
173
  }
@@ -151,7 +177,7 @@ registerTool(
151
177
  registerTool(
152
178
  'delete_replica',
153
179
  {
154
- description: 'Delete a replica',
180
+ description: 'Delete a replica. This terminates the workspace and cannot be undone.',
155
181
  inputSchema: {
156
182
  id: z.string(),
157
183
  },
@@ -165,37 +191,117 @@ registerTool(
165
191
  }
166
192
  );
167
193
 
194
+ // ---------------------------------------------------------------------------
195
+ // Repositories & Repository Sets
196
+ // ---------------------------------------------------------------------------
197
+
168
198
  registerTool(
169
199
  'list_repositories',
170
200
  {
171
- description: 'List available repositories',
201
+ description: 'List available repositories that can be used when creating replicas',
202
+ },
203
+ async () => {
204
+ try {
205
+ return result(await api('GET', '/v1/replica/repositories'));
206
+ } catch (e) {
207
+ return result(e instanceof Error ? e.message : String(e), true);
208
+ }
209
+ }
210
+ );
211
+
212
+ registerTool(
213
+ 'list_repository_sets',
214
+ {
215
+ description: 'List available repository sets (named groups of repositories)',
172
216
  },
173
217
  async () => {
174
218
  try {
175
- return result(await api('GET', '/v1/repositories'));
219
+ return result(await api('GET', '/v1/replica/repository-sets'));
220
+ } catch (e) {
221
+ return result(e instanceof Error ? e.message : String(e), true);
222
+ }
223
+ }
224
+ );
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Chat Management
228
+ // ---------------------------------------------------------------------------
229
+
230
+ registerTool(
231
+ 'list_chats',
232
+ {
233
+ description: 'List chats in a replica workspace',
234
+ inputSchema: {
235
+ id: z.string().describe('Replica ID'),
236
+ },
237
+ },
238
+ async (args) => {
239
+ try {
240
+ return result(await api('GET', `/v1/replica/${args.id}/chats`));
241
+ } catch (e) {
242
+ return result(e instanceof Error ? e.message : String(e), true);
243
+ }
244
+ }
245
+ );
246
+
247
+ registerTool(
248
+ 'create_chat',
249
+ {
250
+ description: 'Create a new chat in a replica workspace',
251
+ inputSchema: {
252
+ id: z.string().describe('Replica ID'),
253
+ provider: z.enum(['claude', 'codex']).describe('Coding agent provider for the chat'),
254
+ title: z.string().optional().describe('Optional title for the chat'),
255
+ },
256
+ },
257
+ async (args) => {
258
+ try {
259
+ const body: Record<string, unknown> = { provider: args.provider };
260
+ if (args.title) body.title = args.title;
261
+ return result(await api('POST', `/v1/replica/${args.id}/chats`, body));
176
262
  } catch (e) {
177
263
  return result(e instanceof Error ? e.message : String(e), true);
178
264
  }
179
265
  }
180
266
  );
181
267
 
268
+ registerTool(
269
+ 'delete_chat',
270
+ {
271
+ description: 'Delete a chat from a replica workspace',
272
+ inputSchema: {
273
+ id: z.string().describe('Replica ID'),
274
+ chat_id: z.string().describe('Chat ID to delete'),
275
+ },
276
+ },
277
+ async (args) => {
278
+ try {
279
+ return result(await api('DELETE', `/v1/replica/${args.id}/chats/${args.chat_id}`));
280
+ } catch (e) {
281
+ return result(e instanceof Error ? e.message : String(e), true);
282
+ }
283
+ }
284
+ );
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // History & Hooks
288
+ // ---------------------------------------------------------------------------
289
+
182
290
  registerTool(
183
291
  'read_replica_history',
184
292
  {
185
293
  description: 'Read conversation history of a replica. Returns parsed DisplayMessage objects (user messages, agent responses, commands, file changes, tool calls, etc). If the workspace is sleeping, it will be woken and you should retry in 30-90 seconds.',
186
294
  inputSchema: {
187
295
  id: z.string(),
188
- limit: z.number().optional().describe('Maximum number of events to return'),
189
- offset: z.number().optional().describe('Number of events to skip from the end'),
296
+ chat_id: z.string().optional().describe('Read history for a specific chat (uses default chat if omitted)'),
190
297
  },
191
298
  },
192
299
  async (args) => {
193
300
  try {
194
301
  const params = new URLSearchParams();
195
- if (args.limit) params.set('limit', String(args.limit));
196
- if (args.offset) params.set('offset', String(args.offset));
302
+ if (args.chat_id) params.set('chat_id', args.chat_id as string);
197
303
  const q = params.toString();
198
- const response = await api<ReadReplicaHistoryResponse>('GET', `/v1/replica/${args.id}/read${q ? '?' + q : ''}`);
304
+ const response = await api<ReadReplicaHistoryResponse>('GET', `/v1/replica/${args.id}/history${q ? '?' + q : ''}`);
199
305
 
200
306
  if (response.waking) {
201
307
  return result({
@@ -212,7 +318,7 @@ registerTool(
212
318
  thread_id: response.thread_id,
213
319
  coding_agent: response.coding_agent,
214
320
  total: response.total,
215
- hasMore: response.hasMore,
321
+ has_more: response.has_more,
216
322
  messages,
217
323
  });
218
324
  } catch (e) {
@@ -221,5 +327,22 @@ registerTool(
221
327
  }
222
328
  );
223
329
 
330
+ registerTool(
331
+ 'get_replica_hooks',
332
+ {
333
+ description: 'Get warm and start hook logs for a replica workspace',
334
+ inputSchema: {
335
+ id: z.string().describe('Replica ID'),
336
+ },
337
+ },
338
+ async (args) => {
339
+ try {
340
+ return result(await api('GET', `/v1/replica/${args.id}/hooks`));
341
+ } catch (e) {
342
+ return result(e instanceof Error ? e.message : String(e), true);
343
+ }
344
+ }
345
+ );
346
+
224
347
  const transport = new StdioServerTransport();
225
348
  await server.connect(transport);