@mindstone-engineering/mcp-server-humaans 0.1.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/dist/auth.d.ts +19 -0
- package/dist/auth.js +26 -0
- package/dist/bridge.d.ts +16 -0
- package/dist/bridge.js +43 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +79 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +25 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +15 -0
- package/dist/tools/company.d.ts +3 -0
- package/dist/tools/company.js +69 -0
- package/dist/tools/configure.d.ts +3 -0
- package/dist/tools/configure.js +46 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/job-roles.d.ts +3 -0
- package/dist/tools/job-roles.js +81 -0
- package/dist/tools/people.d.ts +3 -0
- package/dist/tools/people.js +151 -0
- package/dist/tools/time-away.d.ts +3 -0
- package/dist/tools/time-away.js +157 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +13 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.js +42 -0
- package/package.json +48 -0
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humaans authentication module.
|
|
3
|
+
*
|
|
4
|
+
* Manages the API key lifecycle — env var on startup, runtime update via
|
|
5
|
+
* configure tool, and bridge integration for host-app credential management.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Returns the current API key.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getApiKey(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Returns true if an API key is configured.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isConfigured(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Update the API key at runtime (e.g. after configure_humaans_api_key).
|
|
17
|
+
*/
|
|
18
|
+
export declare function setApiKey(key: string): void;
|
|
19
|
+
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humaans authentication module.
|
|
3
|
+
*
|
|
4
|
+
* Manages the API key lifecycle — env var on startup, runtime update via
|
|
5
|
+
* configure tool, and bridge integration for host-app credential management.
|
|
6
|
+
*/
|
|
7
|
+
let apiKey = process.env.HUMAANS_API_KEY || '';
|
|
8
|
+
/**
|
|
9
|
+
* Returns the current API key.
|
|
10
|
+
*/
|
|
11
|
+
export function getApiKey() {
|
|
12
|
+
return apiKey;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns true if an API key is configured.
|
|
16
|
+
*/
|
|
17
|
+
export function isConfigured() {
|
|
18
|
+
return apiKey.length > 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Update the API key at runtime (e.g. after configure_humaans_api_key).
|
|
22
|
+
*/
|
|
23
|
+
export function setApiKey(key) {
|
|
24
|
+
apiKey = key;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path to bridge state file, supporting both current and legacy env vars.
|
|
3
|
+
*/
|
|
4
|
+
export declare const BRIDGE_STATE_PATH: string;
|
|
5
|
+
/**
|
|
6
|
+
* Send a request to the host app bridge.
|
|
7
|
+
*
|
|
8
|
+
* The bridge is an HTTP server running inside the host app (e.g. Rebel)
|
|
9
|
+
* that handles credential management and other cross-process operations.
|
|
10
|
+
*/
|
|
11
|
+
export declare const bridgeRequest: (urlPath: string, body: Record<string, unknown>) => Promise<{
|
|
12
|
+
success: boolean;
|
|
13
|
+
warning?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=bridge.d.ts.map
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { REQUEST_TIMEOUT_MS } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Path to bridge state file, supporting both current and legacy env vars.
|
|
5
|
+
*/
|
|
6
|
+
export const BRIDGE_STATE_PATH = process.env.MCP_HOST_BRIDGE_STATE || process.env.MINDSTONE_REBEL_BRIDGE_STATE || '';
|
|
7
|
+
const loadBridgeState = () => {
|
|
8
|
+
if (!BRIDGE_STATE_PATH)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const raw = fs.readFileSync(BRIDGE_STATE_PATH, 'utf8');
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Send a request to the host app bridge.
|
|
20
|
+
*
|
|
21
|
+
* The bridge is an HTTP server running inside the host app (e.g. Rebel)
|
|
22
|
+
* that handles credential management and other cross-process operations.
|
|
23
|
+
*/
|
|
24
|
+
export const bridgeRequest = async (urlPath, body) => {
|
|
25
|
+
const bridge = loadBridgeState();
|
|
26
|
+
if (!bridge) {
|
|
27
|
+
return { success: false, error: 'Bridge not available' };
|
|
28
|
+
}
|
|
29
|
+
const response = await fetch(`http://127.0.0.1:${bridge.port}${urlPath}`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
Authorization: `Bearer ${bridge.token}`,
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify(body),
|
|
37
|
+
});
|
|
38
|
+
if (response.status === 401 || response.status === 403) {
|
|
39
|
+
return { success: false, error: `Bridge returned ${response.status}: unauthorized. Check host app authentication.` };
|
|
40
|
+
}
|
|
41
|
+
return response.json();
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=bridge.js.map
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humaans API HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* Centralises Bearer auth header injection, error handling, rate-limit
|
|
5
|
+
* messaging, and timeout handling for all Humaans API calls.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Make an authenticated request to the Humaans API.
|
|
9
|
+
*
|
|
10
|
+
* @param path API path relative to base, e.g. `/people`
|
|
11
|
+
* @param options Additional fetch options
|
|
12
|
+
* @returns Parsed JSON response
|
|
13
|
+
*/
|
|
14
|
+
export declare function humaansFetch<T>(path: string, options?: RequestInit): Promise<T>;
|
|
15
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humaans API HTTP client.
|
|
3
|
+
*
|
|
4
|
+
* Centralises Bearer auth header injection, error handling, rate-limit
|
|
5
|
+
* messaging, and timeout handling for all Humaans API calls.
|
|
6
|
+
*/
|
|
7
|
+
import { getApiKey } from './auth.js';
|
|
8
|
+
import { HumaansError, HUMAANS_API_BASE, REQUEST_TIMEOUT_MS, } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Format a Humaans API error response into a human-readable message.
|
|
11
|
+
*/
|
|
12
|
+
function formatApiError(error) {
|
|
13
|
+
let msg = `${error.name} (${error.code}): ${error.message}`;
|
|
14
|
+
if (error.issues && error.issues.length > 0) {
|
|
15
|
+
const issueLines = error.issues.map((i) => ` - ${i.name}: ${i.reason}${i.forbidden ? ' (insufficient permissions)' : ''}`);
|
|
16
|
+
msg += '\n' + issueLines.join('\n');
|
|
17
|
+
}
|
|
18
|
+
return msg;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Make an authenticated request to the Humaans API.
|
|
22
|
+
*
|
|
23
|
+
* @param path API path relative to base, e.g. `/people`
|
|
24
|
+
* @param options Additional fetch options
|
|
25
|
+
* @returns Parsed JSON response
|
|
26
|
+
*/
|
|
27
|
+
export async function humaansFetch(path, options = {}) {
|
|
28
|
+
const key = getApiKey();
|
|
29
|
+
if (!key) {
|
|
30
|
+
throw new HumaansError('Humaans API key not configured', 'AUTH_REQUIRED', 'Use configure_humaans_api_key to set your API key first.');
|
|
31
|
+
}
|
|
32
|
+
const url = `${HUMAANS_API_BASE}${path}`;
|
|
33
|
+
const headers = {
|
|
34
|
+
Authorization: `Bearer ${key}`,
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
...options.headers,
|
|
37
|
+
};
|
|
38
|
+
let response;
|
|
39
|
+
try {
|
|
40
|
+
response = await fetch(url, {
|
|
41
|
+
...options,
|
|
42
|
+
signal: options.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
43
|
+
headers,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof Error && error.name === 'TimeoutError') {
|
|
48
|
+
throw new HumaansError('Request to Humaans API timed out', 'TIMEOUT', 'The request took too long. Try again or check if the Humaans API is available.');
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
if (response.status === 401 || response.status === 403) {
|
|
53
|
+
throw new HumaansError('Authentication failed', 'AUTH_FAILED', 'Your Humaans API token is invalid or lacks required scopes. Use configure_humaans_api_key to set a new token.');
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 429) {
|
|
56
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
57
|
+
const waitTime = retryAfter ? `${retryAfter} seconds` : 'a moment';
|
|
58
|
+
throw new HumaansError(`Rate limited by Humaans API. Please wait ${waitTime} before retrying.`, 'RATE_LIMITED', `Wait ${waitTime} and try again.`);
|
|
59
|
+
}
|
|
60
|
+
if (response.status === 404) {
|
|
61
|
+
throw new HumaansError('Resource not found', 'NOT_FOUND', 'The requested resource does not exist or you do not have permission to access it.');
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
let errorBody;
|
|
65
|
+
try {
|
|
66
|
+
errorBody = (await response.json());
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Response may not be JSON
|
|
70
|
+
}
|
|
71
|
+
if (errorBody) {
|
|
72
|
+
throw new HumaansError(formatApiError(errorBody), 'API_ERROR', 'Check the request parameters and try again.');
|
|
73
|
+
}
|
|
74
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
75
|
+
throw new HumaansError(`Humaans API error (${response.status}): ${errorText}`, 'API_ERROR', 'Check the request parameters and try again.');
|
|
76
|
+
}
|
|
77
|
+
return response.json();
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=client.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Humaans MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides Humaans HR platform integration via Model Context Protocol.
|
|
6
|
+
* Covers core HR data: people, job roles, locations, company, time away.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* - HUMAANS_API_KEY: User's Humaans API access token
|
|
10
|
+
* - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (optional)
|
|
11
|
+
* - MINDSTONE_REBEL_BRIDGE_STATE: Legacy bridge state path (optional)
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Humaans MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides Humaans HR platform integration via Model Context Protocol.
|
|
6
|
+
* Covers core HR data: people, job roles, locations, company, time away.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* - HUMAANS_API_KEY: User's Humaans API access token
|
|
10
|
+
* - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (optional)
|
|
11
|
+
* - MINDSTONE_REBEL_BRIDGE_STATE: Legacy bridge state path (optional)
|
|
12
|
+
*/
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { createServer } from './server.js';
|
|
15
|
+
async function main() {
|
|
16
|
+
const server = createServer();
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
await server.connect(transport);
|
|
19
|
+
console.error('Humaans MCP server running on stdio');
|
|
20
|
+
}
|
|
21
|
+
main().catch((error) => {
|
|
22
|
+
console.error('Fatal error:', error);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerConfigureTools, registerPeopleTools, registerJobRoleTools, registerCompanyTools, registerTimeAwayTools, } from './tools/index.js';
|
|
3
|
+
export function createServer() {
|
|
4
|
+
const server = new McpServer({
|
|
5
|
+
name: 'humaans-mcp-server',
|
|
6
|
+
version: '0.1.0',
|
|
7
|
+
});
|
|
8
|
+
registerConfigureTools(server);
|
|
9
|
+
registerPeopleTools(server);
|
|
10
|
+
registerJobRoleTools(server);
|
|
11
|
+
registerCompanyTools(server);
|
|
12
|
+
registerTimeAwayTools(server);
|
|
13
|
+
return server;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { humaansFetch } from '../client.js';
|
|
3
|
+
import { withErrorHandling } from '../utils.js';
|
|
4
|
+
import { isConfigured } from '../auth.js';
|
|
5
|
+
function paginationHint(total, skip, count) {
|
|
6
|
+
if (count >= total)
|
|
7
|
+
return `Showing all ${total} results.`;
|
|
8
|
+
const remaining = total - skip - count;
|
|
9
|
+
return `Showing ${count} of ${total} total (skip=${skip}). ${remaining > 0 ? `Use skip=${skip + count} to see more.` : ''}`;
|
|
10
|
+
}
|
|
11
|
+
function noApiKeyError() {
|
|
12
|
+
return JSON.stringify({
|
|
13
|
+
ok: false,
|
|
14
|
+
error: 'Humaans API key not configured',
|
|
15
|
+
resolution: 'Use configure_humaans_api_key to set your API key first.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function registerCompanyTools(server) {
|
|
19
|
+
server.registerTool('list_humaans_locations', {
|
|
20
|
+
description: `List company locations/offices from Humaans.
|
|
21
|
+
|
|
22
|
+
Returns: label, city, country, timezone for each office location.
|
|
23
|
+
Note: Remote employees have locationId="remote" with remoteCity/remoteCountry fields on their person profile.
|
|
24
|
+
|
|
25
|
+
Example: {}`,
|
|
26
|
+
inputSchema: z.object({
|
|
27
|
+
limit: z.number().min(1).max(250).optional()
|
|
28
|
+
.describe('Max results (default 100, max 250)'),
|
|
29
|
+
skip: z.number().min(0).optional()
|
|
30
|
+
.describe('Number of results to skip'),
|
|
31
|
+
}),
|
|
32
|
+
annotations: { readOnlyHint: true },
|
|
33
|
+
}, withErrorHandling(async (args) => {
|
|
34
|
+
if (!isConfigured())
|
|
35
|
+
return noApiKeyError();
|
|
36
|
+
const limit = Math.min(Math.max(args.limit ?? 100, 1), 250);
|
|
37
|
+
const skip = Math.max(args.skip ?? 0, 0);
|
|
38
|
+
const params = new URLSearchParams();
|
|
39
|
+
params.set('$limit', String(limit));
|
|
40
|
+
params.set('$skip', String(skip));
|
|
41
|
+
const result = await humaansFetch(`/locations?${params.toString()}`);
|
|
42
|
+
const hint = paginationHint(result.total, result.skip, result.data.length);
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
ok: true,
|
|
45
|
+
locations: result.data,
|
|
46
|
+
count: result.data.length,
|
|
47
|
+
total: result.total,
|
|
48
|
+
pagination: hint,
|
|
49
|
+
});
|
|
50
|
+
}));
|
|
51
|
+
server.registerTool('get_humaans_company', {
|
|
52
|
+
description: `Get company information from Humaans.
|
|
53
|
+
|
|
54
|
+
Returns: company name, status, trial info, timesheet settings.
|
|
55
|
+
|
|
56
|
+
Example: {}`,
|
|
57
|
+
inputSchema: z.object({}),
|
|
58
|
+
annotations: { readOnlyHint: true },
|
|
59
|
+
}, withErrorHandling(async () => {
|
|
60
|
+
if (!isConfigured())
|
|
61
|
+
return noApiKeyError();
|
|
62
|
+
const result = await humaansFetch('/companies');
|
|
63
|
+
if (result.data.length === 0) {
|
|
64
|
+
return JSON.stringify({ ok: false, error: 'No company found.' });
|
|
65
|
+
}
|
|
66
|
+
return JSON.stringify({ ok: true, company: result.data[0] });
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=company.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { setApiKey } from '../auth.js';
|
|
3
|
+
import { bridgeRequest, BRIDGE_STATE_PATH } from '../bridge.js';
|
|
4
|
+
import { HumaansError } from '../types.js';
|
|
5
|
+
import { withErrorHandling } from '../utils.js';
|
|
6
|
+
export function registerConfigureTools(server) {
|
|
7
|
+
server.registerTool('configure_humaans_api_key', {
|
|
8
|
+
description: 'Configure the Humaans API access token. Call this when the user provides their token. ' +
|
|
9
|
+
'Get a token from https://app.humaans.io/settings/home?tokens=1 — ' +
|
|
10
|
+
'click "Generate new token", name it (e.g., "Rebel"), select scopes: public:read, private:read, private:write.',
|
|
11
|
+
inputSchema: z.object({
|
|
12
|
+
api_key: z.string().min(1).describe('The Humaans API access token'),
|
|
13
|
+
}),
|
|
14
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15
|
+
}, withErrorHandling(async (args) => {
|
|
16
|
+
const trimmedKey = args.api_key.trim();
|
|
17
|
+
// If bridge is available, persist via bridge
|
|
18
|
+
if (BRIDGE_STATE_PATH) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await bridgeRequest('/bundled/humaans/configure', { apiKey: trimmedKey });
|
|
21
|
+
if (result.success) {
|
|
22
|
+
setApiKey(trimmedKey);
|
|
23
|
+
const message = result.warning
|
|
24
|
+
? `Humaans API key configured successfully. Note: ${result.warning}`
|
|
25
|
+
: 'Humaans API key configured successfully! You can now use list_humaans_people to browse your team.';
|
|
26
|
+
return JSON.stringify({ ok: true, message });
|
|
27
|
+
}
|
|
28
|
+
// Bridge returned failure — surface as error, do NOT fall through
|
|
29
|
+
throw new HumaansError(result.error || 'Bridge configuration failed', 'BRIDGE_ERROR', 'The host app bridge rejected the configuration request. Check the host app logs.');
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof HumaansError)
|
|
33
|
+
throw error;
|
|
34
|
+
// Bridge request failed (network, timeout, etc.) — surface as error
|
|
35
|
+
throw new HumaansError(`Bridge request failed: ${error instanceof Error ? error.message : String(error)}`, 'BRIDGE_ERROR', 'Could not reach the host app bridge. Ensure the host app is running.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// No bridge configured — configure in-memory only
|
|
39
|
+
setApiKey(trimmedKey);
|
|
40
|
+
return JSON.stringify({
|
|
41
|
+
ok: true,
|
|
42
|
+
message: 'Humaans API key configured successfully! You can now use list_humaans_people to browse your team.',
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=configure.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerConfigureTools } from './configure.js';
|
|
2
|
+
export { registerPeopleTools } from './people.js';
|
|
3
|
+
export { registerJobRoleTools } from './job-roles.js';
|
|
4
|
+
export { registerCompanyTools } from './company.js';
|
|
5
|
+
export { registerTimeAwayTools } from './time-away.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerConfigureTools } from './configure.js';
|
|
2
|
+
export { registerPeopleTools } from './people.js';
|
|
3
|
+
export { registerJobRoleTools } from './job-roles.js';
|
|
4
|
+
export { registerCompanyTools } from './company.js';
|
|
5
|
+
export { registerTimeAwayTools } from './time-away.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { humaansFetch } from '../client.js';
|
|
3
|
+
import { withErrorHandling } from '../utils.js';
|
|
4
|
+
import { isConfigured } from '../auth.js';
|
|
5
|
+
function paginationHint(total, skip, count) {
|
|
6
|
+
if (count >= total)
|
|
7
|
+
return `Showing all ${total} results.`;
|
|
8
|
+
const remaining = total - skip - count;
|
|
9
|
+
return `Showing ${count} of ${total} total (skip=${skip}). ${remaining > 0 ? `Use skip=${skip + count} to see more.` : ''}`;
|
|
10
|
+
}
|
|
11
|
+
function noApiKeyError() {
|
|
12
|
+
return JSON.stringify({
|
|
13
|
+
ok: false,
|
|
14
|
+
error: 'Humaans API key not configured',
|
|
15
|
+
resolution: 'Use configure_humaans_api_key to set your API key first.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function registerJobRoleTools(server) {
|
|
19
|
+
server.registerTool('list_humaans_job_roles', {
|
|
20
|
+
description: `List job role history for employees in Humaans.
|
|
21
|
+
|
|
22
|
+
Each person can have multiple job roles over time. The role with the most recent
|
|
23
|
+
effectiveDate that is not in the future is the current active role.
|
|
24
|
+
|
|
25
|
+
Use the asOf parameter to find what role someone had at a specific date.
|
|
26
|
+
|
|
27
|
+
Example: { "personId": "VMB1yzL5uL8VvNNCJc9rykJz" }
|
|
28
|
+
|
|
29
|
+
RELATED TOOLS:
|
|
30
|
+
- list_humaans_people: Find personId to filter by`,
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
personId: z.string().optional()
|
|
33
|
+
.describe('Filter by person ID (from list_humaans_people)'),
|
|
34
|
+
asOf: z.string().optional()
|
|
35
|
+
.describe('Find the role in effect on this date (YYYY-MM-DD format)'),
|
|
36
|
+
limit: z.number().min(1).max(250).optional()
|
|
37
|
+
.describe('Max results (default 100, max 250)'),
|
|
38
|
+
skip: z.number().min(0).optional()
|
|
39
|
+
.describe('Number of results to skip'),
|
|
40
|
+
}),
|
|
41
|
+
annotations: { readOnlyHint: true },
|
|
42
|
+
}, withErrorHandling(async (args) => {
|
|
43
|
+
if (!isConfigured())
|
|
44
|
+
return noApiKeyError();
|
|
45
|
+
const limit = Math.min(Math.max(args.limit ?? 100, 1), 250);
|
|
46
|
+
const skip = Math.max(args.skip ?? 0, 0);
|
|
47
|
+
const params = new URLSearchParams();
|
|
48
|
+
params.set('$limit', String(limit));
|
|
49
|
+
params.set('$skip', String(skip));
|
|
50
|
+
if (args.personId)
|
|
51
|
+
params.set('personId', args.personId);
|
|
52
|
+
if (args.asOf)
|
|
53
|
+
params.set('$asOf', args.asOf);
|
|
54
|
+
const result = await humaansFetch(`/job-roles?${params.toString()}`);
|
|
55
|
+
const hint = paginationHint(result.total, result.skip, result.data.length);
|
|
56
|
+
return JSON.stringify({
|
|
57
|
+
ok: true,
|
|
58
|
+
jobRoles: result.data,
|
|
59
|
+
count: result.data.length,
|
|
60
|
+
total: result.total,
|
|
61
|
+
pagination: hint,
|
|
62
|
+
});
|
|
63
|
+
}));
|
|
64
|
+
server.registerTool('get_humaans_job_role', {
|
|
65
|
+
description: `Get a specific job role by ID from Humaans.
|
|
66
|
+
|
|
67
|
+
Returns: job title, department, manager (reportingTo), effectiveDate, endDate, note.
|
|
68
|
+
|
|
69
|
+
Example: { "jobRoleId": "hmA5GnUq9ojK86LLKKWbiuKG" }`,
|
|
70
|
+
inputSchema: z.object({
|
|
71
|
+
jobRoleId: z.string().min(1).describe('The job role ID (from list_humaans_job_roles)'),
|
|
72
|
+
}),
|
|
73
|
+
annotations: { readOnlyHint: true },
|
|
74
|
+
}, withErrorHandling(async (args) => {
|
|
75
|
+
if (!isConfigured())
|
|
76
|
+
return noApiKeyError();
|
|
77
|
+
const jobRole = await humaansFetch(`/job-roles/${encodeURIComponent(args.jobRoleId)}`);
|
|
78
|
+
return JSON.stringify({ ok: true, jobRole });
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=job-roles.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { humaansFetch } from '../client.js';
|
|
3
|
+
import { withErrorHandling } from '../utils.js';
|
|
4
|
+
import { isConfigured } from '../auth.js';
|
|
5
|
+
// Fields to include in compact person list responses (allowlist for security)
|
|
6
|
+
const PERSON_LIST_FIELDS = [
|
|
7
|
+
'id', 'firstName', 'lastName', 'preferredName', 'email',
|
|
8
|
+
'status', 'contractType', 'teams', 'locationId',
|
|
9
|
+
'employmentStartDate', 'employmentEndDate', 'timezone',
|
|
10
|
+
];
|
|
11
|
+
// Fields to strip from full person responses (sensitive data)
|
|
12
|
+
const PERSON_SENSITIVE_FIELDS = [
|
|
13
|
+
'calendarFeedToken', 'taxId', 'taxCode',
|
|
14
|
+
'personalEmail', 'personalPhoneNumber', 'formattedPersonalPhoneNumber',
|
|
15
|
+
'birthday', 'address', 'city', 'state', 'postcode', 'countryCode',
|
|
16
|
+
'profilePhoto', 'profilePhotoId',
|
|
17
|
+
'nationality', 'nationalities', 'gender',
|
|
18
|
+
'dietaryPreference', 'foodAllergies',
|
|
19
|
+
];
|
|
20
|
+
function compactPerson(person) {
|
|
21
|
+
const compact = {};
|
|
22
|
+
for (const field of PERSON_LIST_FIELDS) {
|
|
23
|
+
compact[field] = person[field];
|
|
24
|
+
}
|
|
25
|
+
// Include inline job role info if present
|
|
26
|
+
const jobRole = person.jobRole;
|
|
27
|
+
if (jobRole) {
|
|
28
|
+
compact.jobTitle = jobRole.jobTitle;
|
|
29
|
+
compact.department = jobRole.department;
|
|
30
|
+
}
|
|
31
|
+
return compact;
|
|
32
|
+
}
|
|
33
|
+
function sanitizePerson(person) {
|
|
34
|
+
const sanitized = { ...person };
|
|
35
|
+
for (const field of PERSON_SENSITIVE_FIELDS) {
|
|
36
|
+
delete sanitized[field];
|
|
37
|
+
}
|
|
38
|
+
return sanitized;
|
|
39
|
+
}
|
|
40
|
+
function paginationHint(total, skip, count) {
|
|
41
|
+
if (count >= total)
|
|
42
|
+
return `Showing all ${total} results.`;
|
|
43
|
+
const remaining = total - skip - count;
|
|
44
|
+
return `Showing ${count} of ${total} total (skip=${skip}). ${remaining > 0 ? `Use skip=${skip + count} to see more.` : ''}`;
|
|
45
|
+
}
|
|
46
|
+
function noApiKeyError() {
|
|
47
|
+
return JSON.stringify({
|
|
48
|
+
ok: false,
|
|
49
|
+
error: 'Humaans API key not configured',
|
|
50
|
+
resolution: 'To use Humaans, you need to configure an API access token first.',
|
|
51
|
+
next_step: {
|
|
52
|
+
action: 'Ask the user for their Humaans API token, then call configure_humaans_api_key',
|
|
53
|
+
tool_to_call: 'configure_humaans_api_key',
|
|
54
|
+
tool_parameters: { api_key: '<user_provided_token>' },
|
|
55
|
+
get_key_from: 'https://app.humaans.io/settings/home?tokens=1',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function registerPeopleTools(server) {
|
|
60
|
+
server.registerTool('get_humaans_me', {
|
|
61
|
+
description: `Get the current authenticated user's profile from Humaans.
|
|
62
|
+
|
|
63
|
+
Returns: name, email, job title, department, teams, location, status.
|
|
64
|
+
Use this to get your own personId for other operations (e.g., creating time away).
|
|
65
|
+
|
|
66
|
+
RELATED TOOLS:
|
|
67
|
+
- create_humaans_time_away: Use the returned id as personId
|
|
68
|
+
- list_humaans_time_away: Filter by your personId`,
|
|
69
|
+
inputSchema: z.object({}),
|
|
70
|
+
annotations: { readOnlyHint: true },
|
|
71
|
+
}, withErrorHandling(async () => {
|
|
72
|
+
if (!isConfigured())
|
|
73
|
+
return noApiKeyError();
|
|
74
|
+
const me = await humaansFetch('/me');
|
|
75
|
+
return JSON.stringify({ ok: true, person: sanitizePerson(me) });
|
|
76
|
+
}));
|
|
77
|
+
server.registerTool('list_humaans_people', {
|
|
78
|
+
description: `List employees from Humaans HR.
|
|
79
|
+
|
|
80
|
+
By default returns only active employees. Use status filter for other groups.
|
|
81
|
+
Returns compact summaries: id, name, email, job title, department, teams, location, status.
|
|
82
|
+
|
|
83
|
+
Example: { "status": "active", "team": "Engineering", "limit": 50 }
|
|
84
|
+
|
|
85
|
+
Pagination: Returns up to 'limit' results (default 50, max 250). Use 'skip' for next page.
|
|
86
|
+
|
|
87
|
+
RELATED TOOLS:
|
|
88
|
+
- get_humaans_person: Pass an employee's id to get their full profile
|
|
89
|
+
- list_humaans_job_roles: Pass personId to see job role history`,
|
|
90
|
+
inputSchema: z.object({
|
|
91
|
+
status: z.enum(['active', 'offboarded', 'newHire', 'all']).optional()
|
|
92
|
+
.describe('Filter by employment status. Default: active'),
|
|
93
|
+
email: z.string().optional()
|
|
94
|
+
.describe('Filter by exact work email address'),
|
|
95
|
+
team: z.string().optional()
|
|
96
|
+
.describe('Filter by team name (e.g., "Engineering", "Sales")'),
|
|
97
|
+
limit: z.number().min(1).max(250).optional()
|
|
98
|
+
.describe('Max results per page (default 50, max 250)'),
|
|
99
|
+
skip: z.number().min(0).optional()
|
|
100
|
+
.describe('Number of results to skip (for pagination)'),
|
|
101
|
+
}),
|
|
102
|
+
annotations: { readOnlyHint: true },
|
|
103
|
+
}, withErrorHandling(async (args) => {
|
|
104
|
+
if (!isConfigured())
|
|
105
|
+
return noApiKeyError();
|
|
106
|
+
const limit = Math.min(Math.max(args.limit ?? 50, 1), 250);
|
|
107
|
+
const skip = Math.max(args.skip ?? 0, 0);
|
|
108
|
+
const params = new URLSearchParams();
|
|
109
|
+
params.set('$limit', String(limit));
|
|
110
|
+
params.set('$skip', String(skip));
|
|
111
|
+
if (args.status)
|
|
112
|
+
params.set('status', args.status);
|
|
113
|
+
if (args.email)
|
|
114
|
+
params.set('email', args.email);
|
|
115
|
+
if (args.team)
|
|
116
|
+
params.set('teams', args.team);
|
|
117
|
+
const result = await humaansFetch(`/people?${params.toString()}`);
|
|
118
|
+
const people = result.data.map(compactPerson);
|
|
119
|
+
const hint = paginationHint(result.total, result.skip, people.length);
|
|
120
|
+
return JSON.stringify({
|
|
121
|
+
ok: true,
|
|
122
|
+
people,
|
|
123
|
+
count: people.length,
|
|
124
|
+
total: result.total,
|
|
125
|
+
pagination: hint,
|
|
126
|
+
});
|
|
127
|
+
}));
|
|
128
|
+
server.registerTool('get_humaans_person', {
|
|
129
|
+
description: `Get full employee profile from Humaans by their ID.
|
|
130
|
+
|
|
131
|
+
Returns detailed profile including: name, email, job role, department, teams,
|
|
132
|
+
location, employment dates, contract type, working days, manager, bio, social links.
|
|
133
|
+
Sensitive fields (tax ID, personal email, home address) are redacted for privacy.
|
|
134
|
+
|
|
135
|
+
Example: { "personId": "VMB1yzL5uL8VvNNCJc9rykJz" }
|
|
136
|
+
|
|
137
|
+
WORKFLOW - To find a person:
|
|
138
|
+
1. Call list_humaans_people to search (filter by email or team)
|
|
139
|
+
2. Use the person's id from the results here`,
|
|
140
|
+
inputSchema: z.object({
|
|
141
|
+
personId: z.string().min(1).describe('The person ID (from list_humaans_people)'),
|
|
142
|
+
}),
|
|
143
|
+
annotations: { readOnlyHint: true },
|
|
144
|
+
}, withErrorHandling(async (args) => {
|
|
145
|
+
if (!isConfigured())
|
|
146
|
+
return noApiKeyError();
|
|
147
|
+
const person = await humaansFetch(`/people/${encodeURIComponent(args.personId)}`);
|
|
148
|
+
return JSON.stringify({ ok: true, person: sanitizePerson(person) });
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=people.js.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { humaansFetch } from '../client.js';
|
|
3
|
+
import { withErrorHandling } from '../utils.js';
|
|
4
|
+
import { isConfigured } from '../auth.js';
|
|
5
|
+
function paginationHint(total, skip, count) {
|
|
6
|
+
if (count >= total)
|
|
7
|
+
return `Showing all ${total} results.`;
|
|
8
|
+
const remaining = total - skip - count;
|
|
9
|
+
return `Showing ${count} of ${total} total (skip=${skip}). ${remaining > 0 ? `Use skip=${skip + count} to see more.` : ''}`;
|
|
10
|
+
}
|
|
11
|
+
function noApiKeyError() {
|
|
12
|
+
return JSON.stringify({
|
|
13
|
+
ok: false,
|
|
14
|
+
error: 'Humaans API key not configured',
|
|
15
|
+
resolution: 'Use configure_humaans_api_key to set your API key first.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function registerTimeAwayTools(server) {
|
|
19
|
+
server.registerTool('list_humaans_time_away', {
|
|
20
|
+
description: `List time away (PTO, sick leave, etc.) entries from Humaans.
|
|
21
|
+
|
|
22
|
+
Filter by person, date range, or approval status. Returns: dates, type, days count, approval status, notes.
|
|
23
|
+
|
|
24
|
+
Example: { "personId": "VMB1yzL5uL8VvNNCJc9rykJz" }
|
|
25
|
+
Example: { "startDateAfter": "2024-01-01", "startDateBefore": "2024-12-31" }
|
|
26
|
+
|
|
27
|
+
RELATED TOOLS:
|
|
28
|
+
- list_humaans_people: Find personId to filter by
|
|
29
|
+
- list_humaans_time_away_types: See available time away types
|
|
30
|
+
- create_humaans_time_away: Request new time away`,
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
personId: z.string().optional()
|
|
33
|
+
.describe('Filter by person ID'),
|
|
34
|
+
startDateAfter: z.string().optional()
|
|
35
|
+
.describe('Only entries starting after this date (YYYY-MM-DD)'),
|
|
36
|
+
startDateBefore: z.string().optional()
|
|
37
|
+
.describe('Only entries starting before this date (YYYY-MM-DD)'),
|
|
38
|
+
requestStatus: z.enum(['pending', 'approved', 'declined']).optional()
|
|
39
|
+
.describe('Filter by approval status'),
|
|
40
|
+
limit: z.number().min(1).max(250).optional()
|
|
41
|
+
.describe('Max results (default 50, max 250)'),
|
|
42
|
+
skip: z.number().min(0).optional()
|
|
43
|
+
.describe('Number of results to skip'),
|
|
44
|
+
}),
|
|
45
|
+
annotations: { readOnlyHint: true },
|
|
46
|
+
}, withErrorHandling(async (args) => {
|
|
47
|
+
if (!isConfigured())
|
|
48
|
+
return noApiKeyError();
|
|
49
|
+
const limit = Math.min(Math.max(args.limit ?? 50, 1), 250);
|
|
50
|
+
const skip = Math.max(args.skip ?? 0, 0);
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
params.set('$limit', String(limit));
|
|
53
|
+
params.set('$skip', String(skip));
|
|
54
|
+
if (args.personId)
|
|
55
|
+
params.set('personId', args.personId);
|
|
56
|
+
if (args.requestStatus)
|
|
57
|
+
params.set('requestStatus', args.requestStatus);
|
|
58
|
+
if (args.startDateAfter)
|
|
59
|
+
params.set('startDate[$gte]', args.startDateAfter);
|
|
60
|
+
if (args.startDateBefore)
|
|
61
|
+
params.set('startDate[$lte]', args.startDateBefore);
|
|
62
|
+
const result = await humaansFetch(`/time-away?${params.toString()}`);
|
|
63
|
+
const hint = paginationHint(result.total, result.skip, result.data.length);
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
ok: true,
|
|
66
|
+
timeAway: result.data,
|
|
67
|
+
count: result.data.length,
|
|
68
|
+
total: result.total,
|
|
69
|
+
pagination: hint,
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
server.registerTool('create_humaans_time_away', {
|
|
73
|
+
description: `Create a time away request in Humaans (PTO, sick leave, etc.).
|
|
74
|
+
|
|
75
|
+
WORKFLOW - To request time off:
|
|
76
|
+
1. Call get_humaans_me to get your personId
|
|
77
|
+
2. Call list_humaans_time_away_types to find the timeAwayTypeId (e.g., "Paid time off")
|
|
78
|
+
3. Call this tool with the details
|
|
79
|
+
|
|
80
|
+
Dates must be in YYYY-MM-DD format. Use startPeriod/endPeriod for half days.
|
|
81
|
+
|
|
82
|
+
COMMON MISTAKES:
|
|
83
|
+
- Don't guess timeAwayTypeId - always get it from list_humaans_time_away_types first
|
|
84
|
+
- Dates must be YYYY-MM-DD, not ISO datetime with time/timezone`,
|
|
85
|
+
inputSchema: z.object({
|
|
86
|
+
personId: z.string().min(1)
|
|
87
|
+
.describe('Person ID (from get_humaans_me or list_humaans_people)'),
|
|
88
|
+
startDate: z.string().min(1)
|
|
89
|
+
.describe('First day of time away (YYYY-MM-DD)'),
|
|
90
|
+
endDate: z.string().min(1)
|
|
91
|
+
.describe('Last day of time away (YYYY-MM-DD)'),
|
|
92
|
+
timeAwayTypeId: z.string().min(1)
|
|
93
|
+
.describe('Type ID (from list_humaans_time_away_types)'),
|
|
94
|
+
startPeriod: z.enum(['full', 'am', 'pm']).optional()
|
|
95
|
+
.describe('full (whole day), am (morning off), pm (afternoon off). Default: full'),
|
|
96
|
+
endPeriod: z.enum(['full', 'am']).optional()
|
|
97
|
+
.describe('full (whole day) or am (morning only). Default: full'),
|
|
98
|
+
note: z.string().optional()
|
|
99
|
+
.describe('Optional note (visible to employee, manager, admins)'),
|
|
100
|
+
}),
|
|
101
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
102
|
+
}, withErrorHandling(async (args) => {
|
|
103
|
+
if (!isConfigured())
|
|
104
|
+
return noApiKeyError();
|
|
105
|
+
const body = {
|
|
106
|
+
personId: args.personId,
|
|
107
|
+
startDate: args.startDate,
|
|
108
|
+
endDate: args.endDate,
|
|
109
|
+
timeAwayTypeId: args.timeAwayTypeId,
|
|
110
|
+
};
|
|
111
|
+
if (args.startPeriod)
|
|
112
|
+
body.startPeriod = args.startPeriod;
|
|
113
|
+
if (args.endPeriod)
|
|
114
|
+
body.endPeriod = args.endPeriod;
|
|
115
|
+
if (args.note)
|
|
116
|
+
body.note = args.note;
|
|
117
|
+
const created = await humaansFetch('/time-away', {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
body: JSON.stringify(body),
|
|
120
|
+
});
|
|
121
|
+
return JSON.stringify({ ok: true, message: 'Time away request created.', timeAway: created });
|
|
122
|
+
}));
|
|
123
|
+
server.registerTool('list_humaans_time_away_types', {
|
|
124
|
+
description: `List available time away types in Humaans.
|
|
125
|
+
|
|
126
|
+
Returns type names and IDs (e.g., "Paid time off", "Sick leave", "Working from home").
|
|
127
|
+
You need the type ID to create a time away request.
|
|
128
|
+
|
|
129
|
+
RELATED TOOLS:
|
|
130
|
+
- create_humaans_time_away: Use the returned id as timeAwayTypeId`,
|
|
131
|
+
inputSchema: z.object({
|
|
132
|
+
limit: z.number().min(1).max(250).optional()
|
|
133
|
+
.describe('Max results (default 100, max 250)'),
|
|
134
|
+
skip: z.number().min(0).optional()
|
|
135
|
+
.describe('Number of results to skip'),
|
|
136
|
+
}),
|
|
137
|
+
annotations: { readOnlyHint: true },
|
|
138
|
+
}, withErrorHandling(async (args) => {
|
|
139
|
+
if (!isConfigured())
|
|
140
|
+
return noApiKeyError();
|
|
141
|
+
const limit = Math.min(Math.max(args.limit ?? 100, 1), 250);
|
|
142
|
+
const skip = Math.max(args.skip ?? 0, 0);
|
|
143
|
+
const params = new URLSearchParams();
|
|
144
|
+
params.set('$limit', String(limit));
|
|
145
|
+
params.set('$skip', String(skip));
|
|
146
|
+
const result = await humaansFetch(`/time-away-types?${params.toString()}`);
|
|
147
|
+
const hint = paginationHint(result.total, result.skip, result.data.length);
|
|
148
|
+
return JSON.stringify({
|
|
149
|
+
ok: true,
|
|
150
|
+
timeAwayTypes: result.data,
|
|
151
|
+
count: result.data.length,
|
|
152
|
+
total: result.total,
|
|
153
|
+
pagination: hint,
|
|
154
|
+
});
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=time-away.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const REQUEST_TIMEOUT_MS = 30000;
|
|
2
|
+
export declare const HUMAANS_API_BASE = "https://app.humaans.io/api";
|
|
3
|
+
export interface BridgeState {
|
|
4
|
+
port: number;
|
|
5
|
+
token: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class HumaansError extends Error {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly resolution: string;
|
|
10
|
+
constructor(message: string, code: string, resolution: string);
|
|
11
|
+
}
|
|
12
|
+
export interface HumaansListResponse<T> {
|
|
13
|
+
total: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
skip: number;
|
|
16
|
+
data: T[];
|
|
17
|
+
}
|
|
18
|
+
export interface HumaansErrorResponse {
|
|
19
|
+
id?: string;
|
|
20
|
+
code: number;
|
|
21
|
+
name: string;
|
|
22
|
+
message: string;
|
|
23
|
+
issues?: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
forbidden?: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compact person representation for list responses (allowlist for security).
|
|
31
|
+
*/
|
|
32
|
+
export interface PersonCompact {
|
|
33
|
+
id: string;
|
|
34
|
+
firstName: string;
|
|
35
|
+
lastName: string;
|
|
36
|
+
preferredName: string | null;
|
|
37
|
+
email: string;
|
|
38
|
+
status: string;
|
|
39
|
+
contractType: string | null;
|
|
40
|
+
teams: Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
}>;
|
|
43
|
+
locationId: string | null;
|
|
44
|
+
jobTitle?: string;
|
|
45
|
+
department?: string;
|
|
46
|
+
employmentStartDate: string | null;
|
|
47
|
+
employmentEndDate: string | null;
|
|
48
|
+
timezone: string | null;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const REQUEST_TIMEOUT_MS = 30_000;
|
|
2
|
+
export const HUMAANS_API_BASE = 'https://app.humaans.io/api';
|
|
3
|
+
export class HumaansError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
resolution;
|
|
6
|
+
constructor(message, code, resolution) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.resolution = resolution;
|
|
10
|
+
this.name = 'HumaansError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
type ToolHandler<T> = (args: T, extra: unknown) => Promise<CallToolResult>;
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a tool handler with standard error handling.
|
|
5
|
+
*
|
|
6
|
+
* - On success: returns the string result as a text content block.
|
|
7
|
+
* - On HumaansError: returns a structured JSON error with code and resolution.
|
|
8
|
+
* - On unknown error: returns a generic error message.
|
|
9
|
+
*
|
|
10
|
+
* Secrets are never exposed in error messages.
|
|
11
|
+
*/
|
|
12
|
+
export declare function withErrorHandling<T>(fn: (args: T, extra: unknown) => Promise<string>): ToolHandler<T>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HumaansError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a tool handler with standard error handling.
|
|
4
|
+
*
|
|
5
|
+
* - On success: returns the string result as a text content block.
|
|
6
|
+
* - On HumaansError: returns a structured JSON error with code and resolution.
|
|
7
|
+
* - On unknown error: returns a generic error message.
|
|
8
|
+
*
|
|
9
|
+
* Secrets are never exposed in error messages.
|
|
10
|
+
*/
|
|
11
|
+
export function withErrorHandling(fn) {
|
|
12
|
+
return async (args, extra) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = await fn(args, extra);
|
|
15
|
+
return { content: [{ type: 'text', text: result }] };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof HumaansError) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
ok: false,
|
|
25
|
+
error: error.message,
|
|
26
|
+
code: error.code,
|
|
27
|
+
resolution: error.resolution,
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, error: errorMessage }) }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=utils.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindstone-engineering/mcp-server-humaans",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Humaans HR platform MCP server for Model Context Protocol hosts",
|
|
5
|
+
"license": "FSL-1.1-MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-server-humaans": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.map"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/nspr-io/mcp-servers.git",
|
|
17
|
+
"directory": "connectors/humaans"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/nspr-io/mcp-servers/tree/main/connectors/humaans",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc && shx chmod +x dist/index.js",
|
|
25
|
+
"prepare": "npm run build",
|
|
26
|
+
"watch": "tsc --watch",
|
|
27
|
+
"start": "node dist/index.js",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
34
|
+
"zod": "^3.23.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@mindstone-engineering/mcp-test-harness": "file:../../test-harness",
|
|
38
|
+
"@types/node": "^22",
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.3",
|
|
40
|
+
"msw": "^2.13.2",
|
|
41
|
+
"shx": "^0.3.4",
|
|
42
|
+
"typescript": "^5.8.2",
|
|
43
|
+
"vitest": "^4.1.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
}
|
|
48
|
+
}
|