starthub-mcp 0.1.0 → 0.1.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.
package/build/index.js CHANGED
@@ -25,7 +25,7 @@ async function main() {
25
25
  });
26
26
  const supabase = getSupabase();
27
27
  if (isCandidate) {
28
- registerCandidateTools(server, supabase, auth.id);
28
+ registerCandidateTools(server, supabase, auth.id, STARTHUB_KEY);
29
29
  }
30
30
  else {
31
31
  registerStartupTools(server, supabase, auth);
@@ -8,17 +8,13 @@ const APP_STAGES = [
8
8
  'applied', 'screening', 'interview-1', 'interview-2',
9
9
  'offer', 'hired', 'rejected',
10
10
  ];
11
- export function registerCandidateTools(server, supabase, candidateId) {
11
+ export function registerCandidateTools(server, supabase, candidateId, mcpKey) {
12
12
  // ── get_profile ────────────────────────────────────────────
13
13
  server.registerTool('get_profile', {
14
14
  description: 'Leggi i tuoi dati profilo: bio, ruoli cercati, RAL, preferenze, visibilità.',
15
15
  inputSchema: {},
16
16
  }, async () => {
17
- const { data, error } = await supabase
18
- .from('candidates')
19
- .select('id, full_name, email, bio, role_targets, ral_min, ral_max, work_mode_pref, cities_pref, linkedin_url, is_searchable, created_at')
20
- .eq('id', candidateId)
21
- .single();
17
+ const { data, error } = await supabase.rpc('get_candidate_by_key', { p_key: mcpKey });
22
18
  if (error || !data)
23
19
  throw new Error('Profilo non trovato.');
24
20
  return {
@@ -52,7 +48,10 @@ export function registerCandidateTools(server, supabase, candidateId) {
52
48
  if (Object.keys(updates).length === 0) {
53
49
  return { content: [{ type: 'text', text: 'Nessun campo da aggiornare.' }] };
54
50
  }
55
- const { error } = await supabase.from('candidates').update(updates).eq('id', candidateId);
51
+ const { error } = await supabase.rpc('update_candidate_by_key', {
52
+ p_key: mcpKey,
53
+ p_updates: updates,
54
+ });
56
55
  if (error)
57
56
  throw new Error(error.message);
58
57
  return { content: [{ type: 'text', text: `✅ Profilo aggiornato: ${Object.keys(updates).join(', ')}.` }] };
@@ -64,10 +63,10 @@ export function registerCandidateTools(server, supabase, candidateId) {
64
63
  visible: z.boolean().describe('true = visibile alle startup | false = nascosto'),
65
64
  },
66
65
  }, async ({ visible }) => {
67
- const { error } = await supabase
68
- .from('candidates')
69
- .update({ is_searchable: visible })
70
- .eq('id', candidateId);
66
+ const { error } = await supabase.rpc('update_candidate_by_key', {
67
+ p_key: mcpKey,
68
+ p_updates: { is_searchable: visible },
69
+ });
71
70
  if (error)
72
71
  throw new Error(error.message);
73
72
  const text = visible
@@ -165,23 +164,16 @@ export function registerCandidateTools(server, supabase, candidateId) {
165
164
  .single();
166
165
  if (!job)
167
166
  throw new Error('Offerta non trovata o non più attiva.');
168
- const { data: existing } = await supabase
169
- .from('applications')
170
- .select('id, stage')
171
- .eq('candidate_id', candidateId)
172
- .eq('job_posting_id', job_id)
173
- .single();
174
- if (existing) {
175
- return { content: [{ type: 'text', text: `Hai già applicato. Stage attuale: ${existing.stage}.` }] };
176
- }
177
- const { error } = await supabase.from('applications').insert({
178
- candidate_id: candidateId,
179
- job_posting_id: job_id,
180
- cover_note: cover_note ?? null,
181
- stage: 'applied',
167
+ const { data: result, error } = await supabase.rpc('insert_application_by_key', {
168
+ p_key: mcpKey,
169
+ p_job_posting_id: job_id,
170
+ p_cover_note: cover_note ?? null,
182
171
  });
183
172
  if (error)
184
173
  throw new Error(error.message);
174
+ if (result?.already_applied) {
175
+ return { content: [{ type: 'text', text: `Hai già applicato. Stage attuale: ${result.stage}.` }] };
176
+ }
185
177
  const startupName = job.startups?.name ?? 'la startup';
186
178
  return { content: [{ type: 'text', text: `✅ Candidatura inviata per ${job.role_title} @ ${startupName}. In bocca al lupo!` }] };
187
179
  });
@@ -192,17 +184,13 @@ export function registerCandidateTools(server, supabase, candidateId) {
192
184
  stage: z.enum(APP_STAGES).optional().describe('Filtra per stage (opzionale)'),
193
185
  },
194
186
  }, async ({ stage }) => {
195
- let query = supabase
196
- .from('applications')
197
- .select('id, stage, applied_at, cover_note, job_postings(id, role_title, role_area, work_mode, startups(name))')
198
- .eq('candidate_id', candidateId)
199
- .order('applied_at', { ascending: false });
200
- if (stage)
201
- query = query.eq('stage', stage);
202
- const { data, error } = await query;
187
+ const { data, error } = await supabase.rpc('get_applications_by_key', {
188
+ p_key: mcpKey,
189
+ p_stage: stage ?? null,
190
+ });
203
191
  if (error)
204
192
  throw new Error(error.message);
205
- if (!data?.length) {
193
+ if (!data || (Array.isArray(data) && data.length === 0)) {
206
194
  return { content: [{ type: 'text', text: 'Nessuna candidatura. Usa search_jobs e apply per candidarti.' }] };
207
195
  }
208
196
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starthub-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "StartHub MCP server — trova lavoro nelle startup italiane via Claude",
5
5
  "type": "module",
6
6
  "bin": {