@linkegringo/mcp 1.0.2 → 1.0.4

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 (3) hide show
  1. package/dist/index.d.ts +327 -12
  2. package/dist/index.js +462 -112
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { Experience } from '@linkegringo/core';
2
4
 
3
5
  declare function createLinkeGringoMcpServer(): McpServer;
4
6
 
@@ -19,39 +21,352 @@ interface ChromeTabInfo {
19
21
  webSocketDebuggerUrl?: string;
20
22
  devtoolsFrontendUrl?: string;
21
23
  }
24
+ interface LinkeGringoTabInfo {
25
+ id: string;
26
+ title: string;
27
+ url: string;
28
+ webSocketDebuggerUrl?: string;
29
+ }
30
+ interface LinkeGringoSessionState {
31
+ hasUploadedProfile: boolean;
32
+ candidateName?: string;
33
+ targetRole?: string;
34
+ step?: string;
35
+ inboundScore?: number;
36
+ profile?: any;
37
+ review?: any;
38
+ }
22
39
  interface CdpStatus {
23
40
  isRunning: boolean;
24
41
  port: number;
25
42
  host: string;
26
43
  browser?: string;
27
44
  protocolVersion?: string;
28
- activeTabs: Array<{
29
- id: string;
30
- title: string;
31
- url: string;
32
- webSocketDebuggerUrl?: string;
33
- }>;
45
+ /**
46
+ * Abas ativas restritas estritamente ao escopo do LinkeGringo (Privacy Shield).
47
+ * Abas pessoais (e-mails, mensageiros, bancos) são completamente omitidas.
48
+ */
49
+ activeTabs: LinkeGringoTabInfo[];
50
+ linkeGringoTabs: LinkeGringoTabInfo[];
51
+ otherTabsCount: number;
34
52
  linkeGringoTabFound: boolean;
35
53
  linkeGringoTabUrl?: string;
54
+ sessionState?: LinkeGringoSessionState | null;
36
55
  error?: string;
37
56
  }
38
57
 
58
+ declare function isLinkeGringoUrl(url?: string): boolean;
59
+ declare function findDevToolsActivePort(): {
60
+ port: number;
61
+ wsPath: string;
62
+ } | null;
63
+ declare function probeViaWebSocket(port: number, wsPath: string, timeoutMs?: number): Promise<{
64
+ linkeGringoTabs: LinkeGringoTabInfo[];
65
+ otherTabsCount: number;
66
+ sessionState: LinkeGringoSessionState | null;
67
+ } | null>;
39
68
  declare function checkChromeCdp(port?: number, host?: string, timeoutMs?: number): Promise<CdpStatus>;
40
69
 
70
+ interface McpTarget {
71
+ id: string;
72
+ client: string;
73
+ configPath: string;
74
+ detected: boolean;
75
+ }
41
76
  interface InstallResult {
42
77
  client: string;
43
78
  configPath: string;
44
79
  status: 'created' | 'updated' | 'skipped' | 'error';
45
80
  message?: string;
46
81
  }
47
- declare function getMcpConfigsForSystem(): Array<{
48
- client: string;
49
- configPath: string;
50
- }>;
82
+ interface InstallerOptions {
83
+ all?: boolean;
84
+ local?: boolean;
85
+ client?: string;
86
+ }
87
+ declare function getMcpConfigsForSystem(): McpTarget[];
51
88
  declare function installMcpServerConfig(configPath: string): {
52
89
  status: 'created' | 'updated';
53
90
  path: string;
54
91
  };
55
- declare function runInstaller(): InstallResult[];
92
+ declare function parseArgs(args?: string[]): InstallerOptions;
93
+ declare function runInstaller(args?: string[]): InstallResult[];
94
+
95
+ interface SparseExperience {
96
+ company: string;
97
+ title: string;
98
+ estimatedBullets: number;
99
+ }
100
+ declare function getExperienceBulletCount(exp: {
101
+ description?: string;
102
+ bullets?: string[];
103
+ }): number;
104
+ declare function detectSparseExperiences(experiences?: Experience[]): SparseExperience[];
105
+ declare const auditProfileInputSchema: z.ZodObject<{
106
+ profileText: z.ZodOptional<z.ZodString>;
107
+ headline: z.ZodOptional<z.ZodString>;
108
+ summary: z.ZodOptional<z.ZodString>;
109
+ experiences: z.ZodOptional<z.ZodArray<z.ZodObject<{
110
+ company: z.ZodString;
111
+ title: z.ZodString;
112
+ bullets: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
113
+ description: z.ZodOptional<z.ZodString>;
114
+ }, "strip", z.ZodTypeAny, {
115
+ title: string;
116
+ company: string;
117
+ description?: string | undefined;
118
+ bullets?: string[] | undefined;
119
+ }, {
120
+ title: string;
121
+ company: string;
122
+ description?: string | undefined;
123
+ bullets?: string[] | undefined;
124
+ }>, "many">>;
125
+ skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
126
+ targetRole: z.ZodDefault<z.ZodString>;
127
+ targetMarket: z.ZodDefault<z.ZodString>;
128
+ }, "strip", z.ZodTypeAny, {
129
+ targetMarket: string;
130
+ targetRole: string;
131
+ headline?: string | undefined;
132
+ summary?: string | undefined;
133
+ experiences?: {
134
+ title: string;
135
+ company: string;
136
+ description?: string | undefined;
137
+ bullets?: string[] | undefined;
138
+ }[] | undefined;
139
+ skills?: string[] | undefined;
140
+ profileText?: string | undefined;
141
+ }, {
142
+ headline?: string | undefined;
143
+ summary?: string | undefined;
144
+ experiences?: {
145
+ title: string;
146
+ company: string;
147
+ description?: string | undefined;
148
+ bullets?: string[] | undefined;
149
+ }[] | undefined;
150
+ skills?: string[] | undefined;
151
+ targetMarket?: string | undefined;
152
+ targetRole?: string | undefined;
153
+ profileText?: string | undefined;
154
+ }>;
155
+ type AuditProfileInput = z.infer<typeof auditProfileInputSchema>;
156
+ interface AuditIssue {
157
+ severity: 'critical' | 'warning' | 'info';
158
+ category: 'headline' | 'experience' | 'about' | 'skills';
159
+ message: string;
160
+ recommendation: string;
161
+ }
162
+ declare function handleAuditProfile(input: AuditProfileInput): Promise<{
163
+ content: {
164
+ type: "text";
165
+ text: string;
166
+ }[];
167
+ structuredData: {
168
+ status: string;
169
+ linkeGringoTabUrl: string | undefined;
170
+ message: string;
171
+ actionRequired: string;
172
+ score?: undefined;
173
+ targetRole?: undefined;
174
+ headline?: undefined;
175
+ totalBullets?: undefined;
176
+ bulletsWithMetrics?: undefined;
177
+ sparseExperiences?: undefined;
178
+ triageBottlenecks?: undefined;
179
+ issues?: undefined;
180
+ };
181
+ } | {
182
+ content: {
183
+ type: "text";
184
+ text: string;
185
+ }[];
186
+ structuredData: {
187
+ status: string;
188
+ message: string;
189
+ actionRequired: string;
190
+ linkeGringoTabUrl?: undefined;
191
+ score?: undefined;
192
+ targetRole?: undefined;
193
+ headline?: undefined;
194
+ totalBullets?: undefined;
195
+ bulletsWithMetrics?: undefined;
196
+ sparseExperiences?: undefined;
197
+ triageBottlenecks?: undefined;
198
+ issues?: undefined;
199
+ };
200
+ } | {
201
+ content: {
202
+ type: "text";
203
+ text: string;
204
+ }[];
205
+ structuredData: {
206
+ score: number;
207
+ targetRole: string;
208
+ headline: string;
209
+ totalBullets: number;
210
+ bulletsWithMetrics: number;
211
+ sparseExperiences: SparseExperience[];
212
+ triageBottlenecks: string[];
213
+ issues: AuditIssue[];
214
+ status?: undefined;
215
+ linkeGringoTabUrl?: undefined;
216
+ message?: undefined;
217
+ actionRequired?: undefined;
218
+ };
219
+ }>;
220
+
221
+ declare const simulateRecruiterSearchInputSchema: z.ZodObject<{
222
+ headline: z.ZodString;
223
+ summary: z.ZodDefault<z.ZodString>;
224
+ skills: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
225
+ experienceBullets: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
226
+ targetRole: z.ZodDefault<z.ZodString>;
227
+ requiredKeywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
228
+ }, "strip", z.ZodTypeAny, {
229
+ headline: string;
230
+ summary: string;
231
+ skills: string[];
232
+ targetRole: string;
233
+ experienceBullets: string[];
234
+ requiredKeywords?: string[] | undefined;
235
+ }, {
236
+ headline: string;
237
+ summary?: string | undefined;
238
+ skills?: string[] | undefined;
239
+ targetRole?: string | undefined;
240
+ experienceBullets?: string[] | undefined;
241
+ requiredKeywords?: string[] | undefined;
242
+ }>;
243
+ type SimulateRecruiterSearchInput = z.infer<typeof simulateRecruiterSearchInputSchema>;
244
+ declare function handleSimulateRecruiterSearch(input: SimulateRecruiterSearchInput): Promise<{
245
+ content: {
246
+ type: "text";
247
+ text: string;
248
+ }[];
249
+ structuredData: {
250
+ overallStatus: "weak" | "missing" | "match";
251
+ matchPercentage: number;
252
+ evaluations: {
253
+ term: string;
254
+ status: "weak" | "missing" | "match";
255
+ detail: string;
256
+ }[];
257
+ matchCount: number;
258
+ weakCount: number;
259
+ missingCount: number;
260
+ };
261
+ }>;
262
+
263
+ declare const convertToXyzBulletInputSchema: z.ZodObject<{
264
+ rawBullet: z.ZodString;
265
+ roleContext: z.ZodDefault<z.ZodString>;
266
+ action: z.ZodOptional<z.ZodString>;
267
+ metric: z.ZodOptional<z.ZodString>;
268
+ method: z.ZodOptional<z.ZodString>;
269
+ }, "strip", z.ZodTypeAny, {
270
+ rawBullet: string;
271
+ roleContext: string;
272
+ method?: string | undefined;
273
+ metric?: string | undefined;
274
+ action?: string | undefined;
275
+ }, {
276
+ rawBullet: string;
277
+ method?: string | undefined;
278
+ metric?: string | undefined;
279
+ roleContext?: string | undefined;
280
+ action?: string | undefined;
281
+ }>;
282
+ type ConvertToXyzBulletInput = z.infer<typeof convertToXyzBulletInputSchema>;
283
+ declare function formatGoogleXyzBullet(parts: {
284
+ action: string;
285
+ metric: string;
286
+ method: string;
287
+ }): string;
288
+ declare function handleConvertToXyzBullet(input: ConvertToXyzBulletInput): Promise<{
289
+ content: {
290
+ type: "text";
291
+ text: string;
292
+ }[];
293
+ structuredData: {
294
+ formattedBullet: string;
295
+ parts: {
296
+ action: string;
297
+ metric: string;
298
+ method: string;
299
+ };
300
+ hasMetrics?: undefined;
301
+ rawBullet?: undefined;
302
+ proposals?: undefined;
303
+ };
304
+ } | {
305
+ content: {
306
+ type: "text";
307
+ text: string;
308
+ }[];
309
+ structuredData: {
310
+ hasMetrics: boolean;
311
+ rawBullet: string;
312
+ proposals: string[];
313
+ formattedBullet?: undefined;
314
+ parts?: undefined;
315
+ };
316
+ }>;
317
+
318
+ declare const generateHeadlineInputSchema: z.ZodObject<{
319
+ targetRole: z.ZodDefault<z.ZodString>;
320
+ coreTechnologies: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
321
+ keyDifferentiator: z.ZodOptional<z.ZodString>;
322
+ seniorityOrScope: z.ZodDefault<z.ZodString>;
323
+ }, "strip", z.ZodTypeAny, {
324
+ targetRole: string;
325
+ coreTechnologies: string[];
326
+ seniorityOrScope: string;
327
+ keyDifferentiator?: string | undefined;
328
+ }, {
329
+ targetRole?: string | undefined;
330
+ coreTechnologies?: string[] | undefined;
331
+ keyDifferentiator?: string | undefined;
332
+ seniorityOrScope?: string | undefined;
333
+ }>;
334
+ type GenerateHeadlineInput = z.infer<typeof generateHeadlineInputSchema>;
335
+ declare function handleGenerateHeadline(input: GenerateHeadlineInput): Promise<{
336
+ content: {
337
+ type: "text";
338
+ text: string;
339
+ }[];
340
+ structuredData: {
341
+ proposals: {
342
+ type: string;
343
+ headline: string;
344
+ charCount: number;
345
+ focus: string;
346
+ }[];
347
+ };
348
+ }>;
349
+
350
+ declare const checkChromeCdpInputSchema: z.ZodObject<{
351
+ port: z.ZodDefault<z.ZodNumber>;
352
+ host: z.ZodDefault<z.ZodString>;
353
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
354
+ }, "strip", z.ZodTypeAny, {
355
+ port: number;
356
+ host: string;
357
+ timeoutMs: number;
358
+ }, {
359
+ port?: number | undefined;
360
+ host?: string | undefined;
361
+ timeoutMs?: number | undefined;
362
+ }>;
363
+ type CheckChromeCdpInput = z.infer<typeof checkChromeCdpInputSchema>;
364
+ declare function handleCheckChromeCdp(input: CheckChromeCdpInput): Promise<{
365
+ content: {
366
+ type: "text";
367
+ text: string;
368
+ }[];
369
+ structuredData: CdpStatus;
370
+ }>;
56
371
 
57
- export { type CdpStatus, type ChromeTabInfo, type ChromeVersionResponse, type InstallResult, checkChromeCdp, createLinkeGringoMcpServer, getMcpConfigsForSystem, installMcpServerConfig, runInstaller };
372
+ export { type AuditIssue, type AuditProfileInput, type CdpStatus, type CheckChromeCdpInput, type ChromeTabInfo, type ChromeVersionResponse, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type InstallResult, type InstallerOptions, type LinkeGringoSessionState, type LinkeGringoTabInfo, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, auditProfileInputSchema, checkChromeCdp, checkChromeCdpInputSchema, convertToXyzBulletInputSchema, createLinkeGringoMcpServer, detectSparseExperiences, findDevToolsActivePort, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, handleAuditProfile, handleCheckChromeCdp, handleConvertToXyzBullet, handleGenerateHeadline, handleSimulateRecruiterSearch, installMcpServerConfig, isLinkeGringoUrl, parseArgs, probeViaWebSocket, runInstaller, simulateRecruiterSearchInputSchema };
package/dist/index.js CHANGED
@@ -8,6 +8,233 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
 
9
9
  // src/tools/audit-profile.ts
10
10
  import { z } from "zod";
11
+
12
+ // src/cdp/probe.ts
13
+ import fs from "fs";
14
+ import os from "os";
15
+ import path from "path";
16
+ function isLinkeGringoUrl(url = "") {
17
+ const lower = url.toLowerCase();
18
+ return lower.includes("localhost:5173") || lower.includes("127.0.0.1:5173") || lower.includes("localhost:4173") || lower.includes("127.0.0.1:4173") || lower.includes("linkegringo") || lower.includes("muriel-gasparini.github.io");
19
+ }
20
+ function findDevToolsActivePort() {
21
+ const possiblePaths = [
22
+ // Linux
23
+ path.join(os.homedir(), ".config", "google-chrome", "DevToolsActivePort"),
24
+ path.join(os.homedir(), ".config", "chromium", "DevToolsActivePort"),
25
+ path.join(os.homedir(), ".config", "google-chrome-beta", "DevToolsActivePort"),
26
+ // macOS
27
+ path.join(os.homedir(), "Library", "Application Support", "Google", "Chrome", "DevToolsActivePort"),
28
+ // Windows
29
+ path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "User Data", "DevToolsActivePort")
30
+ ];
31
+ for (const p of possiblePaths) {
32
+ if (p && fs.existsSync(p)) {
33
+ try {
34
+ const lines = fs.readFileSync(p, "utf8").trim().split("\n");
35
+ if (lines.length >= 2) {
36
+ const port = parseInt(lines[0].trim(), 10);
37
+ const wsPath = lines[1].trim();
38
+ if (port > 0 && wsPath) {
39
+ return { port, wsPath };
40
+ }
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ async function probeViaWebSocket(port, wsPath, timeoutMs = 2e3) {
49
+ const wsUrl = `ws://127.0.0.1:${port}${wsPath}`;
50
+ if (typeof globalThis.WebSocket !== "function") return null;
51
+ return new Promise((resolve) => {
52
+ let ws;
53
+ const timer = setTimeout(() => {
54
+ try {
55
+ ws?.close();
56
+ } catch {
57
+ }
58
+ resolve(null);
59
+ }, timeoutMs);
60
+ try {
61
+ ws = new globalThis.WebSocket(wsUrl);
62
+ } catch {
63
+ clearTimeout(timer);
64
+ resolve(null);
65
+ return;
66
+ }
67
+ let linkeTarget = null;
68
+ let linkeGringoTabs = [];
69
+ let otherTabsCount = 0;
70
+ ws.onopen = () => {
71
+ ws.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
72
+ };
73
+ ws.onmessage = (event) => {
74
+ try {
75
+ const msg = JSON.parse(String(event.data));
76
+ if (msg.id === 1) {
77
+ const targets = msg.result?.targetInfos || [];
78
+ const pageTargets = targets.filter((t) => t.type === "page");
79
+ const matched = pageTargets.filter((t) => isLinkeGringoUrl(t.url));
80
+ linkeGringoTabs = matched.map((t) => ({
81
+ id: t.targetId,
82
+ title: t.title,
83
+ url: t.url
84
+ }));
85
+ otherTabsCount = pageTargets.length - matched.length;
86
+ if (linkeGringoTabs.length === 0) {
87
+ clearTimeout(timer);
88
+ ws.close();
89
+ resolve({ linkeGringoTabs: [], otherTabsCount, sessionState: null });
90
+ return;
91
+ }
92
+ linkeTarget = matched[0];
93
+ ws.send(
94
+ JSON.stringify({
95
+ id: 2,
96
+ method: "Target.attachToTarget",
97
+ params: { targetId: linkeTarget.targetId, flatten: true }
98
+ })
99
+ );
100
+ } else if (msg.id === 2) {
101
+ const sessionId = msg.result?.sessionId;
102
+ ws.send(
103
+ JSON.stringify({
104
+ id: 3,
105
+ sessionId,
106
+ method: "Runtime.evaluate",
107
+ params: {
108
+ expression: 'window.localStorage.getItem("linkegringo_active_session")',
109
+ returnByValue: true
110
+ }
111
+ })
112
+ );
113
+ } else if (msg.id === 3) {
114
+ clearTimeout(timer);
115
+ ws.close();
116
+ const raw = msg.result?.result?.value;
117
+ let session = null;
118
+ try {
119
+ if (raw) session = JSON.parse(raw);
120
+ } catch {
121
+ }
122
+ const hasUploadedProfile = Boolean(session && session.profile);
123
+ const sessionState = {
124
+ hasUploadedProfile,
125
+ candidateName: session?.profile?.name,
126
+ targetRole: session?.profile?.targetRole || session?.objective,
127
+ step: session?.step || (hasUploadedProfile ? "diagnostic" : "upload"),
128
+ inboundScore: session?.review?.inboundReadinessScore,
129
+ profile: session?.profile || null,
130
+ review: session?.review || null
131
+ };
132
+ resolve({
133
+ linkeGringoTabs,
134
+ otherTabsCount,
135
+ sessionState
136
+ });
137
+ }
138
+ } catch {
139
+ clearTimeout(timer);
140
+ try {
141
+ ws?.close();
142
+ } catch {
143
+ }
144
+ resolve(null);
145
+ }
146
+ };
147
+ ws.onerror = () => {
148
+ clearTimeout(timer);
149
+ resolve(null);
150
+ };
151
+ });
152
+ }
153
+ async function checkChromeCdp(port = 9222, host = "127.0.0.1", timeoutMs = 2e3) {
154
+ const activePortData = findDevToolsActivePort();
155
+ if (activePortData) {
156
+ const wsResult = await probeViaWebSocket(activePortData.port, activePortData.wsPath, timeoutMs);
157
+ if (wsResult) {
158
+ const found = wsResult.linkeGringoTabs.length > 0;
159
+ return {
160
+ isRunning: true,
161
+ port: activePortData.port,
162
+ host: "127.0.0.1",
163
+ browser: "Google Chrome (DevTools Protocol)",
164
+ protocolVersion: "1.3",
165
+ activeTabs: wsResult.linkeGringoTabs,
166
+ linkeGringoTabs: wsResult.linkeGringoTabs,
167
+ otherTabsCount: wsResult.otherTabsCount,
168
+ linkeGringoTabFound: found,
169
+ linkeGringoTabUrl: wsResult.linkeGringoTabs[0]?.url,
170
+ sessionState: wsResult.sessionState
171
+ };
172
+ }
173
+ }
174
+ const controller = new AbortController();
175
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
176
+ try {
177
+ const versionRes = await fetch(`http://${host}:${port}/json/version`, {
178
+ signal: controller.signal
179
+ });
180
+ if (!versionRes.ok) {
181
+ throw new Error(`HTTP ${versionRes.status}: ${versionRes.statusText}`);
182
+ }
183
+ const versionData = await versionRes.json();
184
+ let pageTabs = [];
185
+ try {
186
+ const listRes = await fetch(`http://${host}:${port}/json/list`, {
187
+ signal: controller.signal
188
+ });
189
+ if (listRes.ok) {
190
+ const rawTabs = await listRes.json();
191
+ if (Array.isArray(rawTabs)) {
192
+ pageTabs = rawTabs.filter((t) => t.type === "page");
193
+ }
194
+ }
195
+ } catch {
196
+ }
197
+ const linkeGringoTabs = pageTabs.filter((t) => isLinkeGringoUrl(t.url)).map((t) => ({
198
+ id: t.id,
199
+ title: t.title,
200
+ url: t.url,
201
+ webSocketDebuggerUrl: t.webSocketDebuggerUrl
202
+ }));
203
+ const otherTabsCount = pageTabs.length - linkeGringoTabs.length;
204
+ const found = linkeGringoTabs.length > 0;
205
+ return {
206
+ isRunning: true,
207
+ port,
208
+ host,
209
+ browser: versionData.Browser,
210
+ protocolVersion: versionData["Protocol-Version"],
211
+ activeTabs: linkeGringoTabs,
212
+ linkeGringoTabs,
213
+ otherTabsCount,
214
+ linkeGringoTabFound: found,
215
+ linkeGringoTabUrl: linkeGringoTabs[0]?.url,
216
+ sessionState: null
217
+ };
218
+ } catch (err) {
219
+ const error = err;
220
+ const isTimeout = error.name === "AbortError" || error.name === "TimeoutError";
221
+ return {
222
+ isRunning: false,
223
+ port,
224
+ host,
225
+ activeTabs: [],
226
+ linkeGringoTabs: [],
227
+ otherTabsCount: 0,
228
+ linkeGringoTabFound: false,
229
+ sessionState: null,
230
+ error: isTimeout ? "Conex\xE3o expirou (Chrome n\xE3o respondeu em 2s na porta " + port + ")" : "Porta fechada ou depura\xE7\xE3o remota desativada. Acesse chrome://inspect/#remote-debugging para ativar."
231
+ };
232
+ } finally {
233
+ clearTimeout(timeoutId);
234
+ }
235
+ }
236
+
237
+ // src/tools/audit-profile.ts
11
238
  function getExperienceBulletCount(exp) {
12
239
  if (Array.isArray(exp.bullets) && exp.bullets.length > 0) {
13
240
  return exp.bullets.length;
@@ -57,7 +284,77 @@ async function handleAuditProfile(input) {
57
284
  let headline = input.headline || "";
58
285
  let summary = input.summary || "";
59
286
  let skills = input.skills || [];
60
- const rawExperiences = input.experiences || [];
287
+ const rawExperiences = [...input.experiences || []];
288
+ if (!headline && !input.profileText && rawExperiences.length === 0) {
289
+ try {
290
+ const cdp = await checkChromeCdp();
291
+ if (cdp.sessionState?.hasUploadedProfile && cdp.sessionState.profile) {
292
+ const p = cdp.sessionState.profile;
293
+ headline = p.headline || "";
294
+ summary = p.summary || "";
295
+ skills = p.skills || [];
296
+ if (Array.isArray(p.experiences)) {
297
+ for (const exp of p.experiences) {
298
+ rawExperiences.push({
299
+ company: exp.companyName || exp.company || "Company",
300
+ title: exp.title || "Engineer",
301
+ bullets: exp.bullets,
302
+ description: exp.description
303
+ });
304
+ }
305
+ }
306
+ } else if (cdp.linkeGringoTabFound) {
307
+ return {
308
+ content: [
309
+ {
310
+ type: "text",
311
+ text: `
312
+ # \u26A0\uFE0F Nenhum Perfil Carregado no LinkeGringo
313
+
314
+ A aplica\xE7\xE3o **LinkeGringo est\xE1 aberta no seu navegador** (\`${cdp.linkeGringoTabUrl || "http://localhost:5173"}\`), mas o **upload do PDF do LinkedIn ainda n\xE3o foi realizado**.
315
+
316
+ ---
317
+
318
+ ### \u{1F4A1} Pr\xF3ximo Passo
319
+ Por favor, acesse a aba do LinkeGringo no seu navegador e **arraste o PDF do seu perfil do LinkedIn para o dropzone** (ou clique para selecionar o arquivo).
320
+
321
+ Assim que o upload for processado pela aplica\xE7\xE3o web, chame o \`audit_profile\` novamente para auditar o perfil real automaticamente!
322
+ `.trim()
323
+ }
324
+ ],
325
+ structuredData: {
326
+ status: "waiting_for_upload",
327
+ linkeGringoTabUrl: cdp.linkeGringoTabUrl,
328
+ message: "O usu\xE1rio ainda n\xE3o subiu o PDF do LinkedIn na aplica\xE7\xE3o web.",
329
+ actionRequired: "upload_pdf"
330
+ }
331
+ };
332
+ } else {
333
+ return {
334
+ content: [
335
+ {
336
+ type: "text",
337
+ text: `
338
+ # \u26A0\uFE0F Nenhum Perfil Fornecido para Auditoria
339
+
340
+ Nenhum dado de perfil foi informado e a aplica\xE7\xE3o LinkeGringo n\xE3o foi detectada no navegador.
341
+
342
+ ### Como prosseguir:
343
+ 1. **Pela Web**: Abra o LinkeGringo em \`http://localhost:5173\` no Google Chrome e fa\xE7a o upload do PDF do seu LinkedIn; OU
344
+ 2. **Via Par\xE2metros**: Forne\xE7a o texto bruto do perfil no par\xE2metro \`profileText\` ou informe \`headline\`, \`experiences\` e \`skills\`.
345
+ `.trim()
346
+ }
347
+ ],
348
+ structuredData: {
349
+ status: "missing_profile",
350
+ message: "Nenhum perfil fornecido e aplica\xE7\xE3o LinkeGringo n\xE3o detectada no navegador.",
351
+ actionRequired: "provide_input_or_open_web"
352
+ }
353
+ };
354
+ }
355
+ } catch {
356
+ }
357
+ }
61
358
  if (input.profileText && !headline && rawExperiences.length === 0) {
62
359
  const lines = input.profileText.split("\n").map((l) => l.trim()).filter(Boolean);
63
360
  if (lines.length > 0) {
@@ -960,68 +1257,6 @@ ${proposals.map(
960
1257
 
961
1258
  // src/tools/cdp-check.ts
962
1259
  import { z as z11 } from "zod";
963
-
964
- // src/cdp/probe.ts
965
- async function checkChromeCdp(port = 9222, host = "127.0.0.1", timeoutMs = 2e3) {
966
- const controller = new AbortController();
967
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
968
- try {
969
- const versionRes = await fetch(`http://${host}:${port}/json/version`, {
970
- signal: controller.signal
971
- });
972
- if (!versionRes.ok) {
973
- throw new Error(`HTTP ${versionRes.status}: ${versionRes.statusText}`);
974
- }
975
- const versionData = await versionRes.json();
976
- let pageTabs = [];
977
- try {
978
- const listRes = await fetch(`http://${host}:${port}/json/list`, {
979
- signal: controller.signal
980
- });
981
- if (listRes.ok) {
982
- const rawTabs = await listRes.json();
983
- if (Array.isArray(rawTabs)) {
984
- pageTabs = rawTabs.filter((t) => t.type === "page");
985
- }
986
- }
987
- } catch {
988
- }
989
- const linkeGringoTab = pageTabs.find((t) => {
990
- const lowerUrl = (t.url || "").toLowerCase();
991
- return lowerUrl.includes("linkegringo") || lowerUrl.includes("5173") || lowerUrl.includes("muriel-gasparini.github.io");
992
- });
993
- return {
994
- isRunning: true,
995
- port,
996
- host,
997
- browser: versionData.Browser,
998
- protocolVersion: versionData["Protocol-Version"],
999
- activeTabs: pageTabs.map((t) => ({
1000
- id: t.id,
1001
- title: t.title,
1002
- url: t.url,
1003
- webSocketDebuggerUrl: t.webSocketDebuggerUrl
1004
- })),
1005
- linkeGringoTabFound: Boolean(linkeGringoTab),
1006
- linkeGringoTabUrl: linkeGringoTab?.url
1007
- };
1008
- } catch (err) {
1009
- const error = err;
1010
- const isTimeout = error.name === "AbortError" || error.name === "TimeoutError";
1011
- return {
1012
- isRunning: false,
1013
- port,
1014
- host,
1015
- activeTabs: [],
1016
- linkeGringoTabFound: false,
1017
- error: isTimeout ? "Conex\xE3o expirou (Chrome n\xE3o respondeu em 2s na porta " + port + ")" : "Porta fechada ou depura\xE7\xE3o remota desativada. Acesse chrome://inspect/#remote-debugging para ativar."
1018
- };
1019
- } finally {
1020
- clearTimeout(timeoutId);
1021
- }
1022
- }
1023
-
1024
- // src/tools/cdp-check.ts
1025
1260
  var checkChromeCdpInputSchema = z11.object({
1026
1261
  port: z11.number().default(9222).describe("Porta do Chrome DevTools Protocol a ser testada (padr\xE3o 9222)"),
1027
1262
  host: z11.string().default("127.0.0.1").describe("Host do Chrome (padr\xE3o 127.0.0.1)"),
@@ -1029,20 +1264,50 @@ var checkChromeCdpInputSchema = z11.object({
1029
1264
  });
1030
1265
  async function handleCheckChromeCdp(input) {
1031
1266
  const status = await checkChromeCdp(input.port, input.host, input.timeoutMs);
1267
+ const session = status.sessionState;
1268
+ const isProfileLoaded = Boolean(session?.hasUploadedProfile);
1269
+ let sessionSection = "";
1270
+ if (status.linkeGringoTabFound) {
1271
+ if (isProfileLoaded) {
1272
+ sessionSection = `
1273
+ ### \u{1F464} Sess\xE3o do LinkeGringo: \u{1F7E2} Perfil Carregado
1274
+ - **Candidato**: ${session?.candidateName || "Identificado"}
1275
+ - **Cargo Alvo**: ${session?.targetRole || "N\xE3o especificado"}
1276
+ - **Passo Atual**: \`${session?.step || "diagnostic"}\`
1277
+ - **Inbound Score**: ${session?.inboundScore !== void 0 ? `${session.inboundScore}/100` : "Pronto para c\xE1lculo"}
1278
+
1279
+ > \u{1F4A1} **Instru\xE7\xE3o para a IA**: O perfil do usu\xE1rio j\xE1 est\xE1 carregado. Voc\xEA pode utilizar diretamente as ferramentas \`audit_profile\`, \`simulate_recruiter_search\`, \`generate_headline_proposals\` e \`convert_to_xyz_bullet\` com os dados do candidato.
1280
+ `;
1281
+ } else {
1282
+ sessionSection = `
1283
+ ### \u{1F464} Sess\xE3o do LinkeGringo: \u{1F7E1} Aguardando Upload do PDF
1284
+ - **Passo Atual**: \`upload\` (Tela inicial)
1285
+ - **Status do Arquivo**: Nenhum PDF do LinkedIn foi carregado ainda pelo usu\xE1rio.
1286
+
1287
+ > \u{1F4A1} **Instru\xE7\xE3o para a IA**: O usu\xE1rio est\xE1 com o LinkeGringo aberto, mas ainda n\xE3o subiu o PDF. **Pe\xE7a educadamente para o usu\xE1rio arrastar ou selecionar o PDF do seu perfil do LinkedIn no dropzone da aplica\xE7\xE3o web** (${status.linkeGringoTabUrl || "http://localhost:5173"}). Assim que o usu\xE1rio subir o PDF, voc\xEA ter\xE1 acesso instant\xE2neo aos dados para auditar e otimizar!
1288
+ `;
1289
+ }
1290
+ } else {
1291
+ sessionSection = `
1292
+ ### \u{1F310} Sess\xE3o do LinkeGringo: \u26A0\uFE0F N\xE3o Detectada
1293
+ > O LinkeGringo n\xE3o foi detectado em nenhuma aba aberta. Pe\xE7a ao usu\xE1rio para abrir \`http://localhost:5173\` no navegador ou fornecer o texto do perfil diretamente.
1294
+ `;
1295
+ }
1032
1296
  const markdownSummary = `
1033
- # Status do Chrome Remote Debugging (CDP)
1297
+ # Status do Chrome Remote Debugging (CDP) & LinkeGringo
1034
1298
 
1035
- **Porta Testada**: ${status.host}:${status.port}
1036
- **Status da Conex\xE3o**: ${status.isRunning ? "\u{1F7E2} Conectado e Ativo" : "\u{1F534} Desconectado"}
1299
+ **Porta CDP**: ${status.host}:${status.port}
1300
+ **Status do Chrome**: ${status.isRunning ? "\u{1F7E2} Conectado e Ativo" : "\u{1F534} Desconectado"}
1037
1301
 
1038
1302
  ${status.isRunning ? `
1039
- - **Vers\xE3o do Navegador**: ${status.browser || "Desconhecido"}
1040
- - **Vers\xE3o do Protocolo DevTools**: ${status.protocolVersion || "1.3"}
1041
- - **Total de Abas Abertas**: ${status.activeTabs.length}
1042
- - **Aba do LinkeGringo**: ${status.linkeGringoTabFound ? `\u2713 Detectada (${status.linkeGringoTabUrl})` : "\u26A0\uFE0F Nenhuma aba do LinkeGringo aberta no momento"}
1303
+ - **Vers\xE3o do Navegador**: ${status.browser || "Google Chrome"}
1304
+ - **Aba do LinkeGringo**: ${status.linkeGringoTabFound ? `\u2713 Detectada (\`${status.linkeGringoTabUrl}\`)` : "\u26A0\uFE0F Nenhuma aba do LinkeGringo aberta"}
1043
1305
 
1044
- ${status.activeTabs.length > 0 ? `### Abas Encontradas:
1045
- ${status.activeTabs.map((t) => `- [${t.title}](${t.url})`).join("\n")}` : ""}
1306
+ ### \u{1F6E1}\uFE0F Privacy Shield Ativo
1307
+ - **Abas Pessoais Protegidas**: ${status.otherTabsCount} aba(s) abertas no navegador foram preservadas sem inspe\xE7\xE3o (e-mails, mensageiros, documentos).
1308
+ - **Escopo Restrito**: O LinkeGringo acessa exclusivamente abas pertencentes \xE0 pr\xF3pria aplica\xE7\xE3o LinkeGringo.
1309
+
1310
+ ${sessionSection}
1046
1311
  ` : `
1047
1312
  > \u274C **Motivo**: ${status.error || "Porta fechada."}
1048
1313
  >
@@ -1157,56 +1422,68 @@ function createLinkeGringoMcpServer() {
1157
1422
  }
1158
1423
 
1159
1424
  // src/cli/installer.ts
1160
- import fs from "fs";
1161
- import path from "path";
1162
- import os from "os";
1425
+ import fs2 from "fs";
1426
+ import path2 from "path";
1427
+ import os2 from "os";
1163
1428
  function getMcpConfigsForSystem() {
1164
- const home = os.homedir();
1165
- const platform = os.platform();
1429
+ const home = os2.homedir();
1430
+ const platform = os2.platform();
1166
1431
  const configs = [];
1432
+ const antigravityPath = path2.join(home, ".gemini", "config", "mcp_config.json");
1167
1433
  configs.push({
1434
+ id: "antigravity",
1168
1435
  client: "Google Antigravity",
1169
- configPath: path.join(home, ".gemini", "config", "mcp_config.json")
1436
+ configPath: antigravityPath,
1437
+ detected: fs2.existsSync(path2.join(home, ".gemini")) || fs2.existsSync(antigravityPath)
1170
1438
  });
1439
+ let claudePath;
1440
+ let claudeDir;
1171
1441
  if (platform === "darwin") {
1172
- configs.push({
1173
- client: "Claude Desktop (macOS)",
1174
- configPath: path.join(
1175
- home,
1176
- "Library",
1177
- "Application Support",
1178
- "Claude",
1179
- "claude_desktop_config.json"
1180
- )
1181
- });
1442
+ claudeDir = path2.join(home, "Library", "Application Support", "Claude");
1443
+ claudePath = path2.join(claudeDir, "claude_desktop_config.json");
1182
1444
  } else if (platform === "win32") {
1183
- const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
1184
- configs.push({
1185
- client: "Claude Desktop (Windows)",
1186
- configPath: path.join(appData, "Claude", "claude_desktop_config.json")
1187
- });
1445
+ claudeDir = path2.join(process.env.APPDATA || path2.join(home, "AppData", "Roaming"), "Claude");
1446
+ claudePath = path2.join(claudeDir, "claude_desktop_config.json");
1188
1447
  } else {
1189
- configs.push({
1190
- client: "Claude Desktop (Linux)",
1191
- configPath: path.join(home, ".config", "Claude", "claude_desktop_config.json")
1192
- });
1448
+ claudeDir = path2.join(home, ".config", "Claude");
1449
+ claudePath = path2.join(claudeDir, "claude_desktop_config.json");
1193
1450
  }
1194
1451
  configs.push({
1452
+ id: "claude",
1453
+ client: `Claude Desktop (${platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : "Linux"})`,
1454
+ configPath: claudePath,
1455
+ detected: fs2.existsSync(claudeDir) || fs2.existsSync(claudePath)
1456
+ });
1457
+ const cursorDir = path2.join(home, ".cursor");
1458
+ const cursorPath = path2.join(cursorDir, "mcp.json");
1459
+ configs.push({
1460
+ id: "cursor",
1195
1461
  client: "Cursor AI",
1196
- configPath: path.join(home, ".cursor", "mcp.json")
1462
+ configPath: cursorPath,
1463
+ detected: fs2.existsSync(cursorDir) || fs2.existsSync(cursorPath)
1197
1464
  });
1465
+ const windsurfDir = path2.join(home, ".codeium", "windsurf");
1466
+ const windsurfPath = path2.join(windsurfDir, "mcp_config.json");
1467
+ if (fs2.existsSync(windsurfDir) || fs2.existsSync(windsurfPath)) {
1468
+ configs.push({
1469
+ id: "windsurf",
1470
+ client: "Windsurf",
1471
+ configPath: windsurfPath,
1472
+ detected: true
1473
+ });
1474
+ }
1198
1475
  return configs;
1199
1476
  }
1200
1477
  function installMcpServerConfig(configPath) {
1201
- const dir = path.dirname(configPath);
1202
- if (!fs.existsSync(dir)) {
1203
- fs.mkdirSync(dir, { recursive: true });
1478
+ const dir = path2.dirname(configPath);
1479
+ if (!fs2.existsSync(dir)) {
1480
+ fs2.mkdirSync(dir, { recursive: true });
1204
1481
  }
1205
1482
  let configData = { mcpServers: {} };
1206
1483
  let isNew = true;
1207
- if (fs.existsSync(configPath)) {
1484
+ if (fs2.existsSync(configPath)) {
1208
1485
  try {
1209
- const raw = fs.readFileSync(configPath, "utf8");
1486
+ const raw = fs2.readFileSync(configPath, "utf8");
1210
1487
  if (raw.trim()) {
1211
1488
  configData = JSON.parse(raw);
1212
1489
  isNew = false;
@@ -1226,19 +1503,74 @@ function installMcpServerConfig(configPath) {
1226
1503
  command: "npx",
1227
1504
  args: ["-y", "chrome-devtools-mcp@latest", "--autoConnect"]
1228
1505
  };
1229
- fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n", "utf8");
1506
+ fs2.writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n", "utf8");
1230
1507
  return {
1231
1508
  status: isNew ? "created" : "updated",
1232
1509
  path: configPath
1233
1510
  };
1234
1511
  }
1235
- function runInstaller() {
1512
+ function parseArgs(args = []) {
1513
+ const options = {};
1514
+ for (const arg of args) {
1515
+ if (arg === "--all" || arg === "--force") {
1516
+ options.all = true;
1517
+ } else if (arg === "--local" || arg === "-l") {
1518
+ options.local = true;
1519
+ } else if (arg.startsWith("--client=")) {
1520
+ options.client = arg.split("=")[1]?.toLowerCase().trim();
1521
+ }
1522
+ }
1523
+ return options;
1524
+ }
1525
+ function runInstaller(args = process.argv) {
1236
1526
  console.log("\n\u{1F680} LinkeGringo MCP - Instalador Autom\xE1tico");
1237
1527
  console.log("================================================");
1238
- console.log("Configurando servidores em todos os clientes locais:\n");
1528
+ console.log("\u2139\uFE0F Modo 100% Aut\xF4nomo: N\xE3o requer o c\xF3digo-fonte do LinkeGringo na m\xE1quina.\n");
1529
+ const options = parseArgs(args);
1239
1530
  const targets = getMcpConfigsForSystem();
1240
1531
  const results = [];
1532
+ if (options.local) {
1533
+ const cwd = process.cwd();
1534
+ const localCursorDir = path2.join(cwd, ".cursor");
1535
+ const localPath = path2.join(localCursorDir, "mcp.json");
1536
+ try {
1537
+ const res = installMcpServerConfig(localPath);
1538
+ results.push({
1539
+ client: "Workspace Local (Cursor)",
1540
+ configPath: localPath,
1541
+ status: res.status
1542
+ });
1543
+ console.log(`\u2705 [Workspace Local]`);
1544
+ console.log(` Arquivo: ${localPath} (${res.status === "created" ? "Criado" : "Atualizado"})
1545
+ `);
1546
+ } catch (err) {
1547
+ results.push({
1548
+ client: "Workspace Local",
1549
+ configPath: localPath,
1550
+ status: "error",
1551
+ message: err.message
1552
+ });
1553
+ console.warn(`\u26A0\uFE0F [Workspace Local] Erro ao configurar: ${err.message}
1554
+ `);
1555
+ }
1556
+ return results;
1557
+ }
1558
+ const detectedTargets = targets.filter((t) => t.detected);
1559
+ const shouldInstallAll = options.all || detectedTargets.length === 0;
1241
1560
  for (const target of targets) {
1561
+ if (options.client && !target.id.includes(options.client)) {
1562
+ continue;
1563
+ }
1564
+ if (!shouldInstallAll && !target.detected) {
1565
+ results.push({
1566
+ client: target.client,
1567
+ configPath: target.configPath,
1568
+ status: "skipped",
1569
+ message: "Cliente n\xE3o detectado nesta m\xE1quina (use --all para for\xE7ar)"
1570
+ });
1571
+ console.log(`\u23ED\uFE0F [${target.client}] N\xE3o detectado nesta m\xE1quina (pulado. Use --all para criar)`);
1572
+ continue;
1573
+ }
1242
1574
  try {
1243
1575
  const res = installMcpServerConfig(target.configPath);
1244
1576
  results.push({
@@ -1246,7 +1578,8 @@ function runInstaller() {
1246
1578
  configPath: target.configPath,
1247
1579
  status: res.status
1248
1580
  });
1249
- console.log(`\u2705 [${target.client}]`);
1581
+ const tag = target.detected ? "(Detectado)" : "(Padr\xE3o)";
1582
+ console.log(`\u2705 [${target.client}] ${tag}`);
1250
1583
  console.log(` Arquivo: ${target.configPath} (${res.status === "created" ? "Criado" : "Atualizado"})
1251
1584
  `);
1252
1585
  } catch (err) {
@@ -1267,16 +1600,16 @@ function runInstaller() {
1267
1600
  console.log(" \u2022 Claude Code CLI: claude mcp add linkegringo npx -y @linkegringo/mcp");
1268
1601
  console.log(' \u2022 Goose CLI: goose configure --add-extension "npx -y @linkegringo/mcp"');
1269
1602
  console.log("================================================");
1270
- console.log("\u{1F389} Instala\xE7\xE3o conclu\xEDda! Reinicie o Claude Desktop, Antigravity ou Cursor para ativar.\n");
1603
+ console.log("\u{1F389} Instala\xE7\xE3o conclu\xEDda! Reinicie o seu cliente de IA para ativar.\n");
1271
1604
  return results;
1272
1605
  }
1273
1606
 
1274
1607
  // src/index.ts
1275
- import fs2 from "fs";
1608
+ import fs3 from "fs";
1276
1609
  import { fileURLToPath } from "url";
1277
1610
  async function main() {
1278
1611
  if (process.argv.includes("install") || process.argv.includes("setup") || process.argv.includes("--install")) {
1279
- runInstaller();
1612
+ runInstaller(process.argv);
1280
1613
  return;
1281
1614
  }
1282
1615
  const server = createLinkeGringoMcpServer();
@@ -1288,7 +1621,7 @@ function isDirectExecution() {
1288
1621
  if (!process.argv[1]) return false;
1289
1622
  try {
1290
1623
  const currentFilePath = fileURLToPath(import.meta.url);
1291
- const scriptPath = fs2.existsSync(process.argv[1]) ? fs2.realpathSync(process.argv[1]) : process.argv[1];
1624
+ const scriptPath = fs3.existsSync(process.argv[1]) ? fs3.realpathSync(process.argv[1]) : process.argv[1];
1292
1625
  return currentFilePath === scriptPath || process.argv[1].endsWith("index.js") || process.argv[1].endsWith("linkegringo-mcp") || process.argv[1].endsWith("mcp") || process.argv[1].endsWith("linkegringo");
1293
1626
  } catch {
1294
1627
  return true;
@@ -1301,9 +1634,26 @@ if (isDirectExecution()) {
1301
1634
  });
1302
1635
  }
1303
1636
  export {
1637
+ auditProfileInputSchema,
1304
1638
  checkChromeCdp,
1639
+ checkChromeCdpInputSchema,
1640
+ convertToXyzBulletInputSchema,
1305
1641
  createLinkeGringoMcpServer,
1642
+ detectSparseExperiences,
1643
+ findDevToolsActivePort,
1644
+ formatGoogleXyzBullet,
1645
+ generateHeadlineInputSchema,
1646
+ getExperienceBulletCount,
1306
1647
  getMcpConfigsForSystem,
1648
+ handleAuditProfile,
1649
+ handleCheckChromeCdp,
1650
+ handleConvertToXyzBullet,
1651
+ handleGenerateHeadline,
1652
+ handleSimulateRecruiterSearch,
1307
1653
  installMcpServerConfig,
1308
- runInstaller
1654
+ isLinkeGringoUrl,
1655
+ parseArgs,
1656
+ probeViaWebSocket,
1657
+ runInstaller,
1658
+ simulateRecruiterSearchInputSchema
1309
1659
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkegringo/mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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": {