Sample code for 30+ languages & platforms
SQL Server Requires Chilkat v11.6.0+

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.

Background. Automatic tool use requires a conversation (created with NewConvo); it cannot be used with a stateless query. The event loop polls with 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.
Using multiple MCP servers. More than one MCP server can be used in the same conversation. Connect each server and call 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

SQL Server
--
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