Streaming End-to-End MCP Tool Use with an AI Model
See more AI Examples
Demonstrates Ai.UseMcp in a streaming Model Context Protocol (MCP) workflow. This is the same idea as the non-streaming MCP example, but the answer is streamed token-by-token and the automatic MCP tool call is driven by the application's event loop rather than happening invisibly inside Ask.
A tool-using conversation spans more than one streamed turn. In the first turn the model streams a js_function_call event; the example hands that event's JSON to StreamingJsToolCall, which routes MCP tools to the server and appends the result to the transcript, and the turn ends with null_terminator. The example then calls Ask again (with no new input, since the tool result is already in the transcript) so the model can continue and stream its final answer. The loop ends when a streamed turn finishes with no tool call outstanding.
Tip: Code to parse the returned JSON can be generated with Chilkat's online tool at https://tools.chilkat.io/jsonParse.
PollAi and reads events with NextAiEvent: a js_function_call event carries a tool request, empty means nothing to report this poll, and null_terminator marks the end of a streamed turn. The Mcp object must stay connected and in scope for the whole conversation. This example uses DeepWiki, a free, public, no-authentication Streamable HTTP MCP server; swap in any URL, setting AuthToken before Connect if a bearer token is required.UseMcp once per server — one call per MCP server — giving each a distinct prefix (for example deepwiki, weather, github) so tools from different servers never collide.Chilkat SQL Server Downloads
--
CREATE PROCEDURE ChilkatSample
AS
BEGIN
DECLARE @hr int
DECLARE @iTmp0 int
DECLARE @sTmp0 nvarchar(4000)
DECLARE @sTmp1 nvarchar(4000)
DECLARE @success int
SELECT @success = 0
-- Streaming, end-to-end MCP example. Same idea as the non-streaming MCP example, but the answer is
-- streamed token-by-token, and the automatic MCP tool call is driven by the application's event loop
-- instead of happening invisibly inside Ask.
--
-- A tool-using conversation spans more than one streamed turn:
--
-- turn 1: the model streams a "js_function_call" event (it wants a tool). We hand that event's
-- JSON to StreamingJsToolCall, which routes MCP tools to the server (Mcp.CallTool) and
-- appends the result to the transcript. The turn then ends with "null_terminator".
-- turn 2: we call Ask again (no new input -- the tool result is already in the transcript) so the
-- model can continue. It streams the final answer and ends with "null_terminator", with
-- no pending tool call.
--
-- The loop ends when a streamed turn finishes with no tool call outstanding.
--
-- MCP server: DeepWiki -- a free, public, no-authentication Streamable HTTP server that answers
-- questions about public GitHub repositories. Swap in any URL; set mcp.AuthToken before Connect if
-- it needs a bearer token.
-- 1) Connect to the MCP server.
DECLARE @mcp int
EXEC @hr = sp_OACreate 'Chilkat.Mcp', @mcp OUT
IF @hr <> 0
BEGIN
PRINT 'Failed to create ActiveX component'
RETURN
END
-- mcp.AuthToken = "..."; // <-- only if the server requires a bearer token
EXEC sp_OAMethod @mcp, 'Connect', @success OUT, 'https://mcp.deepwiki.com/mcp'
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @mcp, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
RETURN
END
EXEC sp_OAGetProperty @mcp, 'ServerName', @sTmp0 OUT
EXEC sp_OAGetProperty @mcp, 'ServerVersion', @sTmp1 OUT
PRINT 'Connected to MCP server: ' + @sTmp0 + ' (version ' + @sTmp1 + ')'
-- 2) Set up the AI conversation and register the MCP tools.
DECLARE @ai int
EXEC @hr = sp_OACreate 'Chilkat.Ai', @ai OUT
EXEC sp_OASetProperty @ai, 'Provider', 'anthropic'
-- The API key should come from a secure source rather than being hard-coded.
EXEC sp_OASetProperty @ai, 'ApiKey', 'AI_PROVIDER_API_KEY'
EXEC sp_OASetProperty @ai, 'Model', 'claude-sonnet-5'
-- Automatic tool use requires a conversation (not a stateless query).
EXEC sp_OAMethod @ai, 'NewConvo', @success OUT, 'test_conversation', 'You are a helpful assistant. Use the available tools when they can help answer the question.', ''
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
RETURN
END
-- Namespace the server's tools with "deepwiki" -- e.g. "deepwiki_ask_question". The Mcp object must
-- stay connected and in scope for the whole conversation.
EXEC sp_OAMethod @ai, 'UseMcp', @success OUT, @mcp, 'deepwiki'
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
RETURN
END
-- Multiple MCP servers can be used in the same conversation. Connect each server and call UseMcp
-- once per server, giving each a distinct prefix so their tool names cannot collide. For example:
-- success = ai.UseMcp(mcpWeather,"weather");
-- success = ai.UseMcp(mcpGitHub,"github");
EXEC sp_OAMethod @ai, 'InputAddText', @success OUT, 'Use the available tools to look up the GitHub repository "modelcontextprotocol/modelcontextprotocol", then give me a brief, sourced summary of which transports the Model Context Protocol defines.'
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
RETURN
END
-- 3) Start streaming.
EXEC sp_OASetProperty @ai, 'Streaming', 1
EXEC sp_OAMethod @ai, 'Ask', @success OUT, 'text'
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
RETURN
END
-- 4) Drive the streaming event loop.
DECLARE @sbEventName int
EXEC @hr = sp_OACreate 'Chilkat.StringBuilder', @sbEventName OUT
DECLARE @sbDelta int
EXEC @hr = sp_OACreate 'Chilkat.StringBuilder', @sbDelta OUT
DECLARE @sbFullResponse int
EXEC @hr = sp_OACreate 'Chilkat.StringBuilder', @sbFullResponse OUT
DECLARE @bConversationDone int
SELECT @bConversationDone = 0
DECLARE @bToolCallPending int
SELECT @bToolCallPending = 0
DECLARE @bCaseSensitive int
SELECT @bCaseSensitive = 1
DECLARE @bAbort int
SELECT @bAbort = 0
DECLARE @loopGuard int
SELECT @loopGuard = 0
DECLARE @result int
SELECT @result = 0
WHILE (@bConversationDone <> 1) and (@loopGuard < 8000)
BEGIN
SELECT @loopGuard = @loopGuard + 1
EXEC sp_OAMethod @ai, 'PollAi', @result OUT, @bAbort
IF @result < 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
RETURN
END
IF @result = 0
BEGIN
-- No event ready yet.
EXEC sp_OAMethod @ai, 'SleepMs', NULL, 100
END
ELSE
BEGIN
EXEC sp_OAMethod @ai, 'NextAiEvent', @success OUT, 5000, @sbEventName, @sbDelta
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
RETURN
END
EXEC sp_OAMethod @sbEventName, 'ContentsEqual', @iTmp0 OUT, 'js_function_call', @bCaseSensitive
IF @iTmp0 = 1
BEGIN
-- (a) The model is requesting a tool call. sbDelta holds the function-call JSON.
-- StreamingJsToolCall runs the tool -- routing MCP tools to the server -- and appends the
-- result to the transcript. It must be called before this turn's null_terminator.
PRINT '[tool call requested]'
EXEC sp_OAMethod @sbDelta, 'GetAsString', @sTmp0 OUT
PRINT @sTmp0
EXEC sp_OAMethod @ai, 'StreamingJsToolCall', @success OUT, @sbDelta
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
RETURN
END
SELECT @bToolCallPending = 1
END
ELSE
BEGIN
EXEC sp_OAMethod @sbEventName, 'ContentsEqual', @iTmp0 OUT, 'null_terminator', @bCaseSensitive
IF @iTmp0 = 1
BEGIN
-- (c) This streamed turn has finished.
IF @bToolCallPending = 1
BEGIN
-- Continue the conversation so the model can use the tool result. No new
-- InputAddText -- the tool result is already in the transcript.
SELECT @bToolCallPending = 0
EXEC sp_OAMethod @ai, 'Ask', @success OUT, 'text'
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
RETURN
END
END
ELSE
BEGIN
SELECT @bConversationDone = 1
END
END
ELSE
BEGIN
-- (b) An "empty" event reports nothing this poll. (d) Any other event is a normal
-- streamed text delta, which we display and accumulate.
EXEC sp_OAMethod @sbEventName, 'ContentsEqual', @iTmp0 OUT, 'empty', @bCaseSensitive
IF @iTmp0 <> 1
BEGIN
EXEC sp_OAMethod @sbEventName, 'GetAsString', @sTmp0 OUT
EXEC sp_OAMethod @sbDelta, 'GetAsString', @sTmp1 OUT
PRINT 'Event: ' + @sTmp0 + ' Delta: ' + @sTmp1
EXEC sp_OAMethod @sbFullResponse, 'AppendSb', @success OUT, @sbDelta
END
END
END
END
END
PRINT 'Final streamed response:'
EXEC sp_OAMethod @sbFullResponse, 'GetAsString', @sTmp0 OUT
PRINT @sTmp0
PRINT '----'
-- (Optional) The full transcript, including the tool call and its result.
DECLARE @convoJson int
EXEC @hr = sp_OACreate 'Chilkat.JsonObject', @convoJson OUT
EXEC sp_OASetProperty @convoJson, 'EmitCompact', 0
EXEC sp_OAMethod @ai, 'ExportConvo', @success OUT, 'test_conversation', @convoJson
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @ai, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
EXEC @hr = sp_OADestroy @convoJson
RETURN
END
PRINT 'Full Conversation:'
EXEC sp_OAMethod @convoJson, 'Emit', @sTmp0 OUT
PRINT @sTmp0
-- 5) Close the MCP session.
EXEC sp_OAMethod @mcp, 'Close', @success OUT
IF @success = 0
BEGIN
EXEC sp_OAGetProperty @mcp, 'LastErrorText', @sTmp0 OUT
PRINT @sTmp0
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
EXEC @hr = sp_OADestroy @convoJson
RETURN
END
EXEC @hr = sp_OADestroy @mcp
EXEC @hr = sp_OADestroy @ai
EXEC @hr = sp_OADestroy @sbEventName
EXEC @hr = sp_OADestroy @sbDelta
EXEC @hr = sp_OADestroy @sbFullResponse
EXEC @hr = sp_OADestroy @convoJson
END
GO