@linkegringo/mcp 1.0.8 → 1.0.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.
package/dist/index.d.ts CHANGED
@@ -392,8 +392,7 @@ declare function handleGetPendingJob(input?: GetPendingJobInput): Promise<{
392
392
  }[];
393
393
  structuredData: {
394
394
  jobFound: boolean;
395
- pendingCount: number;
396
- job?: undefined;
395
+ job: BridgeJob<any, any>;
397
396
  };
398
397
  } | {
399
398
  content: {
@@ -402,13 +401,7 @@ declare function handleGetPendingJob(input?: GetPendingJobInput): Promise<{
402
401
  }[];
403
402
  structuredData: {
404
403
  jobFound: boolean;
405
- job: {
406
- id: string;
407
- type: JobType;
408
- payload: any;
409
- createdAt: number;
410
- };
411
- pendingCount?: undefined;
404
+ pendingCount: number;
412
405
  };
413
406
  }>;
414
407
 
@@ -467,4 +460,38 @@ declare function handleSubmitJobResult(input: SubmitJobResultInput): Promise<{
467
460
  };
468
461
  }>;
469
462
 
470
- export { type AuditIssue, type AuditProfileInput, type BridgeJob, type BridgeServerOptions, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type GetPendingJobInput, type InstallResult, type InstallerOptions, type JobStatus, type JobType, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, type SubmitJobResultInput, type WatcherOptions, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, startBridgeWatcher, stopBridgeServer, submitJobResultInputSchema };
463
+ declare const watchLinkeGringoInputSchema: z.ZodObject<{
464
+ timeoutSeconds: z.ZodOptional<z.ZodNumber>;
465
+ bridgeUrl: z.ZodOptional<z.ZodString>;
466
+ }, "strip", z.ZodTypeAny, {
467
+ timeoutSeconds?: number | undefined;
468
+ bridgeUrl?: string | undefined;
469
+ }, {
470
+ timeoutSeconds?: number | undefined;
471
+ bridgeUrl?: string | undefined;
472
+ }>;
473
+ type WatchLinkeGringoInput = z.infer<typeof watchLinkeGringoInputSchema>;
474
+ declare function waitForNextJob(bridgeUrl: string, timeoutMs: number): Promise<BridgeJob | null>;
475
+ declare function handleWatchLinkeGringo(input?: WatchLinkeGringoInput): Promise<{
476
+ content: {
477
+ type: "text";
478
+ text: string;
479
+ }[];
480
+ structuredData: {
481
+ jobFound: boolean;
482
+ job: BridgeJob<any, any>;
483
+ };
484
+ } | {
485
+ content: {
486
+ type: "text";
487
+ text: string;
488
+ }[];
489
+ structuredData: {
490
+ status: string;
491
+ timeoutSeconds: number;
492
+ pendingCount: number;
493
+ serverOnline: boolean;
494
+ };
495
+ }>;
496
+
497
+ export { type AuditIssue, type AuditProfileInput, type BridgeJob, type BridgeServerOptions, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type GetPendingJobInput, type InstallResult, type InstallerOptions, type JobStatus, type JobType, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, type SubmitJobResultInput, type WatchLinkeGringoInput, type WatcherOptions, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, handleWatchLinkeGringo, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, startBridgeWatcher, stopBridgeServer, submitJobResultInputSchema, waitForNextJob, watchLinkeGringoInputSchema };
package/dist/index.js CHANGED
@@ -172,9 +172,9 @@ var DEFAULT_BRIDGE_URL = process.env.LINKEGRINGO_BRIDGE_URL || (process.env.NODE
172
172
  async function getRemoteOrLocalPendingJob(jobId, bridgeUrl = DEFAULT_BRIDGE_URL) {
173
173
  if (bridgeUrl) {
174
174
  try {
175
- const url = jobId ? `${bridgeUrl}/api/jobs/pending?id=${encodeURIComponent(jobId)}` : `${bridgeUrl}/api/jobs/pending`;
175
+ const url = jobId ? `${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}` : `${bridgeUrl}/api/jobs/pending`;
176
176
  const controller = new AbortController();
177
- const timer = setTimeout(() => controller.abort(), 2e3);
177
+ const timer = setTimeout(() => controller.abort(), 15e3);
178
178
  const res = await fetch(url, { signal: controller.signal });
179
179
  clearTimeout(timer);
180
180
  if (res.ok) {
@@ -192,7 +192,7 @@ async function completeRemoteOrLocalJob(jobId, result, bridgeUrl = DEFAULT_BRIDG
192
192
  if (bridgeUrl) {
193
193
  try {
194
194
  const controller = new AbortController();
195
- const timer = setTimeout(() => controller.abort(), 5e3);
195
+ const timer = setTimeout(() => controller.abort(), 15e3);
196
196
  const res = await fetch(`${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}/complete`, {
197
197
  method: "POST",
198
198
  headers: { "Content-Type": "application/json" },
@@ -1256,26 +1256,10 @@ ${proposals.map(
1256
1256
 
1257
1257
  // src/tools/get-pending-job.ts
1258
1258
  import { z as z11 } from "zod";
1259
- var getPendingJobInputSchema = z11.object({
1260
- jobId: z11.string().optional().describe("ID espec\xEDfico do job a ser buscado (opcional. Se omitido, pega o pr\xF3ximo da fila)")
1261
- });
1262
- async function handleGetPendingJob(input = {}) {
1263
- const job = await getRemoteOrLocalPendingJob(input.jobId);
1264
- if (!job) {
1265
- return {
1266
- content: [
1267
- {
1268
- type: "text",
1269
- text: "Nenhum job pendente no momento na interface web do LinkeGringo."
1270
- }
1271
- ],
1272
- structuredData: {
1273
- jobFound: false,
1274
- pendingCount: bridgeJobStore.getPendingCount()
1275
- }
1276
- };
1277
- }
1278
- bridgeJobStore.markProcessing(job.id);
1259
+
1260
+ // src/tools/format-job.ts
1261
+ function formatJobResponse(job) {
1262
+ let instructions = "";
1279
1263
  let details = "";
1280
1264
  if (job.type === "parse_and_diagnose") {
1281
1265
  const p = job.payload || {};
@@ -1284,53 +1268,210 @@ async function handleGetPendingJob(input = {}) {
1284
1268
  - **Cargo Almejado**: ${p.targetRole || "N\xE3o especificado"}
1285
1269
  - **Possui PDF Base64**: ${Boolean(p.pdfBase64)} (${p.pdfBase64 ? Math.round(p.pdfBase64.length / 1024) + " KB base64" : "N/A"})
1286
1270
  - **Possui Texto**: ${Boolean(p.pdfText)}
1271
+ `.trim();
1272
+ instructions = `
1273
+ ### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
1274
+ 1. **Zero Scripts**: N\xC3O execute scripts Python/Node no disco. N\xC3O salve arquivos no sistema de arquivos. N\xC3O inspecione arquivos locais de c\xF3digo.
1275
+ 2. **Processamento Multimodal**: Analise o PDF/dados recebidos diretamente no seu racioc\xEDnio em 1 \xFAnico turno.
1276
+ 3. **Submiss\xE3o Imediata**: Chame a ferramenta \`submit_job_result\` com o JSON estruturado no campo \`result\`.
1277
+
1278
+ ### \u{1F4CB} Estrutura JSON Exata para \`submit_job_result\`:
1279
+ \`\`\`json
1280
+ {
1281
+ "jobId": "${job.id}",
1282
+ "result": {
1283
+ "profile": {
1284
+ "firstName": "Nome",
1285
+ "lastName": "Sobrenome",
1286
+ "headline": "Role Anchor | Core Techs | Scale/Impact | US Remote",
1287
+ "location": "Cidade, Pa\xEDs",
1288
+ "summary": "Resumo profissional com foco em impacto t\xE9cnico...",
1289
+ "experiences": [
1290
+ {
1291
+ "title": "Senior Software Engineer",
1292
+ "companyName": "Nome da Empresa",
1293
+ "workplaceType": "remote",
1294
+ "location": "Local",
1295
+ "current": true,
1296
+ "startDate": { "year": 2022, "month": 1 },
1297
+ "endDate": undefined,
1298
+ "description": "Responsabilidades e impacto..."
1299
+ }
1300
+ ],
1301
+ "education": [
1302
+ {
1303
+ "schoolName": "Universidade",
1304
+ "degreeName": "Bacharelado",
1305
+ "fieldOfStudy": "Ci\xEAncia da Computa\xE7\xE3o"
1306
+ }
1307
+ ],
1308
+ "skills": [
1309
+ { "name": "Node.js" },
1310
+ { "name": "TypeScript" }
1311
+ ],
1312
+ "languages": [
1313
+ { "name": "English", "proficiency": "professional" }
1314
+ ]
1315
+ },
1316
+ "review": {
1317
+ "targetMarket": "United States Remote",
1318
+ "language": "en",
1319
+ "overallScore": 82,
1320
+ "scores": {
1321
+ "searchRelevance": 85,
1322
+ "humanVoice": 80,
1323
+ "credibility": 80,
1324
+ "positioningClarity": 85,
1325
+ "evidenceCoverage": 80
1326
+ },
1327
+ "scoreExplanations": {
1328
+ "searchRelevance": "Avalia\xE7\xE3o de indexa\xE7\xE3o ATS...",
1329
+ "humanVoice": "Tom profissional e aut\xEAntico...",
1330
+ "credibility": "Evid\xEAncias t\xE9cnicas e consist\xEAncia...",
1331
+ "positioningClarity": "Clareza do papel s\xEAnior...",
1332
+ "evidenceCoverage": "Densidade de m\xE9tricas quantific\xE1veis..."
1333
+ },
1334
+ "executiveSummary": "Resumo executivo do diagn\xF3stico e posicionamento para vagas nos EUA...",
1335
+ "profileDirection": {
1336
+ "positioning": "Senior Full-Stack Engineer",
1337
+ "primaryRole": "${p.targetRole || "Senior Full-Stack Engineer"}",
1338
+ "openToWorkTitles": [
1339
+ "Senior Full Stack Engineer",
1340
+ "Senior Backend Engineer",
1341
+ "Staff Software Engineer",
1342
+ "Lead Software Engineer",
1343
+ "Full Stack Developer"
1344
+ ],
1345
+ "rationale": "Justificativa do posicionamento estrat\xE9gico para o mercado americano..."
1346
+ },
1347
+ "triageBottlenecks": [
1348
+ "Gargalo 1: Descri\xE7\xF5es de cargos com poucos bullets quantific\xE1veis",
1349
+ "Gargalo 2: Headline precisa de palavras-chave de indexa\xE7\xE3o booleana"
1350
+ ],
1351
+ "critique": [
1352
+ {
1353
+ "section": "Headline",
1354
+ "assessment": "Avalia\xE7\xE3o da headline atual",
1355
+ "strengths": ["Clareza do cargo principal"],
1356
+ "issues": ["Falta men\xE7\xE3o a escala e especializa\xE7\xE3o"],
1357
+ "severity": "medium"
1358
+ }
1359
+ ]
1360
+ }
1361
+ }
1362
+ }
1363
+ \`\`\`
1287
1364
  `.trim();
1288
1365
  } else if (job.type === "generate_interview") {
1366
+ const p = job.payload || {};
1289
1367
  details = `
1290
1368
  - **Tipo de A\xE7\xE3o**: Gera\xE7\xE3o de Perguntas de Entrevista T\xE9cnica
1291
- - **Candidato**: ${job.payload?.profile?.name || "N\xE3o informado"}
1292
- - **Cargo Almejado**: ${job.payload?.objective?.primaryRole || "N\xE3o informado"}
1369
+ - **Candidato**: ${p.profile?.firstName || ""} ${p.profile?.lastName || ""}
1370
+ - **Cargo Almejado**: ${p.objective?.primaryRole || "N\xE3o informado"}
1371
+ `.trim();
1372
+ instructions = `
1373
+ ### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
1374
+ 1. Gere perguntas t\xE9cnicas focando nos gargalos e lacunas identificados no perfil.
1375
+ 2. N\xC3O execute scripts nem inspecione arquivos.
1376
+ 3. Chame a ferramenta \`submit_job_result\` com a estrutura:
1377
+
1378
+ \`\`\`json
1379
+ {
1380
+ "jobId": "${job.id}",
1381
+ "result": {
1382
+ "questions": [
1383
+ {
1384
+ "id": "q1",
1385
+ "category": "scale",
1386
+ "question": "Texto claro da pergunta para investigar o escopo e impacto...",
1387
+ "rationale": "Por que esta pergunta \xE9 importante para o recrutador gringo...",
1388
+ "targetSignal": "M\xE9tricas de escala, throughput ou lat\xEAncia que queremos capturar"
1389
+ }
1390
+ ]
1391
+ }
1392
+ }
1393
+ \`\`\`
1293
1394
  `.trim();
1294
1395
  } else if (job.type === "generate_rewritten_profile") {
1396
+ const p = job.payload || {};
1295
1397
  details = `
1296
- - **Tipo de A\xE7\xE3o**: Reescrita do Perfil com Bullets Google XYZ
1297
- - **Candidato**: ${job.payload?.profile?.name || "N\xE3o informado"}
1298
- - **Fatos Confirmados**: ${job.payload?.confirmedFacts?.length || 0} fatos
1398
+ - **Tipo de A\xE7\xE3o**: Reescrita Completa de Perfil com Bullets Google XYZ
1399
+ - **Candidato**: ${p.profile?.firstName || ""} ${p.profile?.lastName || ""}
1400
+ - **Fatos Confirmados**: ${p.confirmedFacts?.length || 0}
1401
+ `.trim();
1402
+ instructions = `
1403
+ ### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
1404
+ 1. Reescreva o perfil aplicando a f\xF3rmula Google XYZ (*Accomplished [X], measured by [Y], by doing [Z]*) e Headline $\\le$ 160 caracteres.
1405
+ 2. N\xC3O execute scripts nem inspecione arquivos.
1406
+ 3. Chame a ferramenta \`submit_job_result\` com a estrutura:
1407
+
1408
+ \`\`\`json
1409
+ {
1410
+ "jobId": "${job.id}",
1411
+ "result": {
1412
+ "rewritten": {
1413
+ "headline": "Role Anchor | 3-4 Core Techs | Scale/Impact | US Remote",
1414
+ "summary": "Resumo About reformulado com gancho forte nos primeiros 250 caracteres...",
1415
+ "experiences": [
1416
+ {
1417
+ "title": "Senior Software Engineer",
1418
+ "companyName": "Nome da Empresa",
1419
+ "workplaceType": "remote",
1420
+ "location": "Local",
1421
+ "current": true,
1422
+ "startDate": { "year": 2022, "month": 1 },
1423
+ "endDate": undefined,
1424
+ "description": "Accomplished [X], measured by [Y], by doing [Z]...\\nAccomplished [X], measured by [Y], by doing [Z]..."
1425
+ }
1426
+ ]
1427
+ }
1428
+ }
1429
+ }
1430
+ \`\`\`
1299
1431
  `.trim();
1300
1432
  }
1301
1433
  return {
1302
1434
  content: [
1303
1435
  {
1304
1436
  type: "text",
1305
- text: `
1306
- # \u{1F4E5} Job da Interface Web Encontrado!
1307
-
1308
- - **Job ID**: \`${job.id}\`
1309
- - **Tipo**: \`${job.type}\`
1310
- - **Criado em**: ${new Date(job.createdAt).toLocaleTimeString()}
1437
+ text: `\u{1F680} **NOVO JOB RECEBIDO DO LINKEGRINGO**: \`${job.id}\`
1311
1438
 
1312
1439
  ${details}
1313
1440
 
1314
- ---
1315
- \u{1F4A1} **Instru\xE7\xF5es para o Agente de IA**:
1316
- 1. Processe a intelig\xEAncia requerida para este job com base nas diretrizes do LinkeGringo.
1317
- 2. Ao concluir, chame a ferramenta \`submit_job_result\` passando \`jobId: "${job.id}"\` e o JSON estruturado no campo \`result\`.
1318
- 3. A interface web em \`localhost:5173\` atualizar\xE1 a tela no mesmo instante!
1319
- `.trim()
1441
+ ${instructions}`
1320
1442
  }
1321
1443
  ],
1322
1444
  structuredData: {
1323
1445
  jobFound: true,
1324
- job: {
1325
- id: job.id,
1326
- type: job.type,
1327
- payload: job.payload,
1328
- createdAt: job.createdAt
1329
- }
1446
+ job
1330
1447
  }
1331
1448
  };
1332
1449
  }
1333
1450
 
1451
+ // src/tools/get-pending-job.ts
1452
+ var getPendingJobInputSchema = z11.object({
1453
+ jobId: z11.string().optional().describe("ID espec\xEDfico do job a ser buscado (opcional. Se omitido, pega o pr\xF3ximo da fila)")
1454
+ });
1455
+ async function handleGetPendingJob(input = {}) {
1456
+ const job = await getRemoteOrLocalPendingJob(input.jobId);
1457
+ if (!job) {
1458
+ return {
1459
+ content: [
1460
+ {
1461
+ type: "text",
1462
+ text: "Nenhum job pendente no momento na interface web do LinkeGringo."
1463
+ }
1464
+ ],
1465
+ structuredData: {
1466
+ jobFound: false,
1467
+ pendingCount: bridgeJobStore.getPendingCount()
1468
+ }
1469
+ };
1470
+ }
1471
+ bridgeJobStore.markProcessing(job.id);
1472
+ return formatJobResponse(job);
1473
+ }
1474
+
1334
1475
  // src/tools/submit-job-result.ts
1335
1476
  import { z as z12 } from "zod";
1336
1477
  var submitJobResultInputSchema = z12.object({
@@ -1396,6 +1537,144 @@ O front-end em \`localhost:5173\` acabou de receber a resposta formatada e atual
1396
1537
  }
1397
1538
  }
1398
1539
 
1540
+ // src/tools/watch-linkegringo.ts
1541
+ import { z as z13 } from "zod";
1542
+ import http from "http";
1543
+ var watchLinkeGringoInputSchema = z13.object({
1544
+ timeoutSeconds: z13.number().optional().describe(
1545
+ "Tempo m\xE1ximo em segundos para aguardar a chegada de um novo job da interface web do LinkeGringo (padr\xE3o: 60s, m\xE1x: 300s). Se j\xE1 houver um job na fila, retorna imediatamente."
1546
+ ),
1547
+ bridgeUrl: z13.string().optional().describe("URL base do bridge HTTP local do LinkeGringo (padr\xE3o: http://127.0.0.1:5174)")
1548
+ });
1549
+ async function waitForNextJob(bridgeUrl, timeoutMs) {
1550
+ const localPending = bridgeJobStore.getPendingJob();
1551
+ if (localPending) {
1552
+ return localPending;
1553
+ }
1554
+ return new Promise((resolve) => {
1555
+ let resolved = false;
1556
+ let req = null;
1557
+ let timer = null;
1558
+ const cleanup = () => {
1559
+ if (timer) {
1560
+ clearTimeout(timer);
1561
+ timer = null;
1562
+ }
1563
+ if (req) {
1564
+ try {
1565
+ req.destroy();
1566
+ } catch {
1567
+ }
1568
+ req = null;
1569
+ }
1570
+ bridgeJobStore.off("job:created", onLocalJob);
1571
+ };
1572
+ const done = (job) => {
1573
+ if (resolved) return;
1574
+ resolved = true;
1575
+ cleanup();
1576
+ resolve(job);
1577
+ };
1578
+ const onLocalJob = (job) => {
1579
+ done(job);
1580
+ };
1581
+ bridgeJobStore.on("job:created", onLocalJob);
1582
+ timer = setTimeout(() => {
1583
+ done(null);
1584
+ }, timeoutMs);
1585
+ try {
1586
+ const parsed = new URL("/api/jobs/stream", bridgeUrl);
1587
+ req = http.get(
1588
+ {
1589
+ hostname: parsed.hostname,
1590
+ port: parsed.port || 5174,
1591
+ path: parsed.pathname,
1592
+ headers: {
1593
+ Accept: "text/event-stream",
1594
+ "Cache-Control": "no-cache",
1595
+ Connection: "keep-alive"
1596
+ }
1597
+ },
1598
+ (res) => {
1599
+ if (res.statusCode !== 200) {
1600
+ return;
1601
+ }
1602
+ let buffer = "";
1603
+ res.setEncoding("utf8");
1604
+ res.on("data", (chunk) => {
1605
+ buffer += chunk;
1606
+ const lines = buffer.split("\n\n");
1607
+ buffer = lines.pop() || "";
1608
+ for (const block of lines) {
1609
+ const trimmed = block.trim();
1610
+ if (!trimmed.startsWith("data:")) continue;
1611
+ const jsonStr = trimmed.replace(/^data:\s*/, "");
1612
+ try {
1613
+ const event = JSON.parse(jsonStr);
1614
+ if (event.type === "connected") {
1615
+ if (event.pendingCount > 0) {
1616
+ getRemoteOrLocalPendingJob().then((pending) => {
1617
+ if (pending) done(pending);
1618
+ });
1619
+ }
1620
+ } else if (event.type === "job_created" && event.job) {
1621
+ done(event.job);
1622
+ }
1623
+ } catch {
1624
+ }
1625
+ }
1626
+ });
1627
+ res.on("end", () => {
1628
+ if (!resolved) {
1629
+ const pending = bridgeJobStore.getPendingJob();
1630
+ if (pending) {
1631
+ done(pending);
1632
+ }
1633
+ }
1634
+ });
1635
+ res.on("error", () => {
1636
+ });
1637
+ }
1638
+ );
1639
+ req.on("error", () => {
1640
+ });
1641
+ } catch {
1642
+ }
1643
+ });
1644
+ }
1645
+ async function handleWatchLinkeGringo(input = {}) {
1646
+ const timeoutSeconds = Math.min(Math.max(input.timeoutSeconds ?? 60, 0.1), 300);
1647
+ const timeoutMs = timeoutSeconds * 1e3;
1648
+ const bridgeUrl = input.bridgeUrl || "http://127.0.0.1:5174";
1649
+ const immediateJob = await getRemoteOrLocalPendingJob();
1650
+ if (immediateJob) {
1651
+ bridgeJobStore.markProcessing(immediateJob.id);
1652
+ return formatJobResponse(immediateJob);
1653
+ }
1654
+ const job = await waitForNextJob(bridgeUrl, timeoutMs);
1655
+ if (!job) {
1656
+ return {
1657
+ content: [
1658
+ {
1659
+ type: "text",
1660
+ text: `\u{1F7E2} **Modo Watch do LinkeGringo Ativo** (Bridge porta 5174)
1661
+
1662
+ Nenhum job recebido nos \xFAltimos ${timeoutSeconds} segundos.
1663
+ A interface web est\xE1 conectada ao Bridge. Assim que o usu\xE1rio clicar em **Analisar com Agente MCP**, **Iniciar Entrevista** ou **Gerar Perfil Reescrito**, execute a tool \`watch_linkegringo\` novamente para receber e processar os dados!`
1664
+ }
1665
+ ],
1666
+ structuredData: {
1667
+ status: "waiting",
1668
+ timeoutSeconds,
1669
+ pendingCount: bridgeJobStore.getPendingCount(),
1670
+ serverOnline: true
1671
+ }
1672
+ };
1673
+ }
1674
+ bridgeJobStore.markProcessing(job.id);
1675
+ return formatJobResponse(job);
1676
+ }
1677
+
1399
1678
  // src/server.ts
1400
1679
  function createLinkeGringoMcpServer() {
1401
1680
  const server = new McpServer({
@@ -1445,7 +1724,7 @@ function createLinkeGringoMcpServer() {
1445
1724
  server.registerTool(
1446
1725
  "get_pending_job",
1447
1726
  {
1448
- description: "Busca o pr\xF3ximo job pendente enviado pela interface gr\xE1fica web do LinkeGringo (ex: upload de perfil para diagn\xF3stico, gera\xE7\xE3o de entrevista, reescrita de perfil).",
1727
+ description: "Obt\xE9m os dados completos de um job pelo ID (ou o pr\xF3ximo da fila). Use APENAS quando for notificado de um job espec\xEDfico (ex: por watcher em background). N\xC3O use esta ferramenta para iniciar escuta ou checar jobs antes de iniciar o watch.",
1449
1728
  inputSchema: getPendingJobInputSchema.shape
1450
1729
  },
1451
1730
  async (args) => {
@@ -1455,13 +1734,23 @@ function createLinkeGringoMcpServer() {
1455
1734
  server.registerTool(
1456
1735
  "submit_job_result",
1457
1736
  {
1458
- description: "Envia o resultado processado pelo agente de IA de volta para a interface gr\xE1fica web do LinkeGringo, desbloqueando o front-end e atualizando a tela imediatamente.",
1737
+ description: "Envia o resultado estruturado do processamento de um job de volta para o navegador do LinkeGringo, desbloqueando a tela do usu\xE1rio instantaneamente. Sempre chame esta ferramenta imediatamente ap\xF3s processar um job recebido por watch_linkegringo ou get_pending_job.",
1459
1738
  inputSchema: submitJobResultInputSchema.shape
1460
1739
  },
1461
1740
  async (args) => {
1462
1741
  return await handleSubmitJobResult(args);
1463
1742
  }
1464
1743
  );
1744
+ server.registerTool(
1745
+ "watch_linkegringo",
1746
+ {
1747
+ description: 'Inicia a escuta ativa (watch) por jobs enviados pela interface web do LinkeGringo (http://127.0.0.1:5174). Use esta ferramenta SEMPRE que o usu\xE1rio disser "iniciar watch", "escutar", "conectar ao LinkeGringo" ou similar. N\xC3O chame get_pending_job antes desta ferramenta. Bloqueia aguardando e retorna o job assim que ele for submetido no navegador.',
1748
+ inputSchema: watchLinkeGringoInputSchema.shape
1749
+ },
1750
+ async (args) => {
1751
+ return await handleWatchLinkeGringo(args);
1752
+ }
1753
+ );
1465
1754
  server.registerResource(
1466
1755
  "guidelines",
1467
1756
  "linkegringo://guidelines",
@@ -1679,7 +1968,7 @@ function runInstaller(args = process.argv) {
1679
1968
  }
1680
1969
 
1681
1970
  // src/bridge/server.ts
1682
- import http from "http";
1971
+ import http2 from "http";
1683
1972
  function setCorsHeaders(res) {
1684
1973
  res.setHeader("Access-Control-Allow-Origin", "*");
1685
1974
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -1713,7 +2002,7 @@ async function parseBody(req) {
1713
2002
  }
1714
2003
  function createBridgeHttpServer(options = {}) {
1715
2004
  const activeSseClients = /* @__PURE__ */ new Map();
1716
- const server = http.createServer(async (req, res) => {
2005
+ const server = http2.createServer(async (req, res) => {
1717
2006
  setCorsHeaders(res);
1718
2007
  if (req.method === "OPTIONS") {
1719
2008
  res.writeHead(204);
@@ -1945,7 +2234,7 @@ async function stopBridgeServer() {
1945
2234
  }
1946
2235
 
1947
2236
  // src/bridge/watcher.ts
1948
- import http2 from "http";
2237
+ import http3 from "http";
1949
2238
  function startBridgeWatcher(options = {}) {
1950
2239
  const bridgeUrl = options.bridgeUrl || process.env.LINKEGRINGO_BRIDGE_URL || "http://127.0.0.1:5174";
1951
2240
  let isClosed = false;
@@ -1956,7 +2245,7 @@ function startBridgeWatcher(options = {}) {
1956
2245
  if (isClosed) return;
1957
2246
  try {
1958
2247
  const url = new URL("/api/jobs/stream", bridgeUrl);
1959
- const req = http2.get(
2248
+ const req = http3.get(
1960
2249
  url.toString(),
1961
2250
  {
1962
2251
  headers: {
@@ -1986,16 +2275,17 @@ function startBridgeWatcher(options = {}) {
1986
2275
  console.log(
1987
2276
  `[LinkeGringo Watcher] \u{1F7E2} Conectado ao bridge (${bridgeUrl}). Watcher ativo: ${payload.watcherId} (Jobs pendentes: ${payload.pendingCount})`
1988
2277
  );
1989
- if (options.once && payload.pendingCount > 0) {
2278
+ if (payload.pendingCount > 0 && options.once) {
1990
2279
  console.log(
1991
- `[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Finalizando para ativar agente...`
2280
+ `[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Chame get_pending_job para processar e submit_job_result para responder.`
1992
2281
  );
1993
2282
  setTimeout(() => process.exit(0), 50);
1994
2283
  }
1995
2284
  } else if (payload.type === "job_created") {
1996
2285
  console.log(
1997
2286
  `
1998
- [LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}`
2287
+ [LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}
2288
+ [LinkeGringo Watcher] \u{1F449} Chame get_pending_job({ jobId: '${payload.job.id}' }) e envie o resultado direto com submit_job_result.`
1999
2289
  );
2000
2290
  options.onJob?.(payload.job);
2001
2291
  if (options.once) {
@@ -2136,6 +2426,7 @@ export {
2136
2426
  handleGetPendingJob,
2137
2427
  handleSimulateRecruiterSearch,
2138
2428
  handleSubmitJobResult,
2429
+ handleWatchLinkeGringo,
2139
2430
  installMcpServerConfig,
2140
2431
  parseArgs,
2141
2432
  runInstaller,
@@ -2143,5 +2434,7 @@ export {
2143
2434
  startBridgeServer,
2144
2435
  startBridgeWatcher,
2145
2436
  stopBridgeServer,
2146
- submitJobResultInputSchema
2437
+ submitJobResultInputSchema,
2438
+ waitForNextJob,
2439
+ watchLinkeGringoInputSchema
2147
2440
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkegringo/mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Servidor MCP oficial do LinkeGringo para auditoria e otimização de perfis para o mercado internacional",
5
5
  "type": "module",
6
6
  "bin": {