Skip to content

Commit 53cae3a

Browse files
committed
adding back todo mcp
1 parent fb2c7bc commit 53cae3a

3 files changed

Lines changed: 381 additions & 1 deletion

File tree

tests/integration/all-resource-types/bicep/source-apim-post-activation.bicep

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,331 @@ var mcpApiPolicyXml = '''
262262
</policies>
263263
'''
264264

265+
var mcpTodosPolicyXml = '''
266+
<policies>
267+
<inbound>
268+
<base />
269+
<set-variable name="rpcMethod" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?[&quot;method&quot;]?.ToString()) ?? string.Empty)" />
270+
<set-variable name="rpcId" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?[&quot;id&quot;]?.ToString()) ?? &quot;1&quot;)" />
271+
<set-variable name="toolName" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?.SelectToken(&quot;params.name&quot;)?.ToString()) ?? string.Empty)" />
272+
<set-variable name="toolId" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?.SelectToken(&quot;params.arguments.id&quot;)?.ToString()) ?? &quot;1&quot;)" />
273+
<set-variable name="createTodoText" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?.SelectToken(&quot;params.arguments.text&quot;)?.ToString()) ?? &quot;Prepare APIOps demo&quot;)" />
274+
<set-variable name="createCompleted" value="@((context.Request.Body?.As&lt;Newtonsoft.Json.Linq.JObject&gt;(preserveContent: true)?.SelectToken(&quot;params.arguments.completed&quot;)?.ToString()) ?? &quot;false&quot;)" />
275+
<choose>
276+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;initialize&quot;)">
277+
<return-response>
278+
<set-status code="200" reason="OK" />
279+
<set-header name="Content-Type" exists-action="override">
280+
<value>application/json</value>
281+
</set-header>
282+
<set-header name="MCP-Protocol-Version" exists-action="override">
283+
<value>2025-03-26</value>
284+
</set-header>
285+
<set-body>@{
286+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
287+
var idRaw = req["id"]?.ToString() ?? "1";
288+
long idLong;
289+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
290+
var response = new Newtonsoft.Json.Linq.JObject
291+
{
292+
["jsonrpc"] = "2.0",
293+
["id"] = idToken,
294+
["result"] = new Newtonsoft.Json.Linq.JObject
295+
{
296+
["protocolVersion"] = "2025-03-26",
297+
["serverInfo"] = new Newtonsoft.Json.Linq.JObject
298+
{
299+
["name"] = "apim-mcp-demo",
300+
["version"] = "1.1.0"
301+
},
302+
["capabilities"] = new Newtonsoft.Json.Linq.JObject
303+
{
304+
["tools"] = new Newtonsoft.Json.Linq.JObject()
305+
}
306+
}
307+
};
308+
return response.ToString(Newtonsoft.Json.Formatting.None);
309+
}</set-body>
310+
</return-response>
311+
</when>
312+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;tools/list&quot;)">
313+
<return-response>
314+
<set-status code="200" reason="OK" />
315+
<set-header name="Content-Type" exists-action="override">
316+
<value>application/json</value>
317+
</set-header>
318+
<set-body>@{
319+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
320+
var idRaw = req["id"]?.ToString() ?? "1";
321+
long idLong;
322+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
323+
var tools = new Newtonsoft.Json.Linq.JArray
324+
{
325+
new Newtonsoft.Json.Linq.JObject
326+
{
327+
["name"] = "healthCheck",
328+
["description"] = "Return APIM MCP server health",
329+
["inputSchema"] = new Newtonsoft.Json.Linq.JObject
330+
{
331+
["type"] = "object",
332+
["properties"] = new Newtonsoft.Json.Linq.JObject()
333+
}
334+
},
335+
new Newtonsoft.Json.Linq.JObject
336+
{
337+
["name"] = "listItems",
338+
["description"] = "List todo items from DummyJSON",
339+
["inputSchema"] = new Newtonsoft.Json.Linq.JObject
340+
{
341+
["type"] = "object",
342+
["properties"] = new Newtonsoft.Json.Linq.JObject()
343+
}
344+
},
345+
new Newtonsoft.Json.Linq.JObject
346+
{
347+
["name"] = "getItem",
348+
["description"] = "Get a todo item by id from DummyJSON",
349+
["inputSchema"] = new Newtonsoft.Json.Linq.JObject
350+
{
351+
["type"] = "object",
352+
["properties"] = new Newtonsoft.Json.Linq.JObject
353+
{
354+
["id"] = new Newtonsoft.Json.Linq.JObject
355+
{
356+
["type"] = "integer"
357+
}
358+
},
359+
["required"] = new Newtonsoft.Json.Linq.JArray { "id" }
360+
}
361+
},
362+
new Newtonsoft.Json.Linq.JObject
363+
{
364+
["name"] = "createItem",
365+
["description"] = "Create a todo item in DummyJSON",
366+
["inputSchema"] = new Newtonsoft.Json.Linq.JObject
367+
{
368+
["type"] = "object",
369+
["properties"] = new Newtonsoft.Json.Linq.JObject
370+
{
371+
["text"] = new Newtonsoft.Json.Linq.JObject { ["type"] = "string" },
372+
["completed"] = new Newtonsoft.Json.Linq.JObject { ["type"] = "boolean" }
373+
},
374+
["required"] = new Newtonsoft.Json.Linq.JArray { "text" }
375+
}
376+
}
377+
};
378+
379+
var response = new Newtonsoft.Json.Linq.JObject
380+
{
381+
["jsonrpc"] = "2.0",
382+
["id"] = idToken,
383+
["result"] = new Newtonsoft.Json.Linq.JObject { ["tools"] = tools }
384+
};
385+
return response.ToString(Newtonsoft.Json.Formatting.None);
386+
}</set-body>
387+
</return-response>
388+
</when>
389+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;tools/call&quot; &amp;&amp; (string)context.Variables[&quot;toolName&quot;] == &quot;healthCheck&quot;)">
390+
<return-response>
391+
<set-status code="200" reason="OK" />
392+
<set-header name="Content-Type" exists-action="override">
393+
<value>application/json</value>
394+
</set-header>
395+
<set-body>@{
396+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
397+
var idRaw = req["id"]?.ToString() ?? "1";
398+
long idLong;
399+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
400+
var payload = new Newtonsoft.Json.Linq.JObject
401+
{
402+
["status"] = "ok",
403+
["source"] = "apim",
404+
["timestampUtc"] = System.DateTime.UtcNow.ToString("o")
405+
};
406+
var response = new Newtonsoft.Json.Linq.JObject
407+
{
408+
["jsonrpc"] = "2.0",
409+
["id"] = idToken,
410+
["result"] = new Newtonsoft.Json.Linq.JObject
411+
{
412+
["content"] = new Newtonsoft.Json.Linq.JArray
413+
{
414+
new Newtonsoft.Json.Linq.JObject
415+
{
416+
["type"] = "text",
417+
["text"] = payload.ToString(Newtonsoft.Json.Formatting.None)
418+
}
419+
}
420+
}
421+
};
422+
return response.ToString(Newtonsoft.Json.Formatting.None);
423+
}</set-body>
424+
</return-response>
425+
</when>
426+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;tools/call&quot; &amp;&amp; (string)context.Variables[&quot;toolName&quot;] == &quot;listItems&quot;)">
427+
<send-request mode="new" response-variable-name="dummyResp" timeout="20" ignore-error="false">
428+
<set-url>https://dummyjson.com/todos?limit=10</set-url>
429+
<set-method>GET</set-method>
430+
</send-request>
431+
<set-variable name="dummyBody" value="@(((IResponse)context.Variables[&quot;dummyResp&quot;]).Body.As&lt;string&gt;(preserveContent: true))" />
432+
<return-response>
433+
<set-status code="200" reason="OK" />
434+
<set-header name="Content-Type" exists-action="override">
435+
<value>application/json</value>
436+
</set-header>
437+
<set-body>@{
438+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
439+
var idRaw = req["id"]?.ToString() ?? "1";
440+
long idLong;
441+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
442+
var text = (string)context.Variables["dummyBody"];
443+
var response = new Newtonsoft.Json.Linq.JObject
444+
{
445+
["jsonrpc"] = "2.0",
446+
["id"] = idToken,
447+
["result"] = new Newtonsoft.Json.Linq.JObject
448+
{
449+
["content"] = new Newtonsoft.Json.Linq.JArray
450+
{
451+
new Newtonsoft.Json.Linq.JObject
452+
{
453+
["type"] = "text",
454+
["text"] = text
455+
}
456+
}
457+
}
458+
};
459+
return response.ToString(Newtonsoft.Json.Formatting.None);
460+
}</set-body>
461+
</return-response>
462+
</when>
463+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;tools/call&quot; &amp;&amp; (string)context.Variables[&quot;toolName&quot;] == &quot;getItem&quot;)">
464+
<send-request mode="new" response-variable-name="dummyResp" timeout="20" ignore-error="false">
465+
<set-url>@($"https://dummyjson.com/todos/{(string)context.Variables[&quot;toolId&quot;]}")</set-url>
466+
<set-method>GET</set-method>
467+
</send-request>
468+
<set-variable name="dummyBody" value="@(((IResponse)context.Variables[&quot;dummyResp&quot;]).Body.As&lt;string&gt;(preserveContent: true))" />
469+
<return-response>
470+
<set-status code="200" reason="OK" />
471+
<set-header name="Content-Type" exists-action="override">
472+
<value>application/json</value>
473+
</set-header>
474+
<set-body>@{
475+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
476+
var idRaw = req["id"]?.ToString() ?? "1";
477+
long idLong;
478+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
479+
var text = (string)context.Variables["dummyBody"];
480+
var response = new Newtonsoft.Json.Linq.JObject
481+
{
482+
["jsonrpc"] = "2.0",
483+
["id"] = idToken,
484+
["result"] = new Newtonsoft.Json.Linq.JObject
485+
{
486+
["content"] = new Newtonsoft.Json.Linq.JArray
487+
{
488+
new Newtonsoft.Json.Linq.JObject
489+
{
490+
["type"] = "text",
491+
["text"] = text
492+
}
493+
}
494+
}
495+
};
496+
return response.ToString(Newtonsoft.Json.Formatting.None);
497+
}</set-body>
498+
</return-response>
499+
</when>
500+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;tools/call&quot; &amp;&amp; (string)context.Variables[&quot;toolName&quot;] == &quot;createItem&quot;)">
501+
<send-request mode="new" response-variable-name="dummyResp" timeout="20" ignore-error="false">
502+
<set-url>https://dummyjson.com/todos/add</set-url>
503+
<set-method>POST</set-method>
504+
<set-header name="Content-Type" exists-action="override">
505+
<value>application/json</value>
506+
</set-header>
507+
<set-body>@{
508+
var text = ((string)context.Variables["createTodoText"] ?? "Prepare APIOps demo").Replace("\\", "\\\\").Replace("\"", "\\\"");
509+
var completedRaw = ((string)context.Variables["createCompleted"] ?? "false").ToLowerInvariant();
510+
var completed = (completedRaw == "true") ? "true" : "false";
511+
return "{\"todo\":\"" + text + "\",\"completed\":" + completed + ",\"userId\":1}";
512+
}</set-body>
513+
</send-request>
514+
<set-variable name="dummyBody" value="@(((IResponse)context.Variables[&quot;dummyResp&quot;]).Body.As&lt;string&gt;(preserveContent: true))" />
515+
<return-response>
516+
<set-status code="200" reason="OK" />
517+
<set-header name="Content-Type" exists-action="override">
518+
<value>application/json</value>
519+
</set-header>
520+
<set-body>@{
521+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
522+
var idRaw = req["id"]?.ToString() ?? "1";
523+
long idLong;
524+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
525+
var text = (string)context.Variables["dummyBody"];
526+
var response = new Newtonsoft.Json.Linq.JObject
527+
{
528+
["jsonrpc"] = "2.0",
529+
["id"] = idToken,
530+
["result"] = new Newtonsoft.Json.Linq.JObject
531+
{
532+
["content"] = new Newtonsoft.Json.Linq.JArray
533+
{
534+
new Newtonsoft.Json.Linq.JObject
535+
{
536+
["type"] = "text",
537+
["text"] = text
538+
}
539+
}
540+
}
541+
};
542+
return response.ToString(Newtonsoft.Json.Formatting.None);
543+
}</set-body>
544+
</return-response>
545+
</when>
546+
<when condition="@((string)context.Variables[&quot;rpcMethod&quot;] == &quot;notifications/initialized&quot;)">
547+
<return-response>
548+
<set-status code="202" reason="Accepted" />
549+
</return-response>
550+
</when>
551+
<otherwise>
552+
<return-response>
553+
<set-status code="200" reason="OK" />
554+
<set-header name="Content-Type" exists-action="override">
555+
<value>application/json</value>
556+
</set-header>
557+
<set-body>@{
558+
var req = context.Request.Body?.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true) ?? new Newtonsoft.Json.Linq.JObject();
559+
var idRaw = req["id"]?.ToString() ?? "1";
560+
long idLong;
561+
Newtonsoft.Json.Linq.JToken idToken = long.TryParse(idRaw, out idLong) ? (Newtonsoft.Json.Linq.JToken)new Newtonsoft.Json.Linq.JValue(idLong) : new Newtonsoft.Json.Linq.JValue(idRaw);
562+
var response = new Newtonsoft.Json.Linq.JObject
563+
{
564+
["jsonrpc"] = "2.0",
565+
["id"] = idToken,
566+
["error"] = new Newtonsoft.Json.Linq.JObject
567+
{
568+
["code"] = -32601,
569+
["message"] = "Method not found"
570+
}
571+
};
572+
return response.ToString(Newtonsoft.Json.Formatting.None);
573+
}</set-body>
574+
</return-response>
575+
</otherwise>
576+
</choose>
577+
</inbound>
578+
<backend>
579+
<base />
580+
</backend>
581+
<outbound>
582+
<base />
583+
</outbound>
584+
<on-error>
585+
<base />
586+
</on-error>
587+
</policies>
588+
'''
589+
265590
var mcpExistingServerPolicyXml = '''
266591
<policies>
267592
<inbound>
@@ -330,6 +655,11 @@ resource apiMcpFromApi 'Microsoft.ApiManagement/service/apis@2025-09-01-preview'
330655
name: 'src-mcp-from-api'
331656
}
332657

658+
resource apiMcpTodos 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' existing = {
659+
parent: apim
660+
name: 'src-mcp-todos'
661+
}
662+
333663
resource apiMcpExistingServer 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' existing = {
334664
parent: apim
335665
name: 'src-mcp-existing-server'
@@ -371,6 +701,15 @@ resource apiMcpFromApiPolicy 'Microsoft.ApiManagement/service/apis/policies@2025
371701
}
372702
}
373703

704+
resource apiMcpTodosPolicy 'Microsoft.ApiManagement/service/apis/policies@2025-09-01-preview' = {
705+
parent: apiMcpTodos
706+
name: 'policy'
707+
properties: {
708+
format: 'rawxml'
709+
value: mcpTodosPolicyXml
710+
}
711+
}
712+
374713
resource apiMcpExistingServerPolicy 'Microsoft.ApiManagement/service/apis/policies@2025-09-01-preview' = {
375714
parent: apiMcpExistingServer
376715
name: 'policy'

tests/integration/all-resource-types/bicep/source-apim.bicep

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,31 @@ resource apiMcpExistingServer 'Microsoft.ApiManagement/service/apis@2025-09-01-p
971971
})
972972
}
973973

974+
// 8b. Policy-based MCP server exposing DummyJSON todo tools
975+
// Kept on its own API (separate from the Petstore tools on src-mcp-from-api) so
976+
// the two tool sets are isolated. The MCP behaviour is implemented entirely in
977+
// the api-level policy (mcpTodosPolicyXml in source-apim-post-activation.bicep),
978+
// so no mcpTools or backend are required here.
979+
resource apiMcpTodos 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
980+
parent: apim
981+
name: 'src-mcp-todos'
982+
properties: any({
983+
displayName: 'KS MCP Todos Server'
984+
description: 'Policy-based MCP server exposing DummyJSON todo tools (listItems, getItem, createItem, healthCheck)'
985+
path: 'ks/mcp-todos'
986+
protocols: ['https']
987+
subscriptionRequired: false
988+
type: 'mcp'
989+
mcpProperties: {
990+
endpoints: {
991+
mcp: {
992+
uriTemplate: '/mcp'
993+
}
994+
}
995+
}
996+
})
997+
}
998+
974999
resource apiA2aRuntimeMock 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
9751000
parent: apim
9761001
name: 'src-a2a-runtime-mock'

0 commit comments

Comments
 (0)