@snokam/mcp-cvpartner 1.0.0

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/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @snokam/mcp-cvpartner
2
+
3
+ MCP (Model Context Protocol) server for CVPartner CV management. Enables AI agents to search and retrieve consultant CVs.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Search CVs** by name, skills, or experience
8
+ - 👥 **List consultants** with their profiles
9
+ - 📄 **Get full CV content** in Norwegian or English
10
+ - 🛠️ **Find consultants by skill** (React, Python, Azure, etc.)
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ cd mcp/@snokam/cvpartner
16
+ pnpm install
17
+ pnpm build
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ ```env
23
+ CVPARTNER_API_KEY=your_api_key
24
+ CVPARTNER_ORG=snokam # Optional, defaults to "snokam"
25
+ ```
26
+
27
+ ## Usage with mcporter
28
+
29
+ Add to your mcporter config:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "cvpartner": {
35
+ "command": "node",
36
+ "args": ["/path/to/mcp/@snokam/cvpartner/dist/index.js"],
37
+ "env": {
38
+ "CVPARTNER_ORG": "snokam"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ API key should be set in environment or fetched from 1Password.
46
+
47
+ ## Available Tools
48
+
49
+ | Tool | Description |
50
+ | --------------------------- | ------------------------------------- |
51
+ | `list_users` | List all consultants |
52
+ | `search_cvs` | Search CVs by any text |
53
+ | `get_user` | Get user profile details |
54
+ | `get_cv` | Get full CV content |
55
+ | `find_consultants_by_skill` | Find consultants with specific skills |
56
+
57
+ ## API Documentation
58
+
59
+ CVPartner API: https://docs.cvpartner.com/
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,145 @@
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
+ // Environment variables
6
+ const API_KEY = process.env.CVPARTNER_API_KEY;
7
+ const ORG = process.env.CVPARTNER_ORG || "snokam";
8
+ const BASE_URL = `https://${ORG}.cvpartner.com/api`;
9
+ async function cvpartnerFetch(endpoint, options = {}) {
10
+ if (!API_KEY) {
11
+ throw new Error("CVPARTNER_API_KEY environment variable is required");
12
+ }
13
+ const response = await fetch(`${BASE_URL}${endpoint}`, {
14
+ ...options,
15
+ headers: {
16
+ Authorization: `Token token=${API_KEY}`,
17
+ "Content-Type": "application/json",
18
+ ...options.headers,
19
+ },
20
+ });
21
+ if (!response.ok) {
22
+ const error = await response.text();
23
+ throw new Error(`CVPartner API error: ${response.status} - ${error || response.statusText}`);
24
+ }
25
+ return response.json();
26
+ }
27
+ const server = new McpServer({
28
+ name: "cvpartner-mcp",
29
+ version: "1.0.0",
30
+ });
31
+ // Tool: List all users/consultants
32
+ server.tool("list_users", "List all users/consultants in CVPartner", {
33
+ include_deactivated: z
34
+ .boolean()
35
+ .default(false)
36
+ .describe("Include deactivated users"),
37
+ }, async ({ include_deactivated }) => {
38
+ const users = await cvpartnerFetch("/v1/users");
39
+ const filtered = include_deactivated
40
+ ? users
41
+ : users.filter((u) => !u.deactivated);
42
+ const simplified = filtered.map((u) => ({
43
+ user_id: u.user_id,
44
+ name: u.name,
45
+ email: u.email,
46
+ title: u.title?.no || u.title?.int || null,
47
+ telephone: u.telephone,
48
+ office: u.office_name,
49
+ cv_id: u.default_cv_id,
50
+ }));
51
+ return {
52
+ content: [{ type: "text", text: JSON.stringify(simplified, null, 2) }],
53
+ };
54
+ });
55
+ // Tool: Search CVs
56
+ server.tool("search_cvs", "Search CVs by name, skills, or experience", {
57
+ query: z.string().describe("Search query (name, skill, technology, etc.)"),
58
+ size: z.number().default(10).describe("Maximum number of results"),
59
+ }, async ({ query, size }) => {
60
+ const response = await cvpartnerFetch("/v4/search", {
61
+ method: "POST",
62
+ body: JSON.stringify({ query, size }),
63
+ });
64
+ const results = response.cvs.map((r) => ({
65
+ user_id: r.cv.user_id,
66
+ name: r.cv.name,
67
+ email: r.cv.email,
68
+ title: r.cv.title || r.cv.titles?.no || r.cv.titles?.int,
69
+ telephone: r.cv.telephone,
70
+ highlight: r.highlight,
71
+ }));
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: JSON.stringify({ total: response.total, results }, null, 2),
77
+ },
78
+ ],
79
+ };
80
+ });
81
+ // Tool: Get user details
82
+ server.tool("get_user", "Get detailed information about a specific user", {
83
+ user_id: z.string().describe("User ID from CVPartner"),
84
+ }, async ({ user_id }) => {
85
+ const user = await cvpartnerFetch(`/v1/users/${user_id}`);
86
+ return {
87
+ content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
88
+ };
89
+ });
90
+ // Tool: Get CV details
91
+ server.tool("get_cv", "Get full CV content for a user", {
92
+ user_id: z.string().describe("User ID"),
93
+ cv_id: z.string().describe("CV ID (from user's default_cv_id)"),
94
+ language: z
95
+ .enum(["no", "int"])
96
+ .default("no")
97
+ .describe("Language version (no=Norwegian, int=English)"),
98
+ }, async ({ user_id, cv_id, language }) => {
99
+ const cv = await cvpartnerFetch(`/v4/cvs/${user_id}/${cv_id}/${language}`);
100
+ return {
101
+ content: [{ type: "text", text: JSON.stringify(cv, null, 2) }],
102
+ };
103
+ });
104
+ // Tool: Search by technology/skill
105
+ server.tool("find_consultants_by_skill", "Find consultants with specific skills or technologies", {
106
+ skill: z
107
+ .string()
108
+ .describe("Skill or technology to search for (e.g., React, Python, Azure)"),
109
+ limit: z.number().default(10).describe("Maximum number of results"),
110
+ }, async ({ skill, limit }) => {
111
+ const response = await cvpartnerFetch("/v4/search", {
112
+ method: "POST",
113
+ body: JSON.stringify({
114
+ query: skill,
115
+ size: limit,
116
+ filter_fields: ["technologies", "project_experiences"],
117
+ }),
118
+ });
119
+ const results = response.cvs
120
+ .filter((r) => !r.cv.is_deactivated)
121
+ .map((r) => ({
122
+ name: r.cv.name,
123
+ email: r.cv.email,
124
+ title: r.cv.title || r.cv.titles?.no,
125
+ match: r.highlight,
126
+ }));
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: JSON.stringify({ skill, total: response.total, consultants: results }, null, 2),
132
+ },
133
+ ],
134
+ };
135
+ });
136
+ // Start server
137
+ async function main() {
138
+ const transport = new StdioServerTransport();
139
+ await server.connect(transport);
140
+ console.error("[cvpartner-mcp] Server running on stdio");
141
+ }
142
+ main().catch((error) => {
143
+ console.error("[cvpartner-mcp] Fatal error:", error);
144
+ process.exit(1);
145
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@snokam/mcp-cvpartner",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for CVPartner CV management",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "snokam-cvpartner-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.26.0",
17
+ "zod": "^3.25.76"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "tsx": "^4.19.0",
22
+ "typescript": "^5.7.0"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }