@bli-cockpit/mcp 0.1.77 → 0.1.79

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/README.md CHANGED
@@ -103,7 +103,7 @@ about what an agent can reach.
103
103
 
104
104
  <!-- BEGIN GENERATED verb census — `npm run mcp:readme` -->
105
105
 
106
- **115 of 120 Tower verbs have an MCP twin.**
106
+ **117 of 122 Tower verbs have an MCP twin.**
107
107
  Each tool goes through the SAME door its CLI verb calls, with the same
108
108
  collector device token — never a second route and never a service-role
109
109
  reader. `src/verb-census.test.ts` fails when a verb is in none of the
@@ -129,7 +129,9 @@ three tables below.
129
129
  | `cockpit cal today` | `cal_today` | `GET /api/cal/today` |
130
130
  | `cockpit cal week` | `cal_week` | `GET /api/cal/week` |
131
131
  | `cockpit careers decide` | `careers_decide` | `POST /api/careers/applications/[id]/decide` |
132
+ | `cockpit careers followup` | `careers_followup` | `POST /api/careers/applications/[id]/followup` |
132
133
  | `cockpit careers grade` | `careers_grade` | `POST /api/careers/applications/[id]/grade` |
134
+ | `cockpit careers interview` | `careers_interview` | `POST /api/careers/applications/[id]/interview` |
133
135
  | `cockpit careers send-takehome` | `careers_send_takehome` | `POST /api/careers/applications/[id]/invite` |
134
136
  | `cockpit careers list` | `careers_list` | `GET /api/careers/applications` |
135
137
  | `cockpit careers rescreen` | `careers_rescreen` | `POST /api/careers/applications/[id]/rescreen` |
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { callAgentDoor } from './agent-door.js';
3
3
  import { dataResult, doorFailureText, errorResult, registrarFor, withSession } from './tool-result.js';
4
4
  /** Verbatim from `lib/careers/applications.ts` `PIPELINE_STAGES`. */
5
- const PIPELINE_STAGES = ['screened', 'invited', 'submitted', 'graded', 'booking_sent', 'passed_on', 'archived'];
5
+ const PIPELINE_STAGES = ['screened', 'invited', 'submitted', 'graded', 'booking_sent', 'interview', 'passed_on', 'archived'];
6
6
  const APPLICATION_ID = z.string().uuid().describe('An application id, as careers_list returns it.');
7
7
  /**
8
8
  * One list row without the screening's prose (BLI-10432). The door returns the
@@ -123,6 +123,37 @@ export function registerCareersTools(server, deps) {
123
123
  const expires = typeof body.expires_in === 'number' && body.expires_in > 0 ? body.expires_in : 60;
124
124
  return dataResult(`Resume link, valid for ${expires} seconds:`, { url: body.url, expires_in_seconds: expires });
125
125
  }));
126
+ // BLI-12183: the second-chance email, or a record that one went out by hand.
127
+ // Same door the drawer's Send follow-up and Already followed up controls call.
128
+ register('careers_followup', {
129
+ title: 'Send or record the follow-up',
130
+ description: 'Super-admin only. With no record_sent_at, sends the one second-chance email now, in the take-home thread, from the configured mailbox: only a row at `invited` with no follow-up yet. With record_sent_at, records a follow-up sent outside Tower at that instant and sends nothing. Either way a second follow-up is refused.',
131
+ inputSchema: {
132
+ id: APPLICATION_ID,
133
+ record_sent_at: z.string().min(1).max(40).optional().describe('ISO-8601 instant with an offset, e.g. 2026-10-03T08:00:00Z. Records a follow-up already sent outside Tower; sends nothing.'),
134
+ },
135
+ }, async (args) => withSession(deps, async (session) => {
136
+ const body = args.record_sent_at === undefined ? {} : { record_sent_at: args.record_sent_at };
137
+ const answer = await callAgentDoor(session, deps.fetchImpl, 'POST', `/api/careers/applications/${encodeURIComponent(String(args.id))}/followup`, body, 60_000);
138
+ return answer.ok ? dataResult(args.record_sent_at === undefined ? 'Follow-up sent' : 'Follow-up recorded', answer.body) : errorResult(doorFailureText('careers_followup', answer));
139
+ }));
140
+ // BLI-12198: an interview Edward booked himself. Same door as the drawer's
141
+ // Interview booked control: `at` books or reschedules, `clear` undoes it.
142
+ register('careers_interview', {
143
+ title: 'Book, reschedule or clear an interview',
144
+ description: 'Super-admin only. With `at`, records an interview Edward booked himself: the row moves to the interview stage (any stage but archived), its chip reads Interview plus the time, and booking again reschedules. With `clear: true`, returns the row to the stage it stood at before. Exactly one of at or clear.',
145
+ inputSchema: {
146
+ id: APPLICATION_ID,
147
+ at: z.string().min(1).max(40).optional().describe('ISO-8601 instant with an offset, e.g. 2026-10-05T10:00:00-07:00. Books or reschedules the interview.'),
148
+ clear: z.boolean().optional().describe('true: remove the interview and return the row to its earlier stage. Not with at.'),
149
+ },
150
+ }, async (args) => withSession(deps, async (session) => {
151
+ if ((args.at === undefined) === (args.clear !== true))
152
+ return errorResult('careers_interview needs exactly one of at or clear: true.');
153
+ const body = args.clear === true ? { clear: true } : { at: args.at };
154
+ const answer = await callAgentDoor(session, deps.fetchImpl, 'POST', `/api/careers/applications/${encodeURIComponent(String(args.id))}/interview`, body, 60_000);
155
+ return answer.ok ? dataResult(args.clear === true ? 'Interview cleared' : 'Interview saved', answer.body) : errorResult(doorFailureText('careers_interview', answer));
156
+ }));
126
157
  register('careers_decide', {
127
158
  title: 'Decide on an application',
128
159
  description: 'Super-admin only. Records the call on one application: `passed_on`, `archived`, or `reopen` to put it back at `screened`.',
@@ -157,7 +188,7 @@ export function registerCareersTools(server, deps) {
157
188
  }));
158
189
  register('careers_settings_show', {
159
190
  title: 'Show the careers settings',
160
- description: 'Super-admin only. The automatic-invite bar, the attention threshold, whether automatic sending is on, and which mailbox it goes out from.',
191
+ description: 'Super-admin only. The automatic-invite bar, the attention threshold, whether automatic sending is on, which mailbox it goes out from, the booking link and grader model, and the three follow-up settings (followupEnabled, followupAfterDays, takehomeWindowHours).',
161
192
  inputSchema: {},
162
193
  }, async () => withSession(deps, async (session) => {
163
194
  const answer = await callAgentDoor(session, deps.fetchImpl, 'GET', '/api/careers/settings');
@@ -165,7 +196,7 @@ export function registerCareersTools(server, deps) {
165
196
  }));
166
197
  register('careers_settings_set', {
167
198
  title: 'Change the careers settings',
168
- description: 'Super-admin only. Writes any of the four careers settings. Each takes effect on the next screening, with no deploy.',
199
+ description: 'Super-admin only. Writes any of the careers settings. Each takes effect on the next use, with no deploy.',
169
200
  inputSchema: {
170
201
  auto_invite_enabled: z.boolean().optional().describe('Send the take-home automatically to applicants over the auto-invite bar.'),
171
202
  auto_invite_min_score: z.number().min(0).max(100).optional().describe('The screening score (0-100) at or over which the take-home goes out automatically.'),
@@ -173,6 +204,9 @@ export function registerCareersTools(server, deps) {
173
204
  invite_account_id: z.string().uuid().nullable().optional().describe('The mailbox id (mail_accounts) invitations are sent from; null clears it.'),
174
205
  booking_link: z.string().url().max(2000).nullable().optional().describe('The interview booking link sent after a passing grade; null clears it.'),
175
206
  grader_model: z.string().min(3).max(200).optional().describe('The take-home grader model, provider:model (models_list ids).'),
207
+ followup_enabled: z.boolean().optional().describe('Send the one second-chance follow-up automatically to overdue invited candidates. Off by default.'),
208
+ followup_after_days: z.number().int().min(1).max(60).optional().describe('Whole days after the invitation before the follow-up is due (1-60).'),
209
+ takehome_window_hours: z.number().int().min(1).max(720).optional().describe('Hours a candidate has: the first deadline and the second-chance deadline (1-720).'),
176
210
  },
177
211
  }, async (args) => withSession(deps, async (session) => {
178
212
  const answer = await callAgentDoor(session, deps.fetchImpl, 'PUT', '/api/careers/settings', args);
@@ -21,7 +21,7 @@ export declare const DRY_RUN_TOOLS: readonly ["ops_recompile", "memory_requeue"]
21
21
  * candidates, share calendars. Never called by the smoke; covered by the unit
22
22
  * suites (`write-tools.test.ts` and each family's own).
23
23
  */
24
- export declare const WRITE_TOOLS: readonly ["emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_takehome_set", "careers_settings_set", "mail_send", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_device_revoke"];
24
+ export declare const WRITE_TOOLS: readonly ["emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_followup", "careers_interview", "careers_takehome_set", "careers_settings_set", "mail_send", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_device_revoke"];
25
25
  /** Reads that spend a model turn. The smoke's `--no-model` leaves these out. */
26
26
  export declare const MODEL_SPENDING_TOOLS: readonly ["jarvis_ask", "jarvis_check"];
27
27
  /**
@@ -105,6 +105,8 @@ export const WRITE_TOOLS = [
105
105
  "careers_invite",
106
106
  "careers_grade",
107
107
  "careers_decide",
108
+ "careers_followup",
109
+ "careers_interview",
108
110
  "careers_takehome_set",
109
111
  "careers_settings_set",
110
112
  "mail_send",
@@ -218,6 +218,10 @@ export const MCP_TWINS = {
218
218
  // README). `cockpit careers invite` and `careers_invite` are the older words
219
219
  // for the identical door, and both still work.
220
220
  "careers invite": { tool: "careers_send_takehome", door: "POST /api/careers/applications/[id]/invite" },
221
+ // BLI-12183: the second-chance follow-up, sent now or recorded as sent elsewhere.
222
+ "careers followup": { tool: "careers_followup", door: "POST /api/careers/applications/[id]/followup" },
223
+ // BLI-12198: an interview Edward booked himself, booked, rescheduled or cleared.
224
+ "careers interview": { tool: "careers_interview", door: "POST /api/careers/applications/[id]/interview" },
221
225
  "careers decide": { tool: "careers_decide", door: "POST /api/careers/applications/[id]/decide" },
222
226
  "careers grade": { tool: "careers_grade", door: "POST /api/careers/applications/[id]/grade" },
223
227
  // BLI-4462: the drawer's Open resume button, typed. Both twins call the door
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/mcp",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "private": false,
5
5
  "description": "bli-tower: an MCP server over BLI Cockpit's agent doors: JARVIS (jarvis_*), documents (docs_*), channels (msg_*), issues (work_*), the daily page (brief_*), meeting notes (notes_*), the ops board (ops_status/slack_*), settings/team/model, Scout and the workbook, plus the legacy event-stream tools (emit_event, get_ticket_timeline, get_active_tickets).",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "readme": "npm run build && node scripts/write-readme-census.mjs"
31
31
  },
32
32
  "dependencies": {
33
- "@bli-cockpit/telemetry-core": "0.1.55",
33
+ "@bli-cockpit/telemetry-core": "0.1.57",
34
34
  "@modelcontextprotocol/sdk": "^1.29.0",
35
35
  "zod": "^4.3.6"
36
36
  },
@@ -38,5 +38,10 @@
38
38
  "@types/node": "^20",
39
39
  "typescript": "^5",
40
40
  "vitest": "^3.2.4"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/BLI-INC/bli-cockpit.git",
45
+ "directory": "packages/bli-cockpit-mcp"
41
46
  }
42
47
  }