@inkeep/agents-manage-api 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/README.md +176 -0
- package/dist/ManagementServer.d.ts +28 -0
- package/dist/ManagementServer.d.ts.map +1 -0
- package/dist/ManagementServer.js +41 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +26 -0
- package/dist/__tests__/utils/testProject.d.ts +18 -0
- package/dist/__tests__/utils/testProject.d.ts.map +1 -0
- package/dist/__tests__/utils/testProject.js +26 -0
- package/dist/__tests__/utils/testRequest.d.ts +2 -0
- package/dist/__tests__/utils/testRequest.d.ts.map +1 -0
- package/dist/__tests__/utils/testRequest.js +11 -0
- package/dist/__tests__/utils/testTenant.d.ts +64 -0
- package/dist/__tests__/utils/testTenant.d.ts.map +1 -0
- package/dist/__tests__/utils/testTenant.js +71 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +140 -0
- package/dist/data/conversations.d.ts +59 -0
- package/dist/data/conversations.d.ts.map +1 -0
- package/dist/data/conversations.js +216 -0
- package/dist/data/db/clean.d.ts +6 -0
- package/dist/data/db/clean.d.ts.map +1 -0
- package/dist/data/db/clean.js +77 -0
- package/dist/data/db/dbClient.d.ts +3 -0
- package/dist/data/db/dbClient.d.ts.map +1 -0
- package/dist/data/db/dbClient.js +13 -0
- package/dist/data/graphFull.d.ts +11 -0
- package/dist/data/graphFull.d.ts.map +1 -0
- package/dist/data/graphFull.js +90 -0
- package/dist/data/graphFullClient.d.ts +22 -0
- package/dist/data/graphFullClient.d.ts.map +1 -0
- package/dist/data/graphFullClient.js +189 -0
- package/dist/data/tools.d.ts +81 -0
- package/dist/data/tools.d.ts.map +1 -0
- package/dist/data/tools.js +266 -0
- package/dist/env.d.ts +41 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +59 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +32 -0
- package/dist/middleware/auth.d.ts +12 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +36 -0
- package/dist/openapi.d.ts +2 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +38 -0
- package/dist/routes/agentArtifactComponents.d.ts +4 -0
- package/dist/routes/agentArtifactComponents.d.ts.map +1 -0
- package/dist/routes/agentArtifactComponents.js +230 -0
- package/dist/routes/agentDataComponents.d.ts +4 -0
- package/dist/routes/agentDataComponents.d.ts.map +1 -0
- package/dist/routes/agentDataComponents.js +225 -0
- package/dist/routes/agentGraph.d.ts +4 -0
- package/dist/routes/agentGraph.d.ts.map +1 -0
- package/dist/routes/agentGraph.js +289 -0
- package/dist/routes/agentRelations.d.ts +4 -0
- package/dist/routes/agentRelations.d.ts.map +1 -0
- package/dist/routes/agentRelations.js +290 -0
- package/dist/routes/agentToolRelations.d.ts +4 -0
- package/dist/routes/agentToolRelations.d.ts.map +1 -0
- package/dist/routes/agentToolRelations.js +342 -0
- package/dist/routes/agents.d.ts +4 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +213 -0
- package/dist/routes/apiKeys.d.ts +4 -0
- package/dist/routes/apiKeys.d.ts.map +1 -0
- package/dist/routes/apiKeys.js +236 -0
- package/dist/routes/artifactComponents.d.ts +4 -0
- package/dist/routes/artifactComponents.d.ts.map +1 -0
- package/dist/routes/artifactComponents.js +202 -0
- package/dist/routes/contextConfigs.d.ts +4 -0
- package/dist/routes/contextConfigs.d.ts.map +1 -0
- package/dist/routes/contextConfigs.js +181 -0
- package/dist/routes/credentials.d.ts +4 -0
- package/dist/routes/credentials.d.ts.map +1 -0
- package/dist/routes/credentials.js +219 -0
- package/dist/routes/dataComponents.d.ts +4 -0
- package/dist/routes/dataComponents.d.ts.map +1 -0
- package/dist/routes/dataComponents.js +188 -0
- package/dist/routes/externalAgents.d.ts +4 -0
- package/dist/routes/externalAgents.d.ts.map +1 -0
- package/dist/routes/externalAgents.js +216 -0
- package/dist/routes/graphFull.d.ts +4 -0
- package/dist/routes/graphFull.d.ts.map +1 -0
- package/dist/routes/graphFull.js +248 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +37 -0
- package/dist/routes/oauth.d.ts +14 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +191 -0
- package/dist/routes/projects.d.ts +4 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +221 -0
- package/dist/routes/tools.d.ts +4 -0
- package/dist/routes/tools.d.ts.map +1 -0
- package/dist/routes/tools.js +547 -0
- package/dist/utils/auth-detection.d.ts +22 -0
- package/dist/utils/auth-detection.d.ts.map +1 -0
- package/dist/utils/auth-detection.js +149 -0
- package/dist/utils/oauth-service.d.ts +88 -0
- package/dist/utils/oauth-service.d.ts.map +1 -0
- package/dist/utils/oauth-service.js +240 -0
- package/package.json +68 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side functions for interacting with the Full Graph API
|
|
3
|
+
* These functions make HTTP requests to the server instead of direct database calls
|
|
4
|
+
*/
|
|
5
|
+
import { getLogger } from '../logger.js';
|
|
6
|
+
const logger = getLogger('graphFullClient');
|
|
7
|
+
/**
|
|
8
|
+
* Create a full graph via HTTP API
|
|
9
|
+
*/
|
|
10
|
+
export async function createFullGraphViaAPI(tenantId, projectId, apiUrl, graphData) {
|
|
11
|
+
logger.info({
|
|
12
|
+
tenantId,
|
|
13
|
+
projectId,
|
|
14
|
+
graphId: graphData.id,
|
|
15
|
+
apiUrl,
|
|
16
|
+
}, 'Creating full graph via API');
|
|
17
|
+
const url = `${apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/graph`;
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(graphData),
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const errorText = await response.text();
|
|
27
|
+
let errorMessage = `Failed to create graph: ${response.status} ${response.statusText}`;
|
|
28
|
+
try {
|
|
29
|
+
const errorJson = JSON.parse(errorText);
|
|
30
|
+
if (errorJson.error) {
|
|
31
|
+
errorMessage = errorJson.error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Use the text as-is if not JSON
|
|
36
|
+
if (errorText) {
|
|
37
|
+
errorMessage = errorText;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
logger.error({
|
|
41
|
+
status: response.status,
|
|
42
|
+
error: errorMessage,
|
|
43
|
+
}, 'Failed to create graph via API');
|
|
44
|
+
throw new Error(errorMessage);
|
|
45
|
+
}
|
|
46
|
+
const result = await response.json();
|
|
47
|
+
logger.info({
|
|
48
|
+
graphId: graphData.id,
|
|
49
|
+
}, 'Successfully created graph via API');
|
|
50
|
+
return result.data;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Update a full graph via HTTP API (upsert behavior)
|
|
54
|
+
*/
|
|
55
|
+
export async function updateFullGraphViaAPI(tenantId, projectId, apiUrl, graphId, graphData) {
|
|
56
|
+
logger.info({
|
|
57
|
+
tenantId,
|
|
58
|
+
projectId,
|
|
59
|
+
graphId,
|
|
60
|
+
apiUrl,
|
|
61
|
+
}, 'Updating full graph via API');
|
|
62
|
+
const url = `${apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/graph/${graphId}`;
|
|
63
|
+
const response = await fetch(url, {
|
|
64
|
+
method: 'PUT',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify(graphData),
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const errorText = await response.text();
|
|
72
|
+
let errorMessage = `Failed to update graph: ${response.status} ${response.statusText}`;
|
|
73
|
+
try {
|
|
74
|
+
const errorJson = JSON.parse(errorText);
|
|
75
|
+
if (errorJson.error) {
|
|
76
|
+
errorMessage = errorJson.error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Use the text as-is if not JSON
|
|
81
|
+
if (errorText) {
|
|
82
|
+
errorMessage = errorText;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
logger.error({
|
|
86
|
+
status: response.status,
|
|
87
|
+
error: errorMessage,
|
|
88
|
+
}, 'Failed to update graph via API');
|
|
89
|
+
throw new Error(errorMessage);
|
|
90
|
+
}
|
|
91
|
+
const result = await response.json();
|
|
92
|
+
logger.info({
|
|
93
|
+
graphId,
|
|
94
|
+
}, 'Successfully updated graph via API');
|
|
95
|
+
return result.data;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get a full graph via HTTP API
|
|
99
|
+
*/
|
|
100
|
+
export async function getFullGraphViaAPI(tenantId, projectId, apiUrl, graphId) {
|
|
101
|
+
logger.info({
|
|
102
|
+
tenantId,
|
|
103
|
+
projectId,
|
|
104
|
+
graphId,
|
|
105
|
+
apiUrl,
|
|
106
|
+
}, 'Getting full graph via API');
|
|
107
|
+
const url = `${apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/graph/${graphId}`;
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
if (response.status === 404) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const errorText = await response.text();
|
|
119
|
+
let errorMessage = `Failed to get graph: ${response.status} ${response.statusText}`;
|
|
120
|
+
try {
|
|
121
|
+
const errorJson = JSON.parse(errorText);
|
|
122
|
+
if (errorJson.error) {
|
|
123
|
+
errorMessage = errorJson.error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Use the text as-is if not JSON
|
|
128
|
+
if (errorText) {
|
|
129
|
+
errorMessage = errorText;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
logger.error({
|
|
133
|
+
status: response.status,
|
|
134
|
+
error: errorMessage,
|
|
135
|
+
}, 'Failed to get graph via API');
|
|
136
|
+
throw new Error(errorMessage);
|
|
137
|
+
}
|
|
138
|
+
const result = await response.json();
|
|
139
|
+
logger.info({
|
|
140
|
+
graphId,
|
|
141
|
+
}, 'Successfully retrieved graph via API');
|
|
142
|
+
return result.data;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Delete a full graph via HTTP API
|
|
146
|
+
*/
|
|
147
|
+
export async function deleteFullGraphViaAPI(tenantId, projectId, apiUrl, graphId) {
|
|
148
|
+
logger.info({
|
|
149
|
+
tenantId,
|
|
150
|
+
projectId,
|
|
151
|
+
graphId,
|
|
152
|
+
apiUrl,
|
|
153
|
+
}, 'Deleting full graph via API');
|
|
154
|
+
const url = `${apiUrl}/tenants/${tenantId}/crud/projects/${projectId}/graph/${graphId}`;
|
|
155
|
+
const response = await fetch(url, {
|
|
156
|
+
method: 'DELETE',
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
if (response.status === 404) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const errorText = await response.text();
|
|
166
|
+
let errorMessage = `Failed to delete graph: ${response.status} ${response.statusText}`;
|
|
167
|
+
try {
|
|
168
|
+
const errorJson = JSON.parse(errorText);
|
|
169
|
+
if (errorJson.error) {
|
|
170
|
+
errorMessage = errorJson.error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Use the text as-is if not JSON
|
|
175
|
+
if (errorText) {
|
|
176
|
+
errorMessage = errorText;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
logger.error({
|
|
180
|
+
status: response.status,
|
|
181
|
+
error: errorMessage,
|
|
182
|
+
}, 'Failed to delete graph via API');
|
|
183
|
+
throw new Error(errorMessage);
|
|
184
|
+
}
|
|
185
|
+
logger.info({
|
|
186
|
+
graphId,
|
|
187
|
+
}, 'Successfully deleted graph via API');
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type McpServerCapabilities, type McpTool, type McpToolDefinition, type McpToolStatus } from '@inkeep/agents-core';
|
|
2
|
+
export declare const updateToolHealth: ({ tenantId, projectId, toolId, status, error, }: {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
projectId: string;
|
|
5
|
+
toolId: string;
|
|
6
|
+
status: McpToolStatus;
|
|
7
|
+
error?: string;
|
|
8
|
+
}) => Promise<{
|
|
9
|
+
tenantId: string;
|
|
10
|
+
projectId: string;
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
config: {
|
|
14
|
+
type: "mcp";
|
|
15
|
+
mcp: import("@inkeep/agents-core").ToolMcpConfig;
|
|
16
|
+
};
|
|
17
|
+
credentialReferenceId: string | null;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
status: string;
|
|
21
|
+
headers: Record<string, string> | null;
|
|
22
|
+
imageUrl: string | null;
|
|
23
|
+
capabilities: import("@inkeep/agents-core").ToolServerCapabilities | null;
|
|
24
|
+
lastHealthCheck: string | null;
|
|
25
|
+
lastError: string | null;
|
|
26
|
+
availableTools: import("@inkeep/agents-core").McpToolDefinition[] | null;
|
|
27
|
+
lastToolsSync: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const checkToolHealth: (tool: McpTool) => Promise<{
|
|
30
|
+
status: McpToolStatus;
|
|
31
|
+
error?: string;
|
|
32
|
+
capabilities?: McpServerCapabilities;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const discoverToolsFromServer: (tool: McpTool) => Promise<McpToolDefinition[]>;
|
|
35
|
+
export declare const syncToolDefinitions: ({ tenantId, projectId, toolId, }: {
|
|
36
|
+
tenantId: string;
|
|
37
|
+
projectId: string;
|
|
38
|
+
toolId: string;
|
|
39
|
+
}) => Promise<{
|
|
40
|
+
tenantId: string;
|
|
41
|
+
projectId: string;
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
config: {
|
|
45
|
+
type: "mcp";
|
|
46
|
+
mcp: import("@inkeep/agents-core").ToolMcpConfig;
|
|
47
|
+
};
|
|
48
|
+
credentialReferenceId: string | null;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
status: string;
|
|
52
|
+
headers: Record<string, string> | null;
|
|
53
|
+
imageUrl: string | null;
|
|
54
|
+
capabilities: import("@inkeep/agents-core").ToolServerCapabilities | null;
|
|
55
|
+
lastHealthCheck: string | null;
|
|
56
|
+
lastError: string | null;
|
|
57
|
+
availableTools: import("@inkeep/agents-core").McpToolDefinition[] | null;
|
|
58
|
+
lastToolsSync: string | null;
|
|
59
|
+
}>;
|
|
60
|
+
export declare const checkAllToolsHealth: (tenantId: string, projectId: string) => Promise<PromiseSettledResult<{
|
|
61
|
+
tenantId: string;
|
|
62
|
+
projectId: string;
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
config: {
|
|
66
|
+
type: "mcp";
|
|
67
|
+
mcp: import("@inkeep/agents-core").ToolMcpConfig;
|
|
68
|
+
};
|
|
69
|
+
credentialReferenceId: string | null;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
updatedAt: string;
|
|
72
|
+
status: string;
|
|
73
|
+
headers: Record<string, string> | null;
|
|
74
|
+
imageUrl: string | null;
|
|
75
|
+
capabilities: import("@inkeep/agents-core").ToolServerCapabilities | null;
|
|
76
|
+
lastHealthCheck: string | null;
|
|
77
|
+
lastError: string | null;
|
|
78
|
+
availableTools: import("@inkeep/agents-core").McpToolDefinition[] | null;
|
|
79
|
+
lastToolsSync: string | null;
|
|
80
|
+
}>[]>;
|
|
81
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/data/tools.ts"],"names":[],"mappings":"AAIA,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,OAAO,EACZ,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAUnB,MAAM,qBAAqB,CAAC;AAgD7B,eAAO,MAAM,gBAAgB,GAAU,iDAMpC;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;EAmBA,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,MAAM,OAAO,KACZ,OAAO,CAAC;IACT,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC,CAkGA,CAAC;AAGF,eAAO,MAAM,uBAAuB,GAAU,MAAM,OAAO,KAAG,OAAO,CAAC,iBAAiB,EAAE,CAoFxF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,kCAIvC;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;;;;;;;;;EA+CA,CAAC;AAGF,eAAO,MAAM,mBAAmB,GAAU,UAAU,MAAM,EAAE,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;KAiB5E,CAAC"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { managementServer } from '../index.js';
|
|
2
|
+
import { getLogger } from '../logger.js';
|
|
3
|
+
import dbClient from './db/dbClient.js';
|
|
4
|
+
import { McpClient, CredentialStuffer, getCredentialReference, updateTool, dbResultToMcpTool, listTools, getToolById, detectAuthenticationRequired, ContextResolver, } from '@inkeep/agents-core';
|
|
5
|
+
/**
|
|
6
|
+
* Extract input schema from MCP tool definition, handling multiple formats
|
|
7
|
+
* Different MCP servers may use different schema structures:
|
|
8
|
+
* - inputSchema (direct) - e.g., Notion MCP
|
|
9
|
+
* - parameters.properties - e.g., some other MCP servers
|
|
10
|
+
* - parameters (direct) - alternative format
|
|
11
|
+
* - schema - another possible location
|
|
12
|
+
*/
|
|
13
|
+
function extractInputSchema(toolDef) {
|
|
14
|
+
// Try different possible locations for the input schema
|
|
15
|
+
if (toolDef.inputSchema) {
|
|
16
|
+
return toolDef.inputSchema;
|
|
17
|
+
}
|
|
18
|
+
if (toolDef.parameters?.properties) {
|
|
19
|
+
return toolDef.parameters.properties;
|
|
20
|
+
}
|
|
21
|
+
if (toolDef.parameters && typeof toolDef.parameters === 'object') {
|
|
22
|
+
return toolDef.parameters;
|
|
23
|
+
}
|
|
24
|
+
if (toolDef.schema) {
|
|
25
|
+
return toolDef.schema;
|
|
26
|
+
}
|
|
27
|
+
// If none found, return empty object
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const logger = getLogger('tools');
|
|
31
|
+
// Helper function to convert McpTool to MCPToolConfig format for CredentialStuffer
|
|
32
|
+
const convertToMCPToolConfig = (tool) => {
|
|
33
|
+
return {
|
|
34
|
+
id: tool.id,
|
|
35
|
+
name: tool.name,
|
|
36
|
+
description: tool.name, // Use name as description fallback
|
|
37
|
+
serverUrl: tool.config.mcp.server.url,
|
|
38
|
+
mcpType: tool.config.mcp.server.url.includes('api.nango.dev') ? 'nango' : 'generic',
|
|
39
|
+
transport: tool.config.mcp.transport,
|
|
40
|
+
headers: tool.headers,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
// Health checking and monitoring
|
|
44
|
+
export const updateToolHealth = async ({ tenantId, projectId, toolId, status, error, }) => {
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
const updateData = {
|
|
47
|
+
status,
|
|
48
|
+
lastHealthCheck: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
};
|
|
51
|
+
if (error !== undefined) {
|
|
52
|
+
updateData.lastError = error;
|
|
53
|
+
}
|
|
54
|
+
const tool = await updateTool(dbClient)({
|
|
55
|
+
scopes: { tenantId, projectId },
|
|
56
|
+
toolId,
|
|
57
|
+
data: updateData,
|
|
58
|
+
});
|
|
59
|
+
return tool;
|
|
60
|
+
};
|
|
61
|
+
export const checkToolHealth = async (tool) => {
|
|
62
|
+
try {
|
|
63
|
+
const transportType = tool.config.mcp.transport?.type || 'streamable_http';
|
|
64
|
+
const baseConfig = {
|
|
65
|
+
url: tool.config.mcp.server.url,
|
|
66
|
+
};
|
|
67
|
+
const credentialReferenceId = tool.credentialReferenceId;
|
|
68
|
+
let serverConfig;
|
|
69
|
+
// Build server config with credentials if available
|
|
70
|
+
if (credentialReferenceId) {
|
|
71
|
+
// Get credential store configuration
|
|
72
|
+
const credentialReference = await getCredentialReference(dbClient)({
|
|
73
|
+
scopes: { tenantId: tool.tenantId, projectId: tool.projectId },
|
|
74
|
+
id: credentialReferenceId,
|
|
75
|
+
});
|
|
76
|
+
if (!credentialReference) {
|
|
77
|
+
throw new Error(`Credential store not found: ${credentialReferenceId}`);
|
|
78
|
+
}
|
|
79
|
+
const storeReference = {
|
|
80
|
+
credentialStoreId: credentialReference.credentialStoreId,
|
|
81
|
+
retrievalParams: credentialReference.retrievalParams || {},
|
|
82
|
+
};
|
|
83
|
+
// Use CredentialStuffer to build proper config with auth headers
|
|
84
|
+
const contextResolver = new ContextResolver(tool.tenantId, tool.projectId, dbClient, managementServer);
|
|
85
|
+
const credentialStuffer = new CredentialStuffer(managementServer, contextResolver);
|
|
86
|
+
serverConfig = await credentialStuffer.buildMcpServerConfig({ tenantId: tool.tenantId, projectId: tool.projectId }, convertToMCPToolConfig(tool), storeReference);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (transportType === 'sse') {
|
|
90
|
+
serverConfig = {
|
|
91
|
+
type: 'sse',
|
|
92
|
+
url: baseConfig.url,
|
|
93
|
+
activeTools: tool.config.mcp.activeTools,
|
|
94
|
+
eventSourceInit: tool.config.mcp.transport?.eventSourceInit,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
serverConfig = {
|
|
99
|
+
type: 'streamable_http',
|
|
100
|
+
url: baseConfig.url,
|
|
101
|
+
activeTools: tool.config.mcp.activeTools,
|
|
102
|
+
requestInit: tool.config.mcp.transport?.requestInit,
|
|
103
|
+
eventSourceInit: tool.config.mcp.transport?.eventSourceInit,
|
|
104
|
+
reconnectionOptions: tool.config.mcp.transport?.reconnectionOptions,
|
|
105
|
+
sessionId: tool.config.mcp.transport?.sessionId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const client = new McpClient({
|
|
110
|
+
name: tool.name,
|
|
111
|
+
server: serverConfig,
|
|
112
|
+
});
|
|
113
|
+
await client.connect();
|
|
114
|
+
// Try to list tools to verify connection
|
|
115
|
+
await client.tools();
|
|
116
|
+
await client.disconnect();
|
|
117
|
+
return {
|
|
118
|
+
status: 'healthy',
|
|
119
|
+
capabilities: {
|
|
120
|
+
tools: true,
|
|
121
|
+
resources: false, // Could be enhanced to check actual capabilities
|
|
122
|
+
prompts: false,
|
|
123
|
+
logging: false,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger.error({ toolId: tool.id, error }, 'Tool health check failed');
|
|
129
|
+
// Check if error indicates authentication is required
|
|
130
|
+
if (error instanceof Error && (await detectAuthenticationRequired(tool, error))) {
|
|
131
|
+
return {
|
|
132
|
+
status: 'needs_auth',
|
|
133
|
+
error: 'Authentication required - OAuth login needed',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
status: 'unhealthy',
|
|
138
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
// Tool discovery
|
|
143
|
+
export const discoverToolsFromServer = async (tool) => {
|
|
144
|
+
try {
|
|
145
|
+
const credentialReferenceId = tool.credentialReferenceId;
|
|
146
|
+
let serverConfig;
|
|
147
|
+
// Build server config with credentials if available
|
|
148
|
+
if (credentialReferenceId) {
|
|
149
|
+
// Get credential store configuration
|
|
150
|
+
const credentialReference = await getCredentialReference(dbClient)({
|
|
151
|
+
scopes: { tenantId: tool.tenantId, projectId: tool.projectId },
|
|
152
|
+
id: credentialReferenceId,
|
|
153
|
+
});
|
|
154
|
+
if (!credentialReference) {
|
|
155
|
+
throw new Error(`Credential store not found: ${credentialReferenceId}`);
|
|
156
|
+
}
|
|
157
|
+
const storeReference = {
|
|
158
|
+
credentialStoreId: credentialReference.credentialStoreId,
|
|
159
|
+
retrievalParams: credentialReference.retrievalParams || {},
|
|
160
|
+
};
|
|
161
|
+
// Use CredentialStuffer to build proper config with auth headers
|
|
162
|
+
const contextResolver = new ContextResolver(tool.tenantId, tool.projectId, dbClient, managementServer);
|
|
163
|
+
const credentialStuffer = new CredentialStuffer(managementServer, contextResolver);
|
|
164
|
+
serverConfig = (await credentialStuffer.buildMcpServerConfig({ tenantId: tool.tenantId, projectId: tool.projectId }, convertToMCPToolConfig(tool), storeReference));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// No credentials - build basic config
|
|
168
|
+
const transportType = tool.config.mcp.transport?.type || 'streamable_http';
|
|
169
|
+
if (transportType === 'sse') {
|
|
170
|
+
serverConfig = {
|
|
171
|
+
type: 'sse',
|
|
172
|
+
url: tool.config.mcp.server.url,
|
|
173
|
+
activeTools: tool.config.mcp.activeTools,
|
|
174
|
+
eventSourceInit: tool.config.mcp.transport?.eventSourceInit,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
serverConfig = {
|
|
179
|
+
type: 'streamable_http',
|
|
180
|
+
url: tool.config.mcp.server.url,
|
|
181
|
+
activeTools: tool.config.mcp.activeTools,
|
|
182
|
+
requestInit: tool.config.mcp.transport?.requestInit,
|
|
183
|
+
eventSourceInit: tool.config.mcp.transport?.eventSourceInit,
|
|
184
|
+
reconnectionOptions: tool.config.mcp.transport?.reconnectionOptions,
|
|
185
|
+
sessionId: tool.config.mcp.transport?.sessionId,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const client = new McpClient({
|
|
190
|
+
name: tool.name,
|
|
191
|
+
server: serverConfig,
|
|
192
|
+
});
|
|
193
|
+
await client.connect();
|
|
194
|
+
// Get tools from the MCP client
|
|
195
|
+
const serverTools = await client.tools();
|
|
196
|
+
await client.disconnect();
|
|
197
|
+
// Convert to our format
|
|
198
|
+
const toolDefinitions = Object.entries(serverTools).map(([name, toolDef]) => ({
|
|
199
|
+
name,
|
|
200
|
+
description: toolDef.description || '',
|
|
201
|
+
inputSchema: extractInputSchema(toolDef),
|
|
202
|
+
}));
|
|
203
|
+
return toolDefinitions;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
logger.error({ toolId: tool.id, error }, 'Tool discovery failed');
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
export const syncToolDefinitions = async ({ tenantId, projectId, toolId, }) => {
|
|
211
|
+
const tool = await getToolById(dbClient)({ scopes: { tenantId, projectId }, toolId });
|
|
212
|
+
if (!tool) {
|
|
213
|
+
throw new Error(`Tool ${toolId} not found`);
|
|
214
|
+
}
|
|
215
|
+
const mcpTool = dbResultToMcpTool(tool);
|
|
216
|
+
try {
|
|
217
|
+
const availableTools = await discoverToolsFromServer(mcpTool);
|
|
218
|
+
const updatedTool = await updateTool(dbClient)({
|
|
219
|
+
scopes: { tenantId, projectId },
|
|
220
|
+
toolId,
|
|
221
|
+
data: {
|
|
222
|
+
availableTools,
|
|
223
|
+
lastToolsSync: new Date().toISOString(),
|
|
224
|
+
status: 'healthy',
|
|
225
|
+
updatedAt: new Date().toISOString(),
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
return updatedTool;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const toolNeedsAuth = error instanceof Error && (await detectAuthenticationRequired(mcpTool, error));
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
const updatedTool = await updateTool(dbClient)({
|
|
234
|
+
scopes: { tenantId, projectId },
|
|
235
|
+
toolId,
|
|
236
|
+
data: {
|
|
237
|
+
availableTools: [],
|
|
238
|
+
lastToolsSync: new Date().toISOString(),
|
|
239
|
+
status: toolNeedsAuth ? 'needs_auth' : 'unhealthy',
|
|
240
|
+
lastError: toolNeedsAuth
|
|
241
|
+
? 'Authentication required - OAuth login needed'
|
|
242
|
+
: error instanceof Error
|
|
243
|
+
? error.message
|
|
244
|
+
: 'Tool sync failed',
|
|
245
|
+
lastHealthCheck: now,
|
|
246
|
+
updatedAt: now,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
return updatedTool;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
// Bulk health checking
|
|
253
|
+
export const checkAllToolsHealth = async (tenantId, projectId) => {
|
|
254
|
+
const toolsList = await listTools(dbClient)({ scopes: { tenantId, projectId } });
|
|
255
|
+
const results = await Promise.allSettled(toolsList.data.map(async (tool) => {
|
|
256
|
+
const healthResult = await checkToolHealth(dbResultToMcpTool(tool));
|
|
257
|
+
return await updateToolHealth({
|
|
258
|
+
tenantId,
|
|
259
|
+
projectId: tool.projectId,
|
|
260
|
+
toolId: tool.id,
|
|
261
|
+
status: healthResult.status,
|
|
262
|
+
error: healthResult.error,
|
|
263
|
+
});
|
|
264
|
+
}));
|
|
265
|
+
return results;
|
|
266
|
+
};
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const envSchema: z.ZodObject<{
|
|
3
|
+
NODE_ENV: z.ZodOptional<z.ZodEnum<{
|
|
4
|
+
development: "development";
|
|
5
|
+
production: "production";
|
|
6
|
+
test: "test";
|
|
7
|
+
}>>;
|
|
8
|
+
ENVIRONMENT: z.ZodOptional<z.ZodEnum<{
|
|
9
|
+
development: "development";
|
|
10
|
+
production: "production";
|
|
11
|
+
pentest: "pentest";
|
|
12
|
+
test: "test";
|
|
13
|
+
}>>;
|
|
14
|
+
DB_FILE_NAME: z.ZodDefault<z.ZodString>;
|
|
15
|
+
PORT: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
16
|
+
MANAGEMENT_PORT: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
|
|
17
|
+
AGENT_BASE_URL: z.ZodOptional<z.ZodString>;
|
|
18
|
+
LOG_LEVEL: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
trace: "trace";
|
|
20
|
+
debug: "debug";
|
|
21
|
+
info: "info";
|
|
22
|
+
warn: "warn";
|
|
23
|
+
error: "error";
|
|
24
|
+
}>>>;
|
|
25
|
+
NANGO_SECRET_KEY: z.ZodOptional<z.ZodString>;
|
|
26
|
+
INKEEP_AGENTS_MANAGE_API_SECRET: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export declare const env: {
|
|
29
|
+
DB_FILE_NAME: string;
|
|
30
|
+
PORT: number;
|
|
31
|
+
MANAGEMENT_PORT: number;
|
|
32
|
+
LOG_LEVEL: "trace" | "debug" | "info" | "warn" | "error";
|
|
33
|
+
NODE_ENV?: "development" | "production" | "test" | undefined;
|
|
34
|
+
ENVIRONMENT?: "development" | "production" | "pentest" | "test" | undefined;
|
|
35
|
+
AGENT_BASE_URL?: string | undefined;
|
|
36
|
+
NANGO_SECRET_KEY?: string | undefined;
|
|
37
|
+
INKEEP_AGENTS_MANAGE_API_SECRET?: string | undefined;
|
|
38
|
+
};
|
|
39
|
+
export type Env = z.infer<typeof envSchema>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkCxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;iBAUb,CAAC;AAuBH,eAAO,MAAM,GAAG;;;;;;;;;;CAAa,CAAC;AAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
dotenv.config({ quiet: true });
|
|
6
|
+
const nodeEnvSchema = z.enum(['development', 'production']).default('development');
|
|
7
|
+
const environmentSchema = z.enum(['development', 'pentest', 'production', 'test']);
|
|
8
|
+
const criticalEnv = z
|
|
9
|
+
.object({
|
|
10
|
+
ENVIRONMENT: environmentSchema,
|
|
11
|
+
})
|
|
12
|
+
.parse(process.env);
|
|
13
|
+
const loadEnvFile = () => {
|
|
14
|
+
// Priority of environment variables:
|
|
15
|
+
// 1. Existing process.env variables (highest priority)
|
|
16
|
+
// 2. Values from .env.{nodeEnv}.nonsecret file (lower priority)
|
|
17
|
+
// 3. Default values defined in schema (lowest priority)
|
|
18
|
+
const envPath = path.resolve(process.cwd(), `.env.${criticalEnv.ENVIRONMENT}.nonsecret`);
|
|
19
|
+
if (fs.existsSync(envPath)) {
|
|
20
|
+
const envConfig = dotenv.parse(fs.readFileSync(envPath));
|
|
21
|
+
for (const k in envConfig) {
|
|
22
|
+
// Only set if the environment variable doesn't already exist
|
|
23
|
+
// This preserves any values that were already set in process.env
|
|
24
|
+
if (!(k in process.env)) {
|
|
25
|
+
process.env[k] = envConfig[k];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
loadEnvFile();
|
|
31
|
+
const envSchema = z.object({
|
|
32
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
|
|
33
|
+
ENVIRONMENT: z.enum(['development', 'production', 'pentest', 'test']).optional(),
|
|
34
|
+
DB_FILE_NAME: z.string().default('file:../local.db'),
|
|
35
|
+
PORT: z.coerce.number().optional().default(3002),
|
|
36
|
+
MANAGEMENT_PORT: z.coerce.number().optional().default(3002),
|
|
37
|
+
AGENT_BASE_URL: z.string().optional(),
|
|
38
|
+
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error']).optional().default('debug'),
|
|
39
|
+
NANGO_SECRET_KEY: z.string().optional(),
|
|
40
|
+
INKEEP_AGENTS_MANAGE_API_SECRET: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
const parseEnv = () => {
|
|
43
|
+
try {
|
|
44
|
+
const parsedEnv = envSchema.parse(process.env);
|
|
45
|
+
// Set default AGENT_BASE_URL if not provided
|
|
46
|
+
if (!parsedEnv.AGENT_BASE_URL) {
|
|
47
|
+
parsedEnv.AGENT_BASE_URL = `http://localhost:${parsedEnv.PORT}`;
|
|
48
|
+
}
|
|
49
|
+
return parsedEnv;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof z.ZodError) {
|
|
53
|
+
const missingVars = error.issues.map((issue) => issue.path.join('.'));
|
|
54
|
+
throw new Error(`❌ Invalid environment variables: ${missingVars.join(', ')}\n${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
export const env = parseEnv();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUzD,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAgBxC,eAAO,MAAM,gBAAgB,kBAQ3B,CAAC"}
|