@adrata/adrata-mcp 1.0.19 → 1.0.40

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.
@@ -1,5 +1,8 @@
1
1
  /** Read-only, product-visible health for Starfield's source-control ingestion. */
2
- export function registerSourceControlTools(server, { z, api, ok }) {
2
+ export function registerSourceControlTools(
3
+ server,
4
+ { z, api, ok, validateApiBridgeRequest, buildMutationHeaders, getGrantedScope }
5
+ ) {
3
6
  server.tool(
4
7
  'list_source_control_connections',
5
8
  'List source-control connections with repository binding count, last durably acknowledged delivery, rejection state, actionable health, and the exact webhook events that are safe to enable.',
@@ -23,9 +26,122 @@ export function registerSourceControlTools(server, { z, api, ok }) {
23
26
  return ok({ events: response?.data ?? response ?? [] });
24
27
  }
25
28
  );
29
+
30
+ server.tool(
31
+ 'bind_repository_to_board',
32
+ 'Bind one repository to one board, so a merged pull request carrying a card id moves that card by itself.'
33
+ + ' Until a board is bound, every merge on it advances nothing and the column timestamps are hand-typed,'
34
+ + ' which is the input every cycle-time and control chart is built from.'
35
+ + ' Name BOTH mergeFromColumnId and mergeToColumnId or neither: one end alone is a half-configured rule'
36
+ + ' that silently does nothing, and the database CHECK constraint refuses it.'
37
+ + ' Governed write: previews by default. A live write requires dryRun:false plus approved:true, a reason,'
38
+ + ' and an idempotencyKey (reuse the SAME key on retry — a duplicate bind is refused as a 409).',
39
+ {
40
+ connectionId: z.string().describe('Source-control connection id, from list_source_control_connections.'),
41
+ boardId: z.string().describe('Board id from list_work_boards. One board binds once per connection.'),
42
+ externalRepoId: z.string().describe('The vendor’s own repository id, not its name.'),
43
+ externalFullName: z.string().describe('owner/name, e.g. adrata/adrata.'),
44
+ mergeFromColumnId: z
45
+ .string()
46
+ .optional()
47
+ .describe('Where a merged pull request’s card must be coming FROM. Both ends or neither.'),
48
+ mergeToColumnId: z
49
+ .string()
50
+ .optional()
51
+ .describe('Where it goes on merge. Both ends or neither.'),
52
+ onMergeAction: z
53
+ .enum(['advance', 'suggest', 'ignore'])
54
+ .optional()
55
+ .describe('Defaults to advance. `suggest` records the proposal without moving the card.'),
56
+ branchSample: z
57
+ .array(z.string())
58
+ .optional()
59
+ .describe(
60
+ 'Recent branch names, newest first. NOT stored: the server measures how many already carry a card'
61
+ + ' reference and seeds the link strategies from what the repository actually does, rather than from'
62
+ + ' a form somebody filled in. Omitting it means the strategies are chosen without that evidence.'
63
+ ),
64
+ advanceOnlyWhenAllMerged: z
65
+ .boolean()
66
+ .optional()
67
+ .describe('Advance only when no linked change request is still open. Defaults true.'),
68
+ openPullsCardIntoProgress: z
69
+ .boolean()
70
+ .optional()
71
+ .describe('Does an OPENED pull request pull its card into In progress? Defaults false.'),
72
+ stagingEnvironmentName: z
73
+ .string()
74
+ .optional()
75
+ .describe('Exact vendor environment name. Absence means that evidence is not configured; never infer a role from a word like "staging".'),
76
+ productionEnvironmentName: z.string().optional().describe('Exact vendor environment name.'),
77
+ dryRun: z.boolean().optional().default(true),
78
+ approved: z.boolean().optional().default(false),
79
+ reason: z.string().optional(),
80
+ idempotencyKey: z.string().optional(),
81
+ },
82
+ async (args) => {
83
+ // Refused HERE rather than at the API, because the 400 this produces reads
84
+ // as a validation error about a column and the actual mistake is that only
85
+ // one end was named. `scm_repositories_move_shape_chk` enforces the same
86
+ // shape in the database; this says why before the round trip.
87
+ const from = args.mergeFromColumnId;
88
+ const to = args.mergeToColumnId;
89
+ if (Boolean(from) !== Boolean(to)) {
90
+ return ok({
91
+ error: true,
92
+ message:
93
+ 'Name both mergeFromColumnId and mergeToColumnId, or neither. One end alone is a half-configured'
94
+ + ' move rule: it is accepted nowhere, and if it were, it would silently never fire — the failure'
95
+ + ' mode that takes longest to notice.',
96
+ });
97
+ }
98
+
99
+ const path = `/api/v1/scm/connections/${encodeURIComponent(args.connectionId)}/repositories`;
100
+ const body = {
101
+ boardId: args.boardId,
102
+ externalRepoId: args.externalRepoId,
103
+ externalFullName: args.externalFullName,
104
+ mergeFromColumnId: from,
105
+ mergeToColumnId: to,
106
+ onMergeAction: args.onMergeAction,
107
+ branchSample: args.branchSample,
108
+ advanceOnlyWhenAllMerged: args.advanceOnlyWhenAllMerged,
109
+ openPullsCardIntoProgress: args.openPullsCardIntoProgress,
110
+ stagingEnvironmentName: args.stagingEnvironmentName,
111
+ productionEnvironmentName: args.productionEnvironmentName,
112
+ };
113
+ const preview = validateApiBridgeRequest({
114
+ method: 'POST',
115
+ path,
116
+ body,
117
+ dryRun: args.dryRun,
118
+ approved: args.approved,
119
+ reason: args.reason,
120
+ idempotencyKey: args.idempotencyKey,
121
+ grantedScope: getGrantedScope(),
122
+ });
123
+ if (preview.dryRun) {
124
+ return ok({
125
+ dryRun: true,
126
+ action: `Bind ${args.externalFullName} to board ${args.boardId}`,
127
+ movesCardsOnMerge:
128
+ to === undefined
129
+ ? 'No. Without both column ends this binding records deliveries but advances nothing.'
130
+ : `Yes: a merged pull request moves its card ${from} -> ${to}.`,
131
+ note: 'Nothing was written. Confirm to create the binding.',
132
+ });
133
+ }
134
+ const data = await api('POST', path, {
135
+ body,
136
+ headers: buildMutationHeaders(args),
137
+ });
138
+ return ok({ bound: true, repository: data?.data ?? data });
139
+ }
140
+ );
26
141
  }
27
142
 
28
143
  export const SOURCE_CONTROL_TOOL_NAMES = [
29
144
  'list_source_control_connections',
30
145
  'get_source_control_connection_events',
146
+ 'bind_repository_to_board',
31
147
  ];