Skip to content

Commit a57832f

Browse files
committed
first public version
0 parents  commit a57832f

File tree

7 files changed

+945
-0
lines changed

7 files changed

+945
-0
lines changed

+mcp/+util/verbatimJSON.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
classdef verbatimJSON
2+
% This class is undocumented and will change in a future release
3+
4+
% Copyright 2024-2025 The MathWorks, Inc.
5+
properties
6+
Value (1,1) string
7+
end
8+
methods
9+
function obj = verbatimJSON(str)
10+
obj.Value = str;
11+
end
12+
function json = jsonencode(obj,varargin)
13+
json = obj.Value;
14+
end
15+
end
16+
end

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.env
2+
*.asv
3+
*.mat
4+
._*
5+
.nfs*
6+
.DS_Store

README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# MATLAB MCP HTTP Client
2+
3+
Create MCP client in MATLAB® to call external tools in LLM workflows
4+
5+
Model Context Protocol (MCP) is a framework for communication between AI agents and external tools. Typically, a large language model (LLM) application sets up one or more MCP clients that each connect to an MCP server. The MCP server provides context and tools the LLM application can use.
6+
7+
This add\-on allows you to:
8+
- Create MCP clients to connect to streamable HTTP servers from MATLAB.
9+
- Call external tools.
10+
- Use external tools with the [Large Language Models (LLMs) with MATLAB](https://www.mathworks.com/matlabcentral/fileexchange/163796-large-language-models-llms-with-matlab) add\-on.
11+
12+
- [MATLAB MCP HTTP Client](#matlab-mcp-http-client)
13+
- [Setup](#setup)
14+
- [Use MATLAB Online](#use-matlab-online)
15+
- [Install Using Add-On Explorer](#install-using-add-on-explorer)
16+
- [Examples](#examples)
17+
- [Create MCP Client](#create-mcp-client)
18+
- [Automatically Call External Tool Using LLM](#automatically-call-external-tool-using-llm)
19+
- [Functions](#functions)
20+
- [mcpHTTPClient](#mcphttpclient)
21+
- [callTool](#calltool)
22+
23+
<a id="setup"></a>
24+
# Setup
25+
26+
Using this add\-on requires MATLAB R2025a or newer.
27+
28+
To generate and execute tool calls from MATLAB, you also need the [Large Language Models (LLMs) with MATLAB](https://www.mathworks.com/matlabcentral/fileexchange/163796-large-language-models-llms-with-matlab) add\-on.
29+
30+
## Use MATLAB Online
31+
32+
You can use the add\-on in MATLAB Online by clicking this link: [![Open in MATLAB Online](https://www.mathworks.com/images/responsive/global/open-in-matlab-online.svg)](https://matlab.mathworks.com/open/github/v1?repo=matlab-deep-learning/mcpHTTPClient)
33+
34+
35+
## Install Using Add\-On Explorer
36+
37+
The recommended way of using the add\-on in an installed version of MATLAB is to use the Add\-On Explorer.
38+
39+
1. In MATLAB, go to the **Home** tab, and in the **Environment** section, click the **Add\-Ons** icon.
40+
2. In the Add\-On Explorer, search for "MATLAB MCP HTTP Client".
41+
3. Select **Install**.
42+
43+
<a id="examples"></a>
44+
# Examples
45+
46+
<a id="create-mcp-client"></a>
47+
## Create MCP Client
48+
49+
Create an MCP client using the `mcpHTTPClient` function. Specify the URL of the MCP server to which you want to connect, for example, `endpoint = "https://mcp.example.com/mcp"`.
50+
51+
```matlab
52+
client = mcpHTTPClient(endpoint);
53+
```
54+
55+
The MCP client stores information about the available tools from the server in the `ServerTools` property.
56+
57+
```matlab
58+
serverTools = client.ServerTools;
59+
```
60+
61+
Inspect the first tool.
62+
63+
```matlab
64+
tool1 = serverTools{1};
65+
```
66+
67+
To call the first tool, use the `callTool` function and specify the name of the tool as a positional input argument. Then, specify the tool arguments as additional name-value arguments.
68+
69+
```matlab
70+
toolName = tool1.name;
71+
output = callTool(client,toolName,Argument1=Value1,...,ArgumentN=ValueN);
72+
```
73+
74+
<a id="automatically-call-external-tool-using-llm"></a>
75+
## Automatically Call External Tool Using LLM
76+
77+
This example shows how to use an LLM together with an MCP client to automatically call and execute external tools. The example requires the [Large Language Models (LLMs) with MATLAB](https://www.mathworks.com/matlabcentral/fileexchange/163796-large-language-models-llms-with-matlab) add\-on.
78+
79+
80+
First, configure the connection to the OpenAI® Chat Completion API following the Large Language Models (LLMs) with MATLAB documentation: [OpenAI](https://github.com/matlab-deep-learning/llms-with-matlab/blob/main/doc/OpenAI.md).
81+
82+
83+
Create an MCP client using the `mcpHTTPClient` function. Specify the URL of the MCP server to which you want to connect, for example, `endpoint = "https://mcp.example.com/mcp"`.
84+
85+
```matlab
86+
client = mcpHTTPClient(endpoint);
87+
```
88+
89+
The MCP client stores information about the available tools from the server in the `ServerTools` property.
90+
91+
```matlab
92+
serverTools = client.ServerTools;
93+
```
94+
95+
To use the tools provided by the MCP Client with the LLM, first convert the tools to an `openAIFunction` object. Connect to the OpenAI Chat Completion API. Give the model access to the server tools using the `Tools` argument.
96+
97+
```matlab
98+
f = openAIFunction(serverTools)
99+
model = openAIChat(Tools=f);
100+
```
101+
102+
Generate output using the `generate` function. Specify a prompt that could result in a tool call. For example, if you provide the model with tools that return information about the weather, then you can ask the model questions about the local weather.
103+
104+
```matlab
105+
userPrompt = "Is it raining in Cambridge?";
106+
[generatedText,completeOutput] = generate(model,userPrompt);
107+
```
108+
109+
If the model detects one or more tool calls, then the `generate` function returns information about the names and any input arguments in the `tool_calls` field of the `completeOutput` output structure.
110+
111+
Execute the first function call using the `callTool` function.
112+
113+
```matlab
114+
toolRequest = completeOutput.tool_calls(1).function;
115+
output = callTool(client,toolRequest);
116+
```
117+
118+
<a id="functions"></a>
119+
# Functions
120+
121+
<a id="mcphttpclient"></a>
122+
## mcpHTTPClient
123+
`client = mcpHTTPClient(endpoint)` returns an MCP client based on the MCP server URL `endpoint`.
124+
125+
The `mcpHTTPClient` object stores the tools associated with the MCP server in the `ServerTools` property, specified as a cell array of structs. Each struct contains information about one tool, including the tool name and arguments.
126+
127+
<a id="calltool"></a>
128+
## callTool
129+
`result = callTool(client,toolName,argumentName1=x1,argumentName2=x2,...)` calls a tool with name `toolName` with input argument `argumentName1` specified as `x1`, etc.
130+
131+
`result = callTool(client,toolRequest)` calls a tool request `toolRequest` returned by an LLM. For example, you can specify `toolRequest` as the `completeOutput.tool_calls.function` output of the [`generate`](https://github.com/matlab-deep-learning/llms-with-matlab/blob/main//doc/functions/generate.md) (Large Language Models (LLMs) with MATLAB) function.
132+
133+
#
134+
When using the MATLAB HTTP MCP Client, you should thoroughly review and validate all tool calls before you run them. Always keep a human in the loop for important actions and only proceed once you’re confident the call will do exactly what you expect. For more information, see information on [trust, safety and security with MCP](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) and [MCP security considerations](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#security-considerations).
135+
136+
*Copyright 2025 The MathWorks, Inc.*
137+

license.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2025, The MathWorks, Inc.
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are
6+
met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
* Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in
12+
the documentation and/or other materials provided with the distribution
13+
* Neither the name of the The MathWorks, Inc. nor the names
14+
of its contributors may be used to endorse or promote products derived
15+
from this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
POSSIBILITY OF SUCH DAMAGE.

mcpHTTPClient.m

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
classdef mcpHTTPClient < handle
2+
% An MCP client (streamable HTTP only)
3+
4+
% Copyright 2025 The MathWorks, Inc.
5+
properties (SetAccess=private)
6+
Endpoint
7+
ServerTools (1,:)
8+
end
9+
10+
properties (Access=private)
11+
counter = 0;
12+
end
13+
14+
properties (Hidden)
15+
% test seam
16+
Send = @send
17+
end
18+
19+
methods
20+
function obj = mcpHTTPClient(endpoint, varargin)
21+
obj.Endpoint = endpoint;
22+
% undocumented test seam
23+
if nargin > 2 && isequal(varargin{1}, "Send")
24+
obj.Send = varargin{2};
25+
end
26+
obj.ServerTools = obj.retrieveTools;
27+
end
28+
29+
function result = callTool(obj, tool, varargin)
30+
if isstruct(tool)
31+
toolName = string(tool.name);
32+
if isfield(tool,"arguments") && ~isstruct(tool.arguments)
33+
tool.arguments = mcp.util.verbatimJSON(tool.arguments);
34+
end
35+
else
36+
toolName = string(tool);
37+
tool = struct(...
38+
"name",tool,"arguments",struct(varargin{:}));
39+
end
40+
for k=1:numel(obj)
41+
toolNames = string(cellfun(@(s) s.name,obj(k).ServerTools,'UniformOutput',false));
42+
if ~any(toolName==toolNames)
43+
continue;
44+
end
45+
sessionIDField = obj(k).initialize();
46+
request = obj(k).makeRequest("tools/call", ...
47+
tool, sessionIDField);
48+
response = obj(k).Send(request,obj(k).Endpoint);
49+
if response.StatusCode == matlab.net.http.StatusCode.Unauthorized
50+
throwError("llms:needsAuthorization");
51+
elseif response.StatusCode ~= matlab.net.http.StatusCode.OK
52+
error(getServerErrorText(response));
53+
end
54+
data = response.Body.Data;
55+
if ~isstruct(data)
56+
data = erase(data,"data: ping"+optionalPattern(char(13))+newline);
57+
data = extractBetween(data,"data: ",optionalPattern(char(13))+newline);
58+
data = jsondecode(data);
59+
end
60+
if isfield(data,"error")
61+
error(data.error);
62+
end
63+
if isempty([data.result.content])
64+
result = "";
65+
else
66+
result = string(join([data.result.content.text],""));
67+
end
68+
if isfield(data.result,"isError") && isequal(data.result.isError,true)
69+
error(result);
70+
end
71+
return
72+
end
73+
% if we fall off the end of this for loop, nothing in the input
74+
% vector knows about that tool.
75+
throwError("llms:unknownTool")
76+
end
77+
end
78+
79+
methods (Access=private)
80+
function sessionIDField = initialize(obj)
81+
request = obj.makeRequest("initialize",struct(...
82+
"protocolVersion", "2025-06-18",...
83+
"capabilities",struct(),...
84+
"clientInfo",struct(...
85+
"name","MATLAB MCP HTTP Client",...
86+
"version","0.0.1"...
87+
)...
88+
),[]);
89+
90+
response = obj.Send(request,obj.Endpoint);
91+
92+
values = [response.Header.Value];
93+
sessionID = values([response.Header.Name] == "mcp-session-id");
94+
sessionIDField = matlab.net.http.field.GenericParameterizedField("mcp-session-id",sessionID);
95+
96+
initDone = obj.makeRequest("notifications/initialized",[],sessionIDField);
97+
obj.Send(initDone,obj.Endpoint);
98+
end
99+
100+
function body = makeBody(obj,method,params)
101+
payload = struct(...
102+
"jsonrpc","2.0",...
103+
"method",method);
104+
105+
if ~strcmp(method,"notifications/initialized")
106+
obj.counter = obj.counter + 1;
107+
payload.id = obj.counter;
108+
end
109+
if ~isequal(params,[])
110+
payload.params = params;
111+
end
112+
body = matlab.net.http.MessageBody(payload);
113+
end
114+
115+
function request = makeRequest(obj,method,params,sessionIDField)
116+
contentTypeField = matlab.net.http.field.ContentTypeField('application/json');
117+
type1 = matlab.net.http.MediaType("application/json");
118+
type2 = matlab.net.http.MediaType("text/event-stream");
119+
acceptField = matlab.net.http.field.AcceptField([type1 type2]);
120+
expectNothing = matlab.net.http.HeaderField('Expect', '');
121+
header = [acceptField contentTypeField sessionIDField expectNothing];
122+
123+
body = obj.makeBody(method,params);
124+
125+
method = matlab.net.http.RequestMethod.POST;
126+
request = matlab.net.http.RequestMessage(method,header,body);
127+
end
128+
129+
function tools = retrieveTools(obj)
130+
arguments
131+
obj (1,1) mcpHTTPClient
132+
end
133+
134+
sessionIDField = obj.initialize();
135+
request = obj.makeRequest("tools/list", struct(), sessionIDField);
136+
response = obj.Send(request,obj.Endpoint);
137+
if response.StatusCode == matlab.net.http.StatusCode.Unauthorized
138+
throwError("llms:needsAuthorization");
139+
elseif response.StatusCode ~= matlab.net.http.StatusCode.OK
140+
error(getServerErrorText(response));
141+
end
142+
tools = response.Body.Data;
143+
if ~isstruct(tools)
144+
tools = jsondecode("{"+extractBetween(tools,"data: {","}"+optionalPattern(char(13))+newline)+"}");
145+
end
146+
if isfield(tools,"result") && isfield(tools.result,"tools")
147+
tools = tools.result.tools;
148+
end
149+
if ~iscell(tools)
150+
tools = mat2cell(tools,ones(numel(tools),1));
151+
end
152+
end
153+
154+
end
155+
end
156+
157+
function errortxt = getServerErrorText(response)
158+
errortxt = response.Body.Data;
159+
if isstruct(errortxt)
160+
if isfield(errortxt,"error_description")
161+
errortxt = errortxt.error_description;
162+
else
163+
errortxt = jsonencode(errortxt);
164+
end
165+
end
166+
errortxt = string(errortxt);
167+
if response.Body.ContentType.Subtype == "html"
168+
errortxt = extractHTMLText(errortxt);
169+
end
170+
if strlength(errortxt) < 1
171+
errortxt = string(response.StatusLine);
172+
end
173+
end
174+
175+
function throwError(id)
176+
persistent catalog
177+
if isempty(catalog)
178+
catalog = dictionary("string", "string");
179+
catalog("llms:needsAuthorization") = "Servers that require authorization are not supported.";
180+
catalog("llms:unknownTool") = "Unknown tool";
181+
end
182+
throwAsCaller(MException(id,catalog(id)));
183+
end
184+

0 commit comments

Comments
 (0)