@remogram/mcp 0.1.0-beta.1 → 0.1.0-beta.10

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/register-tools.mjs +199 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remogram/mcp",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.10",
4
4
  "description": "Remogram MCP server delegating to CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.17.0",
33
- "@remogram/cli": "0.1.0-beta.1",
33
+ "@remogram/cli": "0.1.0-beta.10",
34
34
  "zod": "^3.25.76"
35
35
  }
36
36
  }
@@ -1,13 +1,28 @@
1
1
  import { z } from 'zod';
2
+ import { normalizeAllowedPaths } from '@remogram/core';
2
3
  import { runRemogramCli, packetToMcpContent } from './run-cli.mjs';
3
4
 
5
+ /** @internal Build CLI argv for merge_plan MCP tool (exported for transport tests). */
6
+ export function mergePlanMcpCliArgs(input) {
7
+ const a = ['merge', 'plan', '--number', String(input.number)];
8
+ for (const glob of normalizeAllowedPaths(input.allowed_paths ?? []) ?? []) {
9
+ a.push('--allowed-path', glob);
10
+ }
11
+ return a;
12
+ }
13
+
4
14
  export function registerTools(server) {
5
15
  const tools = [
6
16
  {
7
17
  name: 'doctor',
8
18
  description: 'Read-only provider readiness diagnostics for config, remote trust, auth, capabilities, and checks.',
9
- inputSchema: z.object({}),
10
- args: ['doctor'],
19
+ inputSchema: z.object({
20
+ live: z
21
+ .boolean()
22
+ .optional()
23
+ .describe('When true, perform a bounded live forge API reachability probe'),
24
+ }),
25
+ args: (input) => (input.live ? ['doctor', '--live'] : ['doctor']),
11
26
  },
12
27
  {
13
28
  name: 'provider_capabilities',
@@ -30,6 +45,108 @@ export function registerTools(server) {
30
45
  }),
31
46
  args: (input) => ['refs', 'compare', '--base', input.base, '--head', input.head],
32
47
  },
48
+ {
49
+ name: 'ref_inventory',
50
+ description: 'List repository refs with SHAs, default branch hint, and optional ancestry hints.',
51
+ inputSchema: z.object({}),
52
+ args: ['refs', 'inventory'],
53
+ },
54
+ {
55
+ name: 'cr_inventory',
56
+ description: 'Aggregate open change requests with checks and merge-plan facts into a semantic-diff slice.',
57
+ inputSchema: z.object({
58
+ slice_ref: z.string().optional().describe('Optional slice ref label for consumers'),
59
+ limit: z.number().int().positive().optional().describe('Max open CR entries (default 3)'),
60
+ sort: z
61
+ .enum(['number_asc', 'number_desc', 'recent_update', 'recent_created'])
62
+ .optional()
63
+ .describe('Open-list slice sort preset (default number_asc)'),
64
+ cursor: z.string().optional().describe('Opaque cursor from prior cr_inventory next_cursor'),
65
+ }),
66
+ args: (input) => {
67
+ const a = ['cr', 'inventory'];
68
+ if (input.slice_ref) a.push('--slice-ref', input.slice_ref);
69
+ if (input.limit != null) a.push('--limit', String(input.limit));
70
+ if (input.sort) a.push('--sort', input.sort);
71
+ if (input.cursor) a.push('--cursor', input.cursor);
72
+ return a;
73
+ },
74
+ },
75
+ {
76
+ name: 'cr_open',
77
+ description: 'Open a change request (pull request) on the configured forge.',
78
+ inputSchema: z.object({
79
+ head: z.string().describe('Head branch ref'),
80
+ base: z.string().describe('Base branch ref'),
81
+ title: z.string().describe('Change request title'),
82
+ body: z.string().optional().describe('Optional change request body'),
83
+ idempotency_key: z
84
+ .string()
85
+ .optional()
86
+ .describe('Optional agent idempotency key for retry-safe writes'),
87
+ }),
88
+ args: (input) => {
89
+ const a = ['cr', 'open', '--head', input.head, '--base', input.base, '--title', input.title];
90
+ if (input.body) a.push('--body', input.body);
91
+ if (input.idempotency_key) a.push('--idempotency-key', input.idempotency_key);
92
+ return a;
93
+ },
94
+ readOnlyHint: false,
95
+ destructiveHint: true,
96
+ },
97
+ {
98
+ name: 'status_set',
99
+ description: 'Set a commit status (check context) on the configured forge.',
100
+ inputSchema: z.object({
101
+ sha: z.string().describe('40-character commit SHA'),
102
+ context: z.string().describe('Status context name'),
103
+ state: z.enum(['pending', 'success', 'failure', 'error']).describe('Status state'),
104
+ target_url: z.string().optional().describe('Optional target URL for the status'),
105
+ description: z.string().optional().describe('Optional status description'),
106
+ idempotency_key: z
107
+ .string()
108
+ .optional()
109
+ .describe('Optional agent idempotency key for retry-safe writes'),
110
+ }),
111
+ args: (input) => {
112
+ const a = [
113
+ 'status',
114
+ 'set',
115
+ '--sha',
116
+ input.sha,
117
+ '--context',
118
+ input.context,
119
+ '--state',
120
+ input.state,
121
+ ];
122
+ if (input.target_url) a.push('--target-url', input.target_url);
123
+ if (input.description) a.push('--description', input.description);
124
+ if (input.idempotency_key) a.push('--idempotency-key', input.idempotency_key);
125
+ return a;
126
+ },
127
+ readOnlyHint: false,
128
+ destructiveHint: true,
129
+ },
130
+ {
131
+ name: 'issue_open',
132
+ description: 'Open a forge issue on the configured repository (Gitea v1).',
133
+ inputSchema: z.object({
134
+ title: z.string().describe('Issue title'),
135
+ body: z.string().optional().describe('Optional issue body'),
136
+ idempotency_key: z
137
+ .string()
138
+ .optional()
139
+ .describe('Optional agent idempotency key for retry-safe writes'),
140
+ }),
141
+ args: (input) => {
142
+ const a = ['issue', 'open', '--title', input.title];
143
+ if (input.body) a.push('--body', input.body);
144
+ if (input.idempotency_key) a.push('--idempotency-key', input.idempotency_key);
145
+ return a;
146
+ },
147
+ readOnlyHint: false,
148
+ destructiveHint: true,
149
+ },
33
150
  {
34
151
  name: 'pr_status',
35
152
  description: 'PR metadata and mergeability facts.',
@@ -60,10 +177,85 @@ export function registerTools(server) {
60
177
  {
61
178
  name: 'merge_plan',
62
179
  description: 'Merge readiness facts: mergeability, checks, blockers.',
180
+ inputSchema: z.object({
181
+ number: z.number().int().positive(),
182
+ allowed_paths: z.array(z.string()).optional(),
183
+ }),
184
+ args: mergePlanMcpCliArgs,
185
+ },
186
+ {
187
+ name: 'whoami',
188
+ description: 'Authenticated forge identity facts (login, can_write, token scope/expiry signals).',
189
+ inputSchema: z.object({}),
190
+ args: ['whoami'],
191
+ },
192
+ {
193
+ name: 'branch_protection',
194
+ description:
195
+ 'Branch protection policy facts: required status contexts, protected rules, approvals signal.',
196
+ inputSchema: z.object({
197
+ branch_ref: z.string(),
198
+ }),
199
+ args: (input) => ['branch', 'protection', '--branch-ref', input.branch_ref],
200
+ },
201
+ {
202
+ name: 'cr_files',
203
+ description: 'Changed file paths for a change request (bounded, truncation-aware).',
204
+ inputSchema: z.object({
205
+ number: z.number().int().positive(),
206
+ }),
207
+ args: (input) => ['cr', 'files', '--number', String(input.number)],
208
+ },
209
+ {
210
+ name: 'cr_comments',
211
+ description: 'Review comments for a change request (sanitized bodies, truncation-aware).',
63
212
  inputSchema: z.object({
64
213
  number: z.number().int().positive(),
65
214
  }),
66
- args: (input) => ['merge', 'plan', '--number', String(input.number)],
215
+ args: (input) => ['cr', 'comments', '--number', String(input.number)],
216
+ },
217
+ {
218
+ name: 'forge_changes',
219
+ description:
220
+ 'Forge activity events since an observed_at boundary (PR lifecycle, head SHA moves, check conclusions).',
221
+ inputSchema: z.object({
222
+ since: z.string().optional().describe('ISO-8601 observed_at boundary (required on first page)'),
223
+ cursor: z.string().optional().describe('Opaque cursor from prior forge_changes next_cursor'),
224
+ limit: z.number().int().positive().optional().describe('Events per page (default 64)'),
225
+ }),
226
+ args: (input) => {
227
+ const a = ['forge', 'changes'];
228
+ if (input.since) a.push('--since', input.since);
229
+ if (input.cursor) a.push('--cursor', input.cursor);
230
+ if (input.limit != null) a.push('--limit', String(input.limit));
231
+ return a;
232
+ },
233
+ },
234
+ {
235
+ name: 'merge_execute',
236
+ description: 'Execute a forge merge for an open change request after SHA-bound preflight.',
237
+ inputSchema: z.object({
238
+ number: z.number().int().positive(),
239
+ expected_base_sha: z.string().describe('Expected base SHA (40 hex chars)'),
240
+ expected_head_sha: z.string().describe('Expected head SHA (40 hex chars)'),
241
+ method: z.enum(['merge']).optional().describe('Merge method (v1: merge only)'),
242
+ }),
243
+ args: (input) => {
244
+ const a = [
245
+ 'merge',
246
+ 'execute',
247
+ '--number',
248
+ String(input.number),
249
+ '--expected-base-sha',
250
+ input.expected_base_sha,
251
+ '--expected-head-sha',
252
+ input.expected_head_sha,
253
+ ];
254
+ if (input.method) a.push('--method', input.method);
255
+ return a;
256
+ },
257
+ readOnlyHint: false,
258
+ destructiveHint: true,
67
259
  },
68
260
  {
69
261
  name: 'sync_plan',
@@ -85,7 +277,10 @@ export function registerTools(server) {
85
277
  {
86
278
  description: tool.description,
87
279
  inputSchema: tool.inputSchema,
88
- annotations: { readOnlyHint: true, destructiveHint: false },
280
+ annotations: {
281
+ readOnlyHint: tool.readOnlyHint !== false,
282
+ destructiveHint: tool.destructiveHint === true,
283
+ },
89
284
  },
90
285
  async (input) => {
91
286
  const args = typeof tool.args === 'function' ? tool.args(input) : tool.args;