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 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
+ [![npm version](https://img.shields.io/npm/v/worklink-mcp.svg)](https://www.npmjs.com/package/worklink-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { WorkLinkClient } from "../api-client.js";
3
+ export declare function registerTenderTools(server: McpServer, client: WorkLinkClient): void;
@@ -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,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { WorkLinkClient } from "../api-client.js";
3
+ export declare function registerVacancyTools(server: McpServer, client: WorkLinkClient): void;
@@ -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(/&amp;/g, "&")
32
+ .replace(/&lt;/g, "<")
33
+ .replace(/&gt;/g, ">")
34
+ .replace(/&quot;/g, '"')
35
+ .replace(/&#039;|&apos;/g, "'")
36
+ .replace(/&nbsp;/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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }