@directus/api 30.0.0 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/app.js +5 -0
  2. package/dist/auth/drivers/oauth2.js +17 -3
  3. package/dist/auth/drivers/openid.js +17 -3
  4. package/dist/controllers/mcp.d.ts +2 -0
  5. package/dist/controllers/mcp.js +33 -0
  6. package/dist/controllers/users.js +17 -7
  7. package/dist/controllers/versions.js +3 -2
  8. package/dist/database/errors/dialects/mssql.d.ts +1 -1
  9. package/dist/database/errors/dialects/mssql.js +18 -10
  10. package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
  11. package/dist/database/migrations/20250813A-add-mcp.js +18 -0
  12. package/dist/database/run-ast/README.md +46 -0
  13. package/dist/mcp/define.d.ts +2 -0
  14. package/dist/mcp/define.js +3 -0
  15. package/dist/mcp/index.d.ts +1 -0
  16. package/dist/mcp/index.js +1 -0
  17. package/dist/mcp/schema.d.ts +485 -0
  18. package/dist/mcp/schema.js +219 -0
  19. package/dist/mcp/server.d.ts +97 -0
  20. package/dist/mcp/server.js +310 -0
  21. package/dist/mcp/tools/assets.d.ts +3 -0
  22. package/dist/mcp/tools/assets.js +54 -0
  23. package/dist/mcp/tools/collections.d.ts +84 -0
  24. package/dist/mcp/tools/collections.js +90 -0
  25. package/dist/mcp/tools/fields.d.ts +101 -0
  26. package/dist/mcp/tools/fields.js +157 -0
  27. package/dist/mcp/tools/files.d.ts +235 -0
  28. package/dist/mcp/tools/files.js +103 -0
  29. package/dist/mcp/tools/flows.d.ts +323 -0
  30. package/dist/mcp/tools/flows.js +85 -0
  31. package/dist/mcp/tools/folders.d.ts +95 -0
  32. package/dist/mcp/tools/folders.js +96 -0
  33. package/dist/mcp/tools/index.d.ts +15 -0
  34. package/dist/mcp/tools/index.js +29 -0
  35. package/dist/mcp/tools/items.d.ts +87 -0
  36. package/dist/mcp/tools/items.js +141 -0
  37. package/dist/mcp/tools/operations.d.ts +171 -0
  38. package/dist/mcp/tools/operations.js +77 -0
  39. package/dist/mcp/tools/prompts/assets.md +8 -0
  40. package/dist/mcp/tools/prompts/collections.md +336 -0
  41. package/dist/mcp/tools/prompts/fields.md +521 -0
  42. package/dist/mcp/tools/prompts/files.md +180 -0
  43. package/dist/mcp/tools/prompts/flows.md +495 -0
  44. package/dist/mcp/tools/prompts/folders.md +34 -0
  45. package/dist/mcp/tools/prompts/index.d.ts +16 -0
  46. package/dist/mcp/tools/prompts/index.js +19 -0
  47. package/dist/mcp/tools/prompts/items.md +317 -0
  48. package/dist/mcp/tools/prompts/operations.md +721 -0
  49. package/dist/mcp/tools/prompts/relations.md +386 -0
  50. package/dist/mcp/tools/prompts/schema.md +130 -0
  51. package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
  52. package/dist/mcp/tools/prompts/system-prompt.md +44 -0
  53. package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
  54. package/dist/mcp/tools/relations.d.ts +73 -0
  55. package/dist/mcp/tools/relations.js +93 -0
  56. package/dist/mcp/tools/schema.d.ts +54 -0
  57. package/dist/mcp/tools/schema.js +317 -0
  58. package/dist/mcp/tools/system.d.ts +3 -0
  59. package/dist/mcp/tools/system.js +22 -0
  60. package/dist/mcp/tools/trigger-flow.d.ts +8 -0
  61. package/dist/mcp/tools/trigger-flow.js +48 -0
  62. package/dist/mcp/transport.d.ts +13 -0
  63. package/dist/mcp/transport.js +18 -0
  64. package/dist/mcp/types.d.ts +56 -0
  65. package/dist/mcp/types.js +1 -0
  66. package/dist/services/authentication.js +36 -0
  67. package/dist/services/fields.js +4 -4
  68. package/dist/services/items.js +14 -4
  69. package/dist/services/payload.d.ts +7 -3
  70. package/dist/services/payload.js +26 -12
  71. package/dist/services/server.js +1 -0
  72. package/dist/services/tfa.d.ts +1 -1
  73. package/dist/services/tfa.js +20 -5
  74. package/dist/services/versions.d.ts +6 -4
  75. package/dist/services/versions.js +84 -25
  76. package/dist/types/auth.d.ts +2 -1
  77. package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
  78. package/dist/utils/versioning/deep-map-with-schema.js +81 -0
  79. package/dist/utils/versioning/handle-version.d.ts +2 -2
  80. package/dist/utils/versioning/handle-version.js +47 -43
  81. package/dist/utils/versioning/split-recursive.d.ts +4 -0
  82. package/dist/utils/versioning/split-recursive.js +27 -0
  83. package/package.json +30 -29
package/dist/app.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
3
3
  import { handlePressure } from '@directus/pressure';
4
+ import { toBoolean } from '@directus/utils';
4
5
  import cookieParser from 'cookie-parser';
5
6
  import express from 'express';
6
7
  import { merge } from 'lodash-es';
@@ -23,6 +24,7 @@ import flowsRouter from './controllers/flows.js';
23
24
  import foldersRouter from './controllers/folders.js';
24
25
  import graphqlRouter from './controllers/graphql.js';
25
26
  import itemsRouter from './controllers/items.js';
27
+ import mcpRouter from './controllers/mcp.js';
26
28
  import metricsRouter from './controllers/metrics.js';
27
29
  import notFoundHandler from './controllers/not-found.js';
28
30
  import notificationsRouter from './controllers/notifications.js';
@@ -225,6 +227,9 @@ export default async function createApp() {
225
227
  app.use('/flows', flowsRouter);
226
228
  app.use('/folders', foldersRouter);
227
229
  app.use('/items', itemsRouter);
230
+ if (toBoolean(env['MCP_ENABLED']) === true) {
231
+ app.use('/mcp', mcpRouter);
232
+ }
228
233
  if (env['METRICS_ENABLED'] === true) {
229
234
  app.use('/metrics', metricsRouter);
230
235
  }
@@ -273,10 +273,11 @@ export function createOAuth2AuthRouter(providerName) {
273
273
  const codeVerifier = provider.generateCodeVerifier();
274
274
  const prompt = !!req.query['prompt'];
275
275
  const redirect = req.query['redirect'];
276
+ const otp = req.query['otp'];
276
277
  if (isLoginRedirectAllowed(redirect, providerName) === false) {
277
278
  throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` });
278
279
  }
279
- const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, getSecret(), {
280
+ const token = jwt.sign({ verifier: codeVerifier, redirect, prompt, otp }, getSecret(), {
280
281
  expiresIn: '5m',
281
282
  issuer: 'directus',
282
283
  });
@@ -299,7 +300,8 @@ export function createOAuth2AuthRouter(providerName) {
299
300
  logger.warn(e, `[OAuth2] Couldn't verify OAuth2 cookie`);
300
301
  throw new InvalidCredentialsError();
301
302
  }
302
- const { verifier, redirect, prompt } = tokenData;
303
+ const { verifier, prompt, otp } = tokenData;
304
+ let { redirect } = tokenData;
303
305
  const accountability = createDefaultAccountability({
304
306
  ip: getIPFromReq(req),
305
307
  });
@@ -321,7 +323,7 @@ export function createOAuth2AuthRouter(providerName) {
321
323
  code: req.query['code'],
322
324
  codeVerifier: verifier,
323
325
  state: req.query['state'],
324
- }, { session: authMode === 'session' });
326
+ }, { session: authMode === 'session', ...(otp ? { otp: String(otp) } : {}) });
325
327
  }
326
328
  catch (error) {
327
329
  // Prompt user for a new refresh_token if invalidated
@@ -342,6 +344,18 @@ export function createOAuth2AuthRouter(providerName) {
342
344
  throw error;
343
345
  }
344
346
  const { accessToken, refreshToken, expires } = authResponse;
347
+ try {
348
+ const claims = verifyJWT(accessToken, getSecret());
349
+ if (claims?.enforce_tfa === true) {
350
+ const url = new Url(env['PUBLIC_URL']).addPath('admin', 'tfa-setup');
351
+ if (redirect)
352
+ url.setQuery('redirect', redirect);
353
+ redirect = url.toString();
354
+ }
355
+ }
356
+ catch (e) {
357
+ logger.warn(e, `[OAuth2] Unexpected error during OAuth2 login`);
358
+ }
345
359
  if (redirect) {
346
360
  if (authMode === 'session') {
347
361
  res.cookie(env['SESSION_COOKIE_NAME'], accessToken, SESSION_COOKIE_OPTIONS);
@@ -325,10 +325,11 @@ export function createOpenIDAuthRouter(providerName) {
325
325
  const codeVerifier = provider.generateCodeVerifier();
326
326
  const prompt = !!req.query['prompt'];
327
327
  const redirect = req.query['redirect'];
328
+ const otp = req.query['otp'];
328
329
  if (isLoginRedirectAllowed(redirect, providerName) === false) {
329
330
  throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` });
330
331
  }
331
- const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, getSecret(), {
332
+ const token = jwt.sign({ verifier: codeVerifier, redirect, prompt, otp }, getSecret(), {
332
333
  expiresIn: (env[`AUTH_${providerName.toUpperCase()}_LOGIN_TIMEOUT`] ?? '5m'),
333
334
  issuer: 'directus',
334
335
  });
@@ -361,7 +362,8 @@ export function createOpenIDAuthRouter(providerName) {
361
362
  const url = new Url(env['PUBLIC_URL']).addPath('admin', 'login');
362
363
  return res.redirect(`${url.toString()}?reason=${ErrorCode.InvalidCredentials}`);
363
364
  }
364
- const { verifier, redirect, prompt } = tokenData;
365
+ const { verifier, prompt, otp } = tokenData;
366
+ let { redirect } = tokenData;
365
367
  const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
366
368
  const userAgent = req.get('user-agent')?.substring(0, 1024);
367
369
  if (userAgent)
@@ -382,7 +384,7 @@ export function createOpenIDAuthRouter(providerName) {
382
384
  codeVerifier: verifier,
383
385
  state: req.query['state'],
384
386
  iss: req.query['iss'],
385
- }, { session: authMode === 'session' });
387
+ }, { session: authMode === 'session', ...(otp ? { otp: String(otp) } : {}) });
386
388
  }
387
389
  catch (error) {
388
390
  // Prompt user for a new refresh_token if invalidated
@@ -404,6 +406,18 @@ export function createOpenIDAuthRouter(providerName) {
404
406
  throw error;
405
407
  }
406
408
  const { accessToken, refreshToken, expires } = authResponse;
409
+ try {
410
+ const claims = verifyJWT(accessToken, getSecret());
411
+ if (claims?.enforce_tfa === true) {
412
+ const url = new Url(env['PUBLIC_URL']).addPath('admin', 'tfa-setup');
413
+ if (redirect)
414
+ url.setQuery('redirect', redirect);
415
+ redirect = url.toString();
416
+ }
417
+ }
418
+ catch (e) {
419
+ logger.warn(e, `[OpenID] Unexpected error during OpenID login`);
420
+ }
407
421
  if (redirect) {
408
422
  if (authMode === 'session') {
409
423
  res.cookie(env['SESSION_COOKIE_NAME'], accessToken, SESSION_COOKIE_OPTIONS);
@@ -0,0 +1,2 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
@@ -0,0 +1,33 @@
1
+ import { ForbiddenError } from '@directus/errors';
2
+ import { Router } from 'express';
3
+ import { DirectusMCP } from '../mcp/index.js';
4
+ import { SettingsService } from '../services/settings.js';
5
+ import asyncHandler from '../utils/async-handler.js';
6
+ const router = Router();
7
+ const mcpHandler = asyncHandler(async (req, res) => {
8
+ const settings = new SettingsService({
9
+ schema: req.schema,
10
+ });
11
+ const { mcp_enabled, mcp_allow_deletes, mcp_prompts_collection, mcp_system_prompt, mcp_system_prompt_enabled } = await settings.readSingleton({
12
+ fields: [
13
+ 'mcp_enabled',
14
+ 'mcp_allow_deletes',
15
+ 'mcp_prompts_collection',
16
+ 'mcp_system_prompt',
17
+ 'mcp_system_prompt_enabled',
18
+ ],
19
+ });
20
+ if (!mcp_enabled) {
21
+ throw new ForbiddenError({ reason: 'MCP must be enabled' });
22
+ }
23
+ const mcp = new DirectusMCP({
24
+ promptsCollection: mcp_prompts_collection,
25
+ allowDeletes: mcp_allow_deletes,
26
+ systemPromptEnabled: mcp_system_prompt_enabled,
27
+ systemPrompt: mcp_system_prompt,
28
+ });
29
+ mcp.handleRequest(req, res);
30
+ });
31
+ router.get('/', mcpHandler);
32
+ router.post('/', mcpHandler);
33
+ export default router;
@@ -11,6 +11,8 @@ import { TFAService } from '../services/tfa.js';
11
11
  import { UsersService } from '../services/users.js';
12
12
  import asyncHandler from '../utils/async-handler.js';
13
13
  import { sanitizeQuery } from '../utils/sanitize-query.js';
14
+ import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
15
+ import { getDatabase } from '../database/index.js';
14
16
  const router = express.Router();
15
17
  router.use(useCollection('directus_users'));
16
18
  router.post('/', asyncHandler(async (req, res, next) => {
@@ -231,19 +233,27 @@ router.post('/me/tfa/generate/', asyncHandler(async (req, res, next) => {
231
233
  if (!req.accountability?.user) {
232
234
  throw new InvalidCredentialsError();
233
235
  }
234
- if (!req.body.password) {
236
+ const currentUser = await getDatabase()
237
+ .select('provider')
238
+ .from('directus_users')
239
+ .where({ id: req.accountability.user })
240
+ .first();
241
+ const requiresPassword = currentUser?.['provider'] === DEFAULT_AUTH_PROVIDER;
242
+ if (requiresPassword && !req.body.password) {
235
243
  throw new InvalidPayloadError({ reason: `"password" is required` });
236
244
  }
237
245
  const service = new TFAService({
238
246
  accountability: req.accountability,
239
247
  schema: req.schema,
240
248
  });
241
- const authService = new AuthenticationService({
242
- accountability: req.accountability,
243
- schema: req.schema,
244
- });
245
- await authService.verifyPassword(req.accountability.user, req.body.password);
246
- const { url, secret } = await service.generateTFA(req.accountability.user);
249
+ if (requiresPassword) {
250
+ const authService = new AuthenticationService({
251
+ accountability: req.accountability,
252
+ schema: req.schema,
253
+ });
254
+ await authService.verifyPassword(req.accountability.user, req.body.password);
255
+ }
256
+ const { url, secret } = await service.generateTFA(req.accountability.user, requiresPassword);
247
257
  res.locals['payload'] = { data: { secret, otpauth_url: url } };
248
258
  return next();
249
259
  }), respond);
@@ -154,9 +154,10 @@ router.get('/:pk/compare', asyncHandler(async (req, res, next) => {
154
154
  });
155
155
  const version = await service.readOne(req.params['pk']);
156
156
  const { outdated, mainHash } = await service.verifyHash(version['collection'], version['item'], version['hash']);
157
- const current = assign({}, version['delta']);
157
+ const delta = version.delta ?? {};
158
+ delta[req.schema.collections[version.collection].primary] = version.item;
158
159
  const main = await service.getMainItem(version['collection'], version['item']);
159
- res.locals['payload'] = { data: { outdated, mainHash, current, main } };
160
+ res.locals['payload'] = { data: { outdated, mainHash, current: delta, main } };
160
161
  return next();
161
162
  }), respond);
162
163
  router.post('/:pk/save', asyncHandler(async (req, res, next) => {
@@ -1,3 +1,3 @@
1
- import type { MSSQLError } from './types.js';
2
1
  import type { Item } from '@directus/types';
2
+ import type { MSSQLError } from './types.js';
3
3
  export declare function extractError(error: MSSQLError, data: Partial<Item>): Promise<MSSQLError | Error>;
@@ -5,14 +5,15 @@ var MSSQLErrorCodes;
5
5
  MSSQLErrorCodes[MSSQLErrorCodes["FOREIGN_KEY_VIOLATION"] = 547] = "FOREIGN_KEY_VIOLATION";
6
6
  MSSQLErrorCodes[MSSQLErrorCodes["NOT_NULL_VIOLATION"] = 515] = "NOT_NULL_VIOLATION";
7
7
  MSSQLErrorCodes[MSSQLErrorCodes["NUMERIC_VALUE_OUT_OF_RANGE"] = 220] = "NUMERIC_VALUE_OUT_OF_RANGE";
8
- MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION"] = 2601] = "UNIQUE_VIOLATION";
8
+ MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION_INDEX"] = 2601] = "UNIQUE_VIOLATION_INDEX";
9
+ MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION_CONSTRAINT"] = 2627] = "UNIQUE_VIOLATION_CONSTRAINT";
9
10
  MSSQLErrorCodes[MSSQLErrorCodes["VALUE_LIMIT_VIOLATION"] = 2628] = "VALUE_LIMIT_VIOLATION";
10
11
  })(MSSQLErrorCodes || (MSSQLErrorCodes = {}));
11
12
  export async function extractError(error, data) {
12
13
  switch (error.number) {
13
- case MSSQLErrorCodes.UNIQUE_VIOLATION:
14
- case 2627:
15
- return await uniqueViolation();
14
+ case MSSQLErrorCodes.UNIQUE_VIOLATION_CONSTRAINT:
15
+ case MSSQLErrorCodes.UNIQUE_VIOLATION_INDEX:
16
+ return await uniqueViolation(error);
16
17
  case MSSQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
17
18
  return numericValueOutOfRange();
18
19
  case MSSQLErrorCodes.VALUE_LIMIT_VIOLATION:
@@ -23,14 +24,20 @@ export async function extractError(error, data) {
23
24
  return foreignKeyViolation();
24
25
  }
25
26
  return error;
26
- async function uniqueViolation() {
27
+ async function uniqueViolation(error) {
27
28
  /**
28
29
  * NOTE:
29
- * SQL Server doesn't return the name of the offending column when a unique constraint is thrown:
30
+ * SQL Server doesn't return the name of the offending column when a unique error is thrown:
30
31
  *
32
+ * Constraint:
31
33
  * insert into [articles] ([unique]) values (@p0)
32
- * - Violation of UNIQUE KEY constraint 'UQ__articles__5A062640242004EB'.
33
- * Cannot insert duplicate key in object 'dbo.articles'. The duplicate key value is (rijk).
34
+ * - Violation of UNIQUE KEY constraint 'unique_contraint_name'. Cannot insert duplicate key in object 'dbo.article'.
35
+ * The duplicate key value is (rijk).
36
+ *
37
+ * Index:
38
+ * insert into [articles] ([unique]) values (@p0)
39
+ * - Cannot insert duplicate key row in object 'dbo.articles' with unique index 'unique_index_name'.
40
+ * The duplicate key value is (rijk).
34
41
  *
35
42
  * While it's not ideal, the best next thing we can do is extract the column name from
36
43
  * information_schema when this happens
@@ -41,8 +48,9 @@ export async function extractError(error, data) {
41
48
  const parenMatches = error.message.match(betweenParens);
42
49
  if (!quoteMatches || !parenMatches)
43
50
  return error;
44
- const keyName = quoteMatches[1].slice(1, -1);
45
- let collection = quoteMatches[0].slice(1, -1);
51
+ const [keyNameMatchIndex, collectionNameMatchIndex] = error.number === MSSQLErrorCodes.UNIQUE_VIOLATION_INDEX ? [1, 0] : [0, 1];
52
+ const keyName = quoteMatches[keyNameMatchIndex].slice(1, -1);
53
+ let collection = quoteMatches[collectionNameMatchIndex].slice(1, -1);
46
54
  let field = null;
47
55
  if (keyName) {
48
56
  const database = getDatabase();
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,18 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_settings', (table) => {
3
+ table.boolean('mcp_enabled').defaultTo(false).notNullable();
4
+ table.boolean('mcp_allow_deletes').defaultTo(false).notNullable();
5
+ table.string('mcp_prompts_collection').defaultTo(null).nullable();
6
+ table.boolean('mcp_system_prompt_enabled').defaultTo(true).notNullable();
7
+ table.text('mcp_system_prompt').defaultTo(null).nullable();
8
+ });
9
+ }
10
+ export async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('mcp_enabled');
13
+ table.dropColumn('mcp_allow_deletes');
14
+ table.dropColumn('mcp_prompts_collection');
15
+ table.dropColumn('mcp_system_prompt_enabled');
16
+ table.dropColumn('mcp_system_prompt');
17
+ });
18
+ }
@@ -0,0 +1,46 @@
1
+ # Internal RunAst Documentation
2
+
3
+ As the `runAst()` function is a crucial and complex part of Directus, this document intends to get you started by
4
+ providing a high level overview.
5
+
6
+ ## AST
7
+
8
+ When a request is made through REST, GQL or WebSockets, we internally turn this request into an AST before this AST
9
+ get's translated into one or more SQL requests which are then send to the database.
10
+
11
+ On the root level of an AST is the root node which then references a list of child nodes. Each child node can be either
12
+ a primitive field (FieldNode), a relational field (M2ONode, O2MNode, A2MNode) or a function field (FunctionFieldNode).
13
+ Relational fields can also have additional children and nested queries.
14
+
15
+ ### FieldNode
16
+
17
+ Describes a primitive field and is also a leaf node of the AST.
18
+
19
+ ### Relational Fields
20
+
21
+ The M2ONode, O2MNode and A2MNode all will recursively call the `runAst()` function and the resulting data will then be
22
+ injected into the data of the level above.
23
+
24
+ ### FunctionFieldNode
25
+
26
+ The only case where a function field node get's inserted into the ast, is for the count(o2m_relation) case, all other
27
+ functions are kept as FieldNode's with the name containing the function.
28
+
29
+ ## RunAst()
30
+
31
+ The `runAst()` does translates the AST into an executable SQL request, requests the appropiate data from the database
32
+ and then returns a single or a list of items as the result.
33
+
34
+ ### GetDBQuery()
35
+
36
+ The RootNode and relational fields have each their own query parameters. These parameters need to be applied to the SQL
37
+ query. This is done by the `getDBQuery()` function which internally uses `applyQuery()`.
38
+
39
+ <!-- TODO: Describe in larger detail what the GetDBQuery exactly does, i.e. why and when does it need an inner query, why does it sometimes call applyQuery() multiple times in different places -->
40
+
41
+ #### ApplyQuery()
42
+
43
+ `applyQuery()` takes the query and modifies the SQL request so that filters, sorts and all the other variants of query
44
+ parameters are applied.A special behaviour is filtering, sorting or other parameters on nested fields. To make this
45
+ possible, the `addJoin()` joins the required tables and then registers itself into the aliasMap so that the next time
46
+ the same collection should be joined, instead it will just reuse the already existing join.
@@ -0,0 +1,2 @@
1
+ import type { ToolConfig } from './types.js';
2
+ export declare function defineTool<Args>(tool: ToolConfig<Args>): ToolConfig<Args>;
@@ -0,0 +1,3 @@
1
+ export function defineTool(tool) {
2
+ return tool;
3
+ }
@@ -0,0 +1 @@
1
+ export * from './server.js';
@@ -0,0 +1 @@
1
+ export * from './server.js';