@mondaydotcomorg/atp-server 0.19.10 → 0.19.12

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.
Files changed (49) hide show
  1. package/dist/core/config.d.ts +20 -1
  2. package/dist/core/config.d.ts.map +1 -1
  3. package/dist/core/config.js.map +1 -1
  4. package/dist/core/request-scope.d.ts +33 -0
  5. package/dist/core/request-scope.d.ts.map +1 -0
  6. package/dist/core/request-scope.js +93 -0
  7. package/dist/core/request-scope.js.map +1 -0
  8. package/dist/create-server.d.ts +2 -0
  9. package/dist/create-server.d.ts.map +1 -1
  10. package/dist/create-server.js +12 -0
  11. package/dist/create-server.js.map +1 -1
  12. package/dist/executor/sandbox-builder.d.ts.map +1 -1
  13. package/dist/executor/sandbox-builder.js +27 -9
  14. package/dist/executor/sandbox-builder.js.map +1 -1
  15. package/dist/explorer/index.d.ts +14 -1
  16. package/dist/explorer/index.d.ts.map +1 -1
  17. package/dist/explorer/index.js +75 -6
  18. package/dist/explorer/index.js.map +1 -1
  19. package/dist/handlers/definitions.handler.d.ts.map +1 -1
  20. package/dist/handlers/definitions.handler.js +4 -2
  21. package/dist/handlers/definitions.handler.js.map +1 -1
  22. package/dist/handlers/execute.handler.d.ts.map +1 -1
  23. package/dist/handlers/execute.handler.js +37 -5
  24. package/dist/handlers/execute.handler.js.map +1 -1
  25. package/dist/http/request-handler.d.ts +2 -1
  26. package/dist/http/request-handler.d.ts.map +1 -1
  27. package/dist/http/request-handler.js +68 -57
  28. package/dist/http/request-handler.js.map +1 -1
  29. package/dist/index.cjs +287 -74
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +287 -75
  34. package/dist/index.js.map +1 -1
  35. package/dist/search/index.d.ts +2 -0
  36. package/dist/search/index.d.ts.map +1 -1
  37. package/dist/search/index.js +16 -0
  38. package/dist/search/index.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/core/config.ts +21 -0
  41. package/src/core/request-scope.ts +131 -0
  42. package/src/create-server.ts +14 -0
  43. package/src/executor/sandbox-builder.ts +28 -10
  44. package/src/explorer/index.ts +98 -8
  45. package/src/handlers/definitions.handler.ts +5 -2
  46. package/src/handlers/execute.handler.ts +39 -6
  47. package/src/http/request-handler.ts +70 -58
  48. package/src/index.ts +1 -0
  49. package/src/search/index.ts +18 -0
@@ -2,9 +2,9 @@ import type { RequestContext, ResolvedServerConfig } from '../core/config.js';
2
2
  import type { SandboxExecutor } from '../executor/index.js';
3
3
  import type { ExecutionStateManager } from '../execution-state/index.js';
4
4
  import type { ClientSessionManager } from '../client-sessions.js';
5
- import type { AuditSink, AuditEvent } from '@mondaydotcomorg/atp-protocol';
5
+ import type { AuditSink, AuditEvent, ToolCallEvent } from '@mondaydotcomorg/atp-protocol';
6
6
  import { ExecutionStatus, ProvenanceMode } from '@mondaydotcomorg/atp-protocol';
7
- import { nanoid } from 'nanoid';
7
+ import crypto from 'crypto';
8
8
  import {
9
9
  captureProvenanceSnapshot,
10
10
  verifyProvenanceHints,
@@ -82,6 +82,33 @@ export async function handleExecute(
82
82
  } catch (error) {}
83
83
  }
84
84
 
85
+ const onToolCall = auditSink
86
+ ? (event: ToolCallEvent) => {
87
+ const auditEvent: AuditEvent = {
88
+ eventId: crypto.randomUUID(),
89
+ timestamp: Date.now(),
90
+ clientId: ctx.clientId || 'anonymous',
91
+ eventType: 'tool_call',
92
+ action: 'complete',
93
+ toolName: event.toolName,
94
+ apiGroup: event.apiGroup,
95
+ input: event.input,
96
+ output: event.output,
97
+ status: event.success ? 'success' : 'failed',
98
+ error: event.error
99
+ ? {
100
+ message: event.error.message,
101
+ stack: event.error.stack,
102
+ }
103
+ : undefined,
104
+ metadata: {
105
+ duration: event.duration,
106
+ },
107
+ };
108
+ auditSink.write(auditEvent).catch(() => {});
109
+ }
110
+ : undefined;
111
+
85
112
  const executionConfig = {
86
113
  timeout: requestConfig.timeout || config.execution.timeout,
87
114
  maxMemory: memoryInBytes,
@@ -93,12 +120,18 @@ export async function handleExecute(
93
120
  requestConfig.provenanceMode || config.execution.provenanceMode || ProvenanceMode.NONE,
94
121
  securityPolicies: config.execution.securityPolicies || [],
95
122
  provenanceHints: requestConfig.provenanceHints,
96
- requestContext: requestConfig.requestContext,
123
+ requestContext: {
124
+ ...requestConfig.requestContext,
125
+ headers: ctx.headers,
126
+ path: ctx.path,
127
+ method: ctx.method,
128
+ },
129
+ onToolCall,
97
130
  };
98
131
 
99
132
  // Verify provenance hints if provided
100
133
  let hintMap: Map<string, ProvenanceMetadata> | undefined;
101
- const prelimExecutionId = nanoid();
134
+ const prelimExecutionId = crypto.randomUUID();
102
135
  if (
103
136
  executionConfig.provenanceHints &&
104
137
  executionConfig.provenanceHints.length > 0 &&
@@ -142,7 +175,7 @@ export async function handleExecute(
142
175
 
143
176
  if (auditSink) {
144
177
  const startEvent: AuditEvent = {
145
- eventId: nanoid(),
178
+ eventId: crypto.randomUUID(),
146
179
  timestamp: startTime,
147
180
  clientId: ctx.clientId || 'anonymous',
148
181
  eventType: 'execution',
@@ -215,7 +248,7 @@ export async function handleExecute(
215
248
 
216
249
  if (auditSink) {
217
250
  const endEvent: AuditEvent = {
218
- eventId: nanoid(),
251
+ eventId: crypto.randomUUID(),
219
252
  timestamp: Date.now(),
220
253
  clientId: ctx.clientId || 'anonymous',
221
254
  eventType: 'execution',
@@ -4,8 +4,9 @@ import { log } from '@mondaydotcomorg/atp-runtime';
4
4
  import type { CacheProvider, AuthProvider, AuditSink } from '@mondaydotcomorg/atp-protocol';
5
5
  import { parseBody } from '../core/http.js';
6
6
  import { handleError, createContext } from '../utils/index.js';
7
- import type { RequestContext, Middleware } from '../core/config.js';
7
+ import type { RequestContext, Middleware, ToolRulesProvider } from '../core/config.js';
8
8
  import type { ClientSessionManager } from '../client-sessions.js';
9
+ import { runInRequestScope } from '../core/request-scope.js';
9
10
 
10
11
  export interface RequestHandlerDeps {
11
12
  cacheProvider?: CacheProvider;
@@ -15,6 +16,7 @@ export interface RequestHandlerDeps {
15
16
  middleware: Middleware[];
16
17
  routeHandler: (ctx: RequestContext) => Promise<void>;
17
18
  sessionManager?: ClientSessionManager;
19
+ toolRulesProvider?: ToolRulesProvider;
18
20
  }
19
21
 
20
22
  export async function handleHTTPRequest(
@@ -34,75 +36,85 @@ export async function handleHTTPRequest(
34
36
  const headers = new Map<string, string>();
35
37
  responseHeaders.set(req, headers);
36
38
 
37
- try {
38
- if (req.method === 'POST' || req.method === 'PUT') {
39
- const reqWithBody = req as IncomingMessage & { body?: unknown };
40
- if (reqWithBody.body !== undefined) {
41
- ctx.body = reqWithBody.body;
42
- } else {
43
- ctx.body = await parseBody(req);
44
- }
39
+ if (deps.toolRulesProvider) {
40
+ try {
41
+ ctx.toolRules = deps.toolRulesProvider(ctx);
42
+ } catch (error) {
43
+ log.warn('Tool rules provider failed', { error });
45
44
  }
45
+ }
46
46
 
47
- await runMiddleware(ctx, deps.middleware, deps.routeHandler);
48
-
47
+ await runInRequestScope({ toolRules: ctx.toolRules }, async () => {
49
48
  try {
50
- if (ctx.clientId && deps.sessionManager && ctx.path !== '/api/init') {
51
- try {
52
- const newToken = deps.sessionManager.generateToken(ctx.clientId);
53
- const expiresAt = Date.now() + 60 * 60 * 1000;
54
-
55
- headers.set('X-ATP-Token', newToken);
56
- headers.set('X-ATP-Token-Expires', expiresAt.toString());
57
- } catch (error) {}
49
+ if (req.method === 'POST' || req.method === 'PUT') {
50
+ const reqWithBody = req as IncomingMessage & { body?: unknown };
51
+ if (reqWithBody.body !== undefined) {
52
+ ctx.body = reqWithBody.body;
53
+ } else {
54
+ ctx.body = await parseBody(req);
55
+ }
58
56
  }
59
57
 
60
- const isStringResponse = typeof ctx.responseBody === 'string';
61
- res.writeHead(ctx.status, {
62
- 'Content-Type': isStringResponse ? 'text/plain; charset=utf-8' : 'application/json',
63
- ...Object.fromEntries(headers),
64
- });
65
- res.end(isStringResponse ? ctx.responseBody : JSON.stringify(ctx.responseBody));
66
- } catch (writeError) {}
67
- } catch (error) {
68
- try {
69
- if (ctx.clientId && deps.sessionManager && ctx.path !== '/api/init') {
70
- try {
71
- const newToken = deps.sessionManager.generateToken(ctx.clientId);
72
- const expiresAt = Date.now() + 60 * 60 * 1000;
58
+ await runMiddleware(ctx, deps.middleware, deps.routeHandler);
73
59
 
74
- headers.set('X-ATP-Token', newToken);
75
- headers.set('X-ATP-Token-Expires', expiresAt.toString());
60
+ try {
61
+ if (ctx.clientId && deps.sessionManager && ctx.path !== '/api/init') {
62
+ try {
63
+ const newToken = deps.sessionManager.generateToken(ctx.clientId);
64
+ const expiresAt = Date.now() + 60 * 60 * 1000;
76
65
 
77
- log.debug('Token refresh headers set on error', {
78
- clientId: ctx.clientId,
79
- path: ctx.path,
80
- hasSessionManager: !!deps.sessionManager,
81
- headerCount: headers.size,
82
- });
83
- } catch (tokenError) {
84
- log.warn('Token refresh failed on error', { error: tokenError });
66
+ headers.set('X-ATP-Token', newToken);
67
+ headers.set('X-ATP-Token-Expires', expiresAt.toString());
68
+ } catch (error) {}
85
69
  }
86
- } else {
87
- log.debug('Token refresh skipped on error', {
88
- hasClientId: !!ctx.clientId,
89
- hasSessionManager: !!deps.sessionManager,
90
- path: ctx.path,
91
- });
92
- }
93
70
 
94
- handleError(res, error as Error, nanoid(), headers);
95
- } catch (handlerError) {
71
+ const isStringResponse = typeof ctx.responseBody === 'string';
72
+ res.writeHead(ctx.status, {
73
+ 'Content-Type': isStringResponse ? 'text/plain; charset=utf-8' : 'application/json',
74
+ ...Object.fromEntries(headers),
75
+ });
76
+ res.end(isStringResponse ? ctx.responseBody : JSON.stringify(ctx.responseBody));
77
+ } catch (writeError) {}
78
+ } catch (error) {
96
79
  try {
97
- if (!res.headersSent) {
98
- res.writeHead(500, { 'Content-Type': 'application/json' });
99
- res.end(JSON.stringify({ error: 'Internal server error' }));
80
+ if (ctx.clientId && deps.sessionManager && ctx.path !== '/api/init') {
81
+ try {
82
+ const newToken = deps.sessionManager.generateToken(ctx.clientId);
83
+ const expiresAt = Date.now() + 60 * 60 * 1000;
84
+
85
+ headers.set('X-ATP-Token', newToken);
86
+ headers.set('X-ATP-Token-Expires', expiresAt.toString());
87
+
88
+ log.debug('Token refresh headers set on error', {
89
+ clientId: ctx.clientId,
90
+ path: ctx.path,
91
+ hasSessionManager: !!deps.sessionManager,
92
+ headerCount: headers.size,
93
+ });
94
+ } catch (tokenError) {
95
+ log.warn('Token refresh failed on error', { error: tokenError });
96
+ }
97
+ } else {
98
+ log.debug('Token refresh skipped on error', {
99
+ hasClientId: !!ctx.clientId,
100
+ hasSessionManager: !!deps.sessionManager,
101
+ path: ctx.path,
102
+ });
100
103
  }
101
- } catch {}
104
+
105
+ handleError(res, error as Error, nanoid(), headers);
106
+ } catch (handlerError) {
107
+ try {
108
+ if (!res.headersSent) {
109
+ res.writeHead(500, { 'Content-Type': 'application/json' });
110
+ res.end(JSON.stringify({ error: 'Internal server error' }));
111
+ }
112
+ } catch {}
113
+ }
114
+ } finally {
115
+ responseHeaders.delete(req);
102
116
  }
103
- } finally {
104
- responseHeaders.delete(req);
105
- }
117
+ });
106
118
  }
107
119
 
108
120
  async function runMiddleware(
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ export type {
13
13
  RequestContext,
14
14
  } from './core/config.js';
15
15
  export { MB, GB, SECOND, MINUTE, HOUR, DAY } from './core/config.js';
16
+ export type { ToolRulesProvider } from './core/config.js';
16
17
 
17
18
  export type {
18
19
  ProvenanceMetadata,
@@ -5,6 +5,7 @@ import type {
5
5
  AuthProvider,
6
6
  ScopeFilteringConfig,
7
7
  } from '@mondaydotcomorg/atp-protocol';
8
+ import { filterApiGroups } from '../core/request-scope.js';
8
9
 
9
10
  interface IndexedFunction {
10
11
  apiGroup: string;
@@ -24,6 +25,7 @@ interface IndexedFunction {
24
25
  */
25
26
  export class SearchEngine {
26
27
  private index: IndexedFunction[] = [];
28
+ private apiGroups: APIGroupConfig[] = [];
27
29
 
28
30
  /**
29
31
  * Creates a new SearchEngine instance.
@@ -31,6 +33,7 @@ export class SearchEngine {
31
33
  */
32
34
  constructor(apiGroups?: APIGroupConfig[]) {
33
35
  if (apiGroups) {
36
+ this.apiGroups = apiGroups;
34
37
  this.buildIndex(apiGroups);
35
38
  }
36
39
  }
@@ -67,6 +70,7 @@ export class SearchEngine {
67
70
 
68
71
  /**
69
72
  * Searches for API functions matching the query.
73
+ * Tool rules are automatically applied from the request scope.
70
74
  * @param options - Search options including query and filters
71
75
  * @param userId - Optional user ID for scope filtering
72
76
  * @param authProvider - Optional auth provider for checking user scopes
@@ -79,10 +83,24 @@ export class SearchEngine {
79
83
  authProvider?: AuthProvider,
80
84
  scopeFilteringConfig?: ScopeFilteringConfig
81
85
  ): Promise<SearchResult[]> {
86
+ const allowedGroups = filterApiGroups(this.apiGroups);
87
+ const allowedTools = new Set<string>();
88
+ for (const group of allowedGroups) {
89
+ if (group.functions) {
90
+ for (const func of group.functions) {
91
+ allowedTools.add(`${group.name}:${func.name}`);
92
+ }
93
+ }
94
+ }
95
+
82
96
  const queryWords = this.extractKeywords(options.query);
83
97
  const results: Array<{ result: SearchResult; score: number }> = [];
84
98
 
85
99
  for (const item of this.index) {
100
+ if (!allowedTools.has(`${item.apiGroup}:${item.functionName}`)) {
101
+ continue;
102
+ }
103
+
86
104
  if (options.apiGroups && !options.apiGroups.includes(item.apiGroup)) {
87
105
  continue;
88
106
  }