@sendinel/mcp-server 1.0.57 → 1.0.59
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/build/tools/compound.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anthropicResponseText } from '@sendinel/domain';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
4
|
import { adminDb, db } from '../db.js';
|
|
@@ -99,7 +100,7 @@ Do not include body_html. Sendinel renders body_html from blocks.
|
|
|
99
100
|
Respond with a JSON array.`,
|
|
100
101
|
}],
|
|
101
102
|
});
|
|
102
|
-
const raw = msg
|
|
103
|
+
const raw = anthropicResponseText(msg);
|
|
103
104
|
steps = extractJsonArray(raw).map((step, index) => {
|
|
104
105
|
const blocks = normalizeComposerBlocks(step.blocks, brandVisual.resolvedButtonColor);
|
|
105
106
|
return {
|
|
@@ -208,7 +209,7 @@ Do not include body_html. Sendinel renders body_html from blocks.
|
|
|
208
209
|
Respond with JSON: { "subject": "...", "blocks": [...], "body_text": "..." }`,
|
|
209
210
|
}],
|
|
210
211
|
});
|
|
211
|
-
const raw = msg
|
|
212
|
+
const raw = anthropicResponseText(msg);
|
|
212
213
|
const parsed = extractJsonObject(raw);
|
|
213
214
|
const blocks = normalizeComposerBlocks(parsed.blocks, brandVisual.resolvedButtonColor);
|
|
214
215
|
if (!blocks.length)
|
|
@@ -300,7 +301,7 @@ ${JSON.stringify(diagnosticData, null, 2)}
|
|
|
300
301
|
Identify the most likely root cause and give 3-5 concise actionable next steps.`,
|
|
301
302
|
}],
|
|
302
303
|
});
|
|
303
|
-
const analysis = analysisMsg
|
|
304
|
+
const analysis = anthropicResponseText(analysisMsg);
|
|
304
305
|
return j({
|
|
305
306
|
diagnostic_data: diagnosticData,
|
|
306
307
|
analysis,
|
package/build/tools/gateway.d.ts
CHANGED
|
@@ -14,4 +14,9 @@ export declare function matchesToolQuery(entry: {
|
|
|
14
14
|
group: string;
|
|
15
15
|
description: string;
|
|
16
16
|
}, query: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Keys in args that the target tool's JSON Schema does not declare. Returns [] when the schema
|
|
19
|
+
* has no `properties` or allows additional properties, so free-form tools keep working.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findUnknownArgKeys(inputSchema: unknown, args: Record<string, unknown>): string[];
|
|
17
22
|
export declare function registerGatewayTools(server: McpServer, options: GatewayOptions): void;
|
package/build/tools/gateway.js
CHANGED
|
@@ -54,6 +54,38 @@ async function buildCatalog(options) {
|
|
|
54
54
|
});
|
|
55
55
|
return { client, entries, byName: new Map(entries.map((entry) => [entry.name, entry])) };
|
|
56
56
|
}
|
|
57
|
+
/** Top-level keys invoke_tool accepts; anything else is rejected by name. */
|
|
58
|
+
function unknownKeyMessage(keys) {
|
|
59
|
+
const list = keys.map((k) => `'${k}'`).join(', ');
|
|
60
|
+
const hinted = keys.some((k) => ARGS_LOOKALIKES.has(k.toLowerCase()));
|
|
61
|
+
return `Unknown key(s) on invoke_tool: ${list}${hinted ? "; did you mean 'args'?" : ''} Valid keys: name, args (alias: arguments).`;
|
|
62
|
+
}
|
|
63
|
+
const ARGS_LOOKALIKES = new Set(['args', 'argument', 'params', 'input', 'parameters']);
|
|
64
|
+
function declaredProperties(inputSchema) {
|
|
65
|
+
if (!inputSchema || typeof inputSchema !== 'object')
|
|
66
|
+
return null;
|
|
67
|
+
const schema = inputSchema;
|
|
68
|
+
if (schema.additionalProperties)
|
|
69
|
+
return null;
|
|
70
|
+
const props = schema.properties;
|
|
71
|
+
if (!props || typeof props !== 'object' || Array.isArray(props))
|
|
72
|
+
return null;
|
|
73
|
+
return Object.keys(props);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Keys in args that the target tool's JSON Schema does not declare. Returns [] when the schema
|
|
77
|
+
* has no `properties` or allows additional properties, so free-form tools keep working.
|
|
78
|
+
*/
|
|
79
|
+
export function findUnknownArgKeys(inputSchema, args) {
|
|
80
|
+
const declared = declaredProperties(inputSchema);
|
|
81
|
+
if (!declared)
|
|
82
|
+
return [];
|
|
83
|
+
return Object.keys(args).filter((k) => !declared.includes(k));
|
|
84
|
+
}
|
|
85
|
+
function unknownArgsMessage(name, inputSchema, unknown) {
|
|
86
|
+
const valid = (declaredProperties(inputSchema) ?? []).sort();
|
|
87
|
+
return `Unknown argument(s) for ${name}: ${unknown.map((k) => `'${k}'`).join(', ')}. Valid arguments: ${valid.length ? valid.join(', ') : '(none)'}. Call describe_tool for the full schema.`;
|
|
88
|
+
}
|
|
57
89
|
export function registerGatewayTools(server, options) {
|
|
58
90
|
let catalogPromise = null;
|
|
59
91
|
const getCatalog = () => (catalogPromise ??= buildCatalog(options));
|
|
@@ -95,17 +127,33 @@ export function registerGatewayTools(server, options) {
|
|
|
95
127
|
});
|
|
96
128
|
server.registerTool('invoke_tool', {
|
|
97
129
|
title: 'Invoke Tool',
|
|
98
|
-
description: 'Execute one permitted Sendinel MCP tool by name with the same scope and approval enforcement as calling it directly. Use search_tools and describe_tool first.',
|
|
130
|
+
description: 'Execute one permitted Sendinel MCP tool by name with the same scope and approval enforcement as calling it directly. Pass the target tool arguments under args (alias: arguments). Unknown top-level keys and arguments the target tool does not declare are rejected. Use search_tools and describe_tool first.',
|
|
99
131
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
100
|
-
|
|
132
|
+
// Loose object so unknown top-level keys reach the handler (a raw shape would silently strip them).
|
|
133
|
+
inputSchema: z.looseObject({
|
|
101
134
|
name: z.string().min(1).describe('Tool name to invoke'),
|
|
102
|
-
args: z.record(z.string(), z.unknown()).optional().
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
args: z.record(z.string(), z.unknown()).optional().describe('Arguments for the target tool'),
|
|
136
|
+
arguments: z.record(z.string(), z.unknown()).optional().describe('Alias for args (the MCP-standard field name). Provide args or arguments, not both.'),
|
|
137
|
+
}),
|
|
138
|
+
}, async (input) => {
|
|
139
|
+
const { name, args, arguments: argumentsAlias, ...rest } = input;
|
|
140
|
+
const unknownKeys = Object.keys(rest);
|
|
141
|
+
if (unknownKeys.length > 0) {
|
|
142
|
+
return notFound(unknownKeyMessage(unknownKeys));
|
|
143
|
+
}
|
|
144
|
+
if (args !== undefined && argumentsAlias !== undefined && JSON.stringify(args) !== JSON.stringify(argumentsAlias)) {
|
|
145
|
+
return notFound("Both 'args' and 'arguments' were provided with different values. Provide either 'args' or 'arguments', not both.");
|
|
146
|
+
}
|
|
147
|
+
const effective = args ?? argumentsAlias ?? {};
|
|
105
148
|
const catalog = await getCatalog();
|
|
106
|
-
|
|
149
|
+
const entry = catalog.byName.get(name);
|
|
150
|
+
if (!entry) {
|
|
107
151
|
return notFound(`Tool not found or not permitted: ${name}`);
|
|
108
152
|
}
|
|
109
|
-
|
|
153
|
+
const unknownArgs = findUnknownArgKeys(entry.inputSchema, effective);
|
|
154
|
+
if (unknownArgs.length > 0) {
|
|
155
|
+
return notFound(unknownArgsMessage(name, entry.inputSchema, unknownArgs));
|
|
156
|
+
}
|
|
157
|
+
return await catalog.client.callTool({ name, arguments: effective });
|
|
110
158
|
});
|
|
111
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.59",
|
|
4
4
|
"description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
|
|
5
5
|
"mcpName": "ai.sendinel/mcp-server",
|
|
6
6
|
"type": "module",
|