llmconveyors-mcp 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +216 -164
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { LLMConveyors } from "llmconveyors";
9
9
  import { z } from "zod";
10
10
 
11
11
  // src/utils/error-handler.ts
12
- import { LLMConveyorsError, RateLimitError } from "llmconveyors";
12
+ import { LLMConveyorsError, RateLimitError, NetworkError, TimeoutError } from "llmconveyors";
13
13
  function handleToolError(err) {
14
14
  if (err instanceof LLMConveyorsError) {
15
15
  const payload = {
@@ -19,10 +19,13 @@ function handleToolError(err) {
19
19
  ...err.hint != null && { hint: err.hint },
20
20
  ...err.details != null && { details: err.details },
21
21
  ...err.requestId != null && { requestId: err.requestId },
22
+ ...err.timestamp != null && { timestamp: err.timestamp },
23
+ ...err.path != null && { path: err.path },
22
24
  retryable: err.isRetryable()
23
25
  };
24
26
  if (err instanceof RateLimitError) {
25
- if (err.retryAfter != null) payload.retryAfterMs = err.retryAfter;
27
+ if (err.retryAfter != null) payload.retryAfterSeconds = err.retryAfter;
28
+ if (err.retryAfterMs != null) payload.retryAfterMs = err.retryAfterMs;
26
29
  if (err.rateLimitInfo != null) payload.rateLimitInfo = err.rateLimitInfo;
27
30
  }
28
31
  return {
@@ -30,6 +33,28 @@ function handleToolError(err) {
30
33
  isError: true
31
34
  };
32
35
  }
36
+ if (err instanceof NetworkError) {
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify({
39
+ error: err.message,
40
+ type: "NetworkError",
41
+ retryable: true,
42
+ ...err.cause != null && { cause: String(err.cause) }
43
+ }, null, 2) }],
44
+ isError: true
45
+ };
46
+ }
47
+ if (err instanceof TimeoutError) {
48
+ return {
49
+ content: [{ type: "text", text: JSON.stringify({
50
+ error: err.message,
51
+ type: "TimeoutError",
52
+ retryable: true,
53
+ ...err.cause != null && { cause: String(err.cause) }
54
+ }, null, 2) }],
55
+ isError: true
56
+ };
57
+ }
33
58
  const message = err instanceof Error ? err.message : String(err);
34
59
  return {
35
60
  content: [{ type: "text", text: `Error: ${message}` }],
@@ -41,23 +66,35 @@ function handleToolError(err) {
41
66
  function registerAgentTools(server2, client2) {
42
67
  server2.tool(
43
68
  "job-hunter-run",
44
- "Run the Job Hunter agent \u2014 generates a tailored CV, cover letter, and cold email for a job application. Returns artifacts when complete. Consumes 30-500 credits. Rate limited: 10 req/min. Requires scope: jobs:write. Check balance with settings-usage-summary before running.",
69
+ "Run the Job Hunter agent \u2014 generates a tailored CV, cover letter, and cold email for a job application. Supports phased execution: set autoSelectContacts=false to pause for contact selection (poll with agent-status, respond with agent-interact). Consumes 30-500 credits. Rate limited: 10 req/min. Requires scope: jobs:write. Check balance with settings-usage-summary before running.",
45
70
  {
46
71
  companyName: z.string().describe("Target company name"),
47
72
  jobTitle: z.string().describe("Job title to apply for"),
48
- jobDescription: z.string().optional().describe("Full job description text"),
49
- companyWebsite: z.string().optional().describe("Target company website URL for research phase"),
73
+ jobDescription: z.string().describe("Full job description text"),
74
+ companyWebsite: z.string().describe("Target company website URL for research phase"),
75
+ sessionId: z.string().optional().describe("Existing session ID to continue a previous run"),
76
+ generationId: z.string().optional().describe("Existing generation ID to continue"),
50
77
  masterResumeId: z.string().optional().describe("ID of a stored master resume to use"),
78
+ tier: z.enum(["free", "byo"]).optional().describe("Billing tier: free (platform credits) or byo (bring your own key)"),
79
+ model: z.enum(["flash", "pro"]).optional().describe("AI model: flash (faster/cheaper) or pro (higher quality)"),
80
+ webhookUrl: z.string().optional().describe("Webhook URL for async status updates (generation.completed, generation.failed, generation.awaiting_input)"),
81
+ mode: z.enum(["standard", "cold_outreach"]).optional().describe("Generation mode"),
51
82
  theme: z.enum(["even", "stackoverflow", "class", "professional", "elegant", "macchiato", "react", "academic"]).optional().describe("Resume theme"),
83
+ autoSelectContacts: z.boolean().optional().describe("Set false for phased execution with contact selection gate. Default true for API keys."),
84
+ skipResearchCache: z.boolean().optional().describe("Force fresh company research instead of using cache"),
52
85
  contactName: z.string().optional().describe("Hiring manager or recruiter name"),
53
86
  contactTitle: z.string().optional().describe("Contact job title"),
54
87
  contactEmail: z.string().optional().describe("Contact email for cold outreach"),
55
- mode: z.enum(["standard", "cold_outreach"]).optional().describe("Generation mode"),
56
- model: z.enum(["flash", "pro"]).optional().describe("AI model: flash (faster/cheaper) or pro (higher quality)"),
57
- autoSelectContacts: z.boolean().optional().describe("Set false for phased execution with contact selection gate. Default true for API keys."),
88
+ genericEmail: z.string().optional().describe("Generic company email (e.g. careers@company.com) as fallback"),
89
+ emailAddresses: z.string().optional().describe("Comma-separated email addresses for outreach"),
58
90
  originalCV: z.string().optional().describe("Original CV text to use directly instead of master resume"),
59
91
  extensiveCV: z.string().optional().describe("Extended/detailed CV text for richer generation"),
60
- skipResearchCache: z.boolean().optional().describe("Force fresh company research instead of using cache"),
92
+ cvStrategy: z.string().optional().describe("Strategy instructions for CV generation"),
93
+ coverLetterStrategy: z.string().optional().describe("Strategy instructions for cover letter generation"),
94
+ coldEmailStrategy: z.string().optional().describe("Strategy instructions for cold email generation"),
95
+ reconStrategy: z.string().optional().describe("Strategy instructions for company research/recon phase"),
96
+ specificCore: z.string().optional().describe("Specific core competencies or skills to emphasize"),
97
+ companyProfile: z.string().optional().describe("Pre-researched company profile to skip research phase"),
61
98
  jobSourceUrl: z.string().optional().describe("URL of the original job posting")
62
99
  },
63
100
  async (params) => {
@@ -65,19 +102,31 @@ function registerAgentTools(server2, client2) {
65
102
  const result = await client2.agents.run("job-hunter", {
66
103
  companyName: params.companyName,
67
104
  jobTitle: params.jobTitle,
68
- ...params.jobDescription != null && { jobDescription: params.jobDescription },
69
- ...params.companyWebsite != null && { companyWebsite: params.companyWebsite },
105
+ jobDescription: params.jobDescription,
106
+ companyWebsite: params.companyWebsite,
107
+ ...params.sessionId != null && { sessionId: params.sessionId },
108
+ ...params.generationId != null && { generationId: params.generationId },
70
109
  ...params.masterResumeId != null && { masterResumeId: params.masterResumeId },
110
+ ...params.tier != null && { tier: params.tier },
111
+ ...params.model != null && { model: params.model },
112
+ ...params.webhookUrl != null && { webhookUrl: params.webhookUrl },
113
+ ...params.mode != null && { mode: params.mode },
71
114
  ...params.theme != null && { theme: params.theme },
115
+ ...params.autoSelectContacts != null && { autoSelectContacts: params.autoSelectContacts },
116
+ ...params.skipResearchCache != null && { skipResearchCache: params.skipResearchCache },
72
117
  ...params.contactName != null && { contactName: params.contactName },
73
118
  ...params.contactTitle != null && { contactTitle: params.contactTitle },
74
119
  ...params.contactEmail != null && { contactEmail: params.contactEmail },
75
- ...params.mode != null && { mode: params.mode },
76
- ...params.model != null && { model: params.model },
77
- ...params.autoSelectContacts != null && { autoSelectContacts: params.autoSelectContacts },
120
+ ...params.genericEmail != null && { genericEmail: params.genericEmail },
121
+ ...params.emailAddresses != null && { emailAddresses: params.emailAddresses },
78
122
  ...params.originalCV != null && { originalCV: params.originalCV },
79
123
  ...params.extensiveCV != null && { extensiveCV: params.extensiveCV },
80
- ...params.skipResearchCache != null && { skipResearchCache: params.skipResearchCache },
124
+ ...params.cvStrategy != null && { cvStrategy: params.cvStrategy },
125
+ ...params.coverLetterStrategy != null && { coverLetterStrategy: params.coverLetterStrategy },
126
+ ...params.coldEmailStrategy != null && { coldEmailStrategy: params.coldEmailStrategy },
127
+ ...params.reconStrategy != null && { reconStrategy: params.reconStrategy },
128
+ ...params.specificCore != null && { specificCore: params.specificCore },
129
+ ...params.companyProfile != null && { companyProfile: params.companyProfile },
81
130
  ...params.jobSourceUrl != null && { jobSourceUrl: params.jobSourceUrl }
82
131
  });
83
132
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -88,30 +137,54 @@ function registerAgentTools(server2, client2) {
88
137
  );
89
138
  server2.tool(
90
139
  "b2b-sales-run",
91
- "Run the B2B Sales agent \u2014 researches a company and generates personalized sales outreach. Consumes 50-500 credits. Rate limited: 10 req/min. Requires scope: sales:write. Check balance with settings-usage-summary before running.",
140
+ "Run the B2B Sales agent \u2014 researches a company and generates personalized sales outreach (emails, follow-ups). Supports phased execution: set autoSelectContacts=false to pause for contact selection, autoApproveDraft=false to review before sending. Poll with agent-status, respond with agent-interact. Consumes 50-500 credits. Rate limited: 10 req/min. Requires scope: sales:write. Check balance with settings-usage-summary before running.",
92
141
  {
93
142
  companyName: z.string().describe("Target company name"),
94
143
  companyWebsite: z.string().describe("Target company website URL"),
95
- targetProfile: z.record(z.unknown()).optional().describe("Target contact profile object (name, title, email, etc.)"),
96
- sessionId: z.string().optional().describe("Existing session ID to continue"),
97
- strategy: z.string().optional().describe("Sales strategy to use"),
98
- webhookUrl: z.string().optional().describe("Webhook URL for async status updates"),
144
+ sessionId: z.string().optional().describe("Existing session ID to continue a previous run"),
145
+ generationId: z.string().optional().describe("Existing generation ID to continue"),
99
146
  model: z.enum(["flash", "pro"]).optional().describe("AI model: flash (faster/cheaper) or pro (higher quality)"),
100
147
  userCompanyContext: z.string().optional().describe("Context about your own company for personalization"),
101
- autoSelectContacts: z.boolean().optional().describe("Set false for phased execution with contact selection gate")
148
+ targetCompanyContext: z.string().optional().describe("Pre-researched context about the target company"),
149
+ contactName: z.string().optional().describe("Target contact name"),
150
+ contactTitle: z.string().optional().describe("Target contact job title"),
151
+ contactEmail: z.string().optional().describe("Target contact email address"),
152
+ senderName: z.string().optional().describe("Name of the person sending the outreach"),
153
+ salesStrategy: z.string().optional().describe("Sales strategy to use for outreach generation"),
154
+ reconStrategy: z.string().optional().describe("Strategy instructions for company research/recon phase"),
155
+ companyResearch: z.string().optional().describe("Pre-researched company information to skip research phase"),
156
+ researchMode: z.enum(["parallel", "sequential"]).optional().describe("Research execution mode: parallel (faster) or sequential"),
157
+ autoSelectContacts: z.boolean().optional().describe("Set false for phased execution with contact selection gate"),
158
+ autoApproveDraft: z.boolean().optional().describe("Set false to pause for draft review before finalizing"),
159
+ autoApproveFollowups: z.boolean().optional().describe("Set false to pause for follow-up review before finalizing"),
160
+ followUpCount: z.number().optional().describe("Number of follow-up emails to generate"),
161
+ followUpDelayDays: z.number().optional().describe("Days between follow-up emails"),
162
+ skipResearchCache: z.boolean().optional().describe("Force fresh company research instead of using cache")
102
163
  },
103
164
  async (params) => {
104
165
  try {
105
166
  const result = await client2.agents.run("b2b-sales", {
106
167
  companyName: params.companyName,
107
168
  companyWebsite: params.companyWebsite,
108
- ...params.targetProfile != null && { targetProfile: params.targetProfile },
109
169
  ...params.sessionId != null && { sessionId: params.sessionId },
110
- ...params.strategy != null && { strategy: params.strategy },
111
- ...params.webhookUrl != null && { webhookUrl: params.webhookUrl },
170
+ ...params.generationId != null && { generationId: params.generationId },
112
171
  ...params.model != null && { model: params.model },
113
172
  ...params.userCompanyContext != null && { userCompanyContext: params.userCompanyContext },
114
- ...params.autoSelectContacts != null && { autoSelectContacts: params.autoSelectContacts }
173
+ ...params.targetCompanyContext != null && { targetCompanyContext: params.targetCompanyContext },
174
+ ...params.contactName != null && { contactName: params.contactName },
175
+ ...params.contactTitle != null && { contactTitle: params.contactTitle },
176
+ ...params.contactEmail != null && { contactEmail: params.contactEmail },
177
+ ...params.senderName != null && { senderName: params.senderName },
178
+ ...params.salesStrategy != null && { salesStrategy: params.salesStrategy },
179
+ ...params.reconStrategy != null && { reconStrategy: params.reconStrategy },
180
+ ...params.companyResearch != null && { companyResearch: params.companyResearch },
181
+ ...params.researchMode != null && { researchMode: params.researchMode },
182
+ ...params.autoSelectContacts != null && { autoSelectContacts: params.autoSelectContacts },
183
+ ...params.autoApproveDraft != null && { autoApproveDraft: params.autoApproveDraft },
184
+ ...params.autoApproveFollowups != null && { autoApproveFollowups: params.autoApproveFollowups },
185
+ ...params.followUpCount != null && { followUpCount: params.followUpCount },
186
+ ...params.followUpDelayDays != null && { followUpDelayDays: params.followUpDelayDays },
187
+ ...params.skipResearchCache != null && { skipResearchCache: params.skipResearchCache }
115
188
  });
116
189
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
117
190
  } catch (err) {
@@ -140,7 +213,7 @@ function registerAgentTools(server2, client2) {
140
213
  );
141
214
  server2.tool(
142
215
  "agent-interact",
143
- "Submit a response to a phased agent workflow that is awaiting input. Used when agent-status returns awaiting_input. Rate limited: 10 req/min. Requires scope: jobs:write or sales:write.",
216
+ "Submit a response to a phased agent workflow that is awaiting input (e.g. contact selection, draft approval). Used when agent-status returns status=awaiting_input. The interactionType and interactionData fields come from the awaiting_input response. Rate limited: 10 req/min. Requires scope: jobs:write or sales:write.",
144
217
  {
145
218
  agentType: z.enum(["job-hunter", "b2b-sales"]).describe("Agent type"),
146
219
  generationId: z.string().describe("Generation ID from the run or status response"),
@@ -164,20 +237,14 @@ function registerAgentTools(server2, client2) {
164
237
  );
165
238
  server2.tool(
166
239
  "job-hunter-generate-cv",
167
- "Generate a CV synchronously without running the full Job Hunter pipeline. Faster but produces only a CV, no cover letter or cold email. Consumes credits. Rate limited: 10 req/min. Requires scope: jobs:write.",
240
+ "Generate a CV synchronously without running the full Job Hunter pipeline. Faster but produces only a CV, no cover letter or cold email. Pass a prompt describing the desired CV content, style, or modifications. Consumes credits. Rate limited: 10 req/min. Requires scope: jobs:write.",
168
241
  {
169
- prompt: z.string().describe("Prompt for CV generation \u2014 describe the desired CV content, style, or modifications"),
170
- resume: z.record(z.unknown()).optional().describe("Resume data object in JSON Resume format"),
171
- jobDescription: z.string().optional().describe("Job description text for tailoring"),
172
- theme: z.enum(["even", "stackoverflow", "class", "professional", "elegant", "macchiato", "react", "academic"]).optional().describe("Resume theme")
242
+ prompt: z.string().describe("Prompt for CV generation \u2014 describe the desired CV content, style, or modifications")
173
243
  },
174
244
  async (params) => {
175
245
  try {
176
246
  const result = await client2.agents.generateCv({
177
- prompt: params.prompt,
178
- ...params.resume != null && { resume: params.resume },
179
- ...params.jobDescription != null && { jobDescription: params.jobDescription },
180
- ...params.theme != null && { theme: params.theme }
247
+ prompt: params.prompt
181
248
  });
182
249
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
183
250
  } catch (err) {
@@ -209,8 +276,6 @@ function registerAtsTools(server2, client2) {
209
276
  "ats-score",
210
277
  "Score a resume against a job description for ATS compatibility. Returns overall score, grade, keyword matches, and improvement suggestions. Consumes credits. Requires scope: ats:write.",
211
278
  {
212
- // TODO(SDK): SDK type uses `resume: Record<string, unknown>` and `jobDescription: Record<string, unknown>`
213
- // but the actual API expects string fields. Remove cast once SDK types are fixed.
214
279
  resumeText: z2.string().describe("Resume as plain text"),
215
280
  jobDescription: z2.string().describe("Job description as plain text"),
216
281
  jobTitle: z2.string().optional().describe("Job title for additional context")
@@ -359,33 +424,12 @@ function registerResumeTools(server2, client2) {
359
424
  }
360
425
  );
361
426
  server2.tool(
362
- "master-resume-create",
363
- "Create a new master resume. Returns the saved master resume with its ID. Requires scope: resume:write.",
364
- {
365
- name: z3.string().describe("Name/label for this master resume"),
366
- resume: z3.record(z3.unknown()).describe("Resume data object (structured JSON Resume or raw text in a wrapper)"),
367
- metadata: z3.record(z3.unknown()).optional().describe("Optional metadata for the master resume")
368
- },
369
- async (params) => {
370
- try {
371
- const result = await client2.resume.createMaster({
372
- name: params.name,
373
- resume: params.resume,
374
- ...params.metadata != null && { metadata: params.metadata }
375
- });
376
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
377
- } catch (err) {
378
- return handleToolError(err);
379
- }
380
- }
381
- );
382
- server2.tool(
383
- "master-resume-list",
384
- "List all master resumes. Returns an array of master resume objects. Requires scope: resume:read.",
427
+ "master-resume-get",
428
+ "Get the user's master resume. Returns the master resume with label, rawText, and structuredData. Requires scope: resume:read.",
385
429
  {},
386
430
  async () => {
387
431
  try {
388
- const result = await client2.resume.listMasters();
432
+ const result = await client2.resume.getMaster();
389
433
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
390
434
  } catch (err) {
391
435
  return handleToolError(err);
@@ -393,35 +437,19 @@ function registerResumeTools(server2, client2) {
393
437
  }
394
438
  );
395
439
  server2.tool(
396
- "master-resume-get",
397
- "Get a master resume by ID. Returns the full master resume object. Requires scope: resume:read.",
440
+ "master-resume-upsert",
441
+ "Create or replace the user's master resume (upsert). Requires scope: resume:write.",
398
442
  {
399
- id: z3.string().describe("Master resume ID")
443
+ label: z3.string().describe("Label for the master resume"),
444
+ rawText: z3.string().describe("Raw text content of the resume"),
445
+ structuredData: z3.record(z3.unknown()).optional().describe("Optional structured resume data")
400
446
  },
401
447
  async (params) => {
402
448
  try {
403
- const result = await client2.resume.getMaster(params.id);
404
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
405
- } catch (err) {
406
- return handleToolError(err);
407
- }
408
- }
409
- );
410
- server2.tool(
411
- "master-resume-update",
412
- "Update a master resume by ID. Returns the updated master resume. Requires scope: resume:write.",
413
- {
414
- id: z3.string().describe("Master resume ID"),
415
- name: z3.string().optional().describe("Updated name/label"),
416
- resume: z3.record(z3.unknown()).optional().describe("Updated resume data object"),
417
- metadata: z3.record(z3.unknown()).optional().describe("Updated metadata")
418
- },
419
- async (params) => {
420
- try {
421
- const result = await client2.resume.updateMaster(params.id, {
422
- ...params.name != null && { name: params.name },
423
- ...params.resume != null && { resume: params.resume },
424
- ...params.metadata != null && { metadata: params.metadata }
449
+ const result = await client2.resume.upsertMaster({
450
+ label: params.label,
451
+ rawText: params.rawText,
452
+ ...params.structuredData != null && { structuredData: params.structuredData }
425
453
  });
426
454
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
427
455
  } catch (err) {
@@ -431,14 +459,12 @@ function registerResumeTools(server2, client2) {
431
459
  );
432
460
  server2.tool(
433
461
  "master-resume-delete",
434
- "Delete a master resume by ID. Requires scope: resume:write.",
435
- {
436
- id: z3.string().describe("Master resume ID")
437
- },
438
- async (params) => {
462
+ "Delete the user's master resume. Requires scope: resume:write.",
463
+ {},
464
+ async () => {
439
465
  try {
440
- await client2.resume.deleteMaster(params.id);
441
- return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "Master resume deleted", id: params.id }) }] };
466
+ const result = await client2.resume.deleteMaster();
467
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
442
468
  } catch (err) {
443
469
  return handleToolError(err);
444
470
  }
@@ -474,7 +500,7 @@ function registerUploadTools(server2, client2) {
474
500
  "upload-job-file",
475
501
  "Upload and parse a job description file. Accepts base64-encoded file content. Returns parsed job data. Requires scope: upload:write.",
476
502
  {
477
- fileBase64: z4.string().describe("Base64-encoded file content (PDF, DOCX, etc.)"),
503
+ fileBase64: z4.string().max(13981014).describe("Base64-encoded file content (PDF, DOCX, etc.) \u2014 max ~10 MB"),
478
504
  filename: z4.string().describe("Original filename with extension"),
479
505
  contentType: z4.string().optional().describe("MIME type")
480
506
  },
@@ -496,7 +522,7 @@ function registerUploadTools(server2, client2) {
496
522
  "Upload a job description as plain text or fetch from a URL. At least one of text or url is required. Returns parsed job data. Requires scope: upload:write.",
497
523
  {
498
524
  text: z4.string().max(5e4).optional().describe("Job description text (max 50K characters). Required if url is not provided."),
499
- url: z4.string().optional().describe("URL to fetch job description from. Required if text is not provided."),
525
+ url: z4.string().url().max(2048).optional().describe("URL to fetch job description from. Required if text is not provided."),
500
526
  source: z4.string().optional().describe("Source label/identifier for the job posting")
501
527
  },
502
528
  async (params) => {
@@ -527,13 +553,13 @@ function registerSessionTools(server2, client2) {
527
553
  "session-create",
528
554
  "Create a new session. Returns the created session object with its ID. Requires scope: sessions:write.",
529
555
  {
530
- agentType: z5.enum(["job-hunter", "b2b-sales"]).describe("Agent type for the session"),
556
+ sessionId: z5.string().optional().describe("Optional client-generated session ID"),
531
557
  metadata: z5.record(z5.unknown()).optional().describe("Optional session metadata")
532
558
  },
533
559
  async (params) => {
534
560
  try {
535
561
  const result = await client2.sessions.create({
536
- agentType: params.agentType,
562
+ ...params.sessionId != null && { sessionId: params.sessionId },
537
563
  ...params.metadata != null && { metadata: params.metadata }
538
564
  });
539
565
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -544,18 +570,16 @@ function registerSessionTools(server2, client2) {
544
570
  );
545
571
  server2.tool(
546
572
  "session-list",
547
- "List sessions with optional filtering and pagination. Returns an array of session objects. Requires scope: sessions:read. Note: API may use cursor-based pagination (cursor + limit) \u2014 page-based is the current SDK model.",
573
+ "List sessions with cursor-based pagination. Returns an array of session objects (or paginated envelope when limit is provided). Requires scope: sessions:read.",
548
574
  {
549
- page: z5.number().optional().describe("Page number for pagination"),
550
- limit: z5.number().optional().describe("Number of sessions per page"),
551
- agentType: z5.string().optional().describe("Filter by agent type (job-hunter or b2b-sales)")
575
+ cursor: z5.string().optional().describe("Cursor for pagination (ISO 8601 datetime string)"),
576
+ limit: z5.number().min(1).max(50).optional().describe("Number of sessions per page (1-50)")
552
577
  },
553
578
  async (params) => {
554
579
  try {
555
580
  const result = await client2.sessions.list({
556
- page: params.page,
557
- limit: params.limit,
558
- agentType: params.agentType
581
+ ...params.cursor != null && { cursor: params.cursor },
582
+ ...params.limit != null && { limit: params.limit }
559
583
  });
560
584
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
561
585
  } catch (err) {
@@ -675,6 +699,23 @@ function registerSessionTools(server2, client2) {
675
699
  }
676
700
  }
677
701
  );
702
+ server2.tool(
703
+ "session-stats",
704
+ "Get session statistics (total sessions, active sequences, emails drafted, reply rate, credits used). Requires scope: sessions:read.",
705
+ {
706
+ agentType: z5.enum(["job-hunter", "b2b-sales"]).describe("Agent type to get stats for")
707
+ },
708
+ async (params) => {
709
+ try {
710
+ const result = await client2.sessions.stats({
711
+ agentType: params.agentType
712
+ });
713
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
714
+ } catch (err) {
715
+ return handleToolError(err);
716
+ }
717
+ }
718
+ );
678
719
  }
679
720
 
680
721
  // src/tools/settings.ts
@@ -697,11 +738,13 @@ function registerSettingsTools(server2, client2) {
697
738
  "settings-preferences-get",
698
739
  "Get the current user's preferences. Requires scope: settings:read.",
699
740
  {
700
- agentType: z6.string().optional().describe("Filter preferences by agent type (e.g. job-hunter, b2b-sales). Pending SDK support.")
741
+ agentType: z6.string().optional().describe("Filter preferences by agent type (e.g. job-hunter, b2b-sales)")
701
742
  },
702
743
  async (params) => {
703
744
  try {
704
- const result = await client2.settings.getPreferences();
745
+ const result = await client2.settings.getPreferences(
746
+ params.agentType != null ? { agentType: params.agentType } : void 0
747
+ );
705
748
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
706
749
  } catch (err) {
707
750
  return handleToolError(err);
@@ -713,13 +756,14 @@ function registerSettingsTools(server2, client2) {
713
756
  "Update the current user's preferences. Returns the updated preferences. Requires scope: settings:write.",
714
757
  {
715
758
  preferences: z6.record(z6.unknown()).describe("Preferences object to update"),
716
- agentType: z6.string().optional().describe("Agent type to scope preferences to (e.g. job-hunter, b2b-sales). Pending SDK support.")
759
+ agentType: z6.string().optional().describe("Agent type to scope preferences to (e.g. job-hunter, b2b-sales)")
717
760
  },
718
761
  async (params) => {
719
762
  try {
720
- const result = await client2.settings.updatePreferences({
721
- preferences: params.preferences
722
- });
763
+ const result = await client2.settings.updatePreferences(
764
+ params.preferences,
765
+ params.agentType != null ? { agentType: params.agentType } : void 0
766
+ );
723
767
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
724
768
  } catch (err) {
725
769
  return handleToolError(err);
@@ -762,7 +806,7 @@ function registerSettingsTools(server2, client2) {
762
806
  "api-key-create",
763
807
  "Create a new platform API key. The key value is shown ONLY in this response -- save it immediately. Requires scope: settings:write.",
764
808
  {
765
- name: z6.string().describe("Human-readable name for the API key"),
809
+ label: z6.string().describe("Human-readable label for the API key"),
766
810
  scopes: z6.array(z6.enum([
767
811
  "*",
768
812
  "jobs:write",
@@ -778,7 +822,9 @@ function registerSettingsTools(server2, client2) {
778
822
  "resume:write",
779
823
  "ats:write",
780
824
  "webhook:read",
781
- "webhook:write"
825
+ "webhook:write",
826
+ "outreach:read",
827
+ "outreach:write"
782
828
  ])).describe("Permission scopes for the key. Use '*' for all scopes."),
783
829
  expiresAt: z6.string().optional().describe("Expiration date as ISO 8601 string"),
784
830
  monthlyCreditsLimit: z6.number().optional().describe("Monthly credit usage cap for this key")
@@ -786,7 +832,7 @@ function registerSettingsTools(server2, client2) {
786
832
  async (params) => {
787
833
  try {
788
834
  const result = await client2.settings.createApiKey({
789
- name: params.name,
835
+ label: params.label,
790
836
  scopes: params.scopes,
791
837
  ...params.expiresAt != null && { expiresAt: params.expiresAt },
792
838
  ...params.monthlyCreditsLimit != null && { monthlyCreditsLimit: params.monthlyCreditsLimit }
@@ -830,11 +876,14 @@ function registerSettingsTools(server2, client2) {
830
876
  "Rotate a platform API key -- revokes the old key and returns a new one. Save the new key immediately. Requires scope: settings:write.",
831
877
  {
832
878
  hash: z6.string().describe("API key hash to rotate"),
833
- gracePeriodHours: z6.number().optional().describe("Hours the old key remains valid after rotation (default: 24). Pending SDK support.")
879
+ gracePeriodHours: z6.number().optional().describe("Hours the old key remains valid after rotation (default: 24)")
834
880
  },
835
881
  async (params) => {
836
882
  try {
837
- const result = await client2.settings.rotateApiKey(params.hash);
883
+ const result = await client2.settings.rotateApiKey(
884
+ params.hash,
885
+ params.gracePeriodHours != null ? { gracePeriodHours: params.gracePeriodHours } : void 0
886
+ );
838
887
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
839
888
  } catch (err) {
840
889
  return handleToolError(err);
@@ -843,16 +892,13 @@ function registerSettingsTools(server2, client2) {
843
892
  );
844
893
  server2.tool(
845
894
  "api-key-usage",
846
- "Get usage statistics for a specific API key by its hash. Requires scope: settings:read. Note: uses direct HTTP (SDK method pending).",
895
+ "Get usage statistics for a specific API key by its hash. Requires scope: settings:read.",
847
896
  {
848
897
  hash: z6.string().describe("API key hash")
849
898
  },
850
899
  async (params) => {
851
900
  try {
852
- const httpClient = client2.settings.httpClient;
853
- const result = await httpClient.request(
854
- `/settings/platform-api-keys/${encodeURIComponent(params.hash)}/usage`
855
- );
901
+ const result = await client2.settings.getApiKeyUsage(params.hash);
856
902
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
857
903
  } catch (err) {
858
904
  return handleToolError(err);
@@ -861,11 +907,11 @@ function registerSettingsTools(server2, client2) {
861
907
  );
862
908
  server2.tool(
863
909
  "byo-key-get",
864
- "Check if a Bring Your Own API key is configured and its status. Requires scope: settings:read.",
910
+ "Get the status of all configured BYO provider keys. Returns provider names and their configuration status. Requires scope: settings:read.",
865
911
  {},
866
912
  async () => {
867
913
  try {
868
- const result = await client2.settings.getByoKey();
914
+ const result = await client2.settings.getProviderKeyStatus();
869
915
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
870
916
  } catch (err) {
871
917
  return handleToolError(err);
@@ -874,16 +920,17 @@ function registerSettingsTools(server2, client2) {
874
920
  );
875
921
  server2.tool(
876
922
  "byo-key-set",
877
- "Set a Bring Your Own API key for a provider (e.g. Gemini). BYO tier users get unlimited AI generation but still pay for contact enrichment. Requires scope: settings:write.",
923
+ "Set a Bring Your Own API key for a provider (e.g. gemini). BYO tier users get unlimited AI generation but still pay for contact enrichment. Requires scope: settings:write.",
878
924
  {
925
+ provider: z6.string().describe("Provider name (e.g. gemini)"),
879
926
  apiKey: z6.string().describe("The API key to set"),
880
- provider: z6.string().describe("Provider name (e.g. gemini)")
927
+ baseUrl: z6.string().optional().describe("Optional custom base URL for the provider")
881
928
  },
882
929
  async (params) => {
883
930
  try {
884
- const result = await client2.settings.setByoKey({
931
+ const result = await client2.settings.setProviderKey(params.provider, {
885
932
  apiKey: params.apiKey,
886
- provider: params.provider
933
+ ...params.baseUrl != null && { baseUrl: params.baseUrl }
887
934
  });
888
935
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
889
936
  } catch (err) {
@@ -893,12 +940,27 @@ function registerSettingsTools(server2, client2) {
893
940
  );
894
941
  server2.tool(
895
942
  "byo-key-remove",
896
- "Remove the configured Bring Your Own API key. Requires scope: settings:write.",
943
+ "Remove the configured BYO API key for a provider. Requires scope: settings:write.",
944
+ {
945
+ provider: z6.string().describe("Provider name to remove the key for (e.g. gemini)")
946
+ },
947
+ async (params) => {
948
+ try {
949
+ const result = await client2.settings.removeProviderKey(params.provider);
950
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
951
+ } catch (err) {
952
+ return handleToolError(err);
953
+ }
954
+ }
955
+ );
956
+ server2.tool(
957
+ "settings-supported-providers",
958
+ "List supported BYO key providers. Returns provider names and status. Requires scope: settings:read.",
897
959
  {},
898
960
  async () => {
899
961
  try {
900
- await client2.settings.removeByoKey();
901
- return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "BYO key removed" }) }] };
962
+ const result = await client2.settings.getSupportedProviders();
963
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
902
964
  } catch (err) {
903
965
  return handleToolError(err);
904
966
  }
@@ -937,7 +999,7 @@ import { z as z7 } from "zod";
937
999
  function registerContentTools(server2, client2) {
938
1000
  server2.tool(
939
1001
  "content-save",
940
- "Save a source document for use as context in AI generation. Returns success status. Requires scope: settings:write.",
1002
+ "Save a source document for use as context in AI generation. Returns success status. Requires scope: sessions:write.",
941
1003
  {
942
1004
  docType: z7.enum([
943
1005
  "original_cv",
@@ -965,18 +1027,14 @@ function registerContentTools(server2, client2) {
965
1027
  );
966
1028
  server2.tool(
967
1029
  "content-delete-generation",
968
- "Delete a generation and all its artifacts from a session. Requires scope: sessions:write. Note: uses direct HTTP because SDK lacks sessionId param.",
1030
+ "Delete a generation and all its artifacts from a session. Requires scope: sessions:write.",
969
1031
  {
970
1032
  id: z7.string().describe("Generation ID to delete"),
971
1033
  sessionId: z7.string().describe("Session ID that owns the generation")
972
1034
  },
973
1035
  async (params) => {
974
1036
  try {
975
- const httpClient = client2.content.httpClient;
976
- const result = await httpClient.request(
977
- `/content/generations/${encodeURIComponent(params.id)}`,
978
- { method: "DELETE", query: { sessionId: params.sessionId } }
979
- );
1037
+ const result = await client2.content.deleteGeneration(params.id, params.sessionId);
980
1038
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
981
1039
  } catch (err) {
982
1040
  return handleToolError(err);
@@ -985,21 +1043,23 @@ function registerContentTools(server2, client2) {
985
1043
  );
986
1044
  server2.tool(
987
1045
  "content-research-sender",
988
- "Research and create a sender profile for content generation. Returns sender context. Requires scope: jobs:write or sales:write.",
1046
+ "Research and create a sender profile for content generation. Returns sender context. Requires scope: sessions:write. At least one of companyWebsite or companyName must be provided.",
989
1047
  {
990
1048
  companyWebsite: z7.string().optional().describe("Company website to research"),
991
1049
  companyName: z7.string().optional().describe("Company name for context")
992
1050
  },
993
1051
  async (params) => {
994
1052
  try {
995
- const httpClient = client2.content.httpClient;
996
- const result = await httpClient.request("/content/research-sender", {
997
- method: "POST",
998
- body: {
999
- ...params.companyWebsite != null && { companyWebsite: params.companyWebsite },
1000
- ...params.companyName != null && { companyName: params.companyName }
1001
- }
1002
- });
1053
+ if (!params.companyWebsite && !params.companyName) {
1054
+ return {
1055
+ content: [{ type: "text", text: JSON.stringify({ error: "At least one of companyWebsite or companyName is required" }, null, 2) }],
1056
+ isError: true
1057
+ };
1058
+ }
1059
+ const body = {};
1060
+ if (params.companyWebsite != null) body.companyWebsite = params.companyWebsite;
1061
+ if (params.companyName != null) body.companyName = params.companyName;
1062
+ const result = await client2.content.researchSender(body);
1003
1063
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1004
1064
  } catch (err) {
1005
1065
  return handleToolError(err);
@@ -1008,12 +1068,11 @@ function registerContentTools(server2, client2) {
1008
1068
  );
1009
1069
  server2.tool(
1010
1070
  "content-list-sources",
1011
- "List all saved source documents used as context for AI generation. Requires scope: settings:read.",
1071
+ "List all saved source documents used as context for AI generation. Requires scope: sessions:read.",
1012
1072
  {},
1013
1073
  async () => {
1014
1074
  try {
1015
- const httpClient = client2.content.httpClient;
1016
- const result = await httpClient.request("/content/sources");
1075
+ const result = await client2.content.listSources();
1017
1076
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1018
1077
  } catch (err) {
1019
1078
  return handleToolError(err);
@@ -1022,7 +1081,7 @@ function registerContentTools(server2, client2) {
1022
1081
  );
1023
1082
  server2.tool(
1024
1083
  "content-get-source",
1025
- "Get a specific source document by type. Requires scope: settings:read.",
1084
+ "Get a specific source document by type. Requires scope: sessions:read.",
1026
1085
  {
1027
1086
  docType: z7.enum([
1028
1087
  "original_cv",
@@ -1037,8 +1096,7 @@ function registerContentTools(server2, client2) {
1037
1096
  },
1038
1097
  async (params) => {
1039
1098
  try {
1040
- const httpClient = client2.content.httpClient;
1041
- const result = await httpClient.request(`/content/sources/${encodeURIComponent(params.docType)}`);
1099
+ const result = await client2.content.getSource(params.docType);
1042
1100
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1043
1101
  } catch (err) {
1044
1102
  return handleToolError(err);
@@ -1047,7 +1105,7 @@ function registerContentTools(server2, client2) {
1047
1105
  );
1048
1106
  server2.tool(
1049
1107
  "content-delete-source",
1050
- "Delete a source document by type. Requires scope: settings:write.",
1108
+ "Delete a source document by type. Requires scope: sessions:write.",
1051
1109
  {
1052
1110
  docType: z7.enum([
1053
1111
  "original_cv",
@@ -1062,11 +1120,8 @@ function registerContentTools(server2, client2) {
1062
1120
  },
1063
1121
  async (params) => {
1064
1122
  try {
1065
- const httpClient = client2.content.httpClient;
1066
- const result = await httpClient.request(`/content/sources/${encodeURIComponent(params.docType)}`, {
1067
- method: "DELETE"
1068
- });
1069
- return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true, message: "Source deleted", docType: params.docType }, null, 2) }] };
1123
+ const result = await client2.content.deleteSource(params.docType);
1124
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1070
1125
  } catch (err) {
1071
1126
  return handleToolError(err);
1072
1127
  }
@@ -1079,7 +1134,7 @@ import { z as z8 } from "zod";
1079
1134
  function registerSharesTools(server2, client2) {
1080
1135
  server2.tool(
1081
1136
  "share-create",
1082
- "Create a shareable public link for a generation's artifacts. Returns a slug and public URL. Requires scope: sessions:write.",
1137
+ "Create a shareable public link for a generation's artifacts. Returns a slug and public URL. Requires scope: sessions:read.",
1083
1138
  {
1084
1139
  sessionId: z8.string().describe("Session ID containing the generation"),
1085
1140
  generationId: z8.string().describe("Generation ID to share")
@@ -1098,7 +1153,7 @@ function registerSharesTools(server2, client2) {
1098
1153
  );
1099
1154
  server2.tool(
1100
1155
  "share-stats",
1101
- "Get statistics about your shared links (view counts, etc.). Requires scope: settings:read.",
1156
+ "Get statistics about your shared links (view counts, etc.). Requires scope: sessions:read.",
1102
1157
  {},
1103
1158
  async () => {
1104
1159
  try {
@@ -1126,16 +1181,13 @@ function registerSharesTools(server2, client2) {
1126
1181
  );
1127
1182
  server2.tool(
1128
1183
  "share-slug-stats",
1129
- "Get visit statistics for a specific share link (owner only). Requires scope: settings:read. Note: uses direct HTTP (SDK method pending).",
1184
+ "Get visit statistics for a specific share link (owner only). Requires scope: sessions:read.",
1130
1185
  {
1131
1186
  slug: z8.string().describe("Share link slug")
1132
1187
  },
1133
1188
  async (params) => {
1134
1189
  try {
1135
- const httpClient = client2.shares.httpClient;
1136
- const result = await httpClient.request(
1137
- `/shares/${encodeURIComponent(params.slug)}/stats`
1138
- );
1190
+ const result = await client2.shares.getShareStats(params.slug);
1139
1191
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1140
1192
  } catch (err) {
1141
1193
  return handleToolError(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llmconveyors-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "mcpName": "io.github.ebenezer-isaac/llmconveyors",
5
5
  "description": "MCP server that connects AI agents to LLM Conveyors — run Job Hunter, B2B Sales, and other AI agents from Claude, Cursor, or any MCP client",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@modelcontextprotocol/sdk": "^1.12.1",
54
- "llmconveyors": "^0.1.0",
54
+ "llmconveyors": "^0.3.0",
55
55
  "zod": "^3.24.0"
56
56
  },
57
57
  "devDependencies": {