matimo-core 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {matimo_core-0.2.0 → matimo_core-0.4.0}/PKG-INFO +4 -4
- {matimo_core-0.2.0 → matimo_core-0.4.0}/pyproject.toml +4 -4
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/__init__.py +1 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/approval/handler.py +26 -4
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/decorators/__init__.py +11 -2
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/function_executor.py +5 -2
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/http_executor.py +4 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/instance.py +15 -3
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/README.md +29 -44
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/server.py +51 -113
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/content_validator.py +3 -25
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/default_policy.py +3 -14
- matimo_core-0.4.0/src/matimo/policy/file_roots.py +41 -0
- matimo_core-0.4.0/src/matimo/policy/ssrf.py +150 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/convert_to_file/convert_to_file.py +2 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/edit/edit.py +3 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/execute/definition.yaml +13 -2
- matimo_core-0.4.0/src/matimo/tools/execute/execute.py +206 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/extract_from_file/extract_from_file.py +7 -25
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/read/read.py +3 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/search/search.py +8 -1
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web/web.py +11 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web_scraper/web_scraper.py +7 -23
- matimo_core-0.2.0/src/matimo/tools/execute/execute.py +0 -74
- {matimo_core-0.2.0 → matimo_core-0.4.0}/.gitignore +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/LICENSE +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/README.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/approval/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/injection.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_config.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_handler.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_provider_loader.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/loader.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/models.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/registry.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/response_size_guardrail.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_content_parser.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_loader.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_registry.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/tfidf_embedding.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/encodings/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/encodings/parameter_encoding.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/errors.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/command_executor.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/_async_bridge.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/_pydantic_utils.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/agno.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/crewai.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/langchain.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/logging/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/approval_elicitation.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/secrets/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/secrets/types.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/tool_converter.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/__init__.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/approval_manifest.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/audit_sink.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/integrity_tracker.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/policy_loader.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/risk_classifier.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/types.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/meta-tools-lifecycle/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/policy-validation/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/skill-creator/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/skills-catalog/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/tool-creation/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/tool-discovery/SKILL.md +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/sync.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/calculator/calculator.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/calculator/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/convert_to_file/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/edit/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/extract_from_file/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_approve_tool/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_approve_tool/matimo_approve_tool.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_skill/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_skill/matimo_create_skill.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_tool/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_tool/matimo_create_tool.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill/matimo_get_skill.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_content/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_content/matimo_get_skill_content.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_sections/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_sections/matimo_get_skill_sections.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool/matimo_get_tool.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool_status/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool_status/matimo_get_tool_status.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_skills/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_skills/matimo_list_skills.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_user_tools/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_user_tools/matimo_list_user_tools.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_reload_tools/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_reload_tools/matimo_reload_tools.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_skills/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_skills/matimo_search_skills.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_tools/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_tools/matimo_search_tools.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_skill/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_skill/matimo_validate_skill.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_tool/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_tool/matimo_validate_tool.py +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/read/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/search/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web/definition.yaml +0 -0
- {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web_scraper/definition.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matimo-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Matimo core — framework-agnostic SDK: pre-built providers, skills layer, MCP, and a policy engine you control
|
|
5
5
|
Project-URL: Homepage, https://matimo.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.matimo.dev/
|
|
@@ -39,7 +39,7 @@ Requires-Dist: agno>=3.0; extra == 'all'
|
|
|
39
39
|
Requires-Dist: crewai>=0.80; extra == 'all'
|
|
40
40
|
Requires-Dist: hvac>=2.0; extra == 'all'
|
|
41
41
|
Requires-Dist: langchain-core>=0.3; extra == 'all'
|
|
42
|
-
Requires-Dist: mcp>=
|
|
42
|
+
Requires-Dist: mcp>=2.0; extra == 'all'
|
|
43
43
|
Requires-Dist: python-dotenv>=1.0; extra == 'all'
|
|
44
44
|
Provides-Extra: aws
|
|
45
45
|
Requires-Dist: boto3>=1.34; extra == 'aws'
|
|
@@ -51,7 +51,7 @@ Requires-Dist: boto3>=1.34; extra == 'dev'
|
|
|
51
51
|
Requires-Dist: crewai>=0.80; extra == 'dev'
|
|
52
52
|
Requires-Dist: hvac>=2.0; extra == 'dev'
|
|
53
53
|
Requires-Dist: langchain-core>=0.3; extra == 'dev'
|
|
54
|
-
Requires-Dist: mcp>=
|
|
54
|
+
Requires-Dist: mcp>=2.0; extra == 'dev'
|
|
55
55
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
56
56
|
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
57
57
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
@@ -65,7 +65,7 @@ Requires-Dist: python-dotenv>=1.0; extra == 'dotenv'
|
|
|
65
65
|
Provides-Extra: langchain
|
|
66
66
|
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
67
67
|
Provides-Extra: mcp
|
|
68
|
-
Requires-Dist: mcp>=
|
|
68
|
+
Requires-Dist: mcp>=2.0; extra == 'mcp'
|
|
69
69
|
Provides-Extra: vault
|
|
70
70
|
Requires-Dist: hvac>=2.0; extra == 'vault'
|
|
71
71
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "matimo-core"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Matimo core — framework-agnostic SDK: pre-built providers, skills layer, MCP, and a policy engine you control"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
langchain = ["langchain-core>=0.3"]
|
|
42
42
|
crewai = ["crewai>=0.80"]
|
|
43
43
|
agno = ["agno>=3.0"]
|
|
44
|
-
mcp = ["mcp>=
|
|
44
|
+
mcp = ["mcp>=2.0"]
|
|
45
45
|
vault = ["hvac>=2.0"]
|
|
46
46
|
aws = ["boto3>=1.34"]
|
|
47
47
|
dotenv = ["python-dotenv>=1.0"]
|
|
@@ -49,7 +49,7 @@ all = [
|
|
|
49
49
|
"langchain-core>=0.3",
|
|
50
50
|
"crewai>=0.80",
|
|
51
51
|
"agno>=3.0",
|
|
52
|
-
"mcp>=
|
|
52
|
+
"mcp>=2.0",
|
|
53
53
|
"hvac>=2.0",
|
|
54
54
|
"python-dotenv>=1.0",
|
|
55
55
|
]
|
|
@@ -64,7 +64,7 @@ dev = [
|
|
|
64
64
|
"langchain-core>=0.3",
|
|
65
65
|
"crewai>=0.80",
|
|
66
66
|
"agno>=3.0",
|
|
67
|
-
"mcp>=
|
|
67
|
+
"mcp>=2.0",
|
|
68
68
|
"hvac>=2.0",
|
|
69
69
|
"boto3>=1.34",
|
|
70
70
|
"python-dotenv>=1.0",
|
|
@@ -23,6 +23,10 @@ DEFAULT_DESTRUCTIVE_KEYWORDS: list[str] = [
|
|
|
23
23
|
"INSERT", "UPSERT", "REPLACE", "MERGE", "GRANT", "REVOKE",
|
|
24
24
|
"EDIT", "WRITE", "APPEND", "REMOVE", "PURGE", "RENAME", "SHUTDOWN",
|
|
25
25
|
"EXECUTE", "EXEC",
|
|
26
|
+
# SQL escape hatches: file/OS/remote access and session control, even inside a SELECT
|
|
27
|
+
"COPY", "PROGRAM", "CALL", "DO $$", "DO LANGUAGE", "VACUUM", "REINDEX", "PG_READ_FILE",
|
|
28
|
+
"PG_READ_BINARY_FILE", "PG_LS_DIR", "PG_STAT_FILE", "LO_IMPORT", "LO_EXPORT", "DBLINK",
|
|
29
|
+
"SET_CONFIG", "PG_TERMINATE_BACKEND", "PG_CANCEL_BACKEND",
|
|
26
30
|
]
|
|
27
31
|
|
|
28
32
|
|
|
@@ -33,6 +37,22 @@ DEFAULT_DESTRUCTIVE_KEYWORDS: list[str] = [
|
|
|
33
37
|
NEVER_PRE_APPROVED_TOOLS: frozenset[str] = frozenset({"matimo_approve_tool"})
|
|
34
38
|
|
|
35
39
|
|
|
40
|
+
def requires_mandatory_human_approval(
|
|
41
|
+
tool_name: str, params: dict[str, Any] | None = None
|
|
42
|
+
) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Whether this particular call needs a human decision that nothing may skip:
|
|
45
|
+
neither MATIMO_AUTO_APPROVE, an approved pattern (not even "*"), nor a
|
|
46
|
+
caller-supplied `approved=True`. `execute` with a `shell` runs its command
|
|
47
|
+
through that shell, where metacharacters are live, so every such call is
|
|
48
|
+
shown to a human. Mirrors requiresMandatoryHumanApproval() in approval-handler.ts.
|
|
49
|
+
"""
|
|
50
|
+
if tool_name != "execute" or not params:
|
|
51
|
+
return False
|
|
52
|
+
shell = params.get("shell")
|
|
53
|
+
return shell is not None and shell != ""
|
|
54
|
+
|
|
55
|
+
|
|
36
56
|
def definition_requires_approval(tool: ToolDefinition, mode: GovernanceMode = "secure") -> bool:
|
|
37
57
|
"""
|
|
38
58
|
Whether a tool's definition alone makes every call need approval.
|
|
@@ -112,10 +132,12 @@ class ApprovalHandler:
|
|
|
112
132
|
return any(kw in upper for kw in self.destructive_keywords)
|
|
113
133
|
return False
|
|
114
134
|
|
|
115
|
-
def is_pre_approved(self, tool_name: str) -> bool:
|
|
135
|
+
def is_pre_approved(self, tool_name: str, params: dict[str, Any] | None = None) -> bool:
|
|
116
136
|
"""True when MATIMO_AUTO_APPROVE is on or the tool matches an approved pattern.
|
|
117
|
-
Never for NEVER_PRE_APPROVED_TOOLS."""
|
|
118
|
-
if tool_name in NEVER_PRE_APPROVED_TOOLS
|
|
137
|
+
Never for NEVER_PRE_APPROVED_TOOLS or a call needing mandatory human approval."""
|
|
138
|
+
if tool_name in NEVER_PRE_APPROVED_TOOLS or requires_mandatory_human_approval(
|
|
139
|
+
tool_name, params
|
|
140
|
+
):
|
|
119
141
|
return False
|
|
120
142
|
return self.auto_approve or self._matches_approved_pattern(tool_name)
|
|
121
143
|
|
|
@@ -131,7 +153,7 @@ class ApprovalHandler:
|
|
|
131
153
|
callback = callback if callback is not None else self._callback
|
|
132
154
|
# 1-2. MATIMO_AUTO_APPROVE (CI / testing) or an approved pattern —
|
|
133
155
|
# never for NEVER_PRE_APPROVED_TOOLS
|
|
134
|
-
if self.is_pre_approved(request.tool_name):
|
|
156
|
+
if self.is_pre_approved(request.tool_name, request.params):
|
|
135
157
|
logger.debug(
|
|
136
158
|
"Tool '%s' is pre-approved (MATIMO_AUTO_APPROVE or approved pattern)",
|
|
137
159
|
request.tool_name,
|
|
@@ -13,6 +13,7 @@ Usage:
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
16
|
+
import contextvars
|
|
16
17
|
import functools
|
|
17
18
|
import inspect
|
|
18
19
|
import logging
|
|
@@ -25,6 +26,11 @@ if TYPE_CHECKING:
|
|
|
25
26
|
logger = logging.getLogger("matimo")
|
|
26
27
|
|
|
27
28
|
_global_instance: Matimo | None = None
|
|
29
|
+
# The instance currently dispatching a function tool; lets meta-tools reach their
|
|
30
|
+
# owner without the caller registering it globally.
|
|
31
|
+
_executing_instance: contextvars.ContextVar[Matimo | None] = contextvars.ContextVar(
|
|
32
|
+
"matimo_executing_instance", default=None
|
|
33
|
+
)
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def set_global_matimo_instance(instance: Matimo) -> None:
|
|
@@ -34,8 +40,11 @@ def set_global_matimo_instance(instance: Matimo) -> None:
|
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
def get_global_matimo_instance() -> Matimo | None:
|
|
37
|
-
"""
|
|
38
|
-
|
|
43
|
+
"""
|
|
44
|
+
Return the Matimo instance in effect: the one executing the current function
|
|
45
|
+
tool if any, otherwise the instance registered via set_global_matimo_instance().
|
|
46
|
+
"""
|
|
47
|
+
return _executing_instance.get() or _global_instance
|
|
39
48
|
|
|
40
49
|
|
|
41
50
|
def tool(tool_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
@@ -17,6 +17,7 @@ Example tool definition (definition.yaml):
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import asyncio
|
|
20
|
+
import contextvars
|
|
20
21
|
import importlib.util
|
|
21
22
|
import inspect
|
|
22
23
|
import logging
|
|
@@ -124,9 +125,11 @@ class FunctionExecutor:
|
|
|
124
125
|
if inspect.iscoroutinefunction(run_fn):
|
|
125
126
|
result = await asyncio.wait_for(run_fn(*args), timeout=timeout_s)
|
|
126
127
|
else:
|
|
127
|
-
# Run sync function in executor to avoid blocking the event loop
|
|
128
|
+
# Run sync function in executor to avoid blocking the event loop;
|
|
129
|
+
# copy_context keeps the executing-instance context var visible.
|
|
130
|
+
ctx = contextvars.copy_context()
|
|
128
131
|
result = await asyncio.wait_for(
|
|
129
|
-
asyncio.get_running_loop().run_in_executor(None, run_fn, *args),
|
|
132
|
+
asyncio.get_running_loop().run_in_executor(None, ctx.run, run_fn, *args),
|
|
130
133
|
timeout=timeout_s,
|
|
131
134
|
)
|
|
132
135
|
except TimeoutError:
|
|
@@ -17,6 +17,7 @@ from matimo.core.models import HttpExecution, ToolDefinition
|
|
|
17
17
|
from matimo.encodings.parameter_encoding import apply_parameter_encodings
|
|
18
18
|
from matimo.errors import ErrorCode, MatimoError, from_http_error
|
|
19
19
|
from matimo.policy.content_validator import is_ssrf_target
|
|
20
|
+
from matimo.policy.ssrf import ssrf_event_hooks
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger("matimo")
|
|
22
23
|
|
|
@@ -142,7 +143,9 @@ class HttpExecutor:
|
|
|
142
143
|
timeout_s = timeout_ms / 1000.0
|
|
143
144
|
|
|
144
145
|
try:
|
|
145
|
-
async with httpx.AsyncClient(
|
|
146
|
+
async with httpx.AsyncClient(
|
|
147
|
+
timeout=timeout_s, event_hooks=ssrf_event_hooks()
|
|
148
|
+
) as client, client.stream(
|
|
146
149
|
method=exec_cfg.method,
|
|
147
150
|
url=url,
|
|
148
151
|
headers=headers,
|
|
@@ -33,6 +33,7 @@ from matimo.approval.handler import (
|
|
|
33
33
|
ApprovalRequest,
|
|
34
34
|
definition_requires_approval,
|
|
35
35
|
get_global_approval_handler,
|
|
36
|
+
requires_mandatory_human_approval,
|
|
36
37
|
)
|
|
37
38
|
from matimo.auth.injection import inject_auth_parameters
|
|
38
39
|
from matimo.core.loader import ToolLoader
|
|
@@ -879,7 +880,15 @@ class Matimo:
|
|
|
879
880
|
if exec_type == "function":
|
|
880
881
|
# Function tools also get the caller's PolicyContext (e.g. so
|
|
881
882
|
# matimo_approve_tool can check the approver's role and identity).
|
|
882
|
-
|
|
883
|
+
# The instance is also exposed to the tool so meta-tools such as
|
|
884
|
+
# matimo_list_skills see this instance without a global registration.
|
|
885
|
+
from matimo.decorators import _executing_instance
|
|
886
|
+
|
|
887
|
+
token = _executing_instance.set(self)
|
|
888
|
+
try:
|
|
889
|
+
return await self._function_executor.execute(tool, params, credentials, context)
|
|
890
|
+
finally:
|
|
891
|
+
_executing_instance.reset(token)
|
|
883
892
|
raise MatimoError(
|
|
884
893
|
f"Unknown execution type: '{exec_type}'",
|
|
885
894
|
ErrorCode.EXECUTION_FAILED,
|
|
@@ -1036,12 +1045,15 @@ class Matimo:
|
|
|
1036
1045
|
decides, and with no callback the call fails closed.
|
|
1037
1046
|
"""
|
|
1038
1047
|
handler = self._approval_handler
|
|
1039
|
-
|
|
1048
|
+
# Some calls (execute with a shell) always need a human, whatever the YAML,
|
|
1049
|
+
# the pre-approval settings or the caller's `approved` flag say.
|
|
1050
|
+
mandatory_human = requires_mandatory_human_approval(tool.name, params)
|
|
1051
|
+
if not mandatory_human and not handler.requires_approval(
|
|
1040
1052
|
definition_requires_approval(tool, self.get_governance_mode()),
|
|
1041
1053
|
_approval_scan_content(tool, params),
|
|
1042
1054
|
):
|
|
1043
1055
|
return
|
|
1044
|
-
if skip_prompt or handler.is_pre_approved(tool.name):
|
|
1056
|
+
if (skip_prompt and not mandatory_human) or handler.is_pre_approved(tool.name, params):
|
|
1045
1057
|
return
|
|
1046
1058
|
|
|
1047
1059
|
agent_id = context.agent_id if context else None
|
|
@@ -10,11 +10,8 @@ This module mirrors the TypeScript `@matimo/core/mcp` implementation and maintai
|
|
|
10
10
|
- **Skill resources** - exposes Matimo skills as MCP resources (`skills://name`)
|
|
11
11
|
- **HTTP transport** - Streamable HTTP with bearer-token auth, CORS, health endpoint
|
|
12
12
|
- **Stdio transport** - stdio server for Claude Desktop integration
|
|
13
|
-
- **mcp SDK
|
|
14
|
-
|
|
15
|
-
installed SDK's major version (`_mcp_major_version()`) and builds the server
|
|
16
|
-
via `_build_server_v1()` (decorators, mcp<2.0) or `_build_server_v2()`
|
|
17
|
-
(`on_list_tools=`/`on_call_tool=`/... constructor kwargs, mcp>=2.0)
|
|
13
|
+
- **mcp SDK 2.x** - requires `mcp>=2.0`; `server.py` builds the `Server` with
|
|
14
|
+
`on_list_tools=`/`on_call_tool=`/... constructor callables (`_build_server()`)
|
|
18
15
|
|
|
19
16
|
---
|
|
20
17
|
|
|
@@ -180,31 +177,22 @@ embedding application provides a server-trusted approval signal.
|
|
|
180
177
|
|
|
181
178
|
**Solution:** Register skills as MCP resources so clients can browse them like files:
|
|
182
179
|
|
|
183
|
-
|
|
184
|
-
(`
|
|
185
|
-
pair of constructor callables instead (`_build_skill_resource_handlers_v2`),
|
|
186
|
-
since `@server.list_resources()`/`@server.read_resource()` no longer exist.
|
|
180
|
+
The mapping is built as a pair of `Server` constructor callables
|
|
181
|
+
(`_build_skill_resource_handlers`), only when at least one skill exists:
|
|
187
182
|
|
|
188
183
|
```python
|
|
189
|
-
def
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
for s in skills
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
@server.read_resource() # MCP: resources/read
|
|
205
|
-
async def handle_read_resource(uri: Any) -> str:
|
|
206
|
-
skill_name = str(uri).removeprefix("skills://")
|
|
207
|
-
return self._matimo.get_skill_content(skill_name)
|
|
184
|
+
async def on_list_resources(ctx, params): # MCP: resources/list
|
|
185
|
+
return ListResourcesResult(resources=[
|
|
186
|
+
Resource(uri=f"skills://{s.name}", name=s.name,
|
|
187
|
+
description=s.description, mime_type="text/markdown")
|
|
188
|
+
for s in self._matimo.list_skills()
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
async def on_read_resource(ctx, params): # MCP: resources/read
|
|
192
|
+
skill_name = str(params.uri).removeprefix("skills://")
|
|
193
|
+
content = self._matimo.get_skill_content(skill_name)
|
|
194
|
+
return ReadResourceResult(contents=[TextResourceContents(
|
|
195
|
+
uri=params.uri, mime_type="text/markdown", text=content)])
|
|
208
196
|
```
|
|
209
197
|
|
|
210
198
|
**Result:**
|
|
@@ -286,13 +274,10 @@ async def _run_http(self, server: Any) -> None:
|
|
|
286
274
|
2. await server.start()
|
|
287
275
|
├─ Suppress logging (clean stdout for JSON-RPC)
|
|
288
276
|
├─ Import MCP SDK
|
|
289
|
-
├─
|
|
290
|
-
├─
|
|
291
|
-
├─
|
|
292
|
-
├─
|
|
293
|
-
├─ Register @server.read_resource() handler
|
|
294
|
-
├─ Load all tools: [slack_send_message, slack_post_reaction, ...]
|
|
295
|
-
├─ Filter by allowlist/denylist: [slack_send_message, slack_post_reaction]
|
|
277
|
+
├─ Build Server with on_list_tools / on_call_tool callables
|
|
278
|
+
├─ Add on_list_resources / on_read_resource callables (only if skills exist)
|
|
279
|
+
├─ Load all tools: [slack_send_channel_message, slack_add_reaction, ...]
|
|
280
|
+
├─ Filter by allowlist/denylist: [slack_send_channel_message, slack_add_reaction]
|
|
296
281
|
├─ Extract auth placeholders: {SLACK_BOT_TOKEN}
|
|
297
282
|
├─ Resolve via SecretResolverChain: {SLACK_BOT_TOKEN: "xoxb-abc123"}
|
|
298
283
|
├─ Store in _resolved_secrets
|
|
@@ -306,7 +291,7 @@ async def _run_http(self, server: Any) -> None:
|
|
|
306
291
|
├─ Strips auth params from inputSchema
|
|
307
292
|
├─ Returns: [
|
|
308
293
|
│ {
|
|
309
|
-
│ name: "
|
|
294
|
+
│ name: "slack_send_channel_message",
|
|
310
295
|
│ description: "Send a message to a Slack channel",
|
|
311
296
|
│ inputSchema: {
|
|
312
297
|
│ type: "object",
|
|
@@ -323,14 +308,14 @@ async def _run_http(self, server: Any) -> None:
|
|
|
323
308
|
└─ Client receives tool list
|
|
324
309
|
↓
|
|
325
310
|
5. Client calls: tools/call with {
|
|
326
|
-
name: "
|
|
311
|
+
name: "slack_send_channel_message",
|
|
327
312
|
arguments: {channel: "#general", text: "Hello"}
|
|
328
313
|
}
|
|
329
|
-
├─ Handler calls _call_tool("
|
|
314
|
+
├─ Handler calls _call_tool("slack_send_channel_message", {...})
|
|
330
315
|
├─ Checks requires_approval (false for this tool)
|
|
331
316
|
├─ Gets pre-resolved credentials from _resolved_secrets
|
|
332
317
|
├─ Calls: matimo.execute(
|
|
333
|
-
│ "
|
|
318
|
+
│ "slack_send_channel_message",
|
|
334
319
|
│ {channel: "#general", text: "Hello"},
|
|
335
320
|
│ credentials={SLACK_BOT_TOKEN: "xoxb-abc123"},
|
|
336
321
|
│ approved=False
|
|
@@ -454,8 +439,8 @@ The `tools` and `exclude_tools` options support **Unix shell-style wildcard patt
|
|
|
454
439
|
tools = [
|
|
455
440
|
"slack_*", # All Slack tools
|
|
456
441
|
"github_create_*", # All GitHub create_* tools
|
|
457
|
-
"
|
|
458
|
-
"
|
|
442
|
+
"notion_*", # All Notion tools
|
|
443
|
+
"gmail-send-email", # Exact match (no wildcards)
|
|
459
444
|
]
|
|
460
445
|
|
|
461
446
|
# Exclude patterns
|
|
@@ -483,7 +468,7 @@ If a tool name matches both `tools` (allowlist) and `exclude_tools` (denylist),
|
|
|
483
468
|
| Auth param filtering | ✅ `isAuthParameter()` | ✅ `_is_auth_parameter()` | Parity |
|
|
484
469
|
| Approval via elicitation | ✅ `createElicitationApprovalCallback()` | ✅ `create_elicitation_approval_callback()` | Parity |
|
|
485
470
|
| Pre-resolved secrets | ✅ `seedEnvironmentSecrets()` | ✅ `_seed_environment_secrets()` | Parity |
|
|
486
|
-
| Skill resources | ✅ `registerSkillResources()` | ✅ `
|
|
471
|
+
| Skill resources | ✅ `registerSkillResources()` | ✅ `_build_skill_resource_handlers()` | Parity |
|
|
487
472
|
| HTTP transport | ✅ `StreamableHTTPServerTransport` + sessions | ✅ `StreamableHTTPSessionManager` + stateless | Parity |
|
|
488
473
|
| Stdio logging suppression | ✅ `logLevel: 'silent'` | ✅ `logging.setLevel(CRITICAL+1)` | Parity |
|
|
489
474
|
| Test coverage | 95%+ | 95%+ | Parity |
|
|
@@ -540,9 +525,9 @@ uv run pytest packages/core/tests/unit/test_mcp_server.py -v
|
|
|
540
525
|
**Solution:**
|
|
541
526
|
|
|
542
527
|
```bash
|
|
543
|
-
pip install matimo[mcp]
|
|
528
|
+
pip install "matimo[mcp]"
|
|
544
529
|
# or
|
|
545
|
-
pip install mcp>=
|
|
530
|
+
pip install "mcp>=2.0"
|
|
546
531
|
```
|
|
547
532
|
|
|
548
533
|
### Issue: "Auth token mismatch" in HTTP mode
|