@lightcone-ai/daemon 0.5.0 → 0.6.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.
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import mysql from 'mysql2/promise';
|
|
6
|
+
|
|
7
|
+
const pool = mysql.createPool({
|
|
8
|
+
host: process.env.DB_HOST ?? 'localhost',
|
|
9
|
+
port: Number(process.env.DB_PORT ?? 3306),
|
|
10
|
+
user: process.env.DB_USER ?? 'root',
|
|
11
|
+
password: process.env.DB_PASSWORD ?? '',
|
|
12
|
+
database: process.env.DB_NAME ?? 'snowland',
|
|
13
|
+
waitForConnections: true,
|
|
14
|
+
connectionLimit: 3,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const server = new McpServer({ name: 'mysql-jobs', version: '0.1.0' });
|
|
18
|
+
|
|
19
|
+
// ── search_jobs ───────────────────────────────────────────────────────────────
|
|
20
|
+
server.tool('search_jobs',
|
|
21
|
+
'搜索职位。支持按关键词、公司名、职位类型、工作地点过滤。返回匹配的职位列表(不含完整描述)。',
|
|
22
|
+
{
|
|
23
|
+
keyword: z.string().optional().describe('搜索关键词,匹配职位名称或描述'),
|
|
24
|
+
company: z.string().optional().describe('公司名(模糊匹配)'),
|
|
25
|
+
job_type: z.string().optional().describe('职位类型,如 实习、校招、社招'),
|
|
26
|
+
location: z.string().optional().describe('工作地点,如 北京、上海'),
|
|
27
|
+
limit: z.number().optional().describe('返回数量,默认 10,最大 50'),
|
|
28
|
+
},
|
|
29
|
+
async ({ keyword, company, job_type, location, limit = 10 }) => {
|
|
30
|
+
const max = Math.min(limit, 50);
|
|
31
|
+
const conditions = ['is_valid != "false"'];
|
|
32
|
+
const params = [];
|
|
33
|
+
|
|
34
|
+
if (keyword) {
|
|
35
|
+
conditions.push('(job_title LIKE ? OR job_description LIKE ?)');
|
|
36
|
+
params.push(`%${keyword}%`, `%${keyword}%`);
|
|
37
|
+
}
|
|
38
|
+
if (company) {
|
|
39
|
+
conditions.push('company_name LIKE ?');
|
|
40
|
+
params.push(`%${company}%`);
|
|
41
|
+
}
|
|
42
|
+
if (job_type) {
|
|
43
|
+
conditions.push('job_type LIKE ?');
|
|
44
|
+
params.push(`%${job_type}%`);
|
|
45
|
+
}
|
|
46
|
+
if (location) {
|
|
47
|
+
conditions.push('work_location LIKE ?');
|
|
48
|
+
params.push(`%${location}%`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
params.push(max);
|
|
52
|
+
const sql = `
|
|
53
|
+
SELECT job_id, job_title, company_name, salary, work_location, job_type, degree, tags, publish_date
|
|
54
|
+
FROM job_details
|
|
55
|
+
WHERE ${conditions.join(' AND ')}
|
|
56
|
+
ORDER BY created_at DESC
|
|
57
|
+
LIMIT ?
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
console.error(`[mysql-mcp] search_jobs: ${sql.replace(/\s+/g, ' ').trim()} | params: ${JSON.stringify(params)}`);
|
|
61
|
+
const [rows] = await pool.query(sql, params);
|
|
62
|
+
if (rows.length === 0) {
|
|
63
|
+
return { content: [{ type: 'text', text: '未找到匹配的职位。' }] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const text = rows.map(r => [
|
|
67
|
+
`job_id: ${r.job_id}`,
|
|
68
|
+
`职位: ${r.job_title} @ ${r.company_name}`,
|
|
69
|
+
`类型: ${r.job_type ?? '-'} 地点: ${r.work_location ?? '-'} 学历: ${r.degree ?? '-'}`,
|
|
70
|
+
`薪资: ${r.salary || '未披露'}`,
|
|
71
|
+
`标签: ${(() => { try { return r.tags ? JSON.parse(r.tags).join(', ') : '-'; } catch { return r.tags ?? '-'; } })()}`,
|
|
72
|
+
`发布日期: ${r.publish_date ?? '-'}`,
|
|
73
|
+
].join('\n')).join('\n\n---\n\n');
|
|
74
|
+
|
|
75
|
+
return { content: [{ type: 'text', text: `共 ${rows.length} 条结果:\n\n${text}` }] };
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// ── get_job_detail ────────────────────────────────────────────────────────────
|
|
80
|
+
server.tool('get_job_detail',
|
|
81
|
+
'根据 job_id 获取职位完整详情,包括职位描述全文。用于写手生成文案前获取完整信息。',
|
|
82
|
+
{
|
|
83
|
+
job_id: z.string().describe('职位 ID(从 search_jobs 结果中获取)'),
|
|
84
|
+
},
|
|
85
|
+
async ({ job_id }) => {
|
|
86
|
+
const detailSql = `SELECT jd.*, c.industry, c.scale, c.type as company_type, c.logo_url FROM job_details jd LEFT JOIN companies c ON jd.company_id = c.company_id WHERE jd.job_id = ?`;
|
|
87
|
+
console.error(`[mysql-mcp] get_job_detail: ${detailSql} | params: ${JSON.stringify([job_id])}`);
|
|
88
|
+
const [rows] = await pool.query(detailSql, [job_id]);
|
|
89
|
+
|
|
90
|
+
if (rows.length === 0) {
|
|
91
|
+
return { content: [{ type: 'text', text: `未找到 job_id=${job_id} 的职位。` }] };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const r = rows[0];
|
|
95
|
+
const tags = (() => { try { return r.tags ? JSON.parse(r.tags) : []; } catch { return []; } })();
|
|
96
|
+
const text = [
|
|
97
|
+
`【职位详情】`,
|
|
98
|
+
`职位名称: ${r.job_title}`,
|
|
99
|
+
`公司: ${r.company_name}${r.industry ? `(${r.industry})` : ''}`,
|
|
100
|
+
`公司规模: ${r.scale ?? '-'} 公司类型: ${r.company_type ?? '-'}`,
|
|
101
|
+
`薪资: ${r.salary || '未披露'}`,
|
|
102
|
+
`工作地点: ${r.work_location ?? '-'}`,
|
|
103
|
+
`职位类型: ${r.job_type ?? '-'}`,
|
|
104
|
+
`学历要求: ${r.degree ?? '-'}`,
|
|
105
|
+
`技能标签: ${tags.join(', ') || '-'}`,
|
|
106
|
+
`发布日期: ${r.publish_date ?? '-'}`,
|
|
107
|
+
`截止日期: ${r.close_date ?? '-'}`,
|
|
108
|
+
`原始链接: ${r.job_detail_url}`,
|
|
109
|
+
``,
|
|
110
|
+
`【职位描述】`,
|
|
111
|
+
r.job_description ?? '(无描述)',
|
|
112
|
+
].join('\n');
|
|
113
|
+
|
|
114
|
+
return { content: [{ type: 'text', text }] };
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// ── list_jobs ─────────────────────────────────────────────────────────────────
|
|
119
|
+
server.tool('list_jobs',
|
|
120
|
+
'列出最新职位,支持按类型、地点过滤。用于浏览近期职位。',
|
|
121
|
+
{
|
|
122
|
+
job_type: z.string().optional().describe('职位类型过滤,如 实习、校招、社招'),
|
|
123
|
+
location: z.string().optional().describe('工作地点过滤'),
|
|
124
|
+
limit: z.number().optional().describe('返回数量,默认 20,最大 50'),
|
|
125
|
+
offset: z.number().optional().describe('跳过前 N 条,用于分页'),
|
|
126
|
+
},
|
|
127
|
+
async ({ job_type, location, limit = 20, offset = 0 }) => {
|
|
128
|
+
const max = Math.min(limit, 50);
|
|
129
|
+
const conditions = ['is_valid != "false"'];
|
|
130
|
+
const params = [];
|
|
131
|
+
|
|
132
|
+
if (job_type) { conditions.push('job_type LIKE ?'); params.push(`%${job_type}%`); }
|
|
133
|
+
if (location) { conditions.push('work_location LIKE ?'); params.push(`%${location}%`); }
|
|
134
|
+
|
|
135
|
+
params.push(max, offset);
|
|
136
|
+
const listSql = `SELECT job_id, job_title, company_name, salary, work_location, job_type, degree, tags FROM job_details WHERE ${conditions.join(' AND ')} ORDER BY created_at DESC LIMIT ? OFFSET ?`;
|
|
137
|
+
console.error(`[mysql-mcp] list_jobs: ${listSql} | params: ${JSON.stringify(params)}`);
|
|
138
|
+
const [rows] = await pool.query(listSql, params);
|
|
139
|
+
|
|
140
|
+
if (rows.length === 0) {
|
|
141
|
+
return { content: [{ type: 'text', text: '暂无职位数据。' }] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const text = rows.map(r =>
|
|
145
|
+
`${r.job_id} | ${r.job_title} @ ${r.company_name} | ${r.job_type ?? '-'} | ${r.work_location ?? '-'} | ${r.salary || '薪资未披露'}`
|
|
146
|
+
).join('\n');
|
|
147
|
+
|
|
148
|
+
return { content: [{ type: 'text', text }] };
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// ── start ─────────────────────────────────────────────────────────────────────
|
|
153
|
+
const transport = new StdioServerTransport();
|
|
154
|
+
await server.connect(transport);
|
|
155
|
+
console.error('[mysql-mcp] Server started');
|