Skip to content

Commit f298591

Browse files
committed
Python: Add models for the MCP server SDKs (mcp, fastmcp)
1 parent 22e050f commit f298591

13 files changed

Lines changed: 337 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* 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.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/python-all
4+
extensible: sourceModel
5+
data:
6+
# `fastmcp` (PyPI), a standalone MCP server framework. The parameters of tool, resource and
7+
# prompt handlers are chosen by the MCP client; `tool` and `prompt` also work without parentheses.
8+
- ['fastmcp.FastMCP', 'Member[tool,prompt].Argument[0,name_or_fn:].Parameter[any]', 'remote']
9+
- ['fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
10+
- ['fastmcp.FastMCP', 'Member[add_tool].Argument[0,tool:].Parameter[any]', 'remote']
11+
- ['fastmcp.FastMCP', 'Member[add_prompt].Argument[0,prompt:].Parameter[any]', 'remote']
12+
- ['fastmcp.tools.Tool!', 'Member[from_function].Argument[0,fn:].Parameter[any]', 'remote']
13+
- ['fastmcp', 'Member[tools].Member[tool].Argument[0,name_or_fn:].Parameter[any]', 'remote']
14+
- ['fastmcp', 'Member[tools].Member[tool].ReturnValue.Argument[0].Parameter[any]', 'remote']
15+
# The HTTP headers of the transport.
16+
- ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_headers].ReturnValue', 'remote']
17+
- ['fastmcp', 'Member[server].Member[dependencies].Member[get_http_request].ReturnValue.Member[headers]', 'remote']
18+
# The bearer token of the Authorization header reaches token verifiers and the access-token helper.
19+
- ['fastmcp.server.auth.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote']
20+
- ['fastmcp.server.auth.AuthProvider!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote']
21+
- ['fastmcp', 'Member[server].Member[dependencies].Member[get_access_token].ReturnValue.Member[token]', 'remote']
22+
23+
- addsTo:
24+
pack: codeql/python-all
25+
extensible: typeModel
26+
data:
27+
# The classes are re-exported; instances of any import path are instances of the class.
28+
- ['fastmcp.FastMCP!', 'fastmcp', 'Member[server].Member[FastMCP]']
29+
- ['fastmcp.FastMCP!', 'fastmcp', 'Member[server].Member[server].Member[FastMCP]']
30+
- ['fastmcp.FastMCP', 'fastmcp.FastMCP!', 'Instance']
31+
- ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[FunctionTool]']
32+
- ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[base].Member[Tool]']
33+
- ['fastmcp.tools.Tool!', 'fastmcp', 'Member[tools].Member[function_tool].Member[FunctionTool]']
34+
- ['fastmcp.server.auth.TokenVerifier!', 'fastmcp', 'Member[server].Member[auth].Member[auth].Member[TokenVerifier]']
35+
- ['fastmcp.server.auth.AuthProvider!', 'fastmcp', 'Member[server].Member[auth].Member[auth].Member[AuthProvider]']
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/python-all
4+
extensible: sourceModel
5+
data:
6+
# `mcp`, the official Model Context Protocol SDK. The parameters of tool, resource and prompt
7+
# handlers are chosen by the MCP client, that is by an LLM or by anyone who can reach the server.
8+
# `FastMCP` (1.x) was renamed to `MCPServer` (2.x).
9+
- ['mcp.server.fastmcp.FastMCP', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
10+
- ['mcp.server.fastmcp.FastMCP', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote']
11+
- ['mcp.server.mcpserver.MCPServer', 'Member[tool,resource,prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
12+
- ['mcp.server.mcpserver.MCPServer', 'Member[add_tool].Argument[0,fn:].Parameter[any]', 'remote']
13+
# The low-level server: decorators in 1.x, constructor handlers taking `(ctx, params)` in 2.x.
14+
- ['mcp.server.lowlevel.Server', 'Member[call_tool,read_resource,get_prompt].ReturnValue.Argument[0].Parameter[any]', 'remote']
15+
- ['mcp.server.lowlevel.Server!', 'Argument[on_call_tool:,on_get_prompt:].Parameter[1].Member[arguments]', 'remote']
16+
- ['mcp.server.lowlevel.Server!', 'Argument[on_read_resource:].Parameter[1].Member[uri]', 'remote']
17+
# The bearer token of the Authorization header reaches token verifiers and the access-token helper.
18+
- ['mcp.server.auth.provider.TokenVerifier!', 'Subclass.Member[verify_token].Parameter[0,token:]', 'remote']
19+
- ['mcp', 'Member[server].Member[auth].Member[middleware].Member[auth_context].Member[get_access_token].ReturnValue.Member[token]', 'remote']
20+
21+
- addsTo:
22+
pack: codeql/python-all
23+
extensible: typeModel
24+
data:
25+
# The classes are re-exported; instances of any import path are instances of the class.
26+
- ['mcp.server.fastmcp.FastMCP!', 'mcp', 'Member[server].Member[fastmcp].Member[server].Member[FastMCP]']
27+
- ['mcp.server.fastmcp.FastMCP', 'mcp.server.fastmcp.FastMCP!', 'Instance']
28+
- ['mcp.server.mcpserver.MCPServer!', 'mcp', 'Member[server].Member[MCPServer]']
29+
- ['mcp.server.mcpserver.MCPServer!', 'mcp', 'Member[server].Member[mcpserver].Member[server].Member[MCPServer]']
30+
- ['mcp.server.mcpserver.MCPServer', 'mcp.server.mcpserver.MCPServer!', 'Instance']
31+
- ['mcp.server.lowlevel.Server!', 'mcp', 'Member[server].Member[Server]']
32+
- ['mcp.server.lowlevel.Server!', 'mcp', 'Member[server].Member[lowlevel].Member[server].Member[Server]']
33+
- ['mcp.server.lowlevel.Server', 'mcp.server.lowlevel.Server!', 'Instance']

‎python/ql/test/library-tests/frameworks/fastmcp/MaDTest.expected‎

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.MaDTest
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from fastmcp import FastMCP
2+
from fastmcp.server import FastMCP as ServerFastMCP
3+
from fastmcp.server.server import FastMCP as ModuleFastMCP
4+
from fastmcp.server.auth import AuthProvider, TokenVerifier
5+
from fastmcp.server.auth.auth import TokenVerifier as ModuleTokenVerifier
6+
from fastmcp.server.dependencies import get_access_token, get_http_headers, get_http_request
7+
from fastmcp.tools import FunctionTool, Tool, tool
8+
from fastmcp.tools.base import Tool as BaseTool
9+
from fastmcp.tools.function_tool import FunctionTool as ModuleFunctionTool
10+
11+
app = FastMCP("test")
12+
server_app = ServerFastMCP("test")
13+
module_app = ModuleFastMCP("test")
14+
15+
16+
@app.tool
17+
def bare(command): # $ mad-source__remote=command
18+
return command
19+
20+
21+
@server_app.tool(name="exec")
22+
def called(command): # $ mad-source__remote=command
23+
return command
24+
25+
26+
@module_app.resource("files://{path}")
27+
def resource(path): # $ mad-source__remote=path
28+
return path
29+
30+
31+
@app.prompt
32+
def bare_prompt(topic): # $ mad-source__remote=topic
33+
return topic
34+
35+
36+
@app.prompt()
37+
def called_prompt(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style
38+
return topic + style
39+
40+
41+
def added(command): # $ mad-source__remote=command
42+
return command
43+
44+
45+
app.add_tool(added)
46+
47+
48+
def added_prompt(topic): # $ mad-source__remote=topic
49+
return topic
50+
51+
52+
app.add_prompt(prompt=added_prompt)
53+
54+
55+
def via_tool(url): # $ mad-source__remote=url
56+
return url
57+
58+
59+
def via_function_tool(url): # $ mad-source__remote=url
60+
return url
61+
62+
63+
def via_base_tool(url): # $ mad-source__remote=url
64+
return url
65+
66+
67+
def via_module_function_tool(url): # $ mad-source__remote=url
68+
return url
69+
70+
71+
Tool.from_function(via_tool)
72+
FunctionTool.from_function(via_function_tool)
73+
BaseTool.from_function(fn=via_base_tool)
74+
ModuleFunctionTool.from_function(via_module_function_tool)
75+
76+
77+
@tool
78+
def standalone(command): # $ mad-source__remote=command
79+
return command
80+
81+
82+
@tool()
83+
def standalone_called(command): # $ mad-source__remote=command
84+
return command
85+
86+
87+
@app.tool
88+
def forward_header():
89+
return get_http_headers()["x-target"] # $ mad-source__remote=get_http_headers()
90+
91+
92+
@app.tool
93+
def forward_request_header():
94+
return get_http_request().headers["x-target"] # $ mad-source__remote=get_http_request().headers
95+
96+
97+
def not_registered(value):
98+
return value
99+
100+
101+
class Verifier(TokenVerifier):
102+
async def verify_token(self, token): # $ mad-source__remote=token
103+
return token
104+
105+
106+
class ModuleVerifier(ModuleTokenVerifier):
107+
async def verify_token(self, token): # $ mad-source__remote=token
108+
return token
109+
110+
111+
class Provider(AuthProvider):
112+
async def verify_token(self, token): # $ mad-source__remote=token
113+
return token
114+
115+
116+
class NotAVerifier:
117+
async def verify_token(self, token):
118+
return token
119+
120+
121+
def session():
122+
return get_access_token().token # $ mad-source__remote=get_access_token().token

‎python/ql/test/library-tests/frameworks/mcp/MaDTest.expected‎

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.MaDTest
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from mcp.server.auth.middleware.auth_context import get_access_token
2+
from mcp.server.auth.provider import TokenVerifier
3+
4+
5+
class Verifier(TokenVerifier):
6+
async def verify_token(self, token): # $ mad-source__remote=token
7+
return token
8+
9+
10+
class NotAVerifier:
11+
async def verify_token(self, token):
12+
return token
13+
14+
15+
def session():
16+
return get_access_token().token # $ mad-source__remote=get_access_token().token
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from mcp.server.fastmcp import FastMCP
2+
from mcp.server.fastmcp.server import FastMCP as ModuleFastMCP
3+
4+
server = FastMCP("test")
5+
other = ModuleFastMCP("test")
6+
7+
8+
@server.tool()
9+
def run(command): # $ mad-source__remote=command
10+
return command
11+
12+
13+
@other.resource("notes://{name}")
14+
def note(name): # $ mad-source__remote=name
15+
return name
16+
17+
18+
@server.prompt()
19+
def summarize(topic, style="short"): # $ mad-source__remote=topic mad-source__remote=style
20+
return topic + style
21+
22+
23+
def fetch(url): # $ mad-source__remote=url
24+
return url
25+
26+
27+
other.add_tool(fn=fetch)
28+
29+
30+
def register(app: FastMCP):
31+
@app.tool()
32+
def remove(path): # $ mad-source__remote=path
33+
return path
34+
35+
36+
class Service:
37+
def __init__(self):
38+
self.mcp = FastMCP("test")
39+
40+
def register(self):
41+
@self.mcp.tool()
42+
def lookup(host): # $ mad-source__remote=host
43+
return host
44+
45+
46+
def not_registered(value):
47+
return value

0 commit comments

Comments
 (0)