Skip to content

Commit d2af666

Browse files
committed
Enhance Tools List with OpenAPI Schema #7487
1 parent 1ae245a commit d2af666

3 files changed

Lines changed: 100 additions & 24 deletions

File tree

.github/ISSUE/epic-architect-github-workflow-as-mcp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ We will employ a rapid and agile development approach. The scope and API specifi
4141
- **Goal:** Evolve the server from a REST API to a true MCP tool-providing server.
4242
- **Sub-Tasks:**
4343
- `ticket-refactor-to-mcp-tool-server.md`: Implement `tools/list` and `tools/call` endpoints to dynamically expose the server's capabilities.
44+
- `ticket-enhance-tools-list-with-schema.md`: Enhance the `/tools/list` endpoint to include OpenAPI schema definitions for each tool's parameters and responses.
4445

4546
### Future Scope
4647
- Implementation of the API endpoints.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Enhance Tools List with OpenAPI Schema
3+
labels: enhancement, AI
4+
---
5+
6+
GH ticket id: #7387
7+
8+
**Epic:** #7477
9+
**Phase:** 3
10+
**Assignee:** tobiu
11+
**Status:** Done
12+
13+
## Description
14+
15+
See: https://modelcontextprotocol.io/specification/2025-06-18/server/tools
16+
17+
The current `/tools/list` endpoint provides only the name and description of available tools. To enable robust client-side integration and dynamic tool generation for the agent, the tool list needs to include the full OpenAPI schema for each tool's parameters and response, as dictated by the Model Context Protocol (MCP) specification.
18+
19+
This enhancement will allow the agent to fully understand how to call each tool, including required arguments, their types, and the expected return format, directly from the `/tools/list` response.
20+
21+
## Acceptance Criteria
22+
23+
1. The `toolService.mjs` is updated to extract relevant schema information (parameters, requestBody) for each operation from the `openapi.yaml` and convert it into a single `inputSchema` (JSON Schema) for each tool.
24+
2. The `GET /tools/list` endpoint's response for each tool includes:
25+
- `name` (string): The unique identifier for the tool (from `operationId`).
26+
- `title` (string): A human-readable title for the tool (from `summary`).
27+
- `description` (string): A detailed description of the tool (from `description`).
28+
- `inputSchema` (JSON Schema object): A JSON Schema defining the tool's parameters, derived from the OpenAPI operation's `parameters` and `requestBody`.
29+
3. The `callTool` function in `toolService.mjs` is updated to remove the manual `switch` statement for argument mapping. It should expect `args` to conform to the `inputSchema` and use a more generic method to pass them to the `tool.handler`.
30+
4. The `outputSchema` (JSON Schema object) is optionally included in the tool definition if available in the OpenAPI response.

ai/mcp/server/github-workflow/services/toolService.mjs

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,51 @@ const serviceMapping = {
1818
...pullRequestService
1919
};
2020

21+
/**
22+
* Converts OpenAPI parameters and requestBody into a JSON Schema inputSchema.
23+
* @param {object} operation - The OpenAPI operation object.
24+
* @returns {object} A JSON Schema object for the input.
25+
*/
26+
function buildInputSchema(operation) {
27+
const schema = {
28+
type: 'object',
29+
properties: {},
30+
required: []
31+
};
32+
33+
// Handle parameters (path, query, header, cookie)
34+
if (operation.parameters) {
35+
for (const param of operation.parameters) {
36+
schema.properties[param.name] = {
37+
type: param.schema.type,
38+
description: param.description
39+
};
40+
if (param.required) {
41+
schema.required.push(param.name);
42+
}
43+
}
44+
}
45+
46+
// Handle requestBody
47+
if (operation.requestBody && operation.requestBody.content && operation.requestBody.content['application/json']) {
48+
const requestBodySchema = operation.requestBody.content['application/json'].schema;
49+
// Assuming requestBody is a simple object for now
50+
if (requestBodySchema.properties) {
51+
for (const [propName, propSchema] of Object.entries(requestBodySchema.properties)) {
52+
schema.properties[propName] = {
53+
type: propSchema.type,
54+
description: propSchema.description
55+
};
56+
if (requestBodySchema.required && requestBodySchema.required.includes(propName)) {
57+
schema.required.push(propName);
58+
}
59+
}
60+
}
61+
}
62+
63+
return schema;
64+
}
65+
2166
/**
2267
* Parses the openapi.yaml file to build a mapping of tool names to service functions.
2368
*/
@@ -34,9 +79,11 @@ function initializeToolMapping() {
3479
for (const [method, operation] of Object.entries(pathItem)) {
3580
if (operation.operationId) {
3681
toolMapping[operation.operationId] = {
37-
path,
38-
method,
39-
description: operation.description,
82+
name: operation.operationId,
83+
title: operation.summary || operation.operationId,
84+
description: operation.description || operation.summary,
85+
inputSchema: buildInputSchema(operation),
86+
// outputSchema: buildOutputSchema(operation), // To be implemented later
4087
handler: serviceMapping[operation.operationId]
4188
};
4289
}
@@ -50,9 +97,11 @@ function initializeToolMapping() {
5097
*/
5198
function listTools() {
5299
initializeToolMapping();
53-
return Object.entries(toolMapping).map(([name, tool]) => ({
54-
name: name,
55-
description: tool.description
100+
return Object.values(toolMapping).map(tool => ({
101+
name: tool.name,
102+
title: tool.title,
103+
description: tool.description,
104+
inputSchema: tool.inputSchema
56105
}));
57106
}
58107

@@ -70,27 +119,23 @@ async function callTool(toolName, args) {
70119
throw new Error(`Tool "${toolName}" not found or not implemented.`);
71120
}
72121

73-
// Explicitly map arguments based on toolName
74-
switch (toolName) {
75-
case 'listLabels':
76-
return tool.handler();
77-
case 'listPullRequests':
78-
return tool.handler(args);
79-
case 'checkoutPullRequest':
80-
case 'getPullRequestDiff':
81-
case 'getConversation':
82-
return tool.handler(args.prNumber);
83-
case 'createComment':
84-
return tool.handler(args.prNumber, args.body);
85-
case 'addLabels':
86-
case 'removeLabels':
87-
return tool.handler(args.issueNumber, args.labels);
88-
default:
89-
throw new Error(`Unknown tool: ${toolName}`);
122+
// Dynamically extract arguments based on the inputSchema
123+
const handlerArgs = [];
124+
const inputSchema = tool.inputSchema;
125+
126+
if (inputSchema && inputSchema.properties) {
127+
for (const propName of Object.keys(inputSchema.properties)) {
128+
// For now, we assume the order of properties in inputSchema.properties
129+
// matches the order of arguments expected by the handler function.
130+
// This is a simplification and might need refinement for complex cases.
131+
handlerArgs.push(args[propName]);
132+
}
90133
}
134+
135+
return tool.handler(...handlerArgs);
91136
}
92137

93138
export {
94139
listTools,
95140
callTool
96-
};
141+
};

0 commit comments

Comments
 (0)