matimo-core 0.1.0__py3-none-any.whl

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 (85) hide show
  1. matimo/__init__.py +318 -0
  2. matimo/approval/__init__.py +0 -0
  3. matimo/approval/handler.py +152 -0
  4. matimo/auth/__init__.py +27 -0
  5. matimo/auth/injection.py +141 -0
  6. matimo/auth/oauth2_config.py +76 -0
  7. matimo/auth/oauth2_handler.py +291 -0
  8. matimo/auth/oauth2_provider_loader.py +103 -0
  9. matimo/core/__init__.py +0 -0
  10. matimo/core/loader.py +215 -0
  11. matimo/core/models.py +512 -0
  12. matimo/core/registry.py +162 -0
  13. matimo/core/skill_content_parser.py +242 -0
  14. matimo/core/skill_loader.py +338 -0
  15. matimo/core/skill_registry.py +308 -0
  16. matimo/core/tfidf_embedding.py +155 -0
  17. matimo/decorators/__init__.py +133 -0
  18. matimo/encodings/__init__.py +0 -0
  19. matimo/encodings/parameter_encoding.py +160 -0
  20. matimo/errors.py +96 -0
  21. matimo/executors/__init__.py +0 -0
  22. matimo/executors/command_executor.py +154 -0
  23. matimo/executors/function_executor.py +193 -0
  24. matimo/executors/http_executor.py +292 -0
  25. matimo/instance.py +623 -0
  26. matimo/integrations/__init__.py +0 -0
  27. matimo/integrations/_pydantic_utils.py +79 -0
  28. matimo/integrations/crewai.py +137 -0
  29. matimo/integrations/langchain.py +190 -0
  30. matimo/logging/__init__.py +194 -0
  31. matimo/mcp/README.md +580 -0
  32. matimo/mcp/__init__.py +6 -0
  33. matimo/mcp/secrets/__init__.py +271 -0
  34. matimo/mcp/secrets/types.py +23 -0
  35. matimo/mcp/server.py +490 -0
  36. matimo/mcp/tool_converter.py +120 -0
  37. matimo/policy/__init__.py +0 -0
  38. matimo/policy/approval_manifest.py +205 -0
  39. matimo/policy/content_validator.py +212 -0
  40. matimo/policy/default_policy.py +325 -0
  41. matimo/policy/integrity_tracker.py +92 -0
  42. matimo/policy/policy_loader.py +107 -0
  43. matimo/policy/risk_classifier.py +47 -0
  44. matimo/policy/types.py +193 -0
  45. matimo/sync.py +122 -0
  46. matimo/tools/calculator/calculator.py +36 -0
  47. matimo/tools/calculator/definition.yaml +70 -0
  48. matimo/tools/edit/definition.yaml +108 -0
  49. matimo/tools/edit/edit.py +79 -0
  50. matimo/tools/execute/definition.yaml +90 -0
  51. matimo/tools/execute/execute.py +74 -0
  52. matimo/tools/matimo_approve_tool/definition.yaml +36 -0
  53. matimo/tools/matimo_approve_tool/matimo_approve_tool.py +61 -0
  54. matimo/tools/matimo_create_skill/definition.yaml +46 -0
  55. matimo/tools/matimo_create_skill/matimo_create_skill.py +64 -0
  56. matimo/tools/matimo_create_tool/definition.yaml +48 -0
  57. matimo/tools/matimo_create_tool/matimo_create_tool.py +101 -0
  58. matimo/tools/matimo_get_skill/definition.yaml +60 -0
  59. matimo/tools/matimo_get_skill/matimo_get_skill.py +119 -0
  60. matimo/tools/matimo_get_tool/definition.yaml +36 -0
  61. matimo/tools/matimo_get_tool/matimo_get_tool.py +48 -0
  62. matimo/tools/matimo_get_tool_status/definition.yaml +42 -0
  63. matimo/tools/matimo_get_tool_status/matimo_get_tool_status.py +65 -0
  64. matimo/tools/matimo_list_skills/definition.yaml +50 -0
  65. matimo/tools/matimo_list_skills/matimo_list_skills.py +95 -0
  66. matimo/tools/matimo_list_user_tools/definition.yaml +32 -0
  67. matimo/tools/matimo_list_user_tools/matimo_list_user_tools.py +51 -0
  68. matimo/tools/matimo_reload_tools/definition.yaml +35 -0
  69. matimo/tools/matimo_reload_tools/matimo_reload_tools.py +40 -0
  70. matimo/tools/matimo_search_tools/definition.yaml +32 -0
  71. matimo/tools/matimo_search_tools/matimo_search_tools.py +73 -0
  72. matimo/tools/matimo_validate_skill/definition.yaml +43 -0
  73. matimo/tools/matimo_validate_skill/matimo_validate_skill.py +134 -0
  74. matimo/tools/matimo_validate_tool/definition.yaml +34 -0
  75. matimo/tools/matimo_validate_tool/matimo_validate_tool.py +73 -0
  76. matimo/tools/read/definition.yaml +101 -0
  77. matimo/tools/read/read.py +53 -0
  78. matimo/tools/search/definition.yaml +132 -0
  79. matimo/tools/search/search.py +106 -0
  80. matimo/tools/web/definition.yaml +124 -0
  81. matimo/tools/web/web.py +82 -0
  82. matimo_core-0.1.0.dist-info/METADATA +324 -0
  83. matimo_core-0.1.0.dist-info/RECORD +85 -0
  84. matimo_core-0.1.0.dist-info/WHEEL +4 -0
  85. matimo_core-0.1.0.dist-info/entry_points.txt +2 -0
matimo/__init__.py ADDED
@@ -0,0 +1,318 @@
1
+ """
2
+ Matimo Python SDK — Public API
3
+ ================================
4
+
5
+ Write tools once in YAML, use them everywhere.
6
+
7
+ Quick start:
8
+ from matimo import Matimo
9
+
10
+ matimo = await Matimo.init('./tools')
11
+ result = await matimo.execute('my_tool', {'param': 'value'})
12
+
13
+ LangChain integration:
14
+ from matimo import Matimo, convert_tools_to_langchain
15
+ matimo = await Matimo.init('./tools')
16
+ tools = convert_tools_to_langchain(matimo.list_tools(), matimo)
17
+
18
+ CrewAI integration:
19
+ from matimo import Matimo, convert_tools_to_crewai
20
+ matimo = await Matimo.init('./tools')
21
+ tools = convert_tools_to_crewai(matimo.list_tools(), matimo)
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import importlib.resources
26
+ from pathlib import Path
27
+ from typing import TYPE_CHECKING, Any
28
+
29
+ if TYPE_CHECKING:
30
+ from matimo.instance import Matimo
31
+
32
+ # Approval
33
+ from matimo.approval.handler import (
34
+ ApprovalCallback,
35
+ ApprovalHandler,
36
+ ApprovalRequest,
37
+ get_global_approval_handler,
38
+ set_global_approval_handler,
39
+ )
40
+
41
+ # Auth
42
+ from matimo.auth.injection import extract_parameter_placeholders, inject_auth_parameters
43
+ from matimo.auth.oauth2_config import (
44
+ AuthorizationOptions,
45
+ OAuth2Config,
46
+ OAuth2Token,
47
+ TokenResponse,
48
+ )
49
+ from matimo.auth.oauth2_handler import OAuth2Handler
50
+ from matimo.auth.oauth2_provider_loader import OAuth2ProviderLoader
51
+
52
+ # Core loading + registry
53
+ from matimo.core.loader import ToolLoader
54
+
55
+ # Core models
56
+ from matimo.core.models import (
57
+ AuthConfig,
58
+ AuthType,
59
+ BundledResources,
60
+ CommandExecution,
61
+ ExecuteOptions,
62
+ ExecutionResult,
63
+ FunctionExecution,
64
+ HttpExecution,
65
+ OutputSchema,
66
+ Parameter,
67
+ ParameterEncoding,
68
+ ParameterEncodingConfig,
69
+ ParameterEncodingType,
70
+ ParameterType,
71
+ ParsedSkill,
72
+ PolicyContext,
73
+ ProviderDefinition,
74
+ RateLimitConfig,
75
+ SearchSkillsOptions,
76
+ SkillContentOptions,
77
+ SkillDefinition,
78
+ SkillFrontmatter,
79
+ SkillSection,
80
+ SkillSummary,
81
+ ToolDefinition,
82
+ ToolExample,
83
+ ToolStatus,
84
+ ValidationError,
85
+ ValidationResult,
86
+ )
87
+ from matimo.core.registry import ToolRegistry
88
+
89
+ # Skills
90
+ from matimo.core.skill_content_parser import (
91
+ ParsedSkillContent,
92
+ extract_skill_content,
93
+ list_skill_sections,
94
+ parse_skill_sections,
95
+ )
96
+ from matimo.core.skill_loader import SkillLoader, extract_skill_metadata, parse_skill_content
97
+ from matimo.core.skill_registry import SemanticSearchResult, SkillRegistry
98
+ from matimo.core.tfidf_embedding import (
99
+ EmbeddingProvider,
100
+ TfIdfEmbeddingProvider,
101
+ cosine_similarity,
102
+ )
103
+
104
+ # Decorators
105
+ from matimo.decorators import (
106
+ get_global_matimo_instance,
107
+ set_global_matimo_instance,
108
+ tool,
109
+ )
110
+
111
+ # Encodings
112
+ from matimo.encodings.parameter_encoding import apply_parameter_encodings
113
+
114
+ # Errors
115
+ from matimo.errors import (
116
+ ErrorCode,
117
+ MatimoError,
118
+ create_execution_error,
119
+ create_validation_error,
120
+ from_http_error,
121
+ )
122
+
123
+ # Executors
124
+ from matimo.executors.command_executor import CommandExecutor
125
+ from matimo.executors.function_executor import FunctionExecutor
126
+ from matimo.executors.http_executor import HttpExecutor
127
+
128
+ # Main entry point + sync API
129
+ from matimo.instance import InitOptions, Matimo, ReloadResult, matimo
130
+
131
+ # Logging
132
+ from matimo.logging import (
133
+ MatimoLogger,
134
+ get_global_matimo_logger,
135
+ set_global_matimo_logger,
136
+ setup_logger,
137
+ )
138
+
139
+ # MCP
140
+ from matimo.mcp.secrets import (
141
+ AwsSecretsManagerResolver,
142
+ DotenvSecretResolver,
143
+ EnvSecretResolver,
144
+ SecretResolverChain,
145
+ VaultSecretResolver,
146
+ create_resolver_chain,
147
+ )
148
+ from matimo.mcp.server import MCPServer, MCPServerOptions, create_mcp_server
149
+ from matimo.mcp.tool_converter import convert_parameters_to_mcp_schema
150
+
151
+ # Policy
152
+ from matimo.policy.approval_manifest import ApprovalManifest, ApprovalRecord
153
+ from matimo.policy.content_validator import ContentViolation, validate_tool_content
154
+ from matimo.policy.default_policy import DefaultPolicyEngine, PolicyEngine, get_tier_for_tool
155
+ from matimo.policy.integrity_tracker import IntegrityAction, ToolIntegrityTracker
156
+ from matimo.policy.policy_loader import load_policy_from_file
157
+ from matimo.policy.risk_classifier import classify_risk
158
+ from matimo.policy.types import (
159
+ HITLCallback,
160
+ HITLRequest,
161
+ MatimoEvent,
162
+ MatimoEventHandler,
163
+ PolicyAllowed,
164
+ PolicyConfig,
165
+ PolicyDecision,
166
+ PolicyDenied,
167
+ PolicyPendingApproval,
168
+ PolicyTier,
169
+ RiskLevel,
170
+ )
171
+ from matimo.sync import MatimoSync
172
+
173
+ __version__ = "0.1.0"
174
+
175
+
176
+ def get_core_tools_path() -> str:
177
+ """Return the absolute path to the bundled core tool definitions.
178
+
179
+ Used as a ``matimo.providers`` entry point so that core meta-tools
180
+ (execute, read, edit, search, web, calculator, matimo_create_tool, …)
181
+ are auto-discovered by :meth:`matimo.Matimo.init` when
182
+ ``auto_discover=True``.
183
+ """
184
+ try:
185
+ ref = importlib.resources.files("matimo") / "tools"
186
+ return str(ref)
187
+ except Exception:
188
+ return str(Path(__file__).parent / "tools")
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # Integrations (lazy — raise ImportError with hint on missing optional dep)
192
+ # ---------------------------------------------------------------------------
193
+
194
+
195
+ def convert_tools_to_langchain(
196
+ tools: list[Any],
197
+ matimo_instance: Matimo,
198
+ credentials: dict[str, str] | None = None,
199
+ ) -> list[Any]:
200
+ """Convert Matimo tools to LangChain StructuredTool list. Requires langchain-core."""
201
+ from matimo.integrations.langchain import convert_tools_to_langchain as _inner
202
+ return _inner(tools, matimo_instance, credentials)
203
+
204
+
205
+ def get_skills_metadata(matimo_instance: Matimo) -> list[dict[str, str]]:
206
+ """Return Level-1 metadata (name + description) for all available skills."""
207
+ from matimo.integrations.langchain import get_skills_metadata as _inner
208
+ return _inner(matimo_instance)
209
+
210
+
211
+ async def build_relevant_skill_prompt(
212
+ matimo_instance: Matimo,
213
+ query: str,
214
+ *,
215
+ top_k: int = 3,
216
+ min_score: float = 0.3,
217
+ header: str | None = None,
218
+ ) -> str:
219
+ """Build a per-request skill context prompt using TF-IDF semantic search."""
220
+ from matimo.integrations.langchain import build_relevant_skill_prompt as _inner
221
+ return await _inner(matimo_instance, query, top_k=top_k, min_score=min_score, header=header)
222
+
223
+
224
+ def convert_tools_to_crewai(
225
+ tools: list[Any],
226
+ matimo_instance: Matimo,
227
+ credentials: dict[str, str] | None = None,
228
+ ) -> list[Any]:
229
+ """Convert Matimo tools to CrewAI BaseTool list. Requires crewai."""
230
+ from matimo.integrations.crewai import convert_tools_to_crewai as _inner
231
+ return _inner(tools, matimo_instance, credentials)
232
+
233
+
234
+ # ---------------------------------------------------------------------------
235
+ # Decorators
236
+ # ---------------------------------------------------------------------------
237
+
238
+ # ---------------------------------------------------------------------------
239
+ # Errors
240
+ # ---------------------------------------------------------------------------
241
+
242
+ # ---------------------------------------------------------------------------
243
+ # Main entry point
244
+ # ---------------------------------------------------------------------------
245
+
246
+ # ---------------------------------------------------------------------------
247
+ # Logging
248
+ # ---------------------------------------------------------------------------
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # Synchronous API
252
+ # ---------------------------------------------------------------------------
253
+
254
+ __all__ = [
255
+ # Core models
256
+ "ToolDefinition", "Parameter", "ParameterType", "AuthConfig", "AuthType",
257
+ "HttpExecution", "CommandExecution", "FunctionExecution",
258
+ "OutputSchema", "RateLimitConfig", "ToolExample",
259
+ "ParameterEncoding", "ParameterEncodingConfig", "ParameterEncodingType",
260
+ "ToolStatus",
261
+ "ExecutionResult", "ExecuteOptions", "PolicyContext",
262
+ "ValidationError", "ValidationResult",
263
+ "ParsedSkill", "SkillFrontmatter", "ProviderDefinition",
264
+ "SkillDefinition", "SkillSummary", "SkillSection", "SkillContentOptions",
265
+ "SearchSkillsOptions", "BundledResources",
266
+ # Core
267
+ "ToolLoader", "ToolRegistry",
268
+ # Skills
269
+ "SkillLoader", "SkillRegistry",
270
+ "parse_skill_content", "extract_skill_metadata",
271
+ "parse_skill_sections", "extract_skill_content", "list_skill_sections",
272
+ "ParsedSkillContent", "SemanticSearchResult",
273
+ "TfIdfEmbeddingProvider", "EmbeddingProvider", "cosine_similarity",
274
+ # Executors
275
+ "HttpExecutor", "CommandExecutor", "FunctionExecutor",
276
+ # Auth
277
+ "inject_auth_parameters", "extract_parameter_placeholders",
278
+ "OAuth2Handler", "OAuth2ProviderLoader",
279
+ "OAuth2Config", "OAuth2Token", "AuthorizationOptions", "TokenResponse",
280
+ # Approval
281
+ "ApprovalHandler", "ApprovalRequest", "ApprovalCallback",
282
+ "get_global_approval_handler", "set_global_approval_handler",
283
+ # Encodings
284
+ "apply_parameter_encodings",
285
+ # Policy
286
+ "PolicyEngine", "DefaultPolicyEngine", "PolicyConfig",
287
+ "PolicyDecision", "PolicyAllowed", "PolicyDenied", "PolicyPendingApproval",
288
+ "RiskLevel", "PolicyTier",
289
+ "MatimoEvent", "MatimoEventHandler", "HITLCallback", "HITLRequest",
290
+ "ContentViolation", "validate_tool_content",
291
+ "classify_risk", "get_tier_for_tool",
292
+ "ToolIntegrityTracker", "IntegrityAction",
293
+ "ApprovalManifest", "ApprovalRecord",
294
+ "load_policy_from_file",
295
+ # MCP
296
+ "MCPServer", "MCPServerOptions", "create_mcp_server",
297
+ "convert_parameters_to_mcp_schema",
298
+ "EnvSecretResolver", "DotenvSecretResolver",
299
+ "VaultSecretResolver", "AwsSecretsManagerResolver",
300
+ "SecretResolverChain", "create_resolver_chain",
301
+ # Integrations
302
+ "convert_tools_to_langchain",
303
+ "get_skills_metadata",
304
+ "build_relevant_skill_prompt",
305
+ "convert_tools_to_crewai",
306
+ # Decorators
307
+ "tool", "set_global_matimo_instance", "get_global_matimo_instance",
308
+ # Logging
309
+ "MatimoLogger", "setup_logger",
310
+ "get_global_matimo_logger", "set_global_matimo_logger",
311
+ # Errors
312
+ "MatimoError", "ErrorCode",
313
+ "create_execution_error", "create_validation_error", "from_http_error",
314
+ # Instance
315
+ "Matimo", "MatimoSync", "matimo", "InitOptions", "ReloadResult",
316
+ # Core tools path (entry point)
317
+ "get_core_tools_path",
318
+ ]
File without changes
@@ -0,0 +1,152 @@
1
+ """
2
+ Approval handler — human-in-the-loop approval gating for sensitive tools.
3
+ Mirrors: packages/core/src/approval/approval-handler.ts
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import fnmatch
8
+ import logging
9
+ import os
10
+ from collections.abc import Awaitable, Callable
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ logger = logging.getLogger("matimo")
15
+
16
+ # Destructive action keywords that trigger approval prompts
17
+ DEFAULT_DESTRUCTIVE_KEYWORDS: list[str] = [
18
+ "CREATE", "DELETE", "DESTROY", "DROP", "ALTER", "TRUNCATE", "UPDATE",
19
+ "INSERT", "UPSERT", "REPLACE", "MERGE", "GRANT", "REVOKE",
20
+ "EDIT", "WRITE", "APPEND", "REMOVE", "PURGE", "RENAME", "SHUTDOWN",
21
+ "EXECUTE", "EXEC",
22
+ ]
23
+
24
+
25
+ @dataclass
26
+ class ApprovalRequest:
27
+ """Represents a pending approval for a tool execution."""
28
+
29
+ tool_name: str
30
+ description: str | None
31
+ params: dict[str, Any]
32
+
33
+
34
+ ApprovalCallback = Callable[[ApprovalRequest], Awaitable[bool]]
35
+
36
+
37
+ class ApprovalHandler:
38
+ """
39
+ Manages interactive approval gating for sensitive tool executions.
40
+ Mirrors: ApprovalHandler in approval-handler.ts
41
+
42
+ Approval flow:
43
+ 1. auto_approve=True (MATIMO_AUTO_APPROVE env) → always approve
44
+ 2. tool_name matches an approved pattern → approve
45
+ 3. HITL callback set → invoke it and return its decision
46
+ 4. Default → deny
47
+ """
48
+
49
+ def __init__(self) -> None:
50
+ self.auto_approve: bool = (
51
+ os.environ.get("MATIMO_AUTO_APPROVE", "").lower() == "true"
52
+ )
53
+ self.approved_patterns: set[str] = self._load_approved_patterns()
54
+ self.destructive_keywords: list[str] = list(DEFAULT_DESTRUCTIVE_KEYWORDS)
55
+ self._callback: ApprovalCallback | None = None
56
+
57
+ # ------------------------------------------------------------------
58
+ # Public API
59
+ # ------------------------------------------------------------------
60
+
61
+ def set_approval_callback(self, callback: ApprovalCallback) -> None:
62
+ """Wire an async approval callback (e.g., Slack DM, CLI prompt)."""
63
+ self._callback = callback
64
+
65
+ async def request_approval(self, request: ApprovalRequest) -> bool:
66
+ """
67
+ Gate execution on approval.
68
+ Returns True if approved, False if denied.
69
+ """
70
+ # 1. Hard auto-approve (CI / testing)
71
+ if self.auto_approve:
72
+ logger.debug(
73
+ "Auto-approving tool '%s' (MATIMO_AUTO_APPROVE=true)", request.tool_name
74
+ )
75
+ return True
76
+
77
+ # 2. Pattern allowlist
78
+ if self._matches_approved_pattern(request.tool_name):
79
+ logger.debug(
80
+ "Tool '%s' matches approved pattern — skipping approval prompt",
81
+ request.tool_name,
82
+ )
83
+ return True
84
+
85
+ # 3. HITL callback
86
+ if self._callback is not None:
87
+ approved = await self._callback(request)
88
+ if not approved:
89
+ logger.info(
90
+ "Approval denied for tool '%s' by callback", request.tool_name
91
+ )
92
+ return approved
93
+
94
+ # 4. No callback → default deny
95
+ logger.warning(
96
+ "Approval required for tool '%s' but no callback configured — denying",
97
+ request.tool_name,
98
+ )
99
+ return False
100
+
101
+ def is_destructive(self, tool_name: str, params: dict[str, Any]) -> bool:
102
+ """
103
+ Heuristically determine whether a tool invocation is destructive,
104
+ based on keyword scanning of the tool name and string parameter values.
105
+ """
106
+ combined = tool_name.upper()
107
+ for v in params.values():
108
+ if isinstance(v, str):
109
+ combined += " " + v.upper()
110
+
111
+ return any(kw in combined for kw in self.destructive_keywords)
112
+
113
+ def add_approved_pattern(self, pattern: str) -> None:
114
+ """Add a glob pattern to the pre-approved tool allowlist."""
115
+ self.approved_patterns.add(pattern)
116
+
117
+ # ------------------------------------------------------------------
118
+ # Internal
119
+ # ------------------------------------------------------------------
120
+
121
+ def _load_approved_patterns(self) -> set[str]:
122
+ raw = os.environ.get("MATIMO_APPROVED_PATTERNS", "")
123
+ if not raw:
124
+ return set()
125
+ return {p.strip() for p in raw.split(",") if p.strip()}
126
+
127
+ def _matches_approved_pattern(self, tool_name: str) -> bool:
128
+ return any(
129
+ fnmatch.fnmatch(tool_name, pattern)
130
+ for pattern in self.approved_patterns
131
+ )
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # Module-level singleton
136
+ # ---------------------------------------------------------------------------
137
+
138
+ _global_handler: ApprovalHandler | None = None
139
+
140
+
141
+ def get_global_approval_handler() -> ApprovalHandler:
142
+ """Return the global ApprovalHandler, creating one if necessary."""
143
+ global _global_handler
144
+ if _global_handler is None:
145
+ _global_handler = ApprovalHandler()
146
+ return _global_handler
147
+
148
+
149
+ def set_global_approval_handler(handler: ApprovalHandler) -> None:
150
+ """Replace the global approval handler (useful in tests)."""
151
+ global _global_handler
152
+ _global_handler = handler
@@ -0,0 +1,27 @@
1
+ """
2
+ Matimo auth module — injection helpers and OAuth2 support.
3
+
4
+ Mirrors: packages/core/src/auth/
5
+ """
6
+ from matimo.auth.injection import extract_parameter_placeholders, inject_auth_parameters
7
+ from matimo.auth.oauth2_config import (
8
+ AuthorizationOptions,
9
+ OAuth2Config,
10
+ OAuth2Token,
11
+ TokenResponse,
12
+ )
13
+ from matimo.auth.oauth2_handler import OAuth2Handler
14
+ from matimo.auth.oauth2_provider_loader import OAuth2ProviderLoader
15
+
16
+ __all__ = [
17
+ # Injection
18
+ "inject_auth_parameters",
19
+ "extract_parameter_placeholders",
20
+ # OAuth2
21
+ "AuthorizationOptions",
22
+ "OAuth2Config",
23
+ "OAuth2Handler",
24
+ "OAuth2ProviderLoader",
25
+ "OAuth2Token",
26
+ "TokenResponse",
27
+ ]
@@ -0,0 +1,141 @@
1
+ """
2
+ Auth parameter injection.
3
+ Mirrors: MatimoInstance.injectAuthParameters() in matimo-instance.ts
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import os
9
+ import re
10
+ from typing import Any
11
+
12
+ from matimo.core.models import ToolDefinition
13
+
14
+ logger = logging.getLogger("matimo")
15
+
16
+ # Patterns that indicate a parameter placeholder is auth-related
17
+ _AUTH_PATTERNS = (
18
+ "token", "key", "secret", "password", "credential",
19
+ "auth", "bearer", "api_key",
20
+ )
21
+
22
+ _PLACEHOLDER_RE = re.compile(r"\{([^}]+)\}")
23
+
24
+
25
+ def inject_auth_parameters(
26
+ tool: ToolDefinition,
27
+ params: dict[str, Any],
28
+ credentials: dict[str, str] | None = None,
29
+ ) -> dict[str, Any]:
30
+ """
31
+ Scan all placeholders in the tool's execution config (URL, headers, body,
32
+ query_params). For any placeholder that looks auth-related and has no value
33
+ in params, attempt to resolve it from:
34
+ 1. per-call credentials dict
35
+ 2. process environment (MATIMO_{TOOL_NAME}_{KEY} or just {KEY})
36
+
37
+ SECURITY: injected values are never logged.
38
+
39
+ Returns a new params dict with the resolved auth values merged in.
40
+ """
41
+ placeholders = extract_parameter_placeholders(tool)
42
+ result = dict(params)
43
+
44
+ for placeholder in placeholders:
45
+ # Already supplied by the caller
46
+ if placeholder in result:
47
+ continue
48
+
49
+ lower = placeholder.lower()
50
+ is_auth = any(pattern in lower for pattern in _AUTH_PATTERNS)
51
+ if not is_auth:
52
+ continue
53
+
54
+ # Attempt resolution
55
+ value = _resolve_auth_value(tool.name, placeholder, credentials)
56
+ if value is not None:
57
+ result[placeholder] = value
58
+ logger.debug(
59
+ "Injected auth parameter '%s' for tool '%s'",
60
+ placeholder, tool.name
61
+ )
62
+
63
+ return result
64
+
65
+
66
+ def extract_parameter_placeholders(tool: ToolDefinition) -> set[str]:
67
+ """
68
+ Extract all {placeholder} names from the tool's execution config
69
+ (url, headers, body, query_params, params, args).
70
+ Mirrors: extractParameterPlaceholders() in matimo-instance.ts
71
+ """
72
+ placeholders: set[str] = set()
73
+ exec_cfg = tool.execution
74
+ exec_type = exec_cfg.type
75
+
76
+ if exec_type == "http":
77
+ _scan_string(exec_cfg.url, placeholders) # type: ignore[attr-defined]
78
+ _scan_object(exec_cfg.headers, placeholders) # type: ignore[attr-defined]
79
+ _scan_object(exec_cfg.body, placeholders) # type: ignore[attr-defined]
80
+ _scan_object(exec_cfg.query_params, placeholders) # type: ignore[attr-defined]
81
+ _scan_object(exec_cfg.params, placeholders) # type: ignore[attr-defined]
82
+
83
+ elif exec_type == "command":
84
+ _scan_string(exec_cfg.command, placeholders) # type: ignore[attr-defined]
85
+ for arg in exec_cfg.args or []: # type: ignore[attr-defined]
86
+ _scan_string(arg, placeholders)
87
+
88
+ elif exec_type == "function":
89
+ # Function tools may have params passed directly — nothing to scan
90
+ pass
91
+
92
+ return placeholders
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Internal helpers
97
+ # ---------------------------------------------------------------------------
98
+
99
+
100
+ def _resolve_auth_value(
101
+ tool_name: str,
102
+ placeholder: str,
103
+ credentials: dict[str, str] | None,
104
+ ) -> str | None:
105
+ """
106
+ Try to resolve an auth placeholder in priority order:
107
+ 1. per-call credentials
108
+ 2. env MATIMO_{TOOL_NAME_UPPER}_{PLACEHOLDER_UPPER}
109
+ 3. env {PLACEHOLDER} directly
110
+ """
111
+ if credentials and placeholder in credentials:
112
+ return credentials[placeholder]
113
+
114
+ # e.g. MATIMO_SLACK_SLACK_BOT_TOKEN
115
+ env_key_prefixed = f"MATIMO_{tool_name.upper()}_{placeholder.upper()}"
116
+ val = os.environ.get(env_key_prefixed)
117
+ if val:
118
+ return val
119
+
120
+ # env directly (e.g. SLACK_BOT_TOKEN)
121
+ return os.environ.get(placeholder)
122
+
123
+
124
+ def _scan_string(value: str | None, out: set[str]) -> None:
125
+ if value is None:
126
+ return
127
+ for m in _PLACEHOLDER_RE.finditer(value):
128
+ out.add(m.group(1))
129
+
130
+
131
+ def _scan_object(obj: object, out: set[str]) -> None:
132
+ if obj is None:
133
+ return
134
+ if isinstance(obj, str):
135
+ _scan_string(obj, out)
136
+ elif isinstance(obj, dict):
137
+ for v in obj.values():
138
+ _scan_object(v, out)
139
+ elif isinstance(obj, list):
140
+ for item in obj:
141
+ _scan_object(item, out)