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.
Files changed (112) hide show
  1. {matimo_core-0.2.0 → matimo_core-0.4.0}/PKG-INFO +4 -4
  2. {matimo_core-0.2.0 → matimo_core-0.4.0}/pyproject.toml +4 -4
  3. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/__init__.py +1 -1
  4. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/approval/handler.py +26 -4
  5. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/decorators/__init__.py +11 -2
  6. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/function_executor.py +5 -2
  7. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/http_executor.py +4 -1
  8. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/instance.py +15 -3
  9. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/README.md +29 -44
  10. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/server.py +51 -113
  11. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/content_validator.py +3 -25
  12. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/default_policy.py +3 -14
  13. matimo_core-0.4.0/src/matimo/policy/file_roots.py +41 -0
  14. matimo_core-0.4.0/src/matimo/policy/ssrf.py +150 -0
  15. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/convert_to_file/convert_to_file.py +2 -1
  16. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/edit/edit.py +3 -1
  17. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/execute/definition.yaml +13 -2
  18. matimo_core-0.4.0/src/matimo/tools/execute/execute.py +206 -0
  19. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/extract_from_file/extract_from_file.py +7 -25
  20. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/read/read.py +3 -1
  21. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/search/search.py +8 -1
  22. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web/web.py +11 -0
  23. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web_scraper/web_scraper.py +7 -23
  24. matimo_core-0.2.0/src/matimo/tools/execute/execute.py +0 -74
  25. {matimo_core-0.2.0 → matimo_core-0.4.0}/.gitignore +0 -0
  26. {matimo_core-0.2.0 → matimo_core-0.4.0}/LICENSE +0 -0
  27. {matimo_core-0.2.0 → matimo_core-0.4.0}/README.md +0 -0
  28. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/approval/__init__.py +0 -0
  29. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/__init__.py +0 -0
  30. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/injection.py +0 -0
  31. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_config.py +0 -0
  32. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_handler.py +0 -0
  33. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/auth/oauth2_provider_loader.py +0 -0
  34. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/__init__.py +0 -0
  35. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/loader.py +0 -0
  36. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/models.py +0 -0
  37. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/registry.py +0 -0
  38. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/response_size_guardrail.py +0 -0
  39. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_content_parser.py +0 -0
  40. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_loader.py +0 -0
  41. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/skill_registry.py +0 -0
  42. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/core/tfidf_embedding.py +0 -0
  43. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/encodings/__init__.py +0 -0
  44. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/encodings/parameter_encoding.py +0 -0
  45. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/errors.py +0 -0
  46. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/__init__.py +0 -0
  47. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/executors/command_executor.py +0 -0
  48. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/__init__.py +0 -0
  49. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/_async_bridge.py +0 -0
  50. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/_pydantic_utils.py +0 -0
  51. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/agno.py +0 -0
  52. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/crewai.py +0 -0
  53. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/integrations/langchain.py +0 -0
  54. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/logging/__init__.py +0 -0
  55. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/__init__.py +0 -0
  56. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/approval_elicitation.py +0 -0
  57. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/secrets/__init__.py +0 -0
  58. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/secrets/types.py +0 -0
  59. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/mcp/tool_converter.py +0 -0
  60. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/__init__.py +0 -0
  61. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/approval_manifest.py +0 -0
  62. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/audit_sink.py +0 -0
  63. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/integrity_tracker.py +0 -0
  64. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/policy_loader.py +0 -0
  65. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/risk_classifier.py +0 -0
  66. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/policy/types.py +0 -0
  67. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/meta-tools-lifecycle/SKILL.md +0 -0
  68. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/policy-validation/SKILL.md +0 -0
  69. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/skill-creator/SKILL.md +0 -0
  70. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/skills-catalog/SKILL.md +0 -0
  71. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/tool-creation/SKILL.md +0 -0
  72. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/skills/tool-discovery/SKILL.md +0 -0
  73. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/sync.py +0 -0
  74. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/calculator/calculator.py +0 -0
  75. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/calculator/definition.yaml +0 -0
  76. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/convert_to_file/definition.yaml +0 -0
  77. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/edit/definition.yaml +0 -0
  78. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/extract_from_file/definition.yaml +0 -0
  79. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_approve_tool/definition.yaml +0 -0
  80. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_approve_tool/matimo_approve_tool.py +0 -0
  81. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_skill/definition.yaml +0 -0
  82. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_skill/matimo_create_skill.py +0 -0
  83. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_tool/definition.yaml +0 -0
  84. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_create_tool/matimo_create_tool.py +0 -0
  85. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill/definition.yaml +0 -0
  86. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill/matimo_get_skill.py +0 -0
  87. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_content/definition.yaml +0 -0
  88. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_content/matimo_get_skill_content.py +0 -0
  89. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_sections/definition.yaml +0 -0
  90. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_skill_sections/matimo_get_skill_sections.py +0 -0
  91. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool/definition.yaml +0 -0
  92. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool/matimo_get_tool.py +0 -0
  93. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool_status/definition.yaml +0 -0
  94. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_get_tool_status/matimo_get_tool_status.py +0 -0
  95. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_skills/definition.yaml +0 -0
  96. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_skills/matimo_list_skills.py +0 -0
  97. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_user_tools/definition.yaml +0 -0
  98. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_list_user_tools/matimo_list_user_tools.py +0 -0
  99. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_reload_tools/definition.yaml +0 -0
  100. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_reload_tools/matimo_reload_tools.py +0 -0
  101. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_skills/definition.yaml +0 -0
  102. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_skills/matimo_search_skills.py +0 -0
  103. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_tools/definition.yaml +0 -0
  104. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_search_tools/matimo_search_tools.py +0 -0
  105. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_skill/definition.yaml +0 -0
  106. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_skill/matimo_validate_skill.py +0 -0
  107. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_tool/definition.yaml +0 -0
  108. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/matimo_validate_tool/matimo_validate_tool.py +0 -0
  109. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/read/definition.yaml +0 -0
  110. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/search/definition.yaml +0 -0
  111. {matimo_core-0.2.0 → matimo_core-0.4.0}/src/matimo/tools/web/definition.yaml +0 -0
  112. {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.2.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>=1.28.1; extra == 'all'
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>=1.28.1; extra == 'dev'
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>=1.28.1; extra == '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.2.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>=1.28.1"]
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>=1.28.1",
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>=1.28.1",
67
+ "mcp>=2.0",
68
68
  "hvac>=2.0",
69
69
  "boto3>=1.34",
70
70
  "python-dotenv>=1.0",
@@ -185,7 +185,7 @@ from matimo.policy.types import (
185
185
  )
186
186
  from matimo.sync import MatimoSync
187
187
 
188
- __version__ = "0.2.0"
188
+ __version__ = "0.4.0"
189
189
 
190
190
 
191
191
  def get_core_tools_path() -> str:
@@ -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
- """Return the current global Matimo instance."""
38
- return _global_instance
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(timeout=timeout_s) as client, client.stream(
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
- return await self._function_executor.execute(tool, params, credentials, context)
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
- if not handler.requires_approval(
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 1.x/2.x compatibility** - mcp 2.0 replaced `Server`'s decorator-based
14
- handler registration with constructor callables; `server.py` detects the
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
- On mcp<2.0 this is registered post-construction via decorators
184
- (`_register_skill_resources_v1`); on mcp>=2.0 the same mapping is built as a
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 _register_skill_resources_v1(self, server: Any) -> None:
190
- skills = self._matimo.list_skills()
191
-
192
- @server.list_resources() # MCP: resources/list
193
- async def handle_list_resources() -> list[Resource]:
194
- return [
195
- Resource(
196
- uri=AnyUrl(f"skills://{s.name}"),
197
- name=s.name,
198
- description=s.description,
199
- mimeType="text/markdown"
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
- ├─ Create MCP Server instance
290
- ├─ Register @server.list_tools() handler
291
- ├─ Register @server.call_tool() handler
292
- ├─ Register @server.list_resources() handler
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: "slack_send_message",
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: "slack_send_message",
311
+ name: "slack_send_channel_message",
327
312
  arguments: {channel: "#general", text: "Hello"}
328
313
  }
329
- ├─ Handler calls _call_tool("slack_send_message", {...})
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
- │ "slack_send_message",
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
- "notion_database_*", # All Notion database tools
458
- "gmail_send", # Exact match (no wildcards)
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()` | ✅ `_register_skill_resources_v1()` / `_build_skill_resource_handlers_v2()` | Parity |
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>=1.0
530
+ pip install "mcp>=2.0"
546
531
  ```
547
532
 
548
533
  ### Issue: "Auth token mismatch" in HTTP mode