@dynatrace-oss/dynatrace-mcp-server 0.6.0-rc.2 → 0.6.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 +34 -54
- package/dist/index.js +53 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,12 +18,28 @@
|
|
|
18
18
|
</a>
|
|
19
19
|
</h4>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
The local _Dynatrace MCP server_ allows AI Assistants to interact with the [Dynatrace](https://www.dynatrace.com/) observability platform,
|
|
22
|
+
bringing real-time observability data directly into your development workflow.
|
|
23
23
|
|
|
24
24
|
> Note: This product is not officially supported by Dynatrace.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
If you need help, please contact us via [GitHub Issues](https://github.com/dynatrace-oss/dynatrace-mcp/issues) if you have feature requests, questions, or need help.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
You can add this MCP server to your MCP Client like VSCode, Claude, Cursor, Amazon Q, Windsurf, ChatGPT, or Github Copilot via the npmjs package `@dynatrace-oss/dynatrace-mcp-server`, and type `stdio`.
|
|
31
|
+
You can find more details about the configuration for different AI Assistants, Agents and MCP Clients in the [Configuration section below](#configuration).
|
|
32
|
+
|
|
33
|
+
Furthermore, you need your Dynatrace environment URL, e.g., `https://abc12345.apps.dynatrace.com`, as well as a [Platform Token](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/platform-tokens), e.g., `dt0s16.SAMPLE.abcd1234`, with [required scopes](#scopes-for-authentication).
|
|
34
|
+
|
|
35
|
+
Depending on your MCP Client, you need to configure these as environment variables or as settings in the UI:
|
|
36
|
+
|
|
37
|
+
- `DT_ENVIRONMENT` (string, e.g., `https://abc12345.apps.dynatrace.com`) - URL to your Dynatrace Platform (do not use Dynatrace classic URLs like `abc12345.live.dynatrace.com`)
|
|
38
|
+
- `DT_PLATFORM_TOKEN` (string, e.g., `dt0s16.SAMPLE.abcd1234`) - **Recommended**: Dynatrace Platform Token
|
|
39
|
+
|
|
40
|
+
Once you are done, we recommend looking into [example prompts](#-example-prompts-), like `Get all details of the entity 'my-service'` or `Show me error logs`. Please mind that these prompts lead to executing DQL statements which may incur [costs](#costs) in accordance to your licence.
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
27
43
|
|
|
28
44
|

|
|
29
45
|
|
|
@@ -184,7 +200,7 @@ rules/
|
|
|
184
200
|
|
|
185
201
|
For detailed information about the workshop rules, see the [Rules README](./dynatrace-agent-rules/rules/README.md).
|
|
186
202
|
|
|
187
|
-
##
|
|
203
|
+
## Configuration
|
|
188
204
|
|
|
189
205
|
You can add this MCP server (using STDIO) to your MCP Client like VS Code, Claude, Cursor, Amazon Q Developer CLI, Windsurf Github Copilot via the package `@dynatrace-oss/dynatrace-mcp-server`.
|
|
190
206
|
|
|
@@ -328,11 +344,11 @@ For fetching just error-logs, add `| filter loglevel == "ERROR"`.
|
|
|
328
344
|
|
|
329
345
|
You can set up authentication via **Platform Tokens** (recommended) or **OAuth Client** via the following environment variables:
|
|
330
346
|
|
|
331
|
-
- `DT_ENVIRONMENT` (string, e.g., https://abc12345.apps.dynatrace.com) - URL to your Dynatrace Platform (do not use Dynatrace classic URLs like `abc12345.live.dynatrace.com`)
|
|
347
|
+
- `DT_ENVIRONMENT` (string, e.g., `https://abc12345.apps.dynatrace.com`) - URL to your Dynatrace Platform (do not use Dynatrace classic URLs like `abc12345.live.dynatrace.com`)
|
|
332
348
|
- `DT_PLATFORM_TOKEN` (string, e.g., `dt0s16.SAMPLE.abcd1234`) - **Recommended**: Dynatrace Platform Token
|
|
333
349
|
- `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Alternative: Dynatrace OAuth Client ID (for advanced use cases)
|
|
334
350
|
- `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Alternative: Dynatrace OAuth Client Secret (for advanced use cases)
|
|
335
|
-
- `DT_GRAIL_QUERY_BUDGET_GB` (number, default: 1000) - Budget limit in GB (base 1000) for Grail query bytes scanned per session. The MCP server tracks your Grail usage and warns when approaching or exceeding this limit.
|
|
351
|
+
- `DT_GRAIL_QUERY_BUDGET_GB` (number, default: `1000`) - Budget limit in GB (base 1000) for Grail query bytes scanned per session. The MCP server tracks your Grail usage and warns when approaching or exceeding this limit.
|
|
336
352
|
|
|
337
353
|
**Platform Tokens are recommended** for most use cases as they provide a simpler authentication flow. OAuth Clients should only be used when specific OAuth features are required.
|
|
338
354
|
|
|
@@ -384,6 +400,18 @@ and extend them as needed. They're here to help you imagine how real-time observ
|
|
|
384
400
|
|
|
385
401
|
### **Basic Queries & AI Assistance**
|
|
386
402
|
|
|
403
|
+
**Find a monitored entity**
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
Get all details of the entity 'my-service'
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Find error logs**
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
Show me error logs
|
|
413
|
+
```
|
|
414
|
+
|
|
387
415
|
**Write a DQL query from natural language:**
|
|
388
416
|
|
|
389
417
|
```
|
|
@@ -608,51 +636,3 @@ To disable usage tracking, add this to your environment:
|
|
|
608
636
|
```bash
|
|
609
637
|
DT_MCP_DISABLE_TELEMETRY=true
|
|
610
638
|
```
|
|
611
|
-
|
|
612
|
-
## Development
|
|
613
|
-
|
|
614
|
-
For local development purposes, you can use VSCode and GitHub Copilot.
|
|
615
|
-
|
|
616
|
-
First, enable Copilot for your Workspace `.vscode/settings.json`:
|
|
617
|
-
|
|
618
|
-
```json
|
|
619
|
-
{
|
|
620
|
-
"github.copilot.enable": {
|
|
621
|
-
"*": true
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
and make sure that you are using Agent Mode in Copilot.
|
|
627
|
-
|
|
628
|
-
Second, add the MCP to `.vscode/mcp.json`:
|
|
629
|
-
|
|
630
|
-
```json
|
|
631
|
-
{
|
|
632
|
-
"servers": {
|
|
633
|
-
"my-dynatrace-mcp-server": {
|
|
634
|
-
"command": "node",
|
|
635
|
-
"args": ["--watch", "${workspaceFolder}/dist/index.js"],
|
|
636
|
-
"envFile": "${workspaceFolder}/.env"
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
Third, create a `.env` file in this repository (you can copy from `.env.template`) and configure environment variables as [described above](#environment-variables).
|
|
643
|
-
|
|
644
|
-
Finally, make changes to your code and compile it with `npm run build` or just run `npm run watch` and it auto-compiles.
|
|
645
|
-
|
|
646
|
-
## Releasing
|
|
647
|
-
|
|
648
|
-
When you are preparing for a release, you can use GitHub Copilot to guide you through the preparations.
|
|
649
|
-
|
|
650
|
-
In Visual Studio Code, you can use `/release` in the chat with Copilot in Agent Mode, which will execute [release.prompt.md](.github/prompts/release.prompt.md).
|
|
651
|
-
|
|
652
|
-
You may include additional information such as the version number. If not specified, you will be asked.
|
|
653
|
-
|
|
654
|
-
This will
|
|
655
|
-
|
|
656
|
-
- prepare the [changelog](CHANGELOG.md),
|
|
657
|
-
- update the version number in [package.json](package.json),
|
|
658
|
-
- commit the changes.
|
package/dist/index.js
CHANGED
|
@@ -129,7 +129,7 @@ const main = async () => {
|
|
|
129
129
|
},
|
|
130
130
|
});
|
|
131
131
|
// quick abstraction/wrapper to make it easier for tools to reply text instead of JSON
|
|
132
|
-
const tool = (name, description, paramsSchema, cb) => {
|
|
132
|
+
const tool = (name, description, paramsSchema, annotations, cb) => {
|
|
133
133
|
const wrappedCb = async (args) => {
|
|
134
134
|
// track starttime for telemetry
|
|
135
135
|
const startTime = Date.now();
|
|
@@ -168,10 +168,12 @@ const main = async () => {
|
|
|
168
168
|
.catch((e) => console.warn('Failed to track tool usage:', e));
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
|
-
server.tool(name, description, paramsSchema, (args) => wrappedCb(args));
|
|
171
|
+
server.tool(name, description, paramsSchema, annotations, (args) => wrappedCb(args));
|
|
172
172
|
};
|
|
173
173
|
/** Tool Definitions below */
|
|
174
|
-
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.', {},
|
|
174
|
+
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.', {}, {
|
|
175
|
+
readOnlyHint: true,
|
|
176
|
+
}, async ({}) => {
|
|
175
177
|
// create an oauth-client
|
|
176
178
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
177
179
|
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
@@ -195,6 +197,8 @@ const main = async () => {
|
|
|
195
197
|
.number()
|
|
196
198
|
.default(25)
|
|
197
199
|
.describe('Maximum number of vulnerabilities to display in the response.'),
|
|
200
|
+
}, {
|
|
201
|
+
readOnlyHint: true,
|
|
198
202
|
}, async ({ riskScore, additionalFilter, maxVulnerabilitiesToDisplay }) => {
|
|
199
203
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read', 'storage:buckets:read', 'storage:security.events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
200
204
|
const result = await (0, list_vulnerabilities_1.listVulnerabilities)(dtClient, additionalFilter, riskScore);
|
|
@@ -236,6 +240,8 @@ const main = async () => {
|
|
|
236
240
|
.optional()
|
|
237
241
|
.describe('Additional filter for DQL statement for dt.davis.problems, e.g., \'entity_tags == array("dt.owner:team-foobar", "tag:tag")\''),
|
|
238
242
|
maxProblemsToDisplay: zod_1.z.number().default(10).describe('Maximum number of problems to display in the response.'),
|
|
243
|
+
}, {
|
|
244
|
+
readOnlyHint: true,
|
|
239
245
|
}, async ({ additionalFilter, maxProblemsToDisplay }) => {
|
|
240
246
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read', 'storage:buckets:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
241
247
|
// get problems (uses fetch)
|
|
@@ -270,6 +276,8 @@ const main = async () => {
|
|
|
270
276
|
});
|
|
271
277
|
tool('find_entity_by_name', 'Get the entityId of a monitored entity (service, host, process-group, application, kubernetes-node, ...) within the topology based on the name of the entity on Dynatrace', {
|
|
272
278
|
entityName: zod_1.z.string().describe('Name of the entity to search for, e.g., "my-service" or "my-host"'),
|
|
279
|
+
}, {
|
|
280
|
+
readOnlyHint: true,
|
|
273
281
|
}, async ({ entityName }) => {
|
|
274
282
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
275
283
|
const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
|
|
@@ -277,6 +285,8 @@ const main = async () => {
|
|
|
277
285
|
});
|
|
278
286
|
tool('get_entity_details', 'Get details of a monitored entity based on the entityId on Dynatrace', {
|
|
279
287
|
entityId: zod_1.z.string().optional(),
|
|
288
|
+
}, {
|
|
289
|
+
readOnlyHint: true,
|
|
280
290
|
}, async ({ entityId }) => {
|
|
281
291
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
282
292
|
const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
|
|
@@ -314,6 +324,9 @@ const main = async () => {
|
|
|
314
324
|
tool('send_slack_message', 'Sends a Slack message to a dedicated Slack Channel via Slack Connector on Dynatrace', {
|
|
315
325
|
channel: zod_1.z.string().optional(),
|
|
316
326
|
message: zod_1.z.string().optional(),
|
|
327
|
+
}, {
|
|
328
|
+
// not read-only, not open-world, not destructive
|
|
329
|
+
readOnlyHint: false,
|
|
317
330
|
}, async ({ channel, message }) => {
|
|
318
331
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('app-settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
319
332
|
const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
|
|
@@ -321,6 +334,9 @@ const main = async () => {
|
|
|
321
334
|
});
|
|
322
335
|
tool('verify_dql', 'Verify a Dynatrace Query Language (DQL) statement on Dynatrace GRAIL before executing it. This step is recommended for DQL statements that have been dynamically created by non-expert tools. For statements coming from the `generate_dql_from_natural_language` tool as well as from documentation, this step can be omitted.', {
|
|
323
336
|
dqlStatement: zod_1.z.string(),
|
|
337
|
+
}, {
|
|
338
|
+
readOnlyHint: true,
|
|
339
|
+
idempotentHint: true, // same input always yields same output
|
|
324
340
|
}, async ({ dqlStatement }) => {
|
|
325
341
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
326
342
|
const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
|
|
@@ -345,6 +361,12 @@ const main = async () => {
|
|
|
345
361
|
dqlStatement: zod_1.z
|
|
346
362
|
.string()
|
|
347
363
|
.describe('DQL Statement (Ex: "fetch [logs, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}.", or for metrics: "timeseries { avg(<metric-name>), value.A = avg(<metric-name>, scalar: true) }")'),
|
|
364
|
+
}, {
|
|
365
|
+
// not readonly (DQL statements may modify things), not idempotent (may change over time)
|
|
366
|
+
readOnlyHint: false,
|
|
367
|
+
idempotentHint: false,
|
|
368
|
+
// while we are not strictly talking to the open world here, the response from execute DQL could interpreted as a web-search, which often is referred to open-world
|
|
369
|
+
openWorldHint: true,
|
|
348
370
|
}, async ({ dqlStatement }) => {
|
|
349
371
|
// Create a HTTP Client that has all storage:*:read scopes
|
|
350
372
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
@@ -404,6 +426,9 @@ const main = async () => {
|
|
|
404
426
|
text: zod_1.z
|
|
405
427
|
.string()
|
|
406
428
|
.describe('Natural language description of what you want to query. Be specific and include time ranges, entities, and metrics of interest.'),
|
|
429
|
+
}, {
|
|
430
|
+
readOnlyHint: true,
|
|
431
|
+
idempotentHint: true,
|
|
407
432
|
}, async ({ text }) => {
|
|
408
433
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:nl2dql:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
409
434
|
const response = await (0, davis_copilot_1.generateDqlFromNaturalLanguage)(dtClient, text);
|
|
@@ -430,6 +455,9 @@ const main = async () => {
|
|
|
430
455
|
});
|
|
431
456
|
tool('explain_dql_in_natural_language', 'Explain Dynatrace Query Language (DQL) statements in natural language using Davis CoPilot AI.', {
|
|
432
457
|
dql: zod_1.z.string().describe('The DQL statement to explain'),
|
|
458
|
+
}, {
|
|
459
|
+
readOnlyHint: true,
|
|
460
|
+
idempotentHint: true,
|
|
433
461
|
}, async ({ dql }) => {
|
|
434
462
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:dql2nl:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
435
463
|
const response = await (0, davis_copilot_1.explainDqlInNaturalLanguage)(dtClient, dql);
|
|
@@ -451,6 +479,10 @@ const main = async () => {
|
|
|
451
479
|
text: zod_1.z.string().describe('Your question or request for Davis CoPilot'),
|
|
452
480
|
context: zod_1.z.string().optional().describe('Optional context to provide additional information'),
|
|
453
481
|
instruction: zod_1.z.string().optional().describe('Optional instruction for how to format the response'),
|
|
482
|
+
}, {
|
|
483
|
+
readOnlyHint: true,
|
|
484
|
+
idempotentHint: true,
|
|
485
|
+
openWorldHint: true, // web-search like characteristics
|
|
454
486
|
}, async ({ text, context, instruction }) => {
|
|
455
487
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:conversations:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
456
488
|
const conversationContext = [];
|
|
@@ -500,6 +532,10 @@ const main = async () => {
|
|
|
500
532
|
teamName: zod_1.z.string().optional(),
|
|
501
533
|
channel: zod_1.z.string().optional(),
|
|
502
534
|
isPrivate: zod_1.z.boolean().optional().default(false),
|
|
535
|
+
}, {
|
|
536
|
+
// not read only, not idempotent
|
|
537
|
+
readOnlyHint: false,
|
|
538
|
+
idempotentHint: false, // creating the same workflow multiple times is possible
|
|
503
539
|
}, async ({ problemType, teamName, channel, isPrivate }) => {
|
|
504
540
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
505
541
|
const response = await (0, create_workflow_for_problem_notification_1.createWorkflowForProblemNotification)(dtClient, teamName, channel, problemType, isPrivate);
|
|
@@ -517,6 +553,10 @@ const main = async () => {
|
|
|
517
553
|
});
|
|
518
554
|
tool('make_workflow_public', 'Modify a workflow and make it publicly available to everyone on the Dynatrace Environment', {
|
|
519
555
|
workflowId: zod_1.z.string().optional(),
|
|
556
|
+
}, {
|
|
557
|
+
// not read only, but idempotent
|
|
558
|
+
readOnlyHint: false,
|
|
559
|
+
idempotentHint: true, // making the same workflow public multiple times yields the same result
|
|
520
560
|
}, async ({ workflowId }) => {
|
|
521
561
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
522
562
|
const response = await (0, update_workflow_1.updateWorkflow)(dtClient, workflowId, {
|
|
@@ -529,6 +569,8 @@ const main = async () => {
|
|
|
529
569
|
.string()
|
|
530
570
|
.optional()
|
|
531
571
|
.describe(`The Kubernetes (K8s) Cluster Id, referred to as k8s.cluster.uid (this is NOT the Dynatrace environment)`),
|
|
572
|
+
}, {
|
|
573
|
+
readOnlyHint: true,
|
|
532
574
|
}, async ({ clusterId }) => {
|
|
533
575
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
534
576
|
const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
|
|
@@ -536,6 +578,8 @@ const main = async () => {
|
|
|
536
578
|
});
|
|
537
579
|
tool('get_ownership', 'Get detailed Ownership information for one or multiple entities on Dynatrace', {
|
|
538
580
|
entityIds: zod_1.z.string().optional().describe('Comma separated list of entityIds'),
|
|
581
|
+
}, {
|
|
582
|
+
readOnlyHint: true,
|
|
539
583
|
}, async ({ entityIds }) => {
|
|
540
584
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:entities:read', 'settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
541
585
|
console.error(`Fetching ownership for ${entityIds}`);
|
|
@@ -545,7 +589,10 @@ const main = async () => {
|
|
|
545
589
|
resp += JSON.stringify(ownershipInformation);
|
|
546
590
|
return resp;
|
|
547
591
|
});
|
|
548
|
-
tool('reset_grail_budget', 'Reset the Grail query budget after it was exhausted, allowing new queries to be executed. This clears all tracked bytes scanned in the current session.', {},
|
|
592
|
+
tool('reset_grail_budget', 'Reset the Grail query budget after it was exhausted, allowing new queries to be executed. This clears all tracked bytes scanned in the current session.', {}, {
|
|
593
|
+
readonlyHint: false, // modifies state
|
|
594
|
+
idempotentHint: true, // multiple resets yield the same result
|
|
595
|
+
}, async ({}) => {
|
|
549
596
|
// Reset the global tracker
|
|
550
597
|
(0, grail_budget_tracker_1.resetGrailBudgetTracker)();
|
|
551
598
|
// Get a fresh tracker to show the reset state
|
|
@@ -572,6 +619,8 @@ You can now execute new Grail queries (DQL, etc.) again. If this happens more of
|
|
|
572
619
|
bccRecipients: zod_1.z.array(zod_1.z.string().email()).optional().describe('Array of email addresses for BCC recipients'),
|
|
573
620
|
subject: zod_1.z.string().describe('Subject line of the email'),
|
|
574
621
|
body: zod_1.z.string().describe('Body content of the email (plain text only)'),
|
|
622
|
+
}, {
|
|
623
|
+
openWorldHint: true, // email is as close to the open-world as we can get with our system
|
|
575
624
|
}, async ({ toRecipients, ccRecipients, bccRecipients, subject, body }) => {
|
|
576
625
|
// Validate total recipients limit (10 max across TO, CC, and BCC)
|
|
577
626
|
const totalRecipients = toRecipients.length + (ccRecipients?.length || 0) + (bccRecipients?.length || 0);
|
package/package.json
CHANGED