@haimkastner/workforce-ai-mcp 1.0.0-rc.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/LICENSE +21 -0
- package/README.md +135 -0
- package/dist/core/consts.d.ts +9 -0
- package/dist/core/consts.js +9 -0
- package/dist/core/session.d.ts +29 -0
- package/dist/core/session.js +85 -0
- package/dist/core/streamable-http.d.ts +9 -0
- package/dist/core/streamable-http.js +107 -0
- package/dist/core/utils.d.ts +5 -0
- package/dist/core/utils.js +35 -0
- package/dist/executer/executer.d.ts +19 -0
- package/dist/executer/executer.js +104 -0
- package/dist/executer/validator.d.ts +5 -0
- package/dist/executer/validator.js +19 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +121 -0
- package/dist/tool-filter.d.ts +12 -0
- package/dist/tool-filter.js +54 -0
- package/dist/tools/tools.g.d.ts +11 -0
- package/dist/tools/tools.g.js +3933 -0
- package/dist/types/types.d.ts +46 -0
- package/dist/types/types.js +8 -0
- package/openapi-mcp.json +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Check Point Software Technologies Ltd.
|
|
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,135 @@
|
|
|
1
|
+
# Check Point - Workforce AI MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://github.com/CheckPointSW/workforce-ai-mcp/blob/release/LICENSE) [](https://www.npmjs.com/package/@haimkastner/workforce-ai-mcp)
|
|
4
|
+
|
|
5
|
+
<!-- TODO: Remove this disclaimer before GA release -->
|
|
6
|
+
> **Pre-release disclaimer:** This package is currently in release candidate (RC) stage and is intended for testing and evaluation purposes only. APIs and tool definitions may change before the stable release. Do not use in production environments.
|
|
7
|
+
|
|
8
|
+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that exposes Check Point Workforce AI capabilities as LLM tools — enabling AI assistants to query, analyze, and manage AI & Browse security policies, assets, and applications through natural language.
|
|
9
|
+
|
|
10
|
+
## Getting started
|
|
11
|
+
|
|
12
|
+
### Obtaining API credentials
|
|
13
|
+
|
|
14
|
+
1. Go to the [Infinity Portal API Keys page](https://portal.checkpoint.com/dashboard/settings/api-keys).
|
|
15
|
+
2. Click **New** > **New Account API Key**.
|
|
16
|
+
3. In the **Service** dropdown select **Workforce AI Security** (and for Browse, **Browse Security**) and create the key.
|
|
17
|
+
4. Copy the **Client ID**, **Secret Key**, and **Authentication URL** (gateway).
|
|
18
|
+
|
|
19
|
+
For more information, see [Infinity Portal Administration Guide](https://sc1.checkpoint.com/documents/Infinity_Portal/WebAdminGuides/EN/Infinity-Portal-Admin-Guide/Content/Topics-Infinity-Portal/API-Keys.htm?tocpath=Global%20Settings%7C_____7#API_Keys).
|
|
20
|
+
|
|
21
|
+
### Available gateways
|
|
22
|
+
|
|
23
|
+
| Region | Gateway URL |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Europe | `https://cloudinfra-gw.portal.checkpoint.com` |
|
|
26
|
+
| United States | `https://cloudinfra-gw-us.portal.checkpoint.com` |
|
|
27
|
+
|
|
28
|
+
### Environment variables
|
|
29
|
+
|
|
30
|
+
| Variable | Required | Description |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `CP_CI_CLIENT_ID` | Yes | CloudInfra API key client ID |
|
|
33
|
+
| `CP_CI_ACCESS_KEY` | Yes | CloudInfra API key secret |
|
|
34
|
+
| `CP_CI_GATEWAY` | Yes | CloudInfra gateway URL |
|
|
35
|
+
| `MCP_MODE` | Yes | Transport mode: `stdio` or `http` |
|
|
36
|
+
| `PORT` | When `http` | HTTP server port |
|
|
37
|
+
| `WRITE_MODE` | No | Set to `true` to enable write tools (default: `false`). <br/>**Warning:** enabling write mode allows the LLM to create, modify, and delete security policy rules. Use with caution. |
|
|
38
|
+
|
|
39
|
+
### Running with stdio transport
|
|
40
|
+
|
|
41
|
+
Use stdio mode when connecting directly from an MCP client such as Claude Desktop, VS Code, or Cursor:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
CP_CI_CLIENT_ID="your-client-id" \
|
|
45
|
+
CP_CI_ACCESS_KEY="your-access-key" \
|
|
46
|
+
CP_CI_GATEWAY="https://cloudinfra-gw-us.portal.checkpoint.com" \
|
|
47
|
+
MCP_MODE=stdio \
|
|
48
|
+
npx @haimkastner/workforce-ai-mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Claude Desktop configuration
|
|
52
|
+
|
|
53
|
+
Add to your `claude_desktop_config.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"workforce-ai": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["@haimkastner/workforce-ai-mcp"],
|
|
61
|
+
"env": {
|
|
62
|
+
"CP_CI_CLIENT_ID": "your-client-id",
|
|
63
|
+
"CP_CI_ACCESS_KEY": "your-access-key",
|
|
64
|
+
"CP_CI_GATEWAY": "https://cloudinfra-gw-us.portal.checkpoint.com",
|
|
65
|
+
"MCP_MODE": "stdio"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Running with HTTP transport
|
|
73
|
+
|
|
74
|
+
Use HTTP mode when running the server as a standalone service:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
CP_CI_CLIENT_ID="your-client-id" \
|
|
78
|
+
CP_CI_ACCESS_KEY="your-access-key" \
|
|
79
|
+
CP_CI_GATEWAY="https://cloudinfra-gw-us.portal.checkpoint.com" \
|
|
80
|
+
MCP_MODE=http \
|
|
81
|
+
PORT=3000 \
|
|
82
|
+
npx @haimkastner/workforce-ai-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The server exposes:
|
|
86
|
+
- `POST /mcp` — MCP StreamableHTTP endpoint
|
|
87
|
+
- `GET /health` — Health check
|
|
88
|
+
|
|
89
|
+
## Capabilities
|
|
90
|
+
|
|
91
|
+
### Read mode (default)
|
|
92
|
+
|
|
93
|
+
By default, the server starts in **read-only mode**, exposing tools for querying and analyzing policies without making any changes. This is safe for exploration and auditing.
|
|
94
|
+
|
|
95
|
+
#### Policy inspection
|
|
96
|
+
- **List rulebases** — View all rules for Chats (GenAI DLP), AI Access, Web Access, Agents, Secure Browsing, and DLP policies
|
|
97
|
+
- **Analyze shadow rules** — Detect rules that are shadowed (never matched) by higher-priority rules
|
|
98
|
+
- **Simulate policy matching** — Given a user and target, resolve which rule in the rulebase would apply
|
|
99
|
+
|
|
100
|
+
#### Assets and users
|
|
101
|
+
- **Search assets** — Find managed assets by name or attributes
|
|
102
|
+
- **Count assets** — Get asset counts with optional filters
|
|
103
|
+
- **Search users** — Look up users and groups in the organization
|
|
104
|
+
|
|
105
|
+
#### Applications and data types
|
|
106
|
+
- **Search apps** — Search the GenAI application catalog by name, description, or URL
|
|
107
|
+
- **Get apps by ID** — Retrieve application details by their IDs
|
|
108
|
+
- **List DLP data types** — Browse predefined and custom DLP data types
|
|
109
|
+
- **Get tenant DLP data types** — View data types configured for the tenant
|
|
110
|
+
|
|
111
|
+
#### Policy objects
|
|
112
|
+
- **List domain objects** — View domain-based policy objects
|
|
113
|
+
- **List file protection objects** — View file protection configurations
|
|
114
|
+
|
|
115
|
+
### Write mode
|
|
116
|
+
|
|
117
|
+
To enable write operations, set `WRITE_MODE=true`. This unlocks tools that modify the policy configuration:
|
|
118
|
+
|
|
119
|
+
#### Rule management
|
|
120
|
+
- **Create rules** — Create new Chats, AI Access, Agents, DLP, and Secure Browsing rules with full policy configuration including actions, services, data types, and user/group assignments
|
|
121
|
+
- **Edit rules** — Update rule name, description, and other properties
|
|
122
|
+
- **Activate / deactivate rules** — Toggle rules on or off
|
|
123
|
+
- **Reorder rules** — Change rule priority in the rulebase
|
|
124
|
+
- **Delete rules** — Permanently remove rules from the rulebase
|
|
125
|
+
|
|
126
|
+
## Available tools
|
|
127
|
+
|
|
128
|
+
For a full list of all available tools with descriptions and access modes, see [TOOLS.md](TOOLS.md).
|
|
129
|
+
|
|
130
|
+
## Report Bug
|
|
131
|
+
|
|
132
|
+
In case of an issue or a bug found in the MCP server, please open an [issue](https://github.com/CheckPointSW/workforce-ai-mcp/issues).
|
|
133
|
+
|
|
134
|
+
## Contributors
|
|
135
|
+
- Haim Kastner - [haimk@checkpoint.com](mailto:haimk@checkpoint.com)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server configuration constants
|
|
3
|
+
*/
|
|
4
|
+
export declare const SERVER_NAME = "workforce-ai";
|
|
5
|
+
export declare const SERVER_VERSION = "1.0.0";
|
|
6
|
+
/** Grace period (seconds) before token expiry to trigger refresh */
|
|
7
|
+
export declare const KEEP_ALIVE_GRACE_SECONDS = 30;
|
|
8
|
+
/** Auth endpoint path on CloudInfra gateway */
|
|
9
|
+
export declare const CI_AUTH_PATH = "/auth/external";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server configuration constants
|
|
3
|
+
*/
|
|
4
|
+
export const SERVER_NAME = 'workforce-ai';
|
|
5
|
+
export const SERVER_VERSION = '1.0.0';
|
|
6
|
+
/** Grace period (seconds) before token expiry to trigger refresh */
|
|
7
|
+
export const KEEP_ALIVE_GRACE_SECONDS = 30;
|
|
8
|
+
/** Auth endpoint path on CloudInfra gateway */
|
|
9
|
+
export const CI_AUTH_PATH = '/auth/external';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudInfra JWT session management.
|
|
3
|
+
*
|
|
4
|
+
* Handles login via /auth/external, token storage, and automatic refresh
|
|
5
|
+
* before expiry.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SessionManager {
|
|
8
|
+
private jwtToken;
|
|
9
|
+
private tokenExpiresIn;
|
|
10
|
+
private refreshTimer;
|
|
11
|
+
private gateway;
|
|
12
|
+
private clientId;
|
|
13
|
+
private accessKey;
|
|
14
|
+
/**
|
|
15
|
+
* The gateway base URL (protocol + host only).
|
|
16
|
+
* API calls are proxied through this gateway.
|
|
17
|
+
*/
|
|
18
|
+
get baseUrl(): string;
|
|
19
|
+
/** Current JWT token. */
|
|
20
|
+
get authToken(): string;
|
|
21
|
+
/** Connect and obtain initial JWT token. */
|
|
22
|
+
connect(gateway: string, clientId: string, accessKey: string): Promise<void>;
|
|
23
|
+
/** Disconnect and clear token. */
|
|
24
|
+
disconnect(): void;
|
|
25
|
+
private performLogin;
|
|
26
|
+
private scheduleRefresh;
|
|
27
|
+
}
|
|
28
|
+
/** Shared singleton session manager instance */
|
|
29
|
+
export declare const sessionManager: SessionManager;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudInfra JWT session management.
|
|
3
|
+
*
|
|
4
|
+
* Handles login via /auth/external, token storage, and automatic refresh
|
|
5
|
+
* before expiry.
|
|
6
|
+
*/
|
|
7
|
+
import { CI_AUTH_PATH, KEEP_ALIVE_GRACE_SECONDS } from './consts.js';
|
|
8
|
+
export class SessionManager {
|
|
9
|
+
jwtToken = '';
|
|
10
|
+
tokenExpiresIn = 0;
|
|
11
|
+
refreshTimer;
|
|
12
|
+
gateway = '';
|
|
13
|
+
clientId = '';
|
|
14
|
+
accessKey = '';
|
|
15
|
+
/**
|
|
16
|
+
* The gateway base URL (protocol + host only).
|
|
17
|
+
* API calls are proxied through this gateway.
|
|
18
|
+
*/
|
|
19
|
+
get baseUrl() {
|
|
20
|
+
return this.gateway;
|
|
21
|
+
}
|
|
22
|
+
/** Current JWT token. */
|
|
23
|
+
get authToken() {
|
|
24
|
+
if (!this.jwtToken) {
|
|
25
|
+
throw new Error('JWT token is not set. Please connect first.');
|
|
26
|
+
}
|
|
27
|
+
return this.jwtToken;
|
|
28
|
+
}
|
|
29
|
+
/** Connect and obtain initial JWT token. */
|
|
30
|
+
async connect(gateway, clientId, accessKey) {
|
|
31
|
+
// Normalize gateway to protocol + host only
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL(gateway);
|
|
34
|
+
this.gateway = url.origin;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.gateway = gateway;
|
|
38
|
+
}
|
|
39
|
+
this.clientId = clientId;
|
|
40
|
+
this.accessKey = accessKey;
|
|
41
|
+
await this.performLogin();
|
|
42
|
+
this.scheduleRefresh();
|
|
43
|
+
console.error(`[auth] Connected to ${this.gateway}, token expires in ${this.tokenExpiresIn}s`);
|
|
44
|
+
}
|
|
45
|
+
/** Disconnect and clear token. */
|
|
46
|
+
disconnect() {
|
|
47
|
+
this.jwtToken = '';
|
|
48
|
+
this.tokenExpiresIn = 0;
|
|
49
|
+
if (this.refreshTimer) {
|
|
50
|
+
clearTimeout(this.refreshTimer);
|
|
51
|
+
this.refreshTimer = undefined;
|
|
52
|
+
}
|
|
53
|
+
console.error('[auth] Disconnected');
|
|
54
|
+
}
|
|
55
|
+
async performLogin() {
|
|
56
|
+
const authUrl = `${this.gateway}${CI_AUTH_PATH}`;
|
|
57
|
+
const response = await fetch(authUrl, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ clientId: this.clientId, accessKey: this.accessKey }),
|
|
61
|
+
});
|
|
62
|
+
const body = (await response.json());
|
|
63
|
+
if (!response.ok || !body.success) {
|
|
64
|
+
throw new Error(`CI login failed (${response.status}): ${JSON.stringify(body)}`);
|
|
65
|
+
}
|
|
66
|
+
this.jwtToken = body.data.token;
|
|
67
|
+
this.tokenExpiresIn = body.data.expiresIn ?? 1800;
|
|
68
|
+
}
|
|
69
|
+
scheduleRefresh() {
|
|
70
|
+
const delaySeconds = Math.max(this.tokenExpiresIn - KEEP_ALIVE_GRACE_SECONDS, 1);
|
|
71
|
+
this.refreshTimer = setTimeout(async () => {
|
|
72
|
+
try {
|
|
73
|
+
console.error('[auth] Refreshing token...');
|
|
74
|
+
await this.performLogin();
|
|
75
|
+
this.scheduleRefresh();
|
|
76
|
+
console.error(`[auth] Token refreshed, expires in ${this.tokenExpiresIn}s`);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error(`[auth] Token refresh failed: ${err}`);
|
|
80
|
+
}
|
|
81
|
+
}, delaySeconds * 1000);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Shared singleton session manager instance */
|
|
85
|
+
export const sessionManager = new SessionManager();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamableHTTP server setup for HTTP-based MCP communication using Hono
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Sets up a web server for the MCP server using StreamableHTTP transport
|
|
8
|
+
*/
|
|
9
|
+
export declare function setupStreamableHttpServer(server: Server, port?: number): Promise<Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamableHTTP server setup for HTTP-based MCP communication using Hono
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
import { cors } from 'hono/cors';
|
|
6
|
+
import { serve } from '@hono/node-server';
|
|
7
|
+
import { v4 as uuid } from 'uuid';
|
|
8
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
9
|
+
import { InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import { toReqRes, toFetchResponse } from 'fetch-to-node';
|
|
11
|
+
import { SERVER_NAME, SERVER_VERSION } from './consts.js';
|
|
12
|
+
const SESSION_ID_HEADER_NAME = 'mcp-session-id';
|
|
13
|
+
const JSON_RPC = '2.0';
|
|
14
|
+
class MCPStreamableHttpServer {
|
|
15
|
+
server;
|
|
16
|
+
transports = {};
|
|
17
|
+
constructor(server) {
|
|
18
|
+
this.server = server;
|
|
19
|
+
}
|
|
20
|
+
async handleGetRequest(c) {
|
|
21
|
+
console.error('GET request received - StreamableHTTP transport only supports POST');
|
|
22
|
+
return c.text('Method Not Allowed', 405, { Allow: 'POST' });
|
|
23
|
+
}
|
|
24
|
+
async handlePostRequest(c) {
|
|
25
|
+
const sessionId = c.req.header(SESSION_ID_HEADER_NAME);
|
|
26
|
+
console.error(`POST request received ${sessionId ? 'with session ID: ' + sessionId : 'without session ID'}`);
|
|
27
|
+
try {
|
|
28
|
+
const body = await c.req.json();
|
|
29
|
+
const { req, res } = toReqRes(c.req.raw);
|
|
30
|
+
// Reuse existing transport if we have a session ID
|
|
31
|
+
if (sessionId && this.transports[sessionId]) {
|
|
32
|
+
const transport = this.transports[sessionId];
|
|
33
|
+
await transport.handleRequest(req, res, body);
|
|
34
|
+
res.on('close', () => {
|
|
35
|
+
console.error(`Request closed for session ${sessionId}`);
|
|
36
|
+
});
|
|
37
|
+
return toFetchResponse(res);
|
|
38
|
+
}
|
|
39
|
+
// Create new transport for initialize requests
|
|
40
|
+
if (!sessionId && this.isInitializeRequest(body)) {
|
|
41
|
+
console.error('Creating new StreamableHTTP transport for initialize request');
|
|
42
|
+
const transport = new StreamableHTTPServerTransport({
|
|
43
|
+
sessionIdGenerator: () => uuid(),
|
|
44
|
+
});
|
|
45
|
+
transport.onerror = (err) => {
|
|
46
|
+
console.error('StreamableHTTP transport error:', err);
|
|
47
|
+
};
|
|
48
|
+
await this.server.connect(transport);
|
|
49
|
+
await transport.handleRequest(req, res, body);
|
|
50
|
+
const newSessionId = transport.sessionId;
|
|
51
|
+
if (newSessionId) {
|
|
52
|
+
console.error(`New session established: ${newSessionId}`);
|
|
53
|
+
this.transports[newSessionId] = transport;
|
|
54
|
+
transport.onclose = () => {
|
|
55
|
+
console.error(`Session closed: ${newSessionId}`);
|
|
56
|
+
delete this.transports[newSessionId];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
res.on('close', () => {
|
|
60
|
+
console.error(`Request closed for new session`);
|
|
61
|
+
});
|
|
62
|
+
return toFetchResponse(res);
|
|
63
|
+
}
|
|
64
|
+
return c.json(this.createErrorResponse('Bad Request: invalid session ID or method.'), 400);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('Error handling MCP request:', error);
|
|
68
|
+
return c.json(this.createErrorResponse('Internal server error.'), 500);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
createErrorResponse(message) {
|
|
72
|
+
return {
|
|
73
|
+
jsonrpc: JSON_RPC,
|
|
74
|
+
error: { code: -32000, message },
|
|
75
|
+
id: uuid(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
isInitializeRequest(body) {
|
|
79
|
+
const isInitial = (data) => InitializeRequestSchema.safeParse(data).success;
|
|
80
|
+
if (Array.isArray(body)) {
|
|
81
|
+
return body.some((request) => isInitial(request));
|
|
82
|
+
}
|
|
83
|
+
return isInitial(body);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Sets up a web server for the MCP server using StreamableHTTP transport
|
|
88
|
+
*/
|
|
89
|
+
export async function setupStreamableHttpServer(server, port = 9096) {
|
|
90
|
+
const app = new Hono();
|
|
91
|
+
app.use('*', cors());
|
|
92
|
+
const mcpHandler = new MCPStreamableHttpServer(server);
|
|
93
|
+
app.get('/health', (c) => {
|
|
94
|
+
return c.json({ status: 'OK', server: SERVER_NAME, version: SERVER_VERSION });
|
|
95
|
+
});
|
|
96
|
+
app.get('/mcp', (c) => mcpHandler.handleGetRequest(c));
|
|
97
|
+
app.post('/mcp', (c) => mcpHandler.handlePostRequest(c));
|
|
98
|
+
app.get('/*', async (c) => {
|
|
99
|
+
return c.text('Not Found', 404);
|
|
100
|
+
});
|
|
101
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
102
|
+
console.error(`MCP StreamableHTTP Server running at http://localhost:${info.port}`);
|
|
103
|
+
console.error(`- MCP Endpoint: http://localhost:${info.port}/mcp`);
|
|
104
|
+
console.error(`- Health Check: http://localhost:${info.port}/health`);
|
|
105
|
+
});
|
|
106
|
+
return app;
|
|
107
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats API errors for better readability
|
|
3
|
+
*/
|
|
4
|
+
export function formatApiError(error) {
|
|
5
|
+
let message = 'API request failed.';
|
|
6
|
+
if (error.response) {
|
|
7
|
+
message = `API Error: Status ${error.response.status} (${error.response.statusText || 'Status text not available'}). `;
|
|
8
|
+
const responseData = error.response.data;
|
|
9
|
+
const MAX_LEN = 500;
|
|
10
|
+
if (typeof responseData === 'string') {
|
|
11
|
+
message += `Response: ${responseData.substring(0, MAX_LEN)}${responseData.length > MAX_LEN ? '...' : ''}`;
|
|
12
|
+
}
|
|
13
|
+
else if (responseData) {
|
|
14
|
+
try {
|
|
15
|
+
const jsonString = JSON.stringify(responseData);
|
|
16
|
+
message += `Response: ${jsonString.substring(0, MAX_LEN)}${jsonString.length > MAX_LEN ? '...' : ''}`;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
message += 'Response: [Could not serialize data]';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
message += 'No response body received.';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (error.request) {
|
|
27
|
+
message = 'API Network Error: No response received from server.';
|
|
28
|
+
if (error.code)
|
|
29
|
+
message += ` (Code: ${error.code})`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
message += `API Request Setup Error: ${error.message}`;
|
|
33
|
+
}
|
|
34
|
+
return message;
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
3
|
+
import { JsonObject, McpToolDefinition } from '../types/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Executes an API tool with the provided arguments
|
|
6
|
+
*/
|
|
7
|
+
export declare function executeTool(toolName: string, definition: McpToolDefinition, toolArgs: JsonObject): Promise<CallToolResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Builds an Axios request configuration from a tool definition and arguments
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildRequest(definition: McpToolDefinition, toolArgs: JsonObject): AxiosRequestConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Builds a tool result from an API response
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildResult(response: AxiosResponse): CallToolResult;
|
|
16
|
+
/**
|
|
17
|
+
* Builds an error result from an exception
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildErrorResult(error: unknown): CallToolResult;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { validateLLMArgs } from './validator.js';
|
|
3
|
+
import { formatApiError } from '../core/utils.js';
|
|
4
|
+
import { sessionManager } from '../core/session.js';
|
|
5
|
+
/**
|
|
6
|
+
* Executes an API tool with the provided arguments
|
|
7
|
+
*/
|
|
8
|
+
export async function executeTool(toolName, definition, toolArgs) {
|
|
9
|
+
try {
|
|
10
|
+
const validatedArgs = await validateLLMArgs(toolName, definition, toolArgs);
|
|
11
|
+
const request = buildRequest(definition, validatedArgs);
|
|
12
|
+
const response = await axios(request);
|
|
13
|
+
return buildResult(response);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const result = buildErrorResult(error);
|
|
17
|
+
const errMessage = result.content[0].text || 'Unknown error';
|
|
18
|
+
console.error(`Error during execution of tool '${toolName}':`, errMessage);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Builds an Axios request configuration from a tool definition and arguments
|
|
24
|
+
*/
|
|
25
|
+
export function buildRequest(definition, toolArgs) {
|
|
26
|
+
let urlPath = definition.pathTemplate;
|
|
27
|
+
const queryParams = {};
|
|
28
|
+
const headers = { Accept: 'application/json' };
|
|
29
|
+
let requestBodyData = undefined;
|
|
30
|
+
// Apply parameters to URL path, query, or headers
|
|
31
|
+
definition.executionParameters.forEach((param) => {
|
|
32
|
+
const value = toolArgs[param.name];
|
|
33
|
+
if (typeof value !== 'undefined' && value !== null) {
|
|
34
|
+
if (param.in === 'path') {
|
|
35
|
+
urlPath = urlPath.replace(`{${param.name}}`, encodeURIComponent(String(value)));
|
|
36
|
+
}
|
|
37
|
+
else if (param.in === 'query') {
|
|
38
|
+
queryParams[param.name] = value;
|
|
39
|
+
}
|
|
40
|
+
else if (param.in === 'header') {
|
|
41
|
+
headers[param.name.toLowerCase()] = String(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Ensure all path parameters are resolved
|
|
46
|
+
if (urlPath.includes('{')) {
|
|
47
|
+
throw new Error(`Failed to resolve path parameters: ${urlPath}`);
|
|
48
|
+
}
|
|
49
|
+
// Handle request body
|
|
50
|
+
if (definition.requestBodyContentType && typeof toolArgs['requestBody'] !== 'undefined') {
|
|
51
|
+
requestBodyData = toolArgs['requestBody'];
|
|
52
|
+
headers['content-type'] = definition.requestBodyContentType;
|
|
53
|
+
}
|
|
54
|
+
const requestUrl = `${sessionManager.baseUrl}${urlPath}`;
|
|
55
|
+
headers['Authorization'] = `Bearer ${sessionManager.authToken}`;
|
|
56
|
+
return {
|
|
57
|
+
method: definition.method.toUpperCase(),
|
|
58
|
+
url: requestUrl,
|
|
59
|
+
params: queryParams,
|
|
60
|
+
headers,
|
|
61
|
+
...(requestBodyData !== undefined && { data: requestBodyData }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Builds a tool result from an API response
|
|
66
|
+
*/
|
|
67
|
+
export function buildResult(response) {
|
|
68
|
+
let responseText = '';
|
|
69
|
+
if (!response.data) {
|
|
70
|
+
responseText = `(Status: ${response.status} - No body content)`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
try {
|
|
74
|
+
responseText = JSON.stringify(response.data, null, 2);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
responseText = '[Stringify Error]';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: `API Response (Status: ${response.status}):\n${responseText}`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Builds an error result from an exception
|
|
91
|
+
*/
|
|
92
|
+
export function buildErrorResult(error) {
|
|
93
|
+
let errorMessage;
|
|
94
|
+
if (axios.isAxiosError(error)) {
|
|
95
|
+
errorMessage = formatApiError(error);
|
|
96
|
+
}
|
|
97
|
+
else if (error instanceof Error) {
|
|
98
|
+
errorMessage = error.message;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
errorMessage = 'Unexpected error: ' + String(error);
|
|
102
|
+
}
|
|
103
|
+
return { content: [{ type: 'text', text: errorMessage }] };
|
|
104
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { JsonObject, McpToolDefinition } from '../types/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates arguments provided to a tool against its Zod schema
|
|
4
|
+
*/
|
|
5
|
+
export declare function validateLLMArgs(toolName: string, definition: McpToolDefinition, toolArgs: JsonObject): Promise<JsonObject>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ZodError } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Validates arguments provided to a tool against its Zod schema
|
|
4
|
+
*/
|
|
5
|
+
export async function validateLLMArgs(toolName, definition, toolArgs) {
|
|
6
|
+
try {
|
|
7
|
+
const zodSchema = definition.zodValidationSchema;
|
|
8
|
+
const argsToParse = typeof toolArgs === 'object' && toolArgs !== null ? toolArgs : {};
|
|
9
|
+
return zodSchema.parse(argsToParse);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error instanceof ZodError) {
|
|
13
|
+
const validationErrorMessage = `Invalid arguments for tool '${toolName}': ${error.errors.map((e) => `${e.path.join('.')} (${e.code}): ${e.message}`).join(', ')}`;
|
|
14
|
+
throw new Error(validationErrorMessage);
|
|
15
|
+
}
|
|
16
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
17
|
+
throw new Error(`Internal error during validation setup: ${errorMessage}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Workforce AI MCP Server
|
|
4
|
+
*
|
|
5
|
+
* MCP server that exposes Check Point Workforce AI tools for LLM consumption.
|
|
6
|
+
* Supports both stdio and HTTP (StreamableHTTP) transport modes.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* CP_CI_CLIENT_ID - CloudInfra API key client ID (required)
|
|
10
|
+
* CP_CI_ACCESS_KEY - CloudInfra API key secret (required)
|
|
11
|
+
* CP_CI_GATEWAY - CloudInfra gateway URL (required)
|
|
12
|
+
* WRITE_MODE - Enable write tools (default: false)
|
|
13
|
+
* MCP_MODE - Transport mode: "stdio" or "http" (required)
|
|
14
|
+
* PORT - HTTP port (required when MCP_MODE=http)
|
|
15
|
+
*/
|
|
16
|
+
export {};
|