delimit-cli 2.3.2 → 3.0.0
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.
- package/.dockerignore +7 -0
- package/.github/workflows/ci.yml +22 -0
- package/CHANGELOG.md +33 -0
- package/CODE_OF_CONDUCT.md +48 -0
- package/CONTRIBUTING.md +67 -0
- package/Dockerfile +9 -0
- package/LICENSE +21 -0
- package/README.md +51 -130
- package/SECURITY.md +42 -0
- package/adapters/codex-forge.js +107 -0
- package/adapters/codex-jamsons.js +142 -0
- package/adapters/codex-security.js +94 -0
- package/adapters/gemini-forge.js +120 -0
- package/adapters/gemini-jamsons.js +152 -0
- package/bin/delimit-cli.js +52 -2
- package/bin/delimit-setup.js +258 -0
- package/gateway/ai/backends/__init__.py +0 -0
- package/gateway/ai/backends/async_utils.py +21 -0
- package/gateway/ai/backends/deploy_bridge.py +150 -0
- package/gateway/ai/backends/gateway_core.py +261 -0
- package/gateway/ai/backends/generate_bridge.py +38 -0
- package/gateway/ai/backends/governance_bridge.py +196 -0
- package/gateway/ai/backends/intel_bridge.py +59 -0
- package/gateway/ai/backends/memory_bridge.py +93 -0
- package/gateway/ai/backends/ops_bridge.py +137 -0
- package/gateway/ai/backends/os_bridge.py +82 -0
- package/gateway/ai/backends/repo_bridge.py +117 -0
- package/gateway/ai/backends/ui_bridge.py +118 -0
- package/gateway/ai/backends/vault_bridge.py +129 -0
- package/gateway/ai/server.py +1182 -0
- package/gateway/core/__init__.py +3 -0
- package/gateway/core/__pycache__/__init__.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/auto_baseline.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/ci_formatter.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/contract_ledger.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/dependency_graph.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/dependency_manifest.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/diff_engine_v2.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/event_backbone.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/event_schema.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/explainer.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/gateway.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/gateway_v2.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/gateway_v3.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/impact_analyzer.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/policy_engine.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/registry.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/registry_v2.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/registry_v3.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/semver_classifier.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/spec_detector.cpython-310.pyc +0 -0
- package/gateway/core/__pycache__/surface_bridge.cpython-310.pyc +0 -0
- package/gateway/core/auto_baseline.py +304 -0
- package/gateway/core/ci_formatter.py +283 -0
- package/gateway/core/complexity_analyzer.py +386 -0
- package/gateway/core/contract_ledger.py +345 -0
- package/gateway/core/dependency_graph.py +218 -0
- package/gateway/core/dependency_manifest.py +223 -0
- package/gateway/core/diff_engine_v2.py +477 -0
- package/gateway/core/diff_engine_v2.py.bak +426 -0
- package/gateway/core/event_backbone.py +268 -0
- package/gateway/core/event_schema.py +258 -0
- package/gateway/core/explainer.py +438 -0
- package/gateway/core/gateway.py +128 -0
- package/gateway/core/gateway_v2.py +154 -0
- package/gateway/core/gateway_v3.py +224 -0
- package/gateway/core/impact_analyzer.py +163 -0
- package/gateway/core/policies/default.yml +13 -0
- package/gateway/core/policies/relaxed.yml +48 -0
- package/gateway/core/policies/strict.yml +55 -0
- package/gateway/core/policy_engine.py +464 -0
- package/gateway/core/registry.py +52 -0
- package/gateway/core/registry_v2.py +132 -0
- package/gateway/core/registry_v3.py +134 -0
- package/gateway/core/semver_classifier.py +152 -0
- package/gateway/core/spec_detector.py +130 -0
- package/gateway/core/surface_bridge.py +307 -0
- package/gateway/core/zero_spec/__init__.py +4 -0
- package/gateway/core/zero_spec/__pycache__/__init__.cpython-310.pyc +0 -0
- package/gateway/core/zero_spec/__pycache__/detector.cpython-310.pyc +0 -0
- package/gateway/core/zero_spec/__pycache__/express_extractor.cpython-310.pyc +0 -0
- package/gateway/core/zero_spec/__pycache__/fastapi_extractor.cpython-310.pyc +0 -0
- package/gateway/core/zero_spec/__pycache__/nestjs_extractor.cpython-310.pyc +0 -0
- package/gateway/core/zero_spec/detector.py +353 -0
- package/gateway/core/zero_spec/express_extractor.py +483 -0
- package/gateway/core/zero_spec/fastapi_extractor.py +254 -0
- package/gateway/core/zero_spec/nestjs_extractor.py +369 -0
- package/gateway/tasks/__init__.py +1 -0
- package/gateway/tasks/__pycache__/__init__.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/check_policy.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/check_policy_v2.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/check_policy_v3.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/explain_diff.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/explain_diff_v2.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/validate_api.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/validate_api_v2.cpython-310.pyc +0 -0
- package/gateway/tasks/__pycache__/validate_api_v3.cpython-310.pyc +0 -0
- package/gateway/tasks/check_policy.py +177 -0
- package/gateway/tasks/check_policy_v2.py +255 -0
- package/gateway/tasks/check_policy_v3.py +255 -0
- package/gateway/tasks/explain_diff.py +305 -0
- package/gateway/tasks/explain_diff_v2.py +267 -0
- package/gateway/tasks/validate_api.py +131 -0
- package/gateway/tasks/validate_api_v2.py +208 -0
- package/gateway/tasks/validate_api_v3.py +163 -0
- package/package.json +3 -3
- package/adapters/codex-skill.js +0 -87
- package/adapters/cursor-extension.js +0 -190
- package/adapters/gemini-action.js +0 -93
- package/adapters/openai-function.js +0 -112
- package/adapters/xai-plugin.js +0 -151
- package/test-decision-engine.js +0 -181
- package/test-hook.js +0 -27
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Delimit Unified MCP Server v2.0
|
|
4
|
+
Single agent-facing surface for the entire Delimit platform.
|
|
5
|
+
|
|
6
|
+
Architecture:
|
|
7
|
+
Agent ──→ [this server] ──→ backends/ ──→ gateway core / OS / memory / vault / ...
|
|
8
|
+
|
|
9
|
+
Tier 1 (Core): delimit.lint, delimit.diff, delimit.policy, delimit.ledger, delimit.impact
|
|
10
|
+
Tier 2 (Platform): delimit.os.*, delimit.memory.*, delimit.vault.*, delimit.gov.*
|
|
11
|
+
Tier 3 (Extended): delimit.deploy.*, delimit.intel.*, delimit.generate.*, delimit.repo.*,
|
|
12
|
+
delimit.security.*, delimit.evidence.*
|
|
13
|
+
Tier 4 (Ops/UI): delimit.release.*, delimit.cost.*, delimit.data.*, delimit.obs.*,
|
|
14
|
+
delimit.design.*, delimit.story.*, delimit.test.*, delimit.docs.*
|
|
15
|
+
|
|
16
|
+
All tools follow the Adapter Boundary Contract v1.0:
|
|
17
|
+
- Pure translation (zero governance logic in this file)
|
|
18
|
+
- Deterministic errors on failure
|
|
19
|
+
- Stateless between calls
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import subprocess
|
|
25
|
+
import traceback
|
|
26
|
+
from datetime import datetime, timezone
|
|
27
|
+
from typing import Any, Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
from fastmcp import FastMCP
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("delimit.ai")
|
|
32
|
+
|
|
33
|
+
mcp = FastMCP("delimit")
|
|
34
|
+
mcp.description = "Delimit — The smart lint engine for OpenAPI. Unified agent surface."
|
|
35
|
+
|
|
36
|
+
VERSION = "2.0.0"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _safe_call(fn, **kwargs) -> Dict[str, Any]:
|
|
40
|
+
"""Wrap backend calls with deterministic error handling."""
|
|
41
|
+
try:
|
|
42
|
+
return fn(**kwargs)
|
|
43
|
+
except FileNotFoundError as e:
|
|
44
|
+
return {"error": "file_not_found", "message": str(e)}
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.error("Backend error: %s\n%s", e, traceback.format_exc())
|
|
47
|
+
return {"error": "backend_failure", "message": str(e)}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
51
|
+
# TIER 1: CORE — API Lint Engine
|
|
52
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@mcp.tool()
|
|
56
|
+
def delimit_lint(old_spec: str, new_spec: str, policy_file: Optional[str] = None) -> Dict[str, Any]:
|
|
57
|
+
"""Lint two OpenAPI specs for breaking changes and policy violations.
|
|
58
|
+
Primary CI integration point. Combines diff + policy into pass/fail.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
old_spec: Path to the old (baseline) OpenAPI spec file.
|
|
62
|
+
new_spec: Path to the new (proposed) OpenAPI spec file.
|
|
63
|
+
policy_file: Optional path to a .delimit/policies.yml file.
|
|
64
|
+
"""
|
|
65
|
+
from backends.gateway_core import run_lint
|
|
66
|
+
return _safe_call(run_lint, old_spec=old_spec, new_spec=new_spec, policy_file=policy_file)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@mcp.tool()
|
|
70
|
+
def delimit_diff(old_spec: str, new_spec: str) -> Dict[str, Any]:
|
|
71
|
+
"""Diff two OpenAPI specs and list all changes. Pure diff, no policy.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
old_spec: Path to the old OpenAPI spec file.
|
|
75
|
+
new_spec: Path to the new OpenAPI spec file.
|
|
76
|
+
"""
|
|
77
|
+
from backends.gateway_core import run_diff
|
|
78
|
+
return _safe_call(run_diff, old_spec=old_spec, new_spec=new_spec)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
def delimit_policy(spec_files: List[str], policy_file: Optional[str] = None) -> Dict[str, Any]:
|
|
83
|
+
"""Inspect or validate governance policy configuration.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
spec_files: List of spec file paths.
|
|
87
|
+
policy_file: Optional custom policy file path.
|
|
88
|
+
"""
|
|
89
|
+
from backends.gateway_core import run_policy
|
|
90
|
+
return _safe_call(run_policy, spec_files=spec_files, policy_file=policy_file)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@mcp.tool()
|
|
94
|
+
def delimit_ledger(ledger_path: str, api_name: Optional[str] = None, repository: Optional[str] = None, validate_chain: bool = False) -> Dict[str, Any]:
|
|
95
|
+
"""Query the append-only contract ledger (hash-chained JSONL).
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
ledger_path: Path to the events.jsonl ledger file.
|
|
99
|
+
api_name: Filter events by API name.
|
|
100
|
+
repository: Filter events by repository.
|
|
101
|
+
validate_chain: Validate hash chain integrity.
|
|
102
|
+
"""
|
|
103
|
+
from backends.gateway_core import query_ledger
|
|
104
|
+
return _safe_call(query_ledger, ledger_path=ledger_path, api_name=api_name, repository=repository, validate_chain=validate_chain)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@mcp.tool()
|
|
108
|
+
def delimit_impact(api_name: str, dependency_file: Optional[str] = None) -> Dict[str, Any]:
|
|
109
|
+
"""Analyze downstream impact of an API change. Informational only.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
api_name: The API that changed.
|
|
113
|
+
dependency_file: Optional path to dependency manifest.
|
|
114
|
+
"""
|
|
115
|
+
from backends.gateway_core import run_impact
|
|
116
|
+
return _safe_call(run_impact, api_name=api_name, dependency_file=dependency_file)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@mcp.tool()
|
|
120
|
+
def delimit_semver(old_spec: str, new_spec: str, current_version: Optional[str] = None) -> Dict[str, Any]:
|
|
121
|
+
"""Classify the semver bump for a spec change (MAJOR/MINOR/PATCH/NONE).
|
|
122
|
+
|
|
123
|
+
Deterministic classification based on diff engine output.
|
|
124
|
+
Optionally computes the next version string.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
old_spec: Path to the old OpenAPI spec file.
|
|
128
|
+
new_spec: Path to the new OpenAPI spec file.
|
|
129
|
+
current_version: Optional current version (e.g. "1.2.3") to compute next version.
|
|
130
|
+
"""
|
|
131
|
+
from backends.gateway_core import run_semver
|
|
132
|
+
return _safe_call(run_semver, old_spec=old_spec, new_spec=new_spec, current_version=current_version)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@mcp.tool()
|
|
136
|
+
def delimit_explain(
|
|
137
|
+
old_spec: str,
|
|
138
|
+
new_spec: str,
|
|
139
|
+
template: str = "developer",
|
|
140
|
+
old_version: Optional[str] = None,
|
|
141
|
+
new_version: Optional[str] = None,
|
|
142
|
+
api_name: Optional[str] = None,
|
|
143
|
+
) -> Dict[str, Any]:
|
|
144
|
+
"""Generate a human-readable explanation of API changes.
|
|
145
|
+
|
|
146
|
+
7 templates: developer, team_lead, product, migration, changelog, pr_comment, slack.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
old_spec: Path to the old OpenAPI spec file.
|
|
150
|
+
new_spec: Path to the new OpenAPI spec file.
|
|
151
|
+
template: Template name (default: developer).
|
|
152
|
+
old_version: Previous version string.
|
|
153
|
+
new_version: New version string.
|
|
154
|
+
api_name: API/service name for context.
|
|
155
|
+
"""
|
|
156
|
+
from backends.gateway_core import run_explain
|
|
157
|
+
return _safe_call(run_explain, old_spec=old_spec, new_spec=new_spec, template=template, old_version=old_version, new_version=new_version, api_name=api_name)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@mcp.tool()
|
|
161
|
+
def delimit_zero_spec(
|
|
162
|
+
project_dir: str = ".",
|
|
163
|
+
python_bin: Optional[str] = None,
|
|
164
|
+
) -> Dict[str, Any]:
|
|
165
|
+
"""Extract OpenAPI spec from framework source code (no spec file needed).
|
|
166
|
+
|
|
167
|
+
Detects the API framework (FastAPI, Express, NestJS) and extracts a
|
|
168
|
+
complete OpenAPI specification directly from the source code.
|
|
169
|
+
Currently supports FastAPI with full fidelity.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
project_dir: Path to the project root directory.
|
|
173
|
+
python_bin: Optional Python binary path (auto-detected if omitted).
|
|
174
|
+
"""
|
|
175
|
+
from backends.gateway_core import run_zero_spec
|
|
176
|
+
return _safe_call(run_zero_spec, project_dir=project_dir, python_bin=python_bin)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
180
|
+
# TIER 2: PLATFORM — OS, Governance, Memory, Vault
|
|
181
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ─── OS ─────────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
@mcp.tool()
|
|
187
|
+
def delimit_os_plan(operation: str, target: str, parameters: Optional[Dict[str, Any]] = None, require_approval: bool = True) -> Dict[str, Any]:
|
|
188
|
+
"""Create a governed execution plan.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
operation: Operation to plan (e.g. "deploy", "migrate").
|
|
192
|
+
target: Target component or service.
|
|
193
|
+
parameters: Operation parameters.
|
|
194
|
+
require_approval: Whether to require approval before execution.
|
|
195
|
+
"""
|
|
196
|
+
from backends.os_bridge import create_plan
|
|
197
|
+
return _safe_call(create_plan, operation=operation, target=target, parameters=parameters, require_approval=require_approval)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@mcp.tool()
|
|
201
|
+
def delimit_os_status() -> Dict[str, Any]:
|
|
202
|
+
"""Get current Delimit OS status with plan/task/token counts."""
|
|
203
|
+
from backends.os_bridge import get_status
|
|
204
|
+
return _safe_call(get_status)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@mcp.tool()
|
|
208
|
+
def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
|
|
209
|
+
"""Check governance gates for a plan.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
plan_id: The plan ID (e.g. "PLAN-A1B2C3D4").
|
|
213
|
+
"""
|
|
214
|
+
from backends.os_bridge import check_gates
|
|
215
|
+
return _safe_call(check_gates, plan_id=plan_id)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ─── Governance ─────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
@mcp.tool()
|
|
221
|
+
def delimit_gov_health(repo: str = ".") -> Dict[str, Any]:
|
|
222
|
+
"""Check governance system health.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
repo: Repository path to check.
|
|
226
|
+
"""
|
|
227
|
+
from backends.governance_bridge import health
|
|
228
|
+
return _safe_call(health, repo=repo)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@mcp.tool()
|
|
232
|
+
def delimit_gov_status(repo: str = ".") -> Dict[str, Any]:
|
|
233
|
+
"""Get current governance status for a repository.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
repo: Repository path.
|
|
237
|
+
"""
|
|
238
|
+
from backends.governance_bridge import status
|
|
239
|
+
return _safe_call(status, repo=repo)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool()
|
|
243
|
+
def delimit_gov_policy(repo: str = ".") -> Dict[str, Any]:
|
|
244
|
+
"""Get governance policy for a repository.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
repo: Repository path.
|
|
248
|
+
"""
|
|
249
|
+
from backends.governance_bridge import policy
|
|
250
|
+
return _safe_call(policy, repo=repo)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@mcp.tool()
|
|
254
|
+
def delimit_gov_evaluate(action: str, context: Optional[Dict[str, Any]] = None, repo: str = ".") -> Dict[str, Any]:
|
|
255
|
+
"""Evaluate if governance is required for an action.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
action: The action to evaluate.
|
|
259
|
+
context: Additional context.
|
|
260
|
+
repo: Repository path.
|
|
261
|
+
"""
|
|
262
|
+
from backends.governance_bridge import evaluate_trigger
|
|
263
|
+
return _safe_call(evaluate_trigger, action=action, context=context, repo=repo)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@mcp.tool()
|
|
267
|
+
def delimit_gov_new_task(title: str, scope: str, risk_level: str = "medium", repo: str = ".") -> Dict[str, Any]:
|
|
268
|
+
"""Create a new governance task.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
title: Task title.
|
|
272
|
+
scope: Task scope.
|
|
273
|
+
risk_level: Risk level (low/medium/high/critical).
|
|
274
|
+
repo: Repository path.
|
|
275
|
+
"""
|
|
276
|
+
from backends.governance_bridge import new_task
|
|
277
|
+
return _safe_call(new_task, title=title, scope=scope, risk_level=risk_level, repo=repo)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@mcp.tool()
|
|
281
|
+
def delimit_gov_run(task_id: str, repo: str = ".") -> Dict[str, Any]:
|
|
282
|
+
"""Run a governance task.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
task_id: Task ID to run.
|
|
286
|
+
repo: Repository path.
|
|
287
|
+
"""
|
|
288
|
+
from backends.governance_bridge import run_task
|
|
289
|
+
return _safe_call(run_task, task_id=task_id, repo=repo)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@mcp.tool()
|
|
293
|
+
def delimit_gov_verify(task_id: str, repo: str = ".") -> Dict[str, Any]:
|
|
294
|
+
"""Verify a governance task.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
task_id: Task ID to verify.
|
|
298
|
+
repo: Repository path.
|
|
299
|
+
"""
|
|
300
|
+
from backends.governance_bridge import verify
|
|
301
|
+
return _safe_call(verify, task_id=task_id, repo=repo)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# ─── Memory ─────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
@mcp.tool()
|
|
307
|
+
def delimit_memory_search(query: str, limit: int = 10) -> Dict[str, Any]:
|
|
308
|
+
"""Search conversation memory semantically.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
query: Natural language search query.
|
|
312
|
+
limit: Maximum results to return.
|
|
313
|
+
"""
|
|
314
|
+
from backends.memory_bridge import search
|
|
315
|
+
return _safe_call(search, query=query, limit=limit)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@mcp.tool()
|
|
319
|
+
def delimit_memory_store(content: str, tags: Optional[List[str]] = None, context: Optional[str] = None) -> Dict[str, Any]:
|
|
320
|
+
"""Store a memory entry for future retrieval.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
content: The content to remember.
|
|
324
|
+
tags: Optional categorization tags.
|
|
325
|
+
context: Optional context about when/why this was stored.
|
|
326
|
+
"""
|
|
327
|
+
from backends.memory_bridge import store
|
|
328
|
+
return _safe_call(store, content=content, tags=tags, context=context)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@mcp.tool()
|
|
332
|
+
def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
|
|
333
|
+
"""Get recent work summary from memory.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
limit: Number of recent entries to return.
|
|
337
|
+
"""
|
|
338
|
+
from backends.memory_bridge import get_recent
|
|
339
|
+
return _safe_call(get_recent, limit=limit)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ─── Vault ──────────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
@mcp.tool()
|
|
345
|
+
def delimit_vault_search(query: str) -> Dict[str, Any]:
|
|
346
|
+
"""Search vault entries.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
query: Search query for vault entries.
|
|
350
|
+
"""
|
|
351
|
+
from backends.vault_bridge import search
|
|
352
|
+
return _safe_call(search, query=query)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@mcp.tool()
|
|
356
|
+
def delimit_vault_health() -> Dict[str, Any]:
|
|
357
|
+
"""Check vault health status."""
|
|
358
|
+
from backends.vault_bridge import health
|
|
359
|
+
return _safe_call(health)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@mcp.tool()
|
|
363
|
+
def delimit_vault_snapshot() -> Dict[str, Any]:
|
|
364
|
+
"""Get a vault state snapshot."""
|
|
365
|
+
from backends.vault_bridge import snapshot
|
|
366
|
+
return _safe_call(snapshot)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
370
|
+
# TIER 3: EXTENDED — Deploy, Intel, Generate, Repo, Security, Evidence
|
|
371
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ─── Deploy ─────────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
@mcp.tool()
|
|
377
|
+
def delimit_deploy_plan(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
378
|
+
"""Plan deployment with build steps.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
app: Application name.
|
|
382
|
+
env: Target environment (staging/production).
|
|
383
|
+
git_ref: Git reference (branch, tag, or SHA).
|
|
384
|
+
"""
|
|
385
|
+
from backends.deploy_bridge import plan
|
|
386
|
+
return _safe_call(plan, app=app, env=env, git_ref=git_ref)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@mcp.tool()
|
|
390
|
+
def delimit_deploy_build(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
391
|
+
"""Build Docker images with SHA tags.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
app: Application name.
|
|
395
|
+
git_ref: Git reference.
|
|
396
|
+
"""
|
|
397
|
+
from backends.deploy_bridge import build
|
|
398
|
+
return _safe_call(build, app=app, git_ref=git_ref)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@mcp.tool()
|
|
402
|
+
def delimit_deploy_publish(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
403
|
+
"""Publish images to registry.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
app: Application name.
|
|
407
|
+
git_ref: Git reference.
|
|
408
|
+
"""
|
|
409
|
+
from backends.deploy_bridge import publish
|
|
410
|
+
return _safe_call(publish, app=app, git_ref=git_ref)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@mcp.tool()
|
|
414
|
+
def delimit_deploy_verify(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
415
|
+
"""Verify deployment health.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
app: Application name.
|
|
419
|
+
env: Target environment.
|
|
420
|
+
git_ref: Git reference.
|
|
421
|
+
"""
|
|
422
|
+
from backends.deploy_bridge import verify
|
|
423
|
+
return _safe_call(verify, app=app, env=env, git_ref=git_ref)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@mcp.tool()
|
|
427
|
+
def delimit_deploy_rollback(app: str, env: str, to_sha: Optional[str] = None) -> Dict[str, Any]:
|
|
428
|
+
"""Rollback to previous SHA.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
app: Application name.
|
|
432
|
+
env: Target environment.
|
|
433
|
+
to_sha: SHA to rollback to.
|
|
434
|
+
"""
|
|
435
|
+
from backends.deploy_bridge import rollback
|
|
436
|
+
return _safe_call(rollback, app=app, env=env, to_sha=to_sha)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@mcp.tool()
|
|
440
|
+
def delimit_deploy_status(app: str, env: str) -> Dict[str, Any]:
|
|
441
|
+
"""Get deployment status.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
app: Application name.
|
|
445
|
+
env: Target environment.
|
|
446
|
+
"""
|
|
447
|
+
from backends.deploy_bridge import status
|
|
448
|
+
return _safe_call(status, app=app, env=env)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ─── Intel ──────────────────────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
@mcp.tool()
|
|
454
|
+
def delimit_intel_dataset_register(name: str, schema: Dict[str, Any], description: Optional[str] = None) -> Dict[str, Any]:
|
|
455
|
+
"""Register a new dataset with schema.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
name: Dataset name.
|
|
459
|
+
schema: JSON schema for the dataset.
|
|
460
|
+
description: Human-readable description.
|
|
461
|
+
"""
|
|
462
|
+
from backends.intel_bridge import dataset_register
|
|
463
|
+
return _safe_call(dataset_register, name=name, schema=schema, description=description)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@mcp.tool()
|
|
467
|
+
def delimit_intel_dataset_list() -> Dict[str, Any]:
|
|
468
|
+
"""List registered datasets."""
|
|
469
|
+
from backends.intel_bridge import dataset_list
|
|
470
|
+
return _safe_call(dataset_list)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@mcp.tool()
|
|
474
|
+
def delimit_intel_dataset_freeze(dataset_id: str) -> Dict[str, Any]:
|
|
475
|
+
"""Mark dataset as immutable.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
dataset_id: Dataset identifier.
|
|
479
|
+
"""
|
|
480
|
+
from backends.intel_bridge import dataset_freeze
|
|
481
|
+
return _safe_call(dataset_freeze, dataset_id=dataset_id)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@mcp.tool()
|
|
485
|
+
def delimit_intel_snapshot_ingest(data: Dict[str, Any], provenance: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
486
|
+
"""Store research snapshot with provenance.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
data: Snapshot data.
|
|
490
|
+
provenance: Provenance metadata.
|
|
491
|
+
"""
|
|
492
|
+
from backends.intel_bridge import snapshot_ingest
|
|
493
|
+
return _safe_call(snapshot_ingest, data=data, provenance=provenance)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@mcp.tool()
|
|
497
|
+
def delimit_intel_query(dataset_id: str, query: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
498
|
+
"""Execute deterministic query on dataset.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
dataset_id: Dataset to query.
|
|
502
|
+
query: Query string.
|
|
503
|
+
parameters: Query parameters.
|
|
504
|
+
"""
|
|
505
|
+
from backends.intel_bridge import query_run
|
|
506
|
+
return _safe_call(query_run, dataset_id=dataset_id, query=query, parameters=parameters)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# ─── Generate ───────────────────────────────────────────────────────────
|
|
510
|
+
|
|
511
|
+
@mcp.tool()
|
|
512
|
+
def delimit_generate_template(template_type: str, name: str, framework: str = "nextjs", features: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
513
|
+
"""Generate code template.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
template_type: Template type (component, page, api, etc.).
|
|
517
|
+
name: Name for the generated code.
|
|
518
|
+
framework: Target framework.
|
|
519
|
+
features: Optional feature flags.
|
|
520
|
+
"""
|
|
521
|
+
from backends.generate_bridge import template
|
|
522
|
+
return _safe_call(template, template_type=template_type, name=name, framework=framework, features=features)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@mcp.tool()
|
|
526
|
+
def delimit_generate_scaffold(project_type: str, name: str, packages: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
527
|
+
"""Scaffold new project structure.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
project_type: Project type (nextjs, api, library, etc.).
|
|
531
|
+
name: Project name.
|
|
532
|
+
packages: Packages to include.
|
|
533
|
+
"""
|
|
534
|
+
from backends.generate_bridge import scaffold
|
|
535
|
+
return _safe_call(scaffold, project_type=project_type, name=name, packages=packages)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
# ─── Repo (RepoDoctor + ConfigSentry) ──────────────────────────────────
|
|
539
|
+
|
|
540
|
+
@mcp.tool()
|
|
541
|
+
def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
542
|
+
"""Diagnose repository health issues.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
target: Repository path.
|
|
546
|
+
"""
|
|
547
|
+
from backends.repo_bridge import diagnose
|
|
548
|
+
return _safe_call(diagnose, target=target)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@mcp.tool()
|
|
552
|
+
def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
553
|
+
"""Analyze repository structure and quality.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
target: Repository path.
|
|
557
|
+
"""
|
|
558
|
+
from backends.repo_bridge import analyze
|
|
559
|
+
return _safe_call(analyze, target=target)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@mcp.tool()
|
|
563
|
+
def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
564
|
+
"""Validate configuration files.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
target: Repository or config path.
|
|
568
|
+
"""
|
|
569
|
+
from backends.repo_bridge import config_validate
|
|
570
|
+
return _safe_call(config_validate, target=target)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@mcp.tool()
|
|
574
|
+
def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
575
|
+
"""Audit configuration compliance.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
target: Repository or config path.
|
|
579
|
+
"""
|
|
580
|
+
from backends.repo_bridge import config_audit
|
|
581
|
+
return _safe_call(config_audit, target=target)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# ─── Security ───────────────────────────────────────────────────────────
|
|
585
|
+
|
|
586
|
+
@mcp.tool()
|
|
587
|
+
def delimit_security_scan(target: str = ".") -> Dict[str, Any]:
|
|
588
|
+
"""Scan for security vulnerabilities.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
target: Repository or file path.
|
|
592
|
+
"""
|
|
593
|
+
from backends.repo_bridge import security_scan
|
|
594
|
+
return _safe_call(security_scan, target=target)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@mcp.tool()
|
|
598
|
+
def delimit_security_audit(target: str = ".") -> Dict[str, Any]:
|
|
599
|
+
"""Audit security compliance.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
target: Repository or file path.
|
|
603
|
+
"""
|
|
604
|
+
from backends.repo_bridge import security_audit
|
|
605
|
+
return _safe_call(security_audit, target=target)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
# ─── Evidence ───────────────────────────────────────────────────────────
|
|
609
|
+
|
|
610
|
+
@mcp.tool()
|
|
611
|
+
def delimit_evidence_collect(target: str = ".") -> Dict[str, Any]:
|
|
612
|
+
"""Collect evidence artifacts for governance.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
target: Repository or task path.
|
|
616
|
+
"""
|
|
617
|
+
from backends.repo_bridge import evidence_collect
|
|
618
|
+
return _safe_call(evidence_collect, target=target)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
@mcp.tool()
|
|
622
|
+
def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Optional[str] = None) -> Dict[str, Any]:
|
|
623
|
+
"""Verify evidence bundle integrity.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
bundle_id: Evidence bundle ID to verify.
|
|
627
|
+
bundle_path: Path to evidence bundle file.
|
|
628
|
+
"""
|
|
629
|
+
from backends.repo_bridge import evidence_verify
|
|
630
|
+
return _safe_call(evidence_verify, bundle_id=bundle_id, bundle_path=bundle_path)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
634
|
+
# TIER 4: OPS / UI — Governance Primitives + UI Tooling
|
|
635
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
# ─── ReleasePilot (Governance Primitive) ────────────────────────────────
|
|
639
|
+
|
|
640
|
+
@mcp.tool()
|
|
641
|
+
def delimit_release_plan(environment: str, version: str, repository: str, services: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
642
|
+
"""Create deployment plan with approval gates.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
environment: Target environment (staging/production).
|
|
646
|
+
version: Release version.
|
|
647
|
+
repository: Repository name.
|
|
648
|
+
services: Optional service list.
|
|
649
|
+
"""
|
|
650
|
+
from backends.ops_bridge import release_plan
|
|
651
|
+
return _safe_call(release_plan, environment=environment, version=version, repository=repository, services=services)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@mcp.tool()
|
|
655
|
+
def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
|
|
656
|
+
"""Validate release readiness.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
environment: Target environment.
|
|
660
|
+
version: Release version.
|
|
661
|
+
"""
|
|
662
|
+
from backends.ops_bridge import release_validate
|
|
663
|
+
return _safe_call(release_validate, environment=environment, version=version)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@mcp.tool()
|
|
667
|
+
def delimit_release_status(environment: str) -> Dict[str, Any]:
|
|
668
|
+
"""Check deployment status.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
environment: Target environment.
|
|
672
|
+
"""
|
|
673
|
+
from backends.ops_bridge import release_status
|
|
674
|
+
return _safe_call(release_status, environment=environment)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@mcp.tool()
|
|
678
|
+
def delimit_release_rollback(environment: str, version: str, to_version: str) -> Dict[str, Any]:
|
|
679
|
+
"""Rollback deployment to previous version.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
environment: Target environment.
|
|
683
|
+
version: Current version.
|
|
684
|
+
to_version: Version to rollback to.
|
|
685
|
+
"""
|
|
686
|
+
from backends.ops_bridge import release_rollback
|
|
687
|
+
return _safe_call(release_rollback, environment=environment, version=version, to_version=to_version)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
@mcp.tool()
|
|
691
|
+
def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]:
|
|
692
|
+
"""Show release history.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
environment: Target environment.
|
|
696
|
+
limit: Number of releases to return.
|
|
697
|
+
"""
|
|
698
|
+
from backends.ops_bridge import release_history
|
|
699
|
+
return _safe_call(release_history, environment=environment, limit=limit)
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
# ─── CostGuard (Governance Primitive) ──────────────────────────────────
|
|
703
|
+
|
|
704
|
+
@mcp.tool()
|
|
705
|
+
def delimit_cost_analyze(target: str = ".") -> Dict[str, Any]:
|
|
706
|
+
"""Analyze cost and spending patterns.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
target: Project or infrastructure path.
|
|
710
|
+
"""
|
|
711
|
+
from backends.ops_bridge import cost_analyze
|
|
712
|
+
return _safe_call(cost_analyze, target=target)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
@mcp.tool()
|
|
716
|
+
def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
|
|
717
|
+
"""Generate cost optimization recommendations.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
target: Project or infrastructure path.
|
|
721
|
+
"""
|
|
722
|
+
from backends.ops_bridge import cost_optimize
|
|
723
|
+
return _safe_call(cost_optimize, target=target)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@mcp.tool()
|
|
727
|
+
def delimit_cost_alert(action: str = "list") -> Dict[str, Any]:
|
|
728
|
+
"""Manage cost alerts and notifications.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
action: Action (list/create/delete/update).
|
|
732
|
+
"""
|
|
733
|
+
from backends.ops_bridge import cost_alert
|
|
734
|
+
return _safe_call(cost_alert, action=action)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
# ─── DataSteward (Governance Primitive) ────────────────────────────────
|
|
738
|
+
|
|
739
|
+
@mcp.tool()
|
|
740
|
+
def delimit_data_validate(target: str = ".") -> Dict[str, Any]:
|
|
741
|
+
"""Validate data integrity.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
target: Data source or path.
|
|
745
|
+
"""
|
|
746
|
+
from backends.ops_bridge import data_validate
|
|
747
|
+
return _safe_call(data_validate, target=target)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@mcp.tool()
|
|
751
|
+
def delimit_data_migrate(target: str = ".") -> Dict[str, Any]:
|
|
752
|
+
"""Execute data migration (plan-only by default).
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
target: Data source or migration path.
|
|
756
|
+
"""
|
|
757
|
+
from backends.ops_bridge import data_migrate
|
|
758
|
+
return _safe_call(data_migrate, target=target)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
@mcp.tool()
|
|
762
|
+
def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
|
|
763
|
+
"""Create data backups.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
target: Data source to back up.
|
|
767
|
+
"""
|
|
768
|
+
from backends.ops_bridge import data_backup
|
|
769
|
+
return _safe_call(data_backup, target=target)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
# ─── ObservabilityOps (Internal OS) ────────────────────────────────────
|
|
773
|
+
|
|
774
|
+
@mcp.tool()
|
|
775
|
+
def delimit_obs_metrics(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
776
|
+
"""Query and analyze metrics.
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
query: Metrics query.
|
|
780
|
+
time_range: Time range (e.g. "1h", "24h", "7d").
|
|
781
|
+
source: Optional metrics source.
|
|
782
|
+
"""
|
|
783
|
+
from backends.ops_bridge import obs_metrics
|
|
784
|
+
return _safe_call(obs_metrics, query=query, time_range=time_range, source=source)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
@mcp.tool()
|
|
788
|
+
def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
789
|
+
"""Query and search logs.
|
|
790
|
+
|
|
791
|
+
Args:
|
|
792
|
+
query: Log search query.
|
|
793
|
+
time_range: Time range.
|
|
794
|
+
source: Optional log source.
|
|
795
|
+
"""
|
|
796
|
+
from backends.ops_bridge import obs_logs
|
|
797
|
+
return _safe_call(obs_logs, query=query, time_range=time_range, source=source)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
@mcp.tool()
|
|
801
|
+
def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None, rule_id: Optional[str] = None) -> Dict[str, Any]:
|
|
802
|
+
"""Manage alerting rules.
|
|
803
|
+
|
|
804
|
+
Args:
|
|
805
|
+
action: Action (list/create/delete/update).
|
|
806
|
+
alert_rule: Alert rule definition (for create/update).
|
|
807
|
+
rule_id: Rule ID (for delete/update).
|
|
808
|
+
"""
|
|
809
|
+
from backends.ops_bridge import obs_alerts
|
|
810
|
+
return _safe_call(obs_alerts, action=action, alert_rule=alert_rule, rule_id=rule_id)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
@mcp.tool()
|
|
814
|
+
def delimit_obs_status() -> Dict[str, Any]:
|
|
815
|
+
"""Get observability system status."""
|
|
816
|
+
from backends.ops_bridge import obs_status
|
|
817
|
+
return _safe_call(obs_status)
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
# ─── DesignSystem (UI Tooling) ──────────────────────────────────────────
|
|
821
|
+
|
|
822
|
+
@mcp.tool()
|
|
823
|
+
def delimit_design_extract_tokens(figma_file_key: str, token_types: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
824
|
+
"""Extract design tokens from Figma.
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
figma_file_key: Figma file key.
|
|
828
|
+
token_types: Token types to extract (colors, typography, spacing, etc.).
|
|
829
|
+
"""
|
|
830
|
+
from backends.ui_bridge import design_extract_tokens
|
|
831
|
+
return _safe_call(design_extract_tokens, figma_file_key=figma_file_key, token_types=token_types)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
@mcp.tool()
|
|
835
|
+
def delimit_design_generate_component(component_name: str, figma_node_id: Optional[str] = None, output_path: Optional[str] = None) -> Dict[str, Any]:
|
|
836
|
+
"""Generate Next.js component from Figma design.
|
|
837
|
+
|
|
838
|
+
Args:
|
|
839
|
+
component_name: Component name.
|
|
840
|
+
figma_node_id: Figma node ID.
|
|
841
|
+
output_path: Output file path.
|
|
842
|
+
"""
|
|
843
|
+
from backends.ui_bridge import design_generate_component
|
|
844
|
+
return _safe_call(design_generate_component, component_name=component_name, figma_node_id=figma_node_id, output_path=output_path)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@mcp.tool()
|
|
848
|
+
def delimit_design_generate_tailwind(figma_file_key: str, output_path: Optional[str] = None) -> Dict[str, Any]:
|
|
849
|
+
"""Generate Tailwind config from Figma design tokens.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
figma_file_key: Figma file key.
|
|
853
|
+
output_path: Output file path.
|
|
854
|
+
"""
|
|
855
|
+
from backends.ui_bridge import design_generate_tailwind
|
|
856
|
+
return _safe_call(design_generate_tailwind, figma_file_key=figma_file_key, output_path=output_path)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
@mcp.tool()
|
|
860
|
+
def delimit_design_validate_responsive(project_path: str, check_types: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
861
|
+
"""Validate responsive design patterns.
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
project_path: Project path to validate.
|
|
865
|
+
check_types: Check types (breakpoints, containers, fluid-type, etc.).
|
|
866
|
+
"""
|
|
867
|
+
from backends.ui_bridge import design_validate_responsive
|
|
868
|
+
return _safe_call(design_validate_responsive, project_path=project_path, check_types=check_types)
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
@mcp.tool()
|
|
872
|
+
def delimit_design_component_library(project_path: str, output_format: str = "json") -> Dict[str, Any]:
|
|
873
|
+
"""Generate component library documentation.
|
|
874
|
+
|
|
875
|
+
Args:
|
|
876
|
+
project_path: Project path.
|
|
877
|
+
output_format: Output format (json/markdown).
|
|
878
|
+
"""
|
|
879
|
+
from backends.ui_bridge import design_component_library
|
|
880
|
+
return _safe_call(design_component_library, project_path=project_path, output_format=output_format)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# ─── Storybook (UI Tooling + Visual Regression) ────────────────────────
|
|
884
|
+
|
|
885
|
+
@mcp.tool()
|
|
886
|
+
def delimit_story_generate(component_path: str, story_name: Optional[str] = None, variants: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
887
|
+
"""Generate Storybook story for a component.
|
|
888
|
+
|
|
889
|
+
Args:
|
|
890
|
+
component_path: Path to the component file.
|
|
891
|
+
story_name: Custom story name.
|
|
892
|
+
variants: Variants to generate.
|
|
893
|
+
"""
|
|
894
|
+
from backends.ui_bridge import story_generate
|
|
895
|
+
return _safe_call(story_generate, component_path=component_path, story_name=story_name, variants=variants)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
@mcp.tool()
|
|
899
|
+
def delimit_story_visual_test(url: str, project_path: Optional[str] = None, threshold: float = 0.05) -> Dict[str, Any]:
|
|
900
|
+
"""Run visual regression test with Playwright screenshots.
|
|
901
|
+
|
|
902
|
+
Args:
|
|
903
|
+
url: URL to test.
|
|
904
|
+
project_path: Project path for baseline storage.
|
|
905
|
+
threshold: Diff threshold (0.0-1.0).
|
|
906
|
+
"""
|
|
907
|
+
from backends.ui_bridge import story_visual_test
|
|
908
|
+
return _safe_call(story_visual_test, url=url, project_path=project_path, threshold=threshold)
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
@mcp.tool()
|
|
912
|
+
def delimit_story_build(project_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
913
|
+
"""Build Storybook static site.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
project_path: Project path.
|
|
917
|
+
output_dir: Output directory.
|
|
918
|
+
"""
|
|
919
|
+
from backends.ui_bridge import story_build
|
|
920
|
+
return _safe_call(story_build, project_path=project_path, output_dir=output_dir)
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
@mcp.tool()
|
|
924
|
+
def delimit_story_accessibility(project_path: str, standards: str = "WCAG2AA") -> Dict[str, Any]:
|
|
925
|
+
"""Run WCAG accessibility tests on components.
|
|
926
|
+
|
|
927
|
+
Args:
|
|
928
|
+
project_path: Project path.
|
|
929
|
+
standards: Accessibility standard (WCAG2A/WCAG2AA/WCAG2AAA).
|
|
930
|
+
"""
|
|
931
|
+
from backends.ui_bridge import story_accessibility_test
|
|
932
|
+
return _safe_call(story_accessibility_test, project_path=project_path, standards=standards)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
# ─── TestSmith (Testing) ───────────────────────────────────────────────
|
|
936
|
+
|
|
937
|
+
@mcp.tool()
|
|
938
|
+
def delimit_test_generate(project_path: str, source_files: Optional[List[str]] = None, framework: str = "jest") -> Dict[str, Any]:
|
|
939
|
+
"""Generate tests for source code.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
project_path: Project path.
|
|
943
|
+
source_files: Specific files to generate tests for.
|
|
944
|
+
framework: Test framework (jest/pytest/vitest).
|
|
945
|
+
"""
|
|
946
|
+
from backends.ui_bridge import test_generate
|
|
947
|
+
return _safe_call(test_generate, project_path=project_path, source_files=source_files, framework=framework)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
@mcp.tool()
|
|
951
|
+
def delimit_test_coverage(project_path: str, threshold: int = 80) -> Dict[str, Any]:
|
|
952
|
+
"""Analyze test coverage.
|
|
953
|
+
|
|
954
|
+
Args:
|
|
955
|
+
project_path: Project path.
|
|
956
|
+
threshold: Coverage threshold percentage.
|
|
957
|
+
"""
|
|
958
|
+
from backends.ui_bridge import test_coverage
|
|
959
|
+
return _safe_call(test_coverage, project_path=project_path, threshold=threshold)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
@mcp.tool()
|
|
963
|
+
def delimit_test_smoke(project_path: str, test_suite: Optional[str] = None) -> Dict[str, Any]:
|
|
964
|
+
"""Run smoke tests.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
project_path: Project path.
|
|
968
|
+
test_suite: Specific test suite to run.
|
|
969
|
+
"""
|
|
970
|
+
from backends.ui_bridge import test_smoke
|
|
971
|
+
return _safe_call(test_smoke, project_path=project_path, test_suite=test_suite)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
# ─── Docs ───────────────────────────────────────────────────────────────
|
|
975
|
+
|
|
976
|
+
@mcp.tool()
|
|
977
|
+
def delimit_docs_generate(target: str = ".") -> Dict[str, Any]:
|
|
978
|
+
"""Generate documentation for a project.
|
|
979
|
+
|
|
980
|
+
Args:
|
|
981
|
+
target: Project path.
|
|
982
|
+
"""
|
|
983
|
+
from backends.ui_bridge import docs_generate
|
|
984
|
+
return _safe_call(docs_generate, target=target)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
@mcp.tool()
|
|
988
|
+
def delimit_docs_validate(target: str = ".") -> Dict[str, Any]:
|
|
989
|
+
"""Validate documentation quality and completeness.
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
target: Project path.
|
|
993
|
+
"""
|
|
994
|
+
from backends.ui_bridge import docs_validate
|
|
995
|
+
return _safe_call(docs_validate, target=target)
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1001
|
+
# SENSING LAYER
|
|
1002
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
# --- Sensing Layer (SENSE-002) ---
|
|
1006
|
+
|
|
1007
|
+
_NEGATIVE_KEYWORDS = [
|
|
1008
|
+
"not interested", "won't be", "will not", "don't need", "do not need",
|
|
1009
|
+
"no thanks", "pass on", "not a fit", "not for us", "closing",
|
|
1010
|
+
"won't adopt", "will not adopt", "reject", "declined",
|
|
1011
|
+
]
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
@mcp.tool()
|
|
1015
|
+
async def delimit_sensor_github_issue(
|
|
1016
|
+
repo: str,
|
|
1017
|
+
issue_number: int,
|
|
1018
|
+
since_comment_id: int = 0,
|
|
1019
|
+
) -> Dict[str, Any]:
|
|
1020
|
+
"""Check a GitHub issue for new comments since the last check.
|
|
1021
|
+
|
|
1022
|
+
Sensor tool for monitoring outreach issues. Returns a structured signal
|
|
1023
|
+
with new comments, issue state, and severity classification.
|
|
1024
|
+
|
|
1025
|
+
Args:
|
|
1026
|
+
repo: GitHub repository in owner/repo format (e.g. "activepieces/activepieces").
|
|
1027
|
+
issue_number: The issue number to monitor.
|
|
1028
|
+
since_comment_id: Last seen comment ID. Pass 0 to get all comments.
|
|
1029
|
+
"""
|
|
1030
|
+
try:
|
|
1031
|
+
# Fetch comments
|
|
1032
|
+
comments_jq = (
|
|
1033
|
+
"[.[] | {id: .id, author: .user.login, "
|
|
1034
|
+
"created_at: .created_at, body: (.body | .[0:500])}]"
|
|
1035
|
+
)
|
|
1036
|
+
comments_proc = subprocess.run(
|
|
1037
|
+
[
|
|
1038
|
+
"gh", "api",
|
|
1039
|
+
f"repos/{repo}/issues/{issue_number}/comments",
|
|
1040
|
+
"--jq", comments_jq,
|
|
1041
|
+
],
|
|
1042
|
+
capture_output=True,
|
|
1043
|
+
text=True,
|
|
1044
|
+
timeout=30,
|
|
1045
|
+
)
|
|
1046
|
+
if comments_proc.returncode != 0:
|
|
1047
|
+
return {
|
|
1048
|
+
"error": f"gh api comments failed: {comments_proc.stderr.strip()}",
|
|
1049
|
+
"has_new_activity": False,
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
all_comments = json.loads(comments_proc.stdout) if comments_proc.stdout.strip() else []
|
|
1053
|
+
|
|
1054
|
+
# Filter to new comments only
|
|
1055
|
+
new_comments = [c for c in all_comments if c["id"] > since_comment_id]
|
|
1056
|
+
|
|
1057
|
+
# Fetch issue state
|
|
1058
|
+
issue_jq = "{state: .state, labels: [.labels[].name], reactions: .reactions.total_count}"
|
|
1059
|
+
issue_proc = subprocess.run(
|
|
1060
|
+
[
|
|
1061
|
+
"gh", "api",
|
|
1062
|
+
f"repos/{repo}/issues/{issue_number}",
|
|
1063
|
+
"--jq", issue_jq,
|
|
1064
|
+
],
|
|
1065
|
+
capture_output=True,
|
|
1066
|
+
text=True,
|
|
1067
|
+
timeout=30,
|
|
1068
|
+
)
|
|
1069
|
+
if issue_proc.returncode != 0:
|
|
1070
|
+
return {
|
|
1071
|
+
"error": f"gh api issue failed: {issue_proc.stderr.strip()}",
|
|
1072
|
+
"has_new_activity": False,
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
issue_info = json.loads(issue_proc.stdout) if issue_proc.stdout.strip() else {}
|
|
1076
|
+
issue_state = issue_info.get("state", "unknown")
|
|
1077
|
+
|
|
1078
|
+
# Determine severity
|
|
1079
|
+
severity = "green"
|
|
1080
|
+
|
|
1081
|
+
# Check for negative signals in new comments
|
|
1082
|
+
combined_body = " ".join(c.get("body", "") for c in new_comments).lower()
|
|
1083
|
+
has_negative = any(kw in combined_body for kw in _NEGATIVE_KEYWORDS)
|
|
1084
|
+
|
|
1085
|
+
if has_negative:
|
|
1086
|
+
severity = "red"
|
|
1087
|
+
elif issue_state == "closed" and len(all_comments) == 0:
|
|
1088
|
+
# Closed with no engagement at all
|
|
1089
|
+
severity = "amber"
|
|
1090
|
+
elif issue_state == "closed":
|
|
1091
|
+
# Closed but had some engagement -- could be resolved or rejected
|
|
1092
|
+
severity = "amber"
|
|
1093
|
+
|
|
1094
|
+
latest_comment_id = max((c["id"] for c in all_comments), default=since_comment_id)
|
|
1095
|
+
|
|
1096
|
+
repo_key = repo.replace("/", "_")
|
|
1097
|
+
return {
|
|
1098
|
+
"signal": {
|
|
1099
|
+
"id": f"sensor:github_issue:{repo_key}:{issue_number}",
|
|
1100
|
+
"venture": "delimit",
|
|
1101
|
+
"metric": "outreach_issue_activity",
|
|
1102
|
+
"source": f"https://github.com/{repo}/issues/{issue_number}",
|
|
1103
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1104
|
+
"severity": severity,
|
|
1105
|
+
},
|
|
1106
|
+
"issue_state": issue_state,
|
|
1107
|
+
"new_comments": new_comments,
|
|
1108
|
+
"latest_comment_id": latest_comment_id,
|
|
1109
|
+
"total_comments": len(all_comments),
|
|
1110
|
+
"has_new_activity": len(new_comments) > 0,
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
except subprocess.TimeoutExpired:
|
|
1114
|
+
return {"error": "gh command timed out after 30s", "has_new_activity": False}
|
|
1115
|
+
except json.JSONDecodeError as e:
|
|
1116
|
+
return {"error": f"Failed to parse gh output: {e}", "has_new_activity": False}
|
|
1117
|
+
except Exception as e:
|
|
1118
|
+
logger.error("Sensor error: %s\n%s", e, traceback.format_exc())
|
|
1119
|
+
return {"error": str(e), "has_new_activity": False}
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1123
|
+
# META
|
|
1124
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
@mcp.tool()
|
|
1128
|
+
def delimit_version() -> Dict[str, Any]:
|
|
1129
|
+
"""Return Delimit unified server version, all tiers, and tool count."""
|
|
1130
|
+
tiers = {
|
|
1131
|
+
"tier1_core": ["lint", "diff", "policy", "ledger", "impact", "semver", "explain", "zero_spec"],
|
|
1132
|
+
"tier2_platform": [
|
|
1133
|
+
"os.plan", "os.status", "os.gates",
|
|
1134
|
+
"gov.health", "gov.status", "gov.policy", "gov.evaluate", "gov.new_task", "gov.run", "gov.verify",
|
|
1135
|
+
"memory.search", "memory.store", "memory.recent",
|
|
1136
|
+
"vault.search", "vault.health", "vault.snapshot",
|
|
1137
|
+
],
|
|
1138
|
+
"tier3_extended": [
|
|
1139
|
+
"deploy.plan", "deploy.build", "deploy.publish", "deploy.verify", "deploy.rollback", "deploy.status",
|
|
1140
|
+
"intel.dataset_register", "intel.dataset_list", "intel.dataset_freeze", "intel.snapshot_ingest", "intel.query",
|
|
1141
|
+
"generate.template", "generate.scaffold",
|
|
1142
|
+
"repo.diagnose", "repo.analyze", "repo.config_validate", "repo.config_audit",
|
|
1143
|
+
"security.scan", "security.audit",
|
|
1144
|
+
"evidence.collect", "evidence.verify",
|
|
1145
|
+
],
|
|
1146
|
+
"tier4_ops_ui": [
|
|
1147
|
+
"release.plan", "release.validate", "release.status", "release.rollback", "release.history",
|
|
1148
|
+
"cost.analyze", "cost.optimize", "cost.alert",
|
|
1149
|
+
"data.validate", "data.migrate", "data.backup",
|
|
1150
|
+
"obs.metrics", "obs.logs", "obs.alerts", "obs.status",
|
|
1151
|
+
"design.extract_tokens", "design.generate_component", "design.generate_tailwind", "design.validate_responsive", "design.component_library",
|
|
1152
|
+
"story.generate", "story.visual_test", "story.build", "story.accessibility",
|
|
1153
|
+
"test.generate", "test.coverage", "test.smoke",
|
|
1154
|
+
"docs.generate", "docs.validate",
|
|
1155
|
+
],
|
|
1156
|
+
"sensing": [
|
|
1157
|
+
"sensor.github_issue",
|
|
1158
|
+
],
|
|
1159
|
+
}
|
|
1160
|
+
total = sum(len(v) for v in tiers.values()) + 1 # +1 for version itself
|
|
1161
|
+
return {
|
|
1162
|
+
"version": VERSION,
|
|
1163
|
+
"server": "delimit-unified",
|
|
1164
|
+
"total_tools": total,
|
|
1165
|
+
"tiers": tiers,
|
|
1166
|
+
"adapter_contract": "v1.0",
|
|
1167
|
+
"authority": "delimit-gateway",
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1172
|
+
# ENTRY POINT
|
|
1173
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1174
|
+
|
|
1175
|
+
async def run_mcp_server(server, server_name="delimit"):
|
|
1176
|
+
"""Run the MCP server."""
|
|
1177
|
+
await server.run_stdio_async()
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
if __name__ == "__main__":
|
|
1181
|
+
import asyncio
|
|
1182
|
+
asyncio.run(run_mcp_server(mcp))
|