worklink-mcp 1.0.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/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/api-client.d.ts +24 -0
- package/dist/api-client.js +102 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/tenders.d.ts +3 -0
- package/dist/tools/tenders.js +92 -0
- package/dist/tools/tenders.js.map +1 -0
- package/dist/tools/vacancies.d.ts +3 -0
- package/dist/tools/vacancies.js +123 -0
- package/dist/tools/vacancies.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 md-ar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# worklink-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/worklink-mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
MCP server for the [WorkLink Partner API](https://worklink.sy/api/partner/docs) — search live job vacancies and tenders in Syria.
|
|
7
|
+
|
|
8
|
+
## Get a Token
|
|
9
|
+
|
|
10
|
+
1. Go to https://worklink.sy/api-access
|
|
11
|
+
2. Fill the form (choose vacancies and/or tenders)
|
|
12
|
+
3. Wait for WorkLink to issue your token
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
No installation needed — run directly with `npx`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx -y worklink-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configure
|
|
23
|
+
|
|
24
|
+
### Claude Desktop
|
|
25
|
+
|
|
26
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"worklink": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "worklink-mcp"],
|
|
34
|
+
"env": {
|
|
35
|
+
"WORKLINK_API_TOKEN": "your-token-here"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### OpenCode
|
|
43
|
+
|
|
44
|
+
Add to `.opencode.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcp": {
|
|
49
|
+
"worklink": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "worklink-mcp"],
|
|
52
|
+
"env": {
|
|
53
|
+
"WORKLINK_API_TOKEN": "your-token-here"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Cursor / Windsurf
|
|
61
|
+
|
|
62
|
+
Add to your MCP settings (`.cursor/mcp.json` or equivalent):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"worklink": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "worklink-mcp"],
|
|
70
|
+
"env": {
|
|
71
|
+
"WORKLINK_API_TOKEN": "your-token-here"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### ChatGPT
|
|
79
|
+
|
|
80
|
+
In ChatGPT settings > MCP Servers > Add server:
|
|
81
|
+
|
|
82
|
+
- **Command:** `npx -y worklink-mcp`
|
|
83
|
+
- **Env:** `WORKLINK_API_TOKEN=your-token-here`
|
|
84
|
+
|
|
85
|
+
## Tools
|
|
86
|
+
|
|
87
|
+
| Tool | Description |
|
|
88
|
+
|------|-------------|
|
|
89
|
+
| `list_vacancies` | List active job vacancies with pagination, category/company/open filters |
|
|
90
|
+
| `get_vacancy` | Get full details of a single vacancy by slug |
|
|
91
|
+
| `list_tenders` | List published tenders with pagination and filters |
|
|
92
|
+
| `get_tender` | Get tender preview by slug (full body on WorkLink) |
|
|
93
|
+
|
|
94
|
+
## Example Prompts
|
|
95
|
+
|
|
96
|
+
Once configured, ask your AI assistant:
|
|
97
|
+
|
|
98
|
+
- "Show me the latest job vacancies in Damascus"
|
|
99
|
+
- "Find engineering jobs with salary above 500"
|
|
100
|
+
- "What tenders are open this month?"
|
|
101
|
+
- "Get details for the vacancy at company X"
|
|
102
|
+
|
|
103
|
+
## Troubleshooting
|
|
104
|
+
|
|
105
|
+
### "WORKLINK_API_TOKEN environment variable is required"
|
|
106
|
+
|
|
107
|
+
The token is missing. Make sure you set `WORKLINK_API_TOKEN` in the MCP server config's `env` block (see Configure section above).
|
|
108
|
+
|
|
109
|
+
### "WorkLink API error (401): Unauthorized"
|
|
110
|
+
|
|
111
|
+
Your token is invalid or expired. Get a new one at https://worklink.sy/api-access.
|
|
112
|
+
|
|
113
|
+
### "WorkLink API error (429): Too Many Requests"
|
|
114
|
+
|
|
115
|
+
You've hit the rate limit (120 requests/minute). The server automatically retries with exponential backoff, but if this persists, wait a minute before trying again.
|
|
116
|
+
|
|
117
|
+
### Request timed out
|
|
118
|
+
|
|
119
|
+
The server has a 30-second timeout per request. If WorkLink's API is slow, the request will be retried up to 3 times before failing.
|
|
120
|
+
|
|
121
|
+
## Rate Limiting
|
|
122
|
+
|
|
123
|
+
The server respects WorkLink's 120 requests/minute limit with built-in throttling. If the limit is hit, requests are automatically retried with exponential backoff.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class WorkLinkClient {
|
|
2
|
+
private token;
|
|
3
|
+
private requestTimes;
|
|
4
|
+
private readonly MAX_REQUESTS_PER_MINUTE;
|
|
5
|
+
constructor(token: string);
|
|
6
|
+
private throttle;
|
|
7
|
+
fetch<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
|
|
8
|
+
getVacancies(params?: {
|
|
9
|
+
page?: number;
|
|
10
|
+
per_page?: number;
|
|
11
|
+
category?: number;
|
|
12
|
+
company?: number;
|
|
13
|
+
open?: 0 | 1;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
getVacancy(slug: string): Promise<unknown>;
|
|
16
|
+
getTenders(params?: {
|
|
17
|
+
page?: number;
|
|
18
|
+
per_page?: number;
|
|
19
|
+
category?: number;
|
|
20
|
+
company?: number;
|
|
21
|
+
open?: 0 | 1;
|
|
22
|
+
}): Promise<unknown>;
|
|
23
|
+
getTender(slug: string): Promise<unknown>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const BASE_URL = "https://worklink.sy/api/partner";
|
|
2
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
3
|
+
const MAX_RETRIES = 3;
|
|
4
|
+
const RETRY_BASE_MS = 1_000;
|
|
5
|
+
export class WorkLinkClient {
|
|
6
|
+
token;
|
|
7
|
+
requestTimes = [];
|
|
8
|
+
MAX_REQUESTS_PER_MINUTE = 120;
|
|
9
|
+
constructor(token) {
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
async throttle() {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
this.requestTimes = this.requestTimes.filter((t) => now - t < 60_000);
|
|
15
|
+
if (this.requestTimes.length >= this.MAX_REQUESTS_PER_MINUTE) {
|
|
16
|
+
const oldest = this.requestTimes[0];
|
|
17
|
+
const waitMs = 60_000 - (now - oldest) + 100;
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
19
|
+
this.requestTimes = this.requestTimes.filter((t) => Date.now() - t < 60_000);
|
|
20
|
+
}
|
|
21
|
+
this.requestTimes.push(Date.now());
|
|
22
|
+
}
|
|
23
|
+
async fetch(path, params) {
|
|
24
|
+
const url = new URL(`${BASE_URL}${path}`);
|
|
25
|
+
if (params) {
|
|
26
|
+
for (const [key, value] of Object.entries(params)) {
|
|
27
|
+
if (value !== undefined && value !== null) {
|
|
28
|
+
url.searchParams.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let lastError;
|
|
33
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
34
|
+
await this.throttle();
|
|
35
|
+
try {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
38
|
+
let res;
|
|
39
|
+
try {
|
|
40
|
+
res = await fetch(url.toString(), {
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${this.token}`,
|
|
43
|
+
Accept: "application/json",
|
|
44
|
+
},
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
}
|
|
51
|
+
if (res.status === 429) {
|
|
52
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
53
|
+
const waitMs = retryAfter
|
|
54
|
+
? parseInt(retryAfter, 10) * 1000
|
|
55
|
+
: RETRY_BASE_MS * Math.pow(2, attempt);
|
|
56
|
+
if (attempt < MAX_RETRIES) {
|
|
57
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
let errorMsg = `HTTP ${res.status}`;
|
|
63
|
+
try {
|
|
64
|
+
const body = await res.json();
|
|
65
|
+
errorMsg = body.message || body.error || errorMsg;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// ignore parse error
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`WorkLink API error (${res.status}): ${errorMsg}`);
|
|
71
|
+
}
|
|
72
|
+
return res.json();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
76
|
+
lastError = new Error(`Request to ${path} timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
|
|
77
|
+
if (attempt < MAX_RETRIES) {
|
|
78
|
+
await new Promise((r) => setTimeout(r, RETRY_BASE_MS * Math.pow(2, attempt)));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw lastError ?? new Error(`Failed to fetch ${path} after ${MAX_RETRIES + 1} attempts`);
|
|
88
|
+
}
|
|
89
|
+
async getVacancies(params) {
|
|
90
|
+
return this.fetch("/vacancies", params);
|
|
91
|
+
}
|
|
92
|
+
async getVacancy(slug) {
|
|
93
|
+
return this.fetch(`/vacancies/${encodeURIComponent(slug)}`);
|
|
94
|
+
}
|
|
95
|
+
async getTenders(params) {
|
|
96
|
+
return this.fetch("/tenders", params);
|
|
97
|
+
}
|
|
98
|
+
async getTender(slug) {
|
|
99
|
+
return this.fetch(`/tenders/${encodeURIComponent(slug)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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 { WorkLinkClient } from "./api-client.js";
|
|
5
|
+
import { registerVacancyTools } from "./tools/vacancies.js";
|
|
6
|
+
import { registerTenderTools } from "./tools/tenders.js";
|
|
7
|
+
const token = process.env.WORKLINK_API_TOKEN;
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.error("Error: WORKLINK_API_TOKEN environment variable is required.\n" +
|
|
10
|
+
"Get a token at https://worklink.sy/api-access");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const client = new WorkLinkClient(token);
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "worklink",
|
|
16
|
+
version: "1.0.1",
|
|
17
|
+
});
|
|
18
|
+
registerVacancyTools(server, client);
|
|
19
|
+
registerTenderTools(server, client);
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
console.error("worklink-mcp server running on stdio");
|
|
23
|
+
process.on("SIGINT", async () => {
|
|
24
|
+
await server.close();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
});
|
|
27
|
+
process.on("SIGTERM", async () => {
|
|
28
|
+
await server.close();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CACX,+DAA+D;QAC/D,+CAA+C,CAChD,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;AAEzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACrC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AAEtD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAC9B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IAC/B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function formatTender(t) {
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`# ${t.title}`);
|
|
5
|
+
lines.push("");
|
|
6
|
+
if (t.company)
|
|
7
|
+
lines.push(`**Company:** ${t.company.name}`);
|
|
8
|
+
if (t.category)
|
|
9
|
+
lines.push(`**Category:** ${t.category.name}`);
|
|
10
|
+
if (t.location)
|
|
11
|
+
lines.push(`**Location:** ${t.location}`);
|
|
12
|
+
if (t.deadline)
|
|
13
|
+
lines.push(`**Deadline:** ${t.deadline}`);
|
|
14
|
+
lines.push("");
|
|
15
|
+
lines.push("**Preview:**");
|
|
16
|
+
lines.push(t.preview);
|
|
17
|
+
lines.push("");
|
|
18
|
+
lines.push(`**View full tender and apply:** ${t.url}`);
|
|
19
|
+
return lines.join("\n");
|
|
20
|
+
}
|
|
21
|
+
function formatList(result, label) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
lines.push(`## ${label} (page ${result.meta.current_page}/${result.meta.last_page}, ${result.meta.total} total)`);
|
|
24
|
+
lines.push("");
|
|
25
|
+
for (const t of result.data) {
|
|
26
|
+
const company = t.company?.name ?? "Unknown";
|
|
27
|
+
const category = t.category?.name ?? "";
|
|
28
|
+
const location = t.location ?? "Not specified";
|
|
29
|
+
const deadline = t.deadline ?? "None";
|
|
30
|
+
lines.push(`### ${t.title}`);
|
|
31
|
+
lines.push(`- **Company:** ${company}`);
|
|
32
|
+
if (category)
|
|
33
|
+
lines.push(`- **Category:** ${category}`);
|
|
34
|
+
lines.push(`- **Location:** ${location}`);
|
|
35
|
+
lines.push(`- **Deadline:** ${deadline}`);
|
|
36
|
+
const preview = t.preview.length > 300
|
|
37
|
+
? t.preview.substring(0, 300).trimEnd() + "..."
|
|
38
|
+
: t.preview;
|
|
39
|
+
lines.push(`- **Preview:** ${preview}`);
|
|
40
|
+
lines.push(`- **Link:** ${t.url}`);
|
|
41
|
+
lines.push("");
|
|
42
|
+
}
|
|
43
|
+
if (result.meta.last_page > result.meta.current_page) {
|
|
44
|
+
lines.push(`_More pages available (page ${result.meta.current_page + 1} of ${result.meta.last_page}). Use page parameter to see more._`);
|
|
45
|
+
}
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
export function registerTenderTools(server, client) {
|
|
49
|
+
server.registerTool("list_tenders", {
|
|
50
|
+
description: "List published tenders from WorkLink Syria. Returns paginated results with title, company, location, preview text, and link to full tender.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
page: z.number().int().min(1).optional().describe("Page number (default: 1)"),
|
|
53
|
+
per_page: z.number().int().min(1).max(50).optional().describe("Results per page, 1-50 (default: 20)"),
|
|
54
|
+
category: z.number().int().optional().describe("Filter by category ID"),
|
|
55
|
+
company: z.number().int().optional().describe("Filter by company ID"),
|
|
56
|
+
open: z.union([z.literal(0), z.literal(1)]).optional().describe("1 = only currently-open items (no deadline or deadline is today or later)"),
|
|
57
|
+
},
|
|
58
|
+
}, async (args) => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await client.getTenders(args);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: formatList(result, "Tenders") }],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: `Error listing tenders: ${error instanceof Error ? error.message : String(error)}` }],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
server.registerTool("get_tender", {
|
|
73
|
+
description: "Get details of a single tender by its slug. Returns preview text only (full body is on WorkLink). Always includes link to view full tender and apply.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
slug: z.string().describe("The tender slug (from the URL or from list_tenders results)"),
|
|
76
|
+
},
|
|
77
|
+
}, async (args) => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await client.getTender(args.slug);
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: formatTender(result.data) }],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: `Error getting tender: ${error instanceof Error ? error.message : String(error)}` }],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=tenders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenders.js","sourceRoot":"","sources":["../../src/tools/tenders.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function formatVacancy(v) {
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`# ${v.title}`);
|
|
5
|
+
lines.push("");
|
|
6
|
+
if (v.company)
|
|
7
|
+
lines.push(`**Company:** ${v.company.name}`);
|
|
8
|
+
if (v.category)
|
|
9
|
+
lines.push(`**Category:** ${v.category.name}`);
|
|
10
|
+
if (v.type)
|
|
11
|
+
lines.push(`**Type:** ${v.type}`);
|
|
12
|
+
if (v.salary)
|
|
13
|
+
lines.push(`**Salary:** ${v.salary}`);
|
|
14
|
+
if (v.locations?.length)
|
|
15
|
+
lines.push(`**Locations:** ${v.locations.join(", ")}`);
|
|
16
|
+
if (v.deadline)
|
|
17
|
+
lines.push(`**Deadline:** ${v.deadline}`);
|
|
18
|
+
lines.push(`**Link:** ${v.url}`);
|
|
19
|
+
lines.push("");
|
|
20
|
+
if (v.description) {
|
|
21
|
+
const text = stripHtml(v.description);
|
|
22
|
+
lines.push(text);
|
|
23
|
+
}
|
|
24
|
+
return lines.join("\n");
|
|
25
|
+
}
|
|
26
|
+
function stripHtml(html) {
|
|
27
|
+
return html
|
|
28
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
29
|
+
.replace(/<\/?(p|div|h[1-6]|li|tr)[^>]*>/gi, "\n")
|
|
30
|
+
.replace(/<[^>]+>/g, " ")
|
|
31
|
+
.replace(/&/g, "&")
|
|
32
|
+
.replace(/</g, "<")
|
|
33
|
+
.replace(/>/g, ">")
|
|
34
|
+
.replace(/"/g, '"')
|
|
35
|
+
.replace(/'|'/g, "'")
|
|
36
|
+
.replace(/ /g, " ")
|
|
37
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
38
|
+
.replace(/[ \t]+/g, " ")
|
|
39
|
+
.trim();
|
|
40
|
+
}
|
|
41
|
+
function truncate(text, maxLen) {
|
|
42
|
+
if (text.length <= maxLen)
|
|
43
|
+
return text;
|
|
44
|
+
return text.substring(0, maxLen).trimEnd() + "...";
|
|
45
|
+
}
|
|
46
|
+
function formatList(result, label) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
lines.push(`## ${label} (page ${result.meta.current_page}/${result.meta.last_page}, ${result.meta.total} total)`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
for (const v of result.data) {
|
|
51
|
+
const company = v.company?.name ?? "Unknown";
|
|
52
|
+
const type = v.type ?? "";
|
|
53
|
+
const category = v.category?.name ?? "";
|
|
54
|
+
const salary = v.salary ?? "Not specified";
|
|
55
|
+
const locations = v.locations?.join(", ") ?? "";
|
|
56
|
+
const deadline = v.deadline ?? "None";
|
|
57
|
+
lines.push(`### ${v.title}`);
|
|
58
|
+
lines.push(`- **Company:** ${company}`);
|
|
59
|
+
if (type)
|
|
60
|
+
lines.push(`- **Type:** ${type}`);
|
|
61
|
+
if (category)
|
|
62
|
+
lines.push(`- **Category:** ${category}`);
|
|
63
|
+
lines.push(`- **Salary:** ${salary}`);
|
|
64
|
+
if (locations)
|
|
65
|
+
lines.push(`- **Locations:** ${locations}`);
|
|
66
|
+
lines.push(`- **Deadline:** ${deadline}`);
|
|
67
|
+
if (v.description) {
|
|
68
|
+
const preview = truncate(stripHtml(v.description), 200);
|
|
69
|
+
lines.push(`- **Preview:** ${preview}`);
|
|
70
|
+
}
|
|
71
|
+
lines.push(`- **Link:** ${v.url}`);
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
74
|
+
if (result.meta.last_page > result.meta.current_page) {
|
|
75
|
+
lines.push(`_More pages available (page ${result.meta.current_page + 1} of ${result.meta.last_page}). Use page parameter to see more._`);
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
export function registerVacancyTools(server, client) {
|
|
80
|
+
server.registerTool("list_vacancies", {
|
|
81
|
+
description: "List active job vacancies from WorkLink Syria. Returns paginated results with title, company, salary, locations, and apply link.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
page: z.number().int().min(1).optional().describe("Page number (default: 1)"),
|
|
84
|
+
per_page: z.number().int().min(1).max(50).optional().describe("Results per page, 1-50 (default: 20)"),
|
|
85
|
+
category: z.number().int().optional().describe("Filter by category ID"),
|
|
86
|
+
company: z.number().int().optional().describe("Filter by company ID"),
|
|
87
|
+
open: z.union([z.literal(0), z.literal(1)]).optional().describe("1 = only currently-open items (no deadline or deadline is today or later)"),
|
|
88
|
+
},
|
|
89
|
+
}, async (args) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = await client.getVacancies(args);
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: formatList(result, "Job Vacancies") }],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: `Error listing vacancies: ${error instanceof Error ? error.message : String(error)}` }],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.registerTool("get_vacancy", {
|
|
104
|
+
description: "Get full details of a single job vacancy by its slug. Returns title, description (HTML), salary, type, company, locations, deadline, and apply link.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
slug: z.string().describe("The vacancy slug (from the URL or from list_vacancies results)"),
|
|
107
|
+
},
|
|
108
|
+
}, async (args) => {
|
|
109
|
+
try {
|
|
110
|
+
const result = await client.getVacancy(args.slug);
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: formatVacancy(result.data) }],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text: `Error getting vacancy: ${error instanceof Error ? error.message : String(error)}` }],
|
|
118
|
+
isError: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=vacancies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vacancies.js","sourceRoot":"","sources":["../../src/tools/vacancies.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface Category {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Company {
|
|
6
|
+
name: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
}
|
|
9
|
+
export interface Vacancy {
|
|
10
|
+
id: number;
|
|
11
|
+
title: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
description: string;
|
|
14
|
+
salary: string | null;
|
|
15
|
+
type: string | null;
|
|
16
|
+
category?: Category;
|
|
17
|
+
company?: Company;
|
|
18
|
+
locations: string[];
|
|
19
|
+
deadline: string | null;
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
export interface Tender {
|
|
23
|
+
id: number;
|
|
24
|
+
title: string;
|
|
25
|
+
slug: string;
|
|
26
|
+
preview: string;
|
|
27
|
+
category?: Category;
|
|
28
|
+
company?: Company;
|
|
29
|
+
location: string | null;
|
|
30
|
+
deadline: string | null;
|
|
31
|
+
url: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PaginationMeta {
|
|
34
|
+
current_page: number;
|
|
35
|
+
last_page: number;
|
|
36
|
+
per_page: number;
|
|
37
|
+
total: number;
|
|
38
|
+
from: number;
|
|
39
|
+
to: number;
|
|
40
|
+
}
|
|
41
|
+
export interface PaginationLinks {
|
|
42
|
+
first: string;
|
|
43
|
+
last: string;
|
|
44
|
+
prev: string | null;
|
|
45
|
+
next: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface PaginatedResponse<T> {
|
|
48
|
+
data: T[];
|
|
49
|
+
links: PaginationLinks;
|
|
50
|
+
meta: PaginationMeta;
|
|
51
|
+
}
|
|
52
|
+
export interface SingleResponse<T> {
|
|
53
|
+
data: T;
|
|
54
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "worklink-mcp",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "MCP server for WorkLink Partner API — search Syria jobs and tenders",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"mcpName": "worklink-mcp",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "tsx src/index.ts",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["mcp", "worklink", "syria", "jobs", "tenders"],
|
|
15
|
+
"author": "md-ar",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"worklink-mcp": "dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist/**/*", "README.md", "LICENSE"],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"zod": "^3.25.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^26.1.1",
|
|
34
|
+
"tsx": "^4.23.1",
|
|
35
|
+
"typescript": "^7.0.2"
|
|
36
|
+
}
|
|
37
|
+
}
|