From c971b0147f2a817da73137ae604f3eb62c7153b8 Mon Sep 17 00:00:00 2001 From: Alex Hofer <62021660+Alex-Hofer@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:49:49 +0200 Subject: [PATCH 1/2] Python: Add models for the MCP server SDKs (mcp, fastmcp) --- .../lib/change-notes/2026-09-30-mcp-models.md | 4 + .../python/frameworks/fastmcp.model.yml | 35 +++++ .../semmle/python/frameworks/mcp.model.yml | 33 +++++ .../frameworks/fastmcp/MaDTest.expected | 0 .../frameworks/fastmcp/MaDTest.ql | 2 + .../library-tests/frameworks/fastmcp/test.py | 122 ++++++++++++++++++ .../frameworks/mcp/MaDTest.expected | 0 .../library-tests/frameworks/mcp/MaDTest.ql | 2 + .../test/library-tests/frameworks/mcp/auth.py | 16 +++ .../frameworks/mcp/fastmcp_v1.py | 47 +++++++ .../frameworks/mcp/lowlevel_v1.py | 22 ++++ .../frameworks/mcp/lowlevel_v2.py | 25 ++++ .../frameworks/mcp/mcpserver_v2.py | 29 +++++ 13 files changed, 337 insertions(+) create mode 100644 python/ql/lib/change-notes/2026-09-30-mcp-models.md create mode 100644 python/ql/lib/semmle/python/frameworks/fastmcp.model.yml create mode 100644 python/ql/lib/semmle/python/frameworks/mcp.model.yml create mode 100644 python/ql/test/library-tests/frameworks/fastmcp/MaDTest.expected create mode 100644 python/ql/test/library-tests/frameworks/fastmcp/MaDTest.ql create mode 100644 python/ql/test/library-tests/frameworks/fastmcp/test.py create mode 100644 python/ql/test/library-tests/frameworks/mcp/MaDTest.expected create mode 100644 python/ql/test/library-tests/frameworks/mcp/MaDTest.ql create mode 100644 python/ql/test/library-tests/frameworks/mcp/auth.py create mode 100644 python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py create mode 100644 python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py create mode 100644 python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py create mode 100644 python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py diff --git a/python/ql/lib/change-notes/2026-09-30-mcp-models.md b/python/ql/lib/change-notes/2026-09-30-mcp-models.md new file mode 100644 index 000000000000..d85cb4d6a06d --- /dev/null +++ b/python/ql/lib/change-notes/2026-09-30-mcp-models.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added modeling of the `mcp` (the official Model Context Protocol SDK) and `fastmcp` PyPI packages. The parameters of MCP tool, resource and prompt handlers, the HTTP headers that `fastmcp` handlers read through `get_http_headers()` or `get_http_request()`, and the bearer token that token verifiers (`verify_token`) and `get_access_token()` receive, are now sources of the `remote` threat model. diff --git a/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml b/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml new file mode 100644 index 000000000000..6045d15a8a17 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml @@ -0,0 +1,35 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: sourceModel + data: + # `fastmcp` (PyPI), a standalone MCP server framework. The parameters of tool, resource and + # prompt handlers are chosen by the MCP client; `tool` and `prompt` also work without parentheses. + - ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'remote'] + - ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] + - ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'remote'] + - ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'remote'] + - ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'remote'] + - ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'remote'] + - ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'remote'] + # The HTTP headers of the transport. + - ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_headers].ReturnValue', 'remote'] + - ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_request].ReturnValue.Member[headers]', 'remote'] + # The bearer token of the Authorization header reaches token verifiers and the access-token helper. + - ['fastmcp.server.auth.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote'] + - ['fastmcp.server.auth.AuthProvider!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote'] + - ['fastmcp', 'Member[server].Member[dependencies].Member[get_access_token].ReturnValue.Member[token]', 'remote'] + + - addsTo: + pack: codeql/python-all + extensible: typeModel + data: + # The classes are re-exported; instances of any import path are instances of the class. + - ['fastmcp.FastMCP!', 'fastmcp', 'Member[server].Member[FastMCP]'] + - ['fastmcp.FastMCP!', 'fastmcp', 'Member[server].Member[server].Member[FastMCP]'] + - ['fastmcp.FastMCP', 'fastmcp.FastMCP!', 'Instance'] + - ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[FunctionTool]'] + - ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[base].Member[Tool]'] + - ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[function_tool].Member[FunctionTool]'] + - ['fastmcp.server.auth.TokenVerifier!', 'fastmcp', 'Member[server].Member[auth].Member[auth].Member[TokenVerifier]'] + - ['fastmcp.server.auth.AuthProvider!', 'fastmcp', 'Member[server].Member[auth].Member[auth].Member[AuthProvider]'] diff --git a/python/ql/lib/semmle/python/frameworks/mcp.model.yml b/python/ql/lib/semmle/python/frameworks/mcp.model.yml new file mode 100644 index 000000000000..3adffeaa24d5 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/mcp.model.yml @@ -0,0 +1,33 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: sourceModel + data: + # `mcp`, the official Model Context Protocol SDK. The parameters of tool, resource and prompt + # handlers are chosen by the MCP client, that is by an LLM or by anyone who can reach the server. + # `FastMCP` (1.x) was renamed to `MCPServer` (2.x). + - ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] + - ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote'] + - ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] + - ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote'] + # The low-level server: decorators in 1.x, constructor handlers taking `(ctx, params)` in 2.x. + - ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] + - ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'remote'] + - ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'remote'] + # The bearer token of the Authorization header reaches token verifiers and the access-token helper. + - ['mcp.server.auth.provider.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote'] + - ['mcp', 'Member[server].Member[auth].Member[middleware].Member[auth_context].Member[get_access_token].ReturnValue.Member[token]', 'remote'] + + - addsTo: + pack: codeql/python-all + extensible: typeModel + data: + # The classes are re-exported; instances of any import path are instances of the class. + - ['mcp.server.fastmcp.FastMCP!', 'mcp', 'Member[server].Member[fastmcp].Member[server].Member[FastMCP]'] + - ['mcp.server.fastmcp.FastMCP', 'mcp.server.fastmcp.FastMCP!', 'Instance'] + - ['mcp.server.mcpserver.MCPServer!', 'mcp', 'Member[server].Member[MCPServer]'] + - ['mcp.server.mcpserver.MCPServer!', 'mcp', 'Member[server].Member[mcpserver].Member[server].Member[MCPServer]'] + - ['mcp.server.mcpserver.MCPServer', 'mcp.server.mcpserver.MCPServer!', 'Instance'] + - ['mcp.server.lowlevel.Server!', 'mcp', 'Member[server].Member[Server]'] + - ['mcp.server.lowlevel.Server!', 'mcp', 'Member[server].Member[lowlevel].Member[server].Member[Server]'] + - ['mcp.server.lowlevel.Server', 'mcp.server.lowlevel.Server!', 'Instance'] diff --git a/python/ql/test/library-tests/frameworks/fastmcp/MaDTest.expected b/python/ql/test/library-tests/frameworks/fastmcp/MaDTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/fastmcp/MaDTest.ql b/python/ql/test/library-tests/frameworks/fastmcp/MaDTest.ql new file mode 100644 index 000000000000..fef4356ab35e --- /dev/null +++ b/python/ql/test/library-tests/frameworks/fastmcp/MaDTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.MaDTest diff --git a/python/ql/test/library-tests/frameworks/fastmcp/test.py b/python/ql/test/library-tests/frameworks/fastmcp/test.py new file mode 100644 index 000000000000..22e26e021cef --- /dev/null +++ b/python/ql/test/library-tests/frameworks/fastmcp/test.py @@ -0,0 +1,122 @@ +from fastmcp import FastMCP +from fastmcp.server import FastMCP as ServerFastMCP +from fastmcp.server.server import FastMCP as ModuleFastMCP +from fastmcp.server.auth import AuthProvider, TokenVerifier +from fastmcp.server.auth.auth import TokenVerifier as ModuleTokenVerifier +from fastmcp.server.dependencies import get_access_token, get_http_headers, get_http_request +from fastmcp.tools import FunctionTool, Tool, tool +from fastmcp.tools.base import Tool as BaseTool +from fastmcp.tools.function_tool import FunctionTool as ModuleFunctionTool + +app = FastMCP("test") +server_app = ServerFastMCP("test") +module_app = ModuleFastMCP("test") + + +@app.tool +def bare(command): # $ mad-source__remote=command + return command + + +@server_app.tool(name="exec") +def called(command): # $ mad-source__remote=command + return command + + +@module_app.resource("files://{path}") +def resource(path): # $ mad-source__remote=path + return path + + +@app.prompt +def bare_prompt(topic): # $ mad-source__remote=topic + return topic + + +@app.prompt() +def called_prompt(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style + return topic + style + + +def added(command): # $ mad-source__remote=command + return command + + +app.add_tool(added) + + +def added_prompt(topic): # $ mad-source__remote=topic + return topic + + +app.add_prompt(prompt=added_prompt) + + +def via_tool(url): # $ mad-source__remote=url + return url + + +def via_function_tool(url): # $ mad-source__remote=url + return url + + +def via_base_tool(url): # $ mad-source__remote=url + return url + + +def via_module_function_tool(url): # $ mad-source__remote=url + return url + + +Tool.from_function(via_tool) +FunctionTool.from_function(via_function_tool) +BaseTool.from_function(fn=via_base_tool) +ModuleFunctionTool.from_function(via_module_function_tool) + + +@tool +def standalone(command): # $ mad-source__remote=command + return command + + +@tool() +def standalone_called(command): # $ mad-source__remote=command + return command + + +@app.tool +def forward_header(): + return get_http_headers()["x-target"] # $ mad-source__remote=get_http_headers() + + +@app.tool +def forward_request_header(): + return get_http_request().headers["x-target"] # $ mad-source__remote=get_http_request().headers + + +def not_registered(value): + return value + + +class Verifier(TokenVerifier): + async def verify_token(self, token): # $ mad-source__remote=token + return token + + +class ModuleVerifier(ModuleTokenVerifier): + async def verify_token(self, token): # $ mad-source__remote=token + return token + + +class Provider(AuthProvider): + async def verify_token(self, token): # $ mad-source__remote=token + return token + + +class NotAVerifier: + async def verify_token(self, token): + return token + + +def session(): + return get_access_token().token # $ mad-source__remote=get_access_token().token diff --git a/python/ql/test/library-tests/frameworks/mcp/MaDTest.expected b/python/ql/test/library-tests/frameworks/mcp/MaDTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/mcp/MaDTest.ql b/python/ql/test/library-tests/frameworks/mcp/MaDTest.ql new file mode 100644 index 000000000000..fef4356ab35e --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/MaDTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.MaDTest diff --git a/python/ql/test/library-tests/frameworks/mcp/auth.py b/python/ql/test/library-tests/frameworks/mcp/auth.py new file mode 100644 index 000000000000..59ca724d9733 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/auth.py @@ -0,0 +1,16 @@ +from mcp.server.auth.middleware.auth_context import get_access_token +from mcp.server.auth.provider import TokenVerifier + + +class Verifier(TokenVerifier): + async def verify_token(self, token): # $ mad-source__remote=token + return token + + +class NotAVerifier: + async def verify_token(self, token): + return token + + +def session(): + return get_access_token().token # $ mad-source__remote=get_access_token().token diff --git a/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py b/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py new file mode 100644 index 000000000000..347d92e6005d --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py @@ -0,0 +1,47 @@ +from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp.server import FastMCP as ModuleFastMCP + +server = FastMCP("test") +other = ModuleFastMCP("test") + + +@server.tool() +def run(command): # $ mad-source__remote=command + return command + + +@other.resource("notes://{name}") +def note(name): # $ mad-source__remote=name + return name + + +@server.prompt() +def summarize(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style + return topic + style + + +def fetch(url): # $ mad-source__remote=url + return url + + +other.add_tool(fn=fetch) + + +def register(app: FastMCP): + @app.tool() + def remove(path): # $ mad-source__remote=path + return path + + +class Service: + def __init__(self): + self.mcp = FastMCP("test") + + def register(self): + @self.mcp.tool() + def lookup(host): # $ mad-source__remote=host + return host + + +def not_registered(value): + return value diff --git a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py new file mode 100644 index 000000000000..2ff4e3929250 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py @@ -0,0 +1,22 @@ +from mcp.server import Server +from mcp.server.lowlevel import Server as LowLevelServer +from mcp.server.lowlevel.server import Server as ModuleServer + +server = Server("test") +lowlevel_server = LowLevelServer("test") +module_server = ModuleServer("test") + + +@server.call_tool() +async def call_tool(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments + return arguments + + +@lowlevel_server.read_resource() +async def read_resource(uri): # $ mad-source__remote=uri + return uri + + +@module_server.get_prompt() +async def get_prompt(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments + return arguments diff --git a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py new file mode 100644 index 000000000000..c39b248627fc --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py @@ -0,0 +1,25 @@ +from mcp.server import Server + + +async def handle_call_tool(ctx, params): + return params.arguments # $ mad-source__remote=params.arguments + + +async def handle_read_resource(ctx, params): + return params.uri # $ mad-source__remote=params.uri + + +async def handle_get_prompt(ctx, params): + return params.arguments # $ mad-source__remote=params.arguments + + +async def not_registered(ctx, params): + return params.arguments + + +server = Server( + "test", + on_call_tool=handle_call_tool, + on_read_resource=handle_read_resource, + on_get_prompt=handle_get_prompt, +) diff --git a/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py b/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py new file mode 100644 index 000000000000..e1122d6b4730 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py @@ -0,0 +1,29 @@ +from mcp.server import MCPServer +from mcp.server.mcpserver import MCPServer as PackageMCPServer +from mcp.server.mcpserver.server import MCPServer as ModuleMCPServer + +server = MCPServer("test") +package_server = PackageMCPServer("test") +module_server = ModuleMCPServer("test") + + +@server.tool() +def run(command): # $ mad-source__remote=command + return command + + +@package_server.resource("notes://{name}") +def note(name): # $ mad-source__remote=name + return name + + +@module_server.prompt() +def summarize(topic): # $ mad-source__remote=topic + return topic + + +def fetch(url): # $ mad-source__remote=url + return url + + +server.add_tool(fetch) From 1c9b2968ede3ed7455c15f90d93f4b87d9a50b58 Mon Sep 17 00:00:00 2001 From: Alex Hofer <62021660+Alex-Hofer@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:00:21 +0200 Subject: [PATCH 2/2] Python: Use the ai threat model for MCP handler input Handler parameters are produced by a language model, so they are sources of the opt-in ai threat model added in #22810. HTTP headers and bearer tokens come from the HTTP client and stay sources of the remote threat model. --- .../lib/change-notes/2026-09-30-mcp-models.md | 2 +- .../python/frameworks/fastmcp.model.yml | 14 +++++----- .../semmle/python/frameworks/mcp.model.yml | 14 +++++----- .../library-tests/frameworks/fastmcp/test.py | 26 +++++++++---------- .../frameworks/mcp/fastmcp_v1.py | 12 ++++----- .../frameworks/mcp/lowlevel_v1.py | 6 ++--- .../frameworks/mcp/lowlevel_v2.py | 6 ++--- .../frameworks/mcp/mcpserver_v2.py | 8 +++--- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/python/ql/lib/change-notes/2026-09-30-mcp-models.md b/python/ql/lib/change-notes/2026-09-30-mcp-models.md index d85cb4d6a06d..3b6bfbce8806 100644 --- a/python/ql/lib/change-notes/2026-09-30-mcp-models.md +++ b/python/ql/lib/change-notes/2026-09-30-mcp-models.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Added modeling of the `mcp` (the official Model Context Protocol SDK) and `fastmcp` PyPI packages. The parameters of MCP tool, resource and prompt handlers, the HTTP headers that `fastmcp` handlers read through `get_http_headers()` or `get_http_request()`, and the bearer token that token verifiers (`verify_token`) and `get_access_token()` receive, are now sources of the `remote` threat model. +* Added modeling of the `mcp` (the official Model Context Protocol SDK) and `fastmcp` PyPI packages. The parameters of MCP tool, resource and prompt handlers are now sources of the opt-in `ai` threat model, as their values are produced by a language model. The HTTP headers that `fastmcp` handlers read through `get_http_headers()` or `get_http_request()`, and the bearer token that token verifiers (`verify_token`) and `get_access_token()` receive, are sources of the `remote` threat model. diff --git a/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml b/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml index 6045d15a8a17..46a968405c7d 100644 --- a/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml +++ b/python/ql/lib/semmle/python/frameworks/fastmcp.model.yml @@ -5,13 +5,13 @@ extensions: data: # `fastmcp` (PyPI), a standalone MCP server framework. The parameters of tool, resource and # prompt handlers are chosen by the MCP client; `tool` and `prompt` also work without parentheses. - - ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'remote'] - - ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] - - ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'remote'] - - ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'remote'] - - ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'remote'] - - ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'remote'] - - ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'remote'] + - ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'ai'] + - ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai'] + - ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'ai'] + - ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'ai'] + - ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'ai'] + - ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'ai'] + - ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'ai'] # The HTTP headers of the transport. - ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_headers].ReturnValue', 'remote'] - ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_request].ReturnValue.Member[headers]', 'remote'] diff --git a/python/ql/lib/semmle/python/frameworks/mcp.model.yml b/python/ql/lib/semmle/python/frameworks/mcp.model.yml index 3adffeaa24d5..08d3cf31eb12 100644 --- a/python/ql/lib/semmle/python/frameworks/mcp.model.yml +++ b/python/ql/lib/semmle/python/frameworks/mcp.model.yml @@ -6,14 +6,14 @@ extensions: # `mcp`, the official Model Context Protocol SDK. The parameters of tool, resource and prompt # handlers are chosen by the MCP client, that is by an LLM or by anyone who can reach the server. # `FastMCP` (1.x) was renamed to `MCPServer` (2.x). - - ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] - - ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote'] - - ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] - - ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote'] + - ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai'] + - ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'ai'] + - ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'ai'] + - ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'ai'] # The low-level server: decorators in 1.x, constructor handlers taking `(ctx, params)` in 2.x. - - ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'remote'] - - ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'remote'] - - ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'remote'] + - ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'ai'] + - ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'ai'] + - ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'ai'] # The bearer token of the Authorization header reaches token verifiers and the access-token helper. - ['mcp.server.auth.provider.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote'] - ['mcp', 'Member[server].Member[auth].Member[middleware].Member[auth_context].Member[get_access_token].ReturnValue.Member[token]', 'remote'] diff --git a/python/ql/test/library-tests/frameworks/fastmcp/test.py b/python/ql/test/library-tests/frameworks/fastmcp/test.py index 22e26e021cef..8cabe710a84a 100644 --- a/python/ql/test/library-tests/frameworks/fastmcp/test.py +++ b/python/ql/test/library-tests/frameworks/fastmcp/test.py @@ -14,57 +14,57 @@ @app.tool -def bare(command): # $ mad-source__remote=command +def bare(command): # $ mad-source__ai=command return command @server_app.tool(name="exec") -def called(command): # $ mad-source__remote=command +def called(command): # $ mad-source__ai=command return command @module_app.resource("files://{path}") -def resource(path): # $ mad-source__remote=path +def resource(path): # $ mad-source__ai=path return path @app.prompt -def bare_prompt(topic): # $ mad-source__remote=topic +def bare_prompt(topic): # $ mad-source__ai=topic return topic @app.prompt() -def called_prompt(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style +def called_prompt(topic, style="short"): # $ mad-source__ai=topic mad-source__ai=style return topic + style -def added(command): # $ mad-source__remote=command +def added(command): # $ mad-source__ai=command return command app.add_tool(added) -def added_prompt(topic): # $ mad-source__remote=topic +def added_prompt(topic): # $ mad-source__ai=topic return topic app.add_prompt(prompt=added_prompt) -def via_tool(url): # $ mad-source__remote=url +def via_tool(url): # $ mad-source__ai=url return url -def via_function_tool(url): # $ mad-source__remote=url +def via_function_tool(url): # $ mad-source__ai=url return url -def via_base_tool(url): # $ mad-source__remote=url +def via_base_tool(url): # $ mad-source__ai=url return url -def via_module_function_tool(url): # $ mad-source__remote=url +def via_module_function_tool(url): # $ mad-source__ai=url return url @@ -75,12 +75,12 @@ def via_module_function_tool(url): # $ mad-source__remote=url @tool -def standalone(command): # $ mad-source__remote=command +def standalone(command): # $ mad-source__ai=command return command @tool() -def standalone_called(command): # $ mad-source__remote=command +def standalone_called(command): # $ mad-source__ai=command return command diff --git a/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py b/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py index 347d92e6005d..76753ef13bc4 100644 --- a/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py +++ b/python/ql/test/library-tests/frameworks/mcp/fastmcp_v1.py @@ -6,21 +6,21 @@ @server.tool() -def run(command): # $ mad-source__remote=command +def run(command): # $ mad-source__ai=command return command @other.resource("notes://{name}") -def note(name): # $ mad-source__remote=name +def note(name): # $ mad-source__ai=name return name @server.prompt() -def summarize(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style +def summarize(topic, style="short"): # $ mad-source__ai=topic mad-source__ai=style return topic + style -def fetch(url): # $ mad-source__remote=url +def fetch(url): # $ mad-source__ai=url return url @@ -29,7 +29,7 @@ def fetch(url): # $ mad-source__remote=url def register(app: FastMCP): @app.tool() - def remove(path): # $ mad-source__remote=path + def remove(path): # $ mad-source__ai=path return path @@ -39,7 +39,7 @@ def __init__(self): def register(self): @self.mcp.tool() - def lookup(host): # $ mad-source__remote=host + def lookup(host): # $ mad-source__ai=host return host diff --git a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py index 2ff4e3929250..f301dc95e678 100644 --- a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py +++ b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v1.py @@ -8,15 +8,15 @@ @server.call_tool() -async def call_tool(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments +async def call_tool(name, arguments): # $ mad-source__ai=name mad-source__ai=arguments return arguments @lowlevel_server.read_resource() -async def read_resource(uri): # $ mad-source__remote=uri +async def read_resource(uri): # $ mad-source__ai=uri return uri @module_server.get_prompt() -async def get_prompt(name, arguments): # $ mad-source__remote=name mad-source__remote=arguments +async def get_prompt(name, arguments): # $ mad-source__ai=name mad-source__ai=arguments return arguments diff --git a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py index c39b248627fc..c66066bb86fc 100644 --- a/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py +++ b/python/ql/test/library-tests/frameworks/mcp/lowlevel_v2.py @@ -2,15 +2,15 @@ async def handle_call_tool(ctx, params): - return params.arguments # $ mad-source__remote=params.arguments + return params.arguments # $ mad-source__ai=params.arguments async def handle_read_resource(ctx, params): - return params.uri # $ mad-source__remote=params.uri + return params.uri # $ mad-source__ai=params.uri async def handle_get_prompt(ctx, params): - return params.arguments # $ mad-source__remote=params.arguments + return params.arguments # $ mad-source__ai=params.arguments async def not_registered(ctx, params): diff --git a/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py b/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py index e1122d6b4730..f3637a2dc1d3 100644 --- a/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py +++ b/python/ql/test/library-tests/frameworks/mcp/mcpserver_v2.py @@ -8,21 +8,21 @@ @server.tool() -def run(command): # $ mad-source__remote=command +def run(command): # $ mad-source__ai=command return command @package_server.resource("notes://{name}") -def note(name): # $ mad-source__remote=name +def note(name): # $ mad-source__ai=name return name @module_server.prompt() -def summarize(topic): # $ mad-source__remote=topic +def summarize(topic): # $ mad-source__ai=topic return topic -def fetch(url): # $ mad-source__remote=url +def fetch(url): # $ mad-source__ai=url return url