@jobtarget/profiles-api-mcp 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,331 @@
1
+ # profiles-api-mcp
2
+
3
+ MCP server that exposes JobTarget Profiles API endpoints as MCP tools. Built with [mcp-framework](https://github.com/QuantGeekDev/mcp-framework), TypeScript, and Zod.
4
+
5
+ ---
6
+
7
+ ## Tools
8
+
9
+ | Tool | Method | Endpoint |
10
+ |------|--------|----------|
11
+ | `search_profiles` | POST | `/api/v1/profiles/search` |
12
+ | `get_profile` | GET | `/api/v1/profiles/{id}` |
13
+ | `query_profiles` | POST | `/api/v1/profiles/query` |
14
+ | `get_profile_links` | GET | `/api/v1/recruiter/{id}/profilelink` |
15
+ | `create_profile_link` | POST | `/api/v1/recruiter/{id}/profilelink` |
16
+ | `get_invite` | GET | `/api/v1/profileinvite/{publicId}` |
17
+
18
+ ---
19
+
20
+ ## Setup
21
+
22
+ ### 1. Install dependencies
23
+
24
+ ```bash
25
+ npm install
26
+ ```
27
+
28
+ ### 2. Configure environment
29
+
30
+ Copy `.env.example` to `.env` and fill in your values:
31
+
32
+ ```bash
33
+ cp .env.example .env
34
+ ```
35
+
36
+ ```env
37
+ # Required: Profiles API base URL (no trailing slash)
38
+ PROFILES_API_BASE_URL=https://your-profiles-api-host.example.com
39
+
40
+ # Required: use one of API key or Bearer token
41
+ PROFILES_API_API_KEY=your-api-key-here
42
+ # PROFILES_API_TOKEN=your-bearer-token-here
43
+ ```
44
+
45
+ The API key is sent as the `X-API-Key` header. If `PROFILES_API_TOKEN` is set instead, it is sent as `Authorization: Bearer <token>`.
46
+
47
+ ### 3. Build
48
+
49
+ ```bash
50
+ npm run build
51
+ ```
52
+
53
+ ### 4. Run
54
+
55
+ ```bash
56
+ npm start
57
+ ```
58
+
59
+ For development (no build step):
60
+
61
+ ```bash
62
+ npm run dev
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Claude Desktop / Claude Code configuration
68
+
69
+ Add to your MCP settings (e.g. `claude_desktop_config.json`):
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "profiles-api": {
75
+ "command": "node",
76
+ "args": ["/absolute/path/to/profiles-api-mcp/dist/index.js"],
77
+ "env": {
78
+ "PROFILES_API_BASE_URL": "https://your-profiles-api-host.example.com",
79
+ "PROFILES_API_API_KEY": "your-api-key-here"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Production access via the public gateway (SRC-2936)
89
+
90
+ For hosted / non-local use, the MCP server talks to the **public API Gateway
91
+ façade** (`packages/lambdas/`) instead of the private Profiles API directly. The
92
+ gateway exposes only the 6 tool endpoints, is protected by AWS WAF, and
93
+ authenticates each request with a **per-user Personal Access Token (PAT)**.
94
+
95
+ Point the server at the gateway for the target environment and use the user's PAT
96
+ as the bearer token:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "profiles-api": {
102
+ "command": "npx",
103
+ "args": ["-y", "@jobtarget/profiles-api-mcp"],
104
+ "env": {
105
+ "PROFILES_API_BASE_URL": "https://<gateway-id>.execute-api.us-east-1.amazonaws.com/qa",
106
+ "PROFILES_API_TOKEN": "pat_… (issued via packages/lambdas/scripts/token-admin.mjs)"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ `PROFILES_API_TOKEN` is sent as `Authorization: Bearer <token>` — exactly what the
114
+ gateway's authorizer expects. Get the gateway URL from the stack output
115
+ `GatewayUrl`, and mint/revoke tokens with the token-admin CLI. See
116
+ [`packages/lambdas/README.md`](packages/lambdas/README.md) and
117
+ [`packages/lambdas/RUNBOOK.md`](packages/lambdas/RUNBOOK.md).
118
+
119
+ ---
120
+
121
+ ## Tool Reference
122
+
123
+ ### `search_profiles`
124
+
125
+ Full-text keyword search across profile data sources.
126
+
127
+ **Required inputs:**
128
+ - `source` — `"PeopleDataLabs"` | `"JobTarget"` | `"ResumeLibrary"`
129
+ - `what` — Search keywords (e.g. `"software engineer react"`)
130
+
131
+ **Optional inputs:**
132
+ - `where` — Location string (e.g. `"Austin, TX"`)
133
+ - `latitude` / `longitude` — Geo coordinates (must be paired)
134
+ - `radius` — Search radius in miles (10–200, requires `where` + coordinates)
135
+ - `page` — Page number (1-based, default `1`)
136
+ - `count` — Results per page (1–100, default `10`)
137
+ - `companies` — Array of company name filters
138
+ - `jobTitles` — Array of job title filters
139
+ - `recentlyActive` — Boolean; filter to recently active profiles
140
+ - `companyId` — Required when `source` is `ResumeLibrary`
141
+ - `fuzziness` — `"Auto"` | `"0"` | `"1"` | `"2"`
142
+ - `type` — `"best_fields"` | `"most_fields"` | `"cross_fields"`
143
+ - `weights` — Object with `w_text`, `w_recency`, `w_signal_data`, `w_contact`, `w_job_title`, `w_distance`, `w_phone`
144
+
145
+ **Example:**
146
+ ```json
147
+ {
148
+ "source": "PeopleDataLabs",
149
+ "what": "senior software engineer typescript",
150
+ "where": "San Francisco, CA",
151
+ "radius": 25,
152
+ "latitude": 37.7749,
153
+ "longitude": -122.4194,
154
+ "count": 5
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ### `get_profile`
161
+
162
+ Fetch a single profile by ID.
163
+
164
+ **Required inputs:**
165
+ - `id` — Profile ID with source prefix (e.g. `"pdl_abc123"`, `"jbt_456"`, `"rli_789"`)
166
+
167
+ **Example:**
168
+ ```json
169
+ { "id": "pdl_abc123def456" }
170
+ ```
171
+
172
+ ---
173
+
174
+ ### `query_profiles`
175
+
176
+ Execute an advanced boolean query against the profile index.
177
+
178
+ **Required inputs:**
179
+ - `query` — Object with `must`, `not`, and/or `should` arrays. At least one array must be non-empty.
180
+
181
+ **Optional inputs:**
182
+ - `page` — Zero-based page number (default `0`)
183
+ - `count` — Results per page (1–100, default `10`)
184
+
185
+ **Supported query operators:**
186
+
187
+ | Operator | Description |
188
+ |----------|-------------|
189
+ | `term` | Exact case-sensitive match |
190
+ | `contains` | Wildcard / partial match |
191
+ | `match` | Full-text analyzed match |
192
+ | `fuzzy` | Typo-tolerant approximate match |
193
+ | `startsWith` | Prefix match |
194
+ | `doesNotStartWith` | Negative prefix match |
195
+
196
+ **Example:**
197
+ ```json
198
+ {
199
+ "query": {
200
+ "must": [
201
+ { "match": { "skills": "Python" } },
202
+ { "match": { "skills": "machine learning" } }
203
+ ],
204
+ "not": [
205
+ { "term": { "currentEmployer": "Acme Corp" } }
206
+ ]
207
+ },
208
+ "count": 20
209
+ }
210
+ ```
211
+
212
+ **Searchable fields include:** `fullName`, `firstName`, `lastName`, `currentTitle`, `normalizedTitle`, `currentEmployer`, `skills`, `experience.title.name`, `experience.company.name`, `education.school.name`, `location.name`, `location.region`, `emails.emailAddress`, `industry`, and many more.
213
+
214
+ ---
215
+
216
+ ### `get_profile_links`
217
+
218
+ List profile links (saved/unlocked candidates) for a recruiter.
219
+
220
+ **Required inputs:**
221
+ - `recruiterId` — Recruiter ID (integer)
222
+
223
+ **Optional inputs:**
224
+ - `saved` — Filter by saved status
225
+ - `unlocked` — Filter by unlocked status
226
+ - `hidden` — Filter by hidden status
227
+ - `page` — Zero-based page (default `0`)
228
+ - `count` — Results per page (1–100, default `10`)
229
+ - `includeInvites` — Include associated ProfileInvite records
230
+ - `jobId` — Filter by job ID (implies `includeInvites=true`)
231
+
232
+ **Example:**
233
+ ```json
234
+ {
235
+ "recruiterId": 1234,
236
+ "saved": true,
237
+ "unlocked": true,
238
+ "count": 25
239
+ }
240
+ ```
241
+
242
+ ---
243
+
244
+ ### `create_profile_link`
245
+
246
+ Save or unlock a candidate profile for a recruiter.
247
+
248
+ **Required inputs:**
249
+ - `recruiterId` — Recruiter ID (integer)
250
+ - `profileId` — Profile ID with source prefix (`pdl_*`, `jbt_*`, or `rli_*`)
251
+
252
+ **Optional inputs:**
253
+ - `saved` — Mark as saved (default `false`)
254
+ - `unlocked` — Fetch full profile data and return presigned S3 URL (default `false`)
255
+ - `hidden` — Hide from recruiter's list (default `false`)
256
+ - `notes` — Recruiter notes (max 4092 characters)
257
+
258
+ **Example:**
259
+ ```json
260
+ {
261
+ "recruiterId": 1234,
262
+ "profileId": "pdl_abc123",
263
+ "saved": true,
264
+ "unlocked": true,
265
+ "notes": "Strong Python background, open to relocation"
266
+ }
267
+ ```
268
+
269
+ ---
270
+
271
+ ### `get_invite`
272
+
273
+ Retrieve a profile invite by its public ID.
274
+
275
+ **Required inputs:**
276
+ - `publicId` — Public invite ID (MongoDB ObjectId or UUID slug)
277
+
278
+ **Example:**
279
+ ```json
280
+ { "publicId": "507f1f77bcf86cd799439011" }
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Project Structure
286
+
287
+ ```
288
+ profiles-api-mcp/
289
+ ├── src/
290
+ │ ├── index.ts # Server entry point
291
+ │ ├── config.ts # Environment config + validation
292
+ │ ├── client/
293
+ │ │ └── ProfilesApiClient.ts # HTTP client with auth + error handling
294
+ │ └── tools/
295
+ │ ├── SearchProfiles.ts # search_profiles tool
296
+ │ ├── GetProfile.ts # get_profile tool
297
+ │ ├── QueryProfiles.ts # query_profiles tool
298
+ │ ├── GetProfileLinks.ts # get_profile_links tool
299
+ │ ├── CreateProfileLink.ts # create_profile_link tool
300
+ │ └── GetInvite.ts # get_invite tool
301
+ ├── dist/ # Compiled output (git-ignored)
302
+ ├── .env.example # Environment variable template
303
+ ├── package.json
304
+ ├── tsconfig.json
305
+ └── README.md
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Error Handling
311
+
312
+ All tools surface API errors with the HTTP status code and message:
313
+
314
+ | Status | Meaning |
315
+ |--------|---------|
316
+ | 400 | Validation failure — check your inputs |
317
+ | 401 | Authentication failure — check your API key |
318
+ | 404 | Resource not found |
319
+ | 409 | Conflict — resource already exists |
320
+ | 503 | S3 upload failure (create_profile_link with unlocked=true) |
321
+ | 5xx | Server error |
322
+
323
+ ---
324
+
325
+ ## Assumptions & Limitations
326
+
327
+ - **Authentication:** The Profiles API uses `X-API-Key` header auth. Bearer token support is provided as a fallback via `PROFILES_API_TOKEN`.
328
+ - **No pagination cursor:** All pagination uses page+count offsets. Max page for `query_profiles` is 100 (API-enforced).
329
+ - **ResumeLibrary source:** Requires `companyId` in `search_profiles`. The API returns a 401-shaped 400 error if missing.
330
+ - **Presigned URLs:** The `profileUrl` field in `create_profile_link` responses is valid for 15 minutes.
331
+ - **Profile invites:** `get_invite` retrieves by `publicId` (not the MongoDB `_id`).
@@ -0,0 +1,19 @@
1
+ export interface ApiResponse<T> {
2
+ data: T;
3
+ status: number;
4
+ }
5
+ export declare class ProfilesApiError extends Error {
6
+ readonly status: number;
7
+ readonly body?: unknown | undefined;
8
+ constructor(status: number, message: string, body?: unknown | undefined);
9
+ }
10
+ export declare class ProfilesApiClient {
11
+ private readonly baseUrl;
12
+ private readonly headers;
13
+ constructor();
14
+ get<T>(path: string): Promise<T>;
15
+ post<T>(path: string, body: unknown): Promise<T>;
16
+ private handleResponse;
17
+ private extractErrorMessage;
18
+ }
19
+ export declare const apiClient: ProfilesApiClient;
@@ -0,0 +1,73 @@
1
+ import { config } from "../config.js";
2
+ export class ProfilesApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, message, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = "ProfilesApiError";
10
+ }
11
+ }
12
+ export class ProfilesApiClient {
13
+ baseUrl;
14
+ headers;
15
+ constructor() {
16
+ this.baseUrl = config.api.baseUrl.replace(/\/$/, "");
17
+ this.headers = {
18
+ "Content-Type": "application/json",
19
+ Accept: "application/json",
20
+ };
21
+ if (config.api.apiKey) {
22
+ this.headers["X-API-Key"] = config.api.apiKey;
23
+ }
24
+ if (config.api.token) {
25
+ this.headers["Authorization"] = `Bearer ${config.api.token}`;
26
+ }
27
+ }
28
+ async get(path) {
29
+ const response = await fetch(`${this.baseUrl}${path}`, {
30
+ method: "GET",
31
+ headers: this.headers,
32
+ });
33
+ return this.handleResponse(response);
34
+ }
35
+ async post(path, body) {
36
+ const response = await fetch(`${this.baseUrl}${path}`, {
37
+ method: "POST",
38
+ headers: this.headers,
39
+ body: JSON.stringify(body),
40
+ });
41
+ return this.handleResponse(response);
42
+ }
43
+ async handleResponse(response) {
44
+ let body;
45
+ const contentType = response.headers.get("content-type") ?? "";
46
+ if (contentType.includes("application/json")) {
47
+ body = await response.json();
48
+ }
49
+ else {
50
+ body = await response.text();
51
+ }
52
+ if (!response.ok) {
53
+ const message = this.extractErrorMessage(body, response);
54
+ throw new ProfilesApiError(response.status, message, body);
55
+ }
56
+ return body;
57
+ }
58
+ extractErrorMessage(body, response) {
59
+ if (typeof body === "object" && body !== null) {
60
+ const b = body;
61
+ if (typeof b["title"] === "string")
62
+ return b["title"];
63
+ if (typeof b["message"] === "string")
64
+ return b["message"];
65
+ if (typeof b["error"] === "string")
66
+ return b["error"];
67
+ }
68
+ if (typeof body === "string" && body.length > 0)
69
+ return body;
70
+ return `HTTP ${response.status} ${response.statusText}`;
71
+ }
72
+ }
73
+ export const apiClient = new ProfilesApiClient();
@@ -0,0 +1,12 @@
1
+ export declare const config: {
2
+ api: {
3
+ baseUrl: string;
4
+ apiKey: string;
5
+ token: string;
6
+ };
7
+ server: {
8
+ name: string;
9
+ version: string;
10
+ };
11
+ };
12
+ export declare function validateConfig(): void;
package/dist/config.js ADDED
@@ -0,0 +1,19 @@
1
+ export const config = {
2
+ api: {
3
+ baseUrl: process.env.PROFILES_API_BASE_URL ?? "",
4
+ apiKey: process.env.PROFILES_API_API_KEY ?? "",
5
+ token: process.env.PROFILES_API_TOKEN ?? "",
6
+ },
7
+ server: {
8
+ name: "profiles-api-mcp",
9
+ version: "1.0.0",
10
+ },
11
+ };
12
+ export function validateConfig() {
13
+ if (!config.api.baseUrl) {
14
+ throw new Error("PROFILES_API_BASE_URL environment variable is required");
15
+ }
16
+ if (!config.api.apiKey && !config.api.token) {
17
+ throw new Error("Either PROFILES_API_API_KEY or PROFILES_API_TOKEN environment variable is required");
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { MCPServer } from "mcp-framework";
4
+ import { config, validateConfig } from "./config.js";
5
+ validateConfig();
6
+ const server = new MCPServer({
7
+ name: config.server.name,
8
+ version: config.server.version,
9
+ });
10
+ server.start();
@@ -0,0 +1,34 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ export declare class CreateProfileLink extends MCPTool {
4
+ name: string;
5
+ description: string;
6
+ schema: z.ZodObject<{
7
+ recruiterId: z.ZodNumber;
8
+ profileId: z.ZodString;
9
+ saved: z.ZodOptional<z.ZodBoolean>;
10
+ unlocked: z.ZodOptional<z.ZodBoolean>;
11
+ hidden: z.ZodOptional<z.ZodBoolean>;
12
+ notes: z.ZodOptional<z.ZodString>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ recruiterId: number;
15
+ profileId: string;
16
+ saved?: boolean | undefined;
17
+ unlocked?: boolean | undefined;
18
+ hidden?: boolean | undefined;
19
+ notes?: string | undefined;
20
+ }, {
21
+ recruiterId: number;
22
+ profileId: string;
23
+ saved?: boolean | undefined;
24
+ unlocked?: boolean | undefined;
25
+ hidden?: boolean | undefined;
26
+ notes?: string | undefined;
27
+ }>;
28
+ execute(input: z.infer<typeof this.schema>): Promise<{
29
+ content: Array<{
30
+ type: string;
31
+ text: string;
32
+ }>;
33
+ }>;
34
+ }
@@ -0,0 +1,82 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { apiClient, ProfilesApiError } from "../client/ProfilesApiClient.js";
4
+ export class CreateProfileLink extends MCPTool {
5
+ name = "create_profile_link";
6
+ description = "Save or unlock a candidate profile for a recruiter, creating a ProfileLink record. " +
7
+ "The profileId must include the source prefix (pdl_, jbt_, or rli_). " +
8
+ "Setting unlocked=true triggers a profile data fetch and S3 upload, " +
9
+ "and returns a 15-minute presigned URL to the full profile. " +
10
+ "Returns 409 Conflict if the recruiter already has a link for this profile.";
11
+ schema = z.object({
12
+ recruiterId: z
13
+ .number()
14
+ .int()
15
+ .positive()
16
+ .describe("Recruiter ID that is saving/unlocking the profile"),
17
+ profileId: z
18
+ .string()
19
+ .min(1)
20
+ .describe("Profile ID including source prefix: pdl_<id>, jbt_<id>, or rli_<id>"),
21
+ saved: z
22
+ .boolean()
23
+ .optional()
24
+ .describe("Mark the profile as saved (default false)"),
25
+ unlocked: z
26
+ .boolean()
27
+ .optional()
28
+ .describe("Unlock the full profile data. Triggers S3 fetch and returns a presigned URL (default false)"),
29
+ hidden: z
30
+ .boolean()
31
+ .optional()
32
+ .describe("Hide the profile from the recruiter's list (default false)"),
33
+ notes: z
34
+ .string()
35
+ .max(4092)
36
+ .optional()
37
+ .describe("Recruiter notes about this candidate (max 4092 characters)"),
38
+ });
39
+ async execute(input) {
40
+ const { recruiterId, profileId, saved, unlocked, hidden, notes } = input;
41
+ if (!profileId.startsWith("pdl_") &&
42
+ !profileId.startsWith("jbt_") &&
43
+ !profileId.startsWith("rli_")) {
44
+ throw new Error("profileId must start with pdl_, jbt_, or rli_ (e.g. 'pdl_abc123')");
45
+ }
46
+ const body = { profileId };
47
+ if (saved !== undefined)
48
+ body["saved"] = saved;
49
+ if (unlocked !== undefined)
50
+ body["unlocked"] = unlocked;
51
+ if (hidden !== undefined)
52
+ body["hidden"] = hidden;
53
+ if (notes !== undefined)
54
+ body["notes"] = notes;
55
+ try {
56
+ const link = await apiClient.post(`/api/v1/recruiter/${encodeURIComponent(recruiterId)}/profilelink`, body);
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: JSON.stringify(link, null, 2),
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ catch (error) {
67
+ if (error instanceof ProfilesApiError) {
68
+ if (error.status === 404) {
69
+ throw new Error(`Recruiter not found: ${recruiterId}`);
70
+ }
71
+ if (error.status === 409) {
72
+ throw new Error(`Profile link already exists for recruiter ${recruiterId} and profile ${profileId}`);
73
+ }
74
+ if (error.status === 503) {
75
+ throw new Error("Failed to upload profile to storage. The profile link may have been created without an unlocked URL.");
76
+ }
77
+ throw new Error(`Profiles API error (${error.status}): ${error.message}`);
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,19 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ export declare class GetInvite extends MCPTool {
4
+ name: string;
5
+ description: string;
6
+ schema: z.ZodObject<{
7
+ publicId: z.ZodString;
8
+ }, "strip", z.ZodTypeAny, {
9
+ publicId: string;
10
+ }, {
11
+ publicId: string;
12
+ }>;
13
+ execute(input: z.infer<typeof this.schema>): Promise<{
14
+ content: Array<{
15
+ type: string;
16
+ text: string;
17
+ }>;
18
+ }>;
19
+ }
@@ -0,0 +1,39 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { apiClient, ProfilesApiError } from "../client/ProfilesApiClient.js";
4
+ export class GetInvite extends MCPTool {
5
+ name = "get_invite";
6
+ description = "Retrieve a profile invite by its public ID. " +
7
+ "Profile invites are sent to candidates via email or SMS and track " +
8
+ "whether the candidate applied or connected. " +
9
+ "Returns the full ProfileInvite object including invite type, success status, " +
10
+ "job ID, and all associated events (opens, clicks, replies, etc.).";
11
+ schema = z.object({
12
+ publicId: z
13
+ .string()
14
+ .min(1)
15
+ .describe("The public invite ID (MongoDB ObjectId or UUID slug) from the invite URL or profile link"),
16
+ });
17
+ async execute(input) {
18
+ try {
19
+ const invite = await apiClient.get(`/api/v1/profileinvite/${encodeURIComponent(input.publicId)}`);
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: JSON.stringify(invite, null, 2),
25
+ },
26
+ ],
27
+ };
28
+ }
29
+ catch (error) {
30
+ if (error instanceof ProfilesApiError) {
31
+ if (error.status === 404) {
32
+ throw new Error(`Profile invite not found: ${input.publicId}`);
33
+ }
34
+ throw new Error(`Profiles API error (${error.status}): ${error.message}`);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,19 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ export declare class GetProfile extends MCPTool {
4
+ name: string;
5
+ description: string;
6
+ schema: z.ZodObject<{
7
+ id: z.ZodString;
8
+ }, "strip", z.ZodTypeAny, {
9
+ id: string;
10
+ }, {
11
+ id: string;
12
+ }>;
13
+ execute(input: z.infer<typeof this.schema>): Promise<{
14
+ content: Array<{
15
+ type: string;
16
+ text: string;
17
+ }>;
18
+ }>;
19
+ }