Skip to content

Commit 4aecb45

Browse files
authored
Merge pull request #27 from mcpc-tech/feat/improve-agentic-schema-validation
feat: improve agentic mode schema by removing `action`
2 parents 2395e3b + cbb4a12 commit 4aecb45

File tree

8 files changed

+379
-132
lines changed

8 files changed

+379
-132
lines changed

packages/core/src/executors/agentic/agentic-executor.ts

Lines changed: 78 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ export class AgenticExecutor {
1616
private allToolNames: string[],
1717
private toolNameToDetailList: [string, unknown][],
1818
private server: ComposableMCPServer,
19-
private ACTION_KEY: string = "action",
20-
private NEXT_ACTION_KEY: string = "nextAction",
19+
private USE_TOOL_KEY: string = "useTool",
2120
) {
2221
this.logger = createLogger(`mcpc.agentic.${name}`, server);
2322

@@ -48,12 +47,15 @@ export class AgenticExecutor {
4847
): Promise<CallToolResult> {
4948
// Create a span for this execute call
5049
const executeSpan: Span | null = this.tracingEnabled
51-
? startSpan("mcpc.agentic_execute", {
52-
agent: this.name,
53-
action: String(args[this.ACTION_KEY] ?? "unknown"),
54-
nextAction: String(args[this.NEXT_ACTION_KEY] ?? "none"),
55-
args: JSON.stringify(args),
56-
}, parentSpan ?? undefined)
50+
? startSpan(
51+
"mcpc.agentic_execute",
52+
{
53+
agent: this.name,
54+
selectTool: String(args[this.USE_TOOL_KEY] ?? "unknown"),
55+
args: JSON.stringify(args),
56+
},
57+
parentSpan ?? undefined,
58+
)
5759
: null;
5860

5961
try {
@@ -69,7 +71,7 @@ export class AgenticExecutor {
6971

7072
this.logger.warning({
7173
message: "Validation failed",
72-
action: args[this.ACTION_KEY],
74+
selectTool: args[this.USE_TOOL_KEY],
7375
error: validationResult.error,
7476
});
7577

@@ -86,16 +88,16 @@ export class AgenticExecutor {
8688
};
8789
}
8890

89-
const actionName = args[this.ACTION_KEY] as string;
91+
const useTool = args[this.USE_TOOL_KEY] as string;
92+
const definitionsOf = (args.definitionsOf as string[]) || [];
93+
const hasDefinitions = (args.hasDefinitions as string[]) || [];
9094

91-
// Update span name to include action
92-
if (executeSpan && actionName) {
95+
// Update span name to include selected tool
96+
if (executeSpan && useTool) {
9397
try {
94-
const safeAction = String(actionName).replace(/\s+/g, "_");
98+
const safeTool = String(useTool).replace(/\s+/g, "_");
9599
if (typeof (executeSpan as any).updateName === "function") {
96-
(executeSpan as any).updateName(
97-
`mcpc.agentic_execute.${safeAction}`,
98-
);
100+
(executeSpan as any).updateName(`mcpc.agentic_execute.${safeTool}`);
99101
}
100102
} catch {
101103
// Ignore errors while updating span name
@@ -104,58 +106,37 @@ export class AgenticExecutor {
104106

105107
// First check external tools
106108
const currentTool = this.toolNameToDetailList.find(
107-
([name, _detail]: [string, unknown]) => name === actionName,
109+
([name, _detail]: [string, unknown]) => name === useTool,
108110
)?.[1] as
109111
| { execute: (args: unknown) => Promise<CallToolResult> }
110112
| undefined;
111113

112114
if (currentTool) {
113115
// Execute external tool
114-
const nextAction = args[this.NEXT_ACTION_KEY] as string;
115-
116116
if (executeSpan) {
117117
executeSpan.setAttributes({
118118
toolType: "external",
119-
actionName: actionName,
120-
nextAction: nextAction || "none",
119+
selectedTool: useTool,
121120
});
122121
}
123122

124123
this.logger.debug({
125124
message: "Executing external tool",
126-
action: actionName,
127-
nextAction: nextAction,
125+
selectTool: useTool,
128126
});
129127

130128
const currentResult = await currentTool.execute({
131-
...(args[actionName] as Record<string, unknown>),
129+
...(args[useTool] as Record<string, unknown>),
132130
});
133131

134-
if (args[nextAction]) {
135-
currentResult?.content?.push({
136-
type: "text",
137-
text: CompiledPrompts.actionSuccess({
138-
toolName: this.name,
139-
nextAction: nextAction,
140-
currentAction: actionName,
141-
}),
142-
});
143-
} else {
144-
currentResult?.content?.push({
145-
type: "text",
146-
text: CompiledPrompts.planningPrompt({
147-
currentAction: actionName,
148-
toolName: this.name,
149-
}),
150-
});
151-
}
132+
// Provide tool schemas if requested
133+
this.appendToolSchemas(currentResult, definitionsOf, hasDefinitions);
152134

153135
if (executeSpan) {
154136
executeSpan.setAttributes({
155137
success: true,
156138
isError: !!currentResult.isError,
157139
resultContentLength: currentResult.content?.length || 0,
158-
hasNextAction: !!args[nextAction],
159140
toolResult: JSON.stringify(currentResult),
160141
});
161142
endSpan(executeSpan);
@@ -165,54 +146,35 @@ export class AgenticExecutor {
165146
}
166147

167148
// If not found in external tools, check internal tools
168-
if (this.allToolNames.includes(actionName)) {
149+
if (this.allToolNames.includes(useTool)) {
169150
if (executeSpan) {
170151
executeSpan.setAttributes({
171152
toolType: "internal",
172-
actionName: actionName,
153+
selectedTool: useTool,
173154
});
174155
}
175156

176157
this.logger.debug({
177158
message: "Executing internal tool",
178-
action: actionName,
159+
selectTool: useTool,
179160
});
180161

181162
try {
182163
const result = await this.server.callTool(
183-
actionName,
184-
args[actionName] as Record<string, unknown>,
164+
useTool,
165+
args[useTool] as Record<string, unknown>,
185166
);
186167

187-
const nextAction = args[this.NEXT_ACTION_KEY] as string;
188168
const callToolResult = (result as CallToolResult) ?? { content: [] };
189169

190-
if (nextAction && this.allToolNames.includes(nextAction)) {
191-
callToolResult.content.push({
192-
type: "text",
193-
text: CompiledPrompts.actionSuccess({
194-
toolName: this.name,
195-
nextAction: nextAction,
196-
currentAction: actionName,
197-
}),
198-
});
199-
} else {
200-
callToolResult.content.push({
201-
type: "text",
202-
text: CompiledPrompts.planningPrompt({
203-
currentAction: actionName,
204-
toolName: this.name,
205-
}),
206-
});
207-
}
170+
// Provide tool schemas if requested
171+
this.appendToolSchemas(callToolResult, definitionsOf, hasDefinitions);
208172

209173
if (executeSpan) {
210174
executeSpan.setAttributes({
211175
success: true,
212176
isError: !!callToolResult.isError,
213177
resultContentLength: callToolResult.content?.length || 0,
214-
hasNextAction:
215-
!!(nextAction && this.allToolNames.includes(nextAction)),
216178
toolResult: JSON.stringify(callToolResult),
217179
});
218180
endSpan(executeSpan);
@@ -226,15 +188,15 @@ export class AgenticExecutor {
226188

227189
this.logger.error({
228190
message: "Error executing internal tool",
229-
action: actionName,
191+
useTool,
230192
error: String(error),
231193
});
232194

233195
return {
234196
content: [
235197
{
236198
type: "text",
237-
text: `Error executing internal tool ${actionName}: ${
199+
text: `Error executing internal tool ${useTool}: ${
238200
error instanceof Error ? error.message : String(error)
239201
}`,
240202
},
@@ -248,25 +210,22 @@ export class AgenticExecutor {
248210
if (executeSpan) {
249211
executeSpan.setAttributes({
250212
toolType: "not_found",
251-
actionName: actionName || "unknown",
213+
useTool: useTool || "unknown",
252214
completion: true,
253215
});
254216
endSpan(executeSpan);
255217
}
256218

257219
this.logger.debug({
258220
message: "Tool not found, returning completion message",
259-
action: actionName,
221+
useTool,
260222
});
261223

262-
return {
263-
content: [
264-
{
265-
type: "text",
266-
text: CompiledPrompts.completionMessage(),
267-
},
268-
],
224+
const result: CallToolResult = {
225+
content: [],
269226
};
227+
this.appendToolSchemas(result, definitionsOf, hasDefinitions);
228+
return result;
270229
} catch (error) {
271230
// Catch any unexpected errors
272231
if (executeSpan) {
@@ -292,6 +251,45 @@ export class AgenticExecutor {
292251
}
293252
}
294253

254+
// Append tool schemas to result if requested
255+
private appendToolSchemas(
256+
result: CallToolResult,
257+
definitionsOf: string[],
258+
hasDefinitions: string[],
259+
): void {
260+
// Filter out tools that are already available
261+
const schemasToProvide = definitionsOf.filter(
262+
(toolName) => !hasDefinitions.includes(toolName),
263+
);
264+
265+
if (schemasToProvide.length === 0) {
266+
return;
267+
}
268+
269+
const definitionTexts: string[] = [];
270+
271+
for (const toolName of schemasToProvide) {
272+
const toolDetail = this.toolNameToDetailList.find(
273+
([name]) => name === toolName,
274+
);
275+
276+
if (toolDetail) {
277+
const [name, schema] = toolDetail;
278+
const schemaJson = JSON.stringify(schema, null, 2);
279+
definitionTexts.push(
280+
`<tool_definition name="${name}">\n${schemaJson}\n</tool_definition>`,
281+
);
282+
}
283+
}
284+
285+
if (definitionTexts.length > 0) {
286+
result.content.push({
287+
type: "text",
288+
text: `${definitionTexts.join("\n\n")}`,
289+
});
290+
}
291+
}
292+
295293
// Validate arguments using JSON schema
296294
validate(
297295
args: Record<string, unknown>,
@@ -300,10 +298,6 @@ export class AgenticExecutor {
300298
valid: boolean;
301299
error?: string;
302300
} {
303-
// Skip validation for complete decision
304-
if (args.decision === "complete") {
305-
return { valid: true };
306-
}
307301
return validateSchema(args, schema);
308302
}
309303
}

0 commit comments

Comments
 (0)