tradingcodex 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.
- apps/__init__.py +1 -0
- apps/audit/__init__.py +1 -0
- apps/audit/admin.py +6 -0
- apps/audit/apps.py +8 -0
- apps/audit/migrations/0001_initial.py +35 -0
- apps/audit/migrations/__init__.py +0 -0
- apps/audit/models.py +22 -0
- apps/harness/__init__.py +1 -0
- apps/harness/admin.py +6 -0
- apps/harness/apps.py +8 -0
- apps/harness/migrations/0001_initial.py +35 -0
- apps/harness/migrations/__init__.py +0 -0
- apps/harness/models.py +22 -0
- apps/harness/templatetags/__init__.py +1 -0
- apps/integrations/__init__.py +1 -0
- apps/integrations/admin.py +6 -0
- apps/integrations/apps.py +8 -0
- apps/integrations/migrations/0001_initial.py +29 -0
- apps/integrations/migrations/__init__.py +0 -0
- apps/integrations/models.py +16 -0
- apps/integrations/services.py +31 -0
- apps/mcp/__init__.py +1 -0
- apps/mcp/admin.py +20 -0
- apps/mcp/apps.py +8 -0
- apps/mcp/migrations/0001_initial.py +168 -0
- apps/mcp/migrations/__init__.py +0 -0
- apps/mcp/models.py +154 -0
- apps/mcp/services.py +327 -0
- apps/orders/__init__.py +1 -0
- apps/orders/admin.py +6 -0
- apps/orders/apps.py +8 -0
- apps/orders/migrations/0001_initial.py +79 -0
- apps/orders/migrations/__init__.py +0 -0
- apps/orders/models.py +66 -0
- apps/orders/services.py +107 -0
- apps/policy/__init__.py +1 -0
- apps/policy/admin.py +6 -0
- apps/policy/apps.py +8 -0
- apps/policy/migrations/0001_initial.py +75 -0
- apps/policy/migrations/__init__.py +0 -0
- apps/policy/models.py +61 -0
- apps/policy/services.py +110 -0
- apps/portfolio/__init__.py +1 -0
- apps/portfolio/admin.py +6 -0
- apps/portfolio/apps.py +8 -0
- apps/portfolio/migrations/0001_initial.py +67 -0
- apps/portfolio/migrations/__init__.py +0 -0
- apps/portfolio/models.py +53 -0
- apps/research/__init__.py +1 -0
- apps/research/admin.py +1 -0
- apps/research/apps.py +8 -0
- apps/research/migrations/__init__.py +0 -0
- apps/research/models.py +1 -0
- apps/workflows/__init__.py +1 -0
- apps/workflows/admin.py +6 -0
- apps/workflows/apps.py +8 -0
- apps/workflows/migrations/0001_initial.py +51 -0
- apps/workflows/migrations/__init__.py +0 -0
- apps/workflows/models.py +44 -0
- tradingcodex-0.1.0.dist-info/METADATA +337 -0
- tradingcodex-0.1.0.dist-info/RECORD +254 -0
- tradingcodex-0.1.0.dist-info/WHEEL +5 -0
- tradingcodex-0.1.0.dist-info/entry_points.txt +2 -0
- tradingcodex-0.1.0.dist-info/licenses/LICENSE +202 -0
- tradingcodex-0.1.0.dist-info/licenses/NOTICE +24 -0
- tradingcodex-0.1.0.dist-info/top_level.txt +4 -0
- tradingcodex_cli/__init__.py +1 -0
- tradingcodex_cli/__main__.py +124 -0
- tradingcodex_cli/commands/__init__.py +2 -0
- tradingcodex_cli/commands/bootstrap.py +157 -0
- tradingcodex_cli/commands/db.py +36 -0
- tradingcodex_cli/commands/doctor.py +230 -0
- tradingcodex_cli/commands/mcp.py +186 -0
- tradingcodex_cli/commands/orders.py +89 -0
- tradingcodex_cli/commands/policy.py +21 -0
- tradingcodex_cli/commands/profile.py +110 -0
- tradingcodex_cli/commands/research.py +76 -0
- tradingcodex_cli/commands/skills.py +93 -0
- tradingcodex_cli/commands/strategies.py +67 -0
- tradingcodex_cli/commands/subagents.py +106 -0
- tradingcodex_cli/commands/utils.py +134 -0
- tradingcodex_cli/commands/workspaces.py +53 -0
- tradingcodex_cli/generator.py +234 -0
- tradingcodex_cli/mcp_stdio.py +26 -0
- tradingcodex_cli/service_autostart.py +127 -0
- tradingcodex_service/__init__.py +5 -0
- tradingcodex_service/admin.py +1 -0
- tradingcodex_service/api.py +486 -0
- tradingcodex_service/application/__init__.py +2 -0
- tradingcodex_service/application/agents.py +1470 -0
- tradingcodex_service/application/audit.py +71 -0
- tradingcodex_service/application/common.py +88 -0
- tradingcodex_service/application/components.py +299 -0
- tradingcodex_service/application/harness.py +747 -0
- tradingcodex_service/application/markdown_preview.py +179 -0
- tradingcodex_service/application/orders.py +404 -0
- tradingcodex_service/application/policy.py +150 -0
- tradingcodex_service/application/portfolio.py +166 -0
- tradingcodex_service/application/research.py +356 -0
- tradingcodex_service/application/runtime.py +321 -0
- tradingcodex_service/asgi.py +6 -0
- tradingcodex_service/mcp_http.py +59 -0
- tradingcodex_service/mcp_runtime.py +565 -0
- tradingcodex_service/settings.py +91 -0
- tradingcodex_service/templates/web/activity.html +24 -0
- tradingcodex_service/templates/web/agent_skills.html +111 -0
- tradingcodex_service/templates/web/agents.html +121 -0
- tradingcodex_service/templates/web/base.html +150 -0
- tradingcodex_service/templates/web/dashboard.html +109 -0
- tradingcodex_service/templates/web/fragments/role_inspector.html +81 -0
- tradingcodex_service/templates/web/fragments/starter_prompt.html +24 -0
- tradingcodex_service/templates/web/fragments/topology_canvas.html +85 -0
- tradingcodex_service/templates/web/harness.html +87 -0
- tradingcodex_service/templates/web/mcp_router.html +250 -0
- tradingcodex_service/templates/web/orders.html +68 -0
- tradingcodex_service/templates/web/policy.html +81 -0
- tradingcodex_service/templates/web/portfolio.html +52 -0
- tradingcodex_service/templates/web/research.html +73 -0
- tradingcodex_service/templates/web/starter_prompt.html +40 -0
- tradingcodex_service/templates/web/strategies.html +74 -0
- tradingcodex_service/urls.py +42 -0
- tradingcodex_service/version.py +1 -0
- tradingcodex_service/web.py +885 -0
- tradingcodex_service/wsgi.py +6 -0
- workspace_templates/__init__.py +1 -0
- workspace_templates/modules/audit/files/.tradingcodex/audit/README.md +5 -0
- workspace_templates/modules/audit/files/trading/audit/.gitkeep +1 -0
- workspace_templates/modules/audit/module.json +16 -0
- workspace_templates/modules/codex-base/files/.codex/config.toml +272 -0
- workspace_templates/modules/codex-base/files/.codex/hooks/tradingcodex_hook.py +173 -0
- workspace_templates/modules/codex-base/files/.codex/hooks.json +105 -0
- workspace_templates/modules/codex-base/files/.codex/prompts/base_instructions/head-manager.md +129 -0
- workspace_templates/modules/codex-base/files/.codex/rules/tradingcodex.rules +50 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/capabilities.yaml +56 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/cli.py +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/config.yaml +53 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/policy-bindings.yaml +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/principals.yaml +17 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/roles.yaml +27 -0
- workspace_templates/modules/codex-base/files/AGENTS.md +56 -0
- workspace_templates/modules/codex-base/files/pyproject.toml +13 -0
- workspace_templates/modules/codex-base/files/tcx +35 -0
- workspace_templates/modules/codex-base/module.json +17 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/access-policies.yaml +39 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/restricted-list.yaml +6 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/approval_receipt.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/audit_event.schema.json +11 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/evidence_pack.schema.json +16 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/execution_result.schema.json +12 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/fundamental_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/news_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/order_intent.schema.json +30 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/portfolio_review.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/postmortem_report.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/risk_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/technical_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/thesis.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/valuation.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/scripts/validate-order-intent.py +15 -0
- workspace_templates/modules/enforcement-guardrails/module.json +19 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/execution-operator.toml +71 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/fundamental-analyst.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/instrument-analyst.toml +64 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/macro-analyst.toml +64 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/news-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/portfolio-manager.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/risk-manager.toml +65 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/technical-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/valuation-analyst.toml +59 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/head-manager.yaml +66 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/skill-change-proposals/.gitkeep +1 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/subagent-registry.yaml +56 -0
- workspace_templates/modules/fixed-subagents/module.json +23 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/guardrails.md +17 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/task-quality-checklist.md +37 -0
- workspace_templates/modules/guidance-guardrails/module.json +18 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/policies/information-barriers.yaml +211 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/secrets.md +9 -0
- workspace_templates/modules/information-barriers/files/trading/approvals/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/market-data/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/approved/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/draft/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/executed/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/rejected/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/fundamental/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/instrument/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/macro/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/news/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/policy/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/postmortem/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/risk/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/technical/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/valuation/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/research/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/module.json +19 -0
- workspace_templates/modules/paper-trading/files/.tradingcodex/mcp/adapters/paper-trading.py +4 -0
- workspace_templates/modules/paper-trading/module.json +16 -0
- workspace_templates/modules/postmortem/files/.tradingcodex/workflows/postmortem.yaml +12 -0
- workspace_templates/modules/postmortem/module.json +16 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/SKILL.md +106 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/SKILL.md +101 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/SKILL.md +140 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/SKILL.md +140 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/SKILL.md +31 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/SKILL.md +138 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/SKILL.md +109 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/SKILL.md +54 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/SKILL.md +35 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/SKILL.md +44 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/SKILL.md +38 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/SKILL.md +45 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/SKILL.md +66 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/SKILL.md +47 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/module.json +37 -0
- workspace_templates/modules/stub-execution/files/.tradingcodex/mcp/adapters/stub-execution.py +4 -0
- workspace_templates/modules/stub-execution/module.json +15 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/adapters/live-adapter.contract.md +25 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/enforcer/README.md +5 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/gateway/README.md +8 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/server.py +27 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/smoke-call.py +18 -0
- workspace_templates/modules/tradingcodex-mcp/module.json +24 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
RESEARCH_ROLES = {
|
|
11
|
+
"head-manager",
|
|
12
|
+
"fundamental-analyst",
|
|
13
|
+
"technical-analyst",
|
|
14
|
+
"news-analyst",
|
|
15
|
+
"macro-analyst",
|
|
16
|
+
"instrument-analyst",
|
|
17
|
+
"valuation-analyst",
|
|
18
|
+
"portfolio-manager",
|
|
19
|
+
"risk-manager",
|
|
20
|
+
}
|
|
21
|
+
POLICY_ROLES = {"head-manager", "valuation-analyst", "portfolio-manager", "risk-manager", "execution-operator"}
|
|
22
|
+
PORTFOLIO_ROLES = {"head-manager", "portfolio-manager", "risk-manager", "execution-operator"}
|
|
23
|
+
APPROVAL_ROLES = {"risk-manager"}
|
|
24
|
+
EXECUTION_ROLES = {"execution-operator"}
|
|
25
|
+
SAFE_HOME_TOOL_NAMES = frozenset({
|
|
26
|
+
"get_tradingcodex_status",
|
|
27
|
+
"get_order_status",
|
|
28
|
+
"get_positions",
|
|
29
|
+
"get_portfolio_snapshot",
|
|
30
|
+
"list_workflow_artifacts",
|
|
31
|
+
"get_research_artifact",
|
|
32
|
+
"list_research_artifacts",
|
|
33
|
+
"search_research_artifacts",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class McpToolSpec:
|
|
39
|
+
name: str
|
|
40
|
+
description: str
|
|
41
|
+
category: str
|
|
42
|
+
risk_level: str
|
|
43
|
+
allowed_roles: frozenset[str]
|
|
44
|
+
handler_name: str
|
|
45
|
+
input_schema: dict[str, Any]
|
|
46
|
+
capability_required: str = ""
|
|
47
|
+
requires_approval: bool = False
|
|
48
|
+
audit_required: bool = True
|
|
49
|
+
experimental: bool = False
|
|
50
|
+
|
|
51
|
+
def public_definition(self) -> dict[str, Any]:
|
|
52
|
+
return {
|
|
53
|
+
"name": self.name,
|
|
54
|
+
"description": self.description,
|
|
55
|
+
"inputSchema": self.input_schema,
|
|
56
|
+
"annotations": {
|
|
57
|
+
"category": self.category,
|
|
58
|
+
"risk_level": self.risk_level,
|
|
59
|
+
"requires_approval": self.requires_approval,
|
|
60
|
+
"audit_required": self.audit_required,
|
|
61
|
+
"allowed_roles": sorted(self.allowed_roles),
|
|
62
|
+
"experimental": self.experimental,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def object_schema(properties: dict[str, Any] | None = None, required: list[str] | None = None) -> dict[str, Any]:
|
|
68
|
+
return {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": properties or {},
|
|
71
|
+
"required": required or [],
|
|
72
|
+
"additionalProperties": True,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
TOOL_SPECS: tuple[McpToolSpec, ...] = (
|
|
77
|
+
McpToolSpec(
|
|
78
|
+
name="get_tradingcodex_status",
|
|
79
|
+
description="Return TradingCodex service, DB, workspace, and active profile status.",
|
|
80
|
+
category="harness",
|
|
81
|
+
risk_level="read",
|
|
82
|
+
allowed_roles=frozenset({"head-manager"}),
|
|
83
|
+
handler_name="get_tradingcodex_status",
|
|
84
|
+
input_schema=object_schema(),
|
|
85
|
+
),
|
|
86
|
+
McpToolSpec(
|
|
87
|
+
name="simulate_policy",
|
|
88
|
+
description="Evaluate TradingCodex policy for a proposed action without bypassing service-layer checks.",
|
|
89
|
+
category="policy",
|
|
90
|
+
risk_level="read",
|
|
91
|
+
allowed_roles=frozenset(POLICY_ROLES),
|
|
92
|
+
handler_name="simulate_policy",
|
|
93
|
+
input_schema=object_schema({
|
|
94
|
+
"principal_id": {"type": "string"},
|
|
95
|
+
"action": {"type": "string"},
|
|
96
|
+
"resource": {"type": "string"},
|
|
97
|
+
"order_intent": {"type": "object"},
|
|
98
|
+
"approval_receipt": {"type": "object"},
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
McpToolSpec(
|
|
102
|
+
name="validate_order_intent",
|
|
103
|
+
description="Validate a draft order intent against schema, restricted list, limits, and adapter policy.",
|
|
104
|
+
category="orders",
|
|
105
|
+
risk_level="write",
|
|
106
|
+
allowed_roles=frozenset({"portfolio-manager", "risk-manager", "execution-operator"}),
|
|
107
|
+
handler_name="validate_order_intent",
|
|
108
|
+
input_schema=object_schema({"order_intent": {"type": "object"}, "order_intent_path": {"type": "string"}}),
|
|
109
|
+
capability_required="order_intent.validate",
|
|
110
|
+
),
|
|
111
|
+
McpToolSpec(
|
|
112
|
+
name="validate_approval_receipt",
|
|
113
|
+
description="Validate that an approval receipt is valid, unexpired, and matches its order intent.",
|
|
114
|
+
category="orders",
|
|
115
|
+
risk_level="write",
|
|
116
|
+
allowed_roles=frozenset({"risk-manager", "execution-operator"}),
|
|
117
|
+
handler_name="validate_approval_receipt",
|
|
118
|
+
input_schema=object_schema({"order_intent": {"type": "object"}, "approval_receipt": {"type": "object"}}),
|
|
119
|
+
capability_required="approval_receipt.validate",
|
|
120
|
+
),
|
|
121
|
+
McpToolSpec(
|
|
122
|
+
name="create_approval_receipt",
|
|
123
|
+
description="Create an approval receipt only after order-intent and policy validation.",
|
|
124
|
+
category="approvals",
|
|
125
|
+
risk_level="approval",
|
|
126
|
+
allowed_roles=frozenset(APPROVAL_ROLES),
|
|
127
|
+
handler_name="create_approval_receipt",
|
|
128
|
+
input_schema=object_schema({"order_intent": {"type": "object"}, "approved_by": {"type": "string"}, "expires_hours": {"type": "integer"}}),
|
|
129
|
+
capability_required="approval_receipt.create",
|
|
130
|
+
requires_approval=True,
|
|
131
|
+
),
|
|
132
|
+
McpToolSpec(
|
|
133
|
+
name="submit_approved_order",
|
|
134
|
+
description="Experimental: submit an approved order through a non-live adapter after order, approval, idempotency, and policy revalidation.",
|
|
135
|
+
category="execution",
|
|
136
|
+
risk_level="execution",
|
|
137
|
+
allowed_roles=frozenset(EXECUTION_ROLES),
|
|
138
|
+
handler_name="submit_approved_order",
|
|
139
|
+
input_schema=object_schema({"order_intent": {"type": "object"}, "approval_receipt": {"type": "object"}, "order_intent_id": {"type": "string"}}),
|
|
140
|
+
capability_required="mcp.tradingcodex.submit_approved_order",
|
|
141
|
+
requires_approval=True,
|
|
142
|
+
experimental=True,
|
|
143
|
+
),
|
|
144
|
+
McpToolSpec(
|
|
145
|
+
name="cancel_approved_order",
|
|
146
|
+
description="Experimental placeholder cancellation gateway; records audit but does not call a live broker.",
|
|
147
|
+
category="execution",
|
|
148
|
+
risk_level="execution",
|
|
149
|
+
allowed_roles=frozenset(EXECUTION_ROLES),
|
|
150
|
+
handler_name="cancel_approved_order",
|
|
151
|
+
input_schema=object_schema({"order_id": {"type": "string"}}),
|
|
152
|
+
capability_required="mcp.tradingcodex.cancel_approved_order",
|
|
153
|
+
requires_approval=True,
|
|
154
|
+
experimental=True,
|
|
155
|
+
),
|
|
156
|
+
McpToolSpec(
|
|
157
|
+
name="get_order_status",
|
|
158
|
+
description="Experimental: return local-only order status information for the initial harness.",
|
|
159
|
+
category="execution",
|
|
160
|
+
risk_level="read",
|
|
161
|
+
allowed_roles=frozenset(EXECUTION_ROLES | {"head-manager"}),
|
|
162
|
+
handler_name="get_order_status",
|
|
163
|
+
input_schema=object_schema({"order_id": {"type": "string"}}),
|
|
164
|
+
experimental=True,
|
|
165
|
+
),
|
|
166
|
+
McpToolSpec(
|
|
167
|
+
name="get_positions",
|
|
168
|
+
description="Return paper portfolio positions and cash state.",
|
|
169
|
+
category="portfolio",
|
|
170
|
+
risk_level="read",
|
|
171
|
+
allowed_roles=frozenset(PORTFOLIO_ROLES),
|
|
172
|
+
handler_name="list_positions",
|
|
173
|
+
input_schema=object_schema(),
|
|
174
|
+
),
|
|
175
|
+
McpToolSpec(
|
|
176
|
+
name="get_portfolio_snapshot",
|
|
177
|
+
description="Return portfolio snapshot for portfolio, risk, and execution checks.",
|
|
178
|
+
category="portfolio",
|
|
179
|
+
risk_level="read",
|
|
180
|
+
allowed_roles=frozenset(PORTFOLIO_ROLES),
|
|
181
|
+
handler_name="list_positions",
|
|
182
|
+
input_schema=object_schema(),
|
|
183
|
+
),
|
|
184
|
+
McpToolSpec(
|
|
185
|
+
name="list_workflow_artifacts",
|
|
186
|
+
description="List workflow artifacts from workspace paths and file-native research memory.",
|
|
187
|
+
category="workflows",
|
|
188
|
+
risk_level="read",
|
|
189
|
+
allowed_roles=frozenset(RESEARCH_ROLES | {"execution-operator"}),
|
|
190
|
+
handler_name="list_workflow_artifacts",
|
|
191
|
+
input_schema=object_schema(),
|
|
192
|
+
),
|
|
193
|
+
McpToolSpec(
|
|
194
|
+
name="create_research_artifact",
|
|
195
|
+
description="Store markdown research as a workspace-native file through the service layer.",
|
|
196
|
+
category="research",
|
|
197
|
+
risk_level="write",
|
|
198
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
199
|
+
handler_name="create_research_artifact",
|
|
200
|
+
input_schema=object_schema({
|
|
201
|
+
"artifact_id": {"type": "string"},
|
|
202
|
+
"artifact_type": {"type": "string"},
|
|
203
|
+
"universe": {"type": "string"},
|
|
204
|
+
"symbol": {"type": "string"},
|
|
205
|
+
"title": {"type": "string"},
|
|
206
|
+
"markdown": {"type": "string"},
|
|
207
|
+
"markdown_path": {"type": "string"},
|
|
208
|
+
"source_as_of": {"type": "string"},
|
|
209
|
+
"readiness_label": {"type": "string"},
|
|
210
|
+
}),
|
|
211
|
+
capability_required="research_artifact.write",
|
|
212
|
+
),
|
|
213
|
+
McpToolSpec(
|
|
214
|
+
name="append_research_artifact_version",
|
|
215
|
+
description="Append a workspace-file version for an existing research artifact.",
|
|
216
|
+
category="research",
|
|
217
|
+
risk_level="write",
|
|
218
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
219
|
+
handler_name="append_research_artifact_version",
|
|
220
|
+
input_schema=object_schema({"artifact_id": {"type": "string"}, "markdown": {"type": "string"}}, ["artifact_id"]),
|
|
221
|
+
capability_required="research_artifact.write",
|
|
222
|
+
),
|
|
223
|
+
McpToolSpec(
|
|
224
|
+
name="get_research_artifact",
|
|
225
|
+
description="Fetch a workspace-native research artifact by artifact_id.",
|
|
226
|
+
category="research",
|
|
227
|
+
risk_level="read",
|
|
228
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
229
|
+
handler_name="get_research_artifact",
|
|
230
|
+
input_schema=object_schema({"artifact_id": {"type": "string"}, "include_markdown": {"type": "boolean"}}, ["artifact_id"]),
|
|
231
|
+
),
|
|
232
|
+
McpToolSpec(
|
|
233
|
+
name="list_research_artifacts",
|
|
234
|
+
description="List workspace-native research artifacts and metadata.",
|
|
235
|
+
category="research",
|
|
236
|
+
risk_level="read",
|
|
237
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
238
|
+
handler_name="list_research_artifacts",
|
|
239
|
+
input_schema=object_schema({"artifact_type": {"type": "string"}, "universe": {"type": "string"}, "symbol": {"type": "string"}, "limit": {"type": "integer"}}),
|
|
240
|
+
),
|
|
241
|
+
McpToolSpec(
|
|
242
|
+
name="search_research_artifacts",
|
|
243
|
+
description="Search workspace-native markdown research artifacts with lexical file search.",
|
|
244
|
+
category="research",
|
|
245
|
+
risk_level="read",
|
|
246
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
247
|
+
handler_name="search_research_artifacts",
|
|
248
|
+
input_schema=object_schema({"query": {"type": "string"}, "limit": {"type": "integer"}}, ["query"]),
|
|
249
|
+
),
|
|
250
|
+
McpToolSpec(
|
|
251
|
+
name="export_research_artifact_md",
|
|
252
|
+
description="Export or copy a workspace-native research artifact markdown file.",
|
|
253
|
+
category="research",
|
|
254
|
+
risk_level="write",
|
|
255
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
256
|
+
handler_name="export_research_artifact_md",
|
|
257
|
+
input_schema=object_schema({"artifact_id": {"type": "string"}, "export_path": {"type": "string"}}, ["artifact_id"]),
|
|
258
|
+
capability_required="research_artifact.export",
|
|
259
|
+
),
|
|
260
|
+
McpToolSpec(
|
|
261
|
+
name="record_source_snapshot",
|
|
262
|
+
description="Record provider/as-of/retrieved metadata and warnings as a workspace source-snapshot JSON file.",
|
|
263
|
+
category="research",
|
|
264
|
+
risk_level="write",
|
|
265
|
+
allowed_roles=frozenset(RESEARCH_ROLES),
|
|
266
|
+
handler_name="record_source_snapshot",
|
|
267
|
+
input_schema=object_schema({"provider": {"type": "string"}, "source_category": {"type": "string"}, "as_of": {"type": "string"}, "artifact_id": {"type": "string"}, "warnings": {"type": "array"}, "payload": {"type": "object"}}),
|
|
268
|
+
capability_required="source_snapshot.record",
|
|
269
|
+
),
|
|
270
|
+
McpToolSpec(
|
|
271
|
+
name="record_audit_event",
|
|
272
|
+
description="Append an explicit audit event through the TradingCodex audit ledger.",
|
|
273
|
+
category="audit",
|
|
274
|
+
risk_level="write",
|
|
275
|
+
allowed_roles=frozenset(RESEARCH_ROLES | {"execution-operator"}),
|
|
276
|
+
handler_name="record_audit_event",
|
|
277
|
+
input_schema=object_schema({"event": {"type": "object"}, "principal_id": {"type": "string"}}),
|
|
278
|
+
capability_required="audit_event.record",
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
TOOL_REGISTRY = {tool.name: tool for tool in TOOL_SPECS}
|
|
283
|
+
_REGISTRY_SYNCED = False
|
|
284
|
+
_REGISTRY_SYNCED_DB = ""
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def prepare_mcp_runtime(workspace_root: Path | str | None = None) -> None:
|
|
288
|
+
global _REGISTRY_SYNCED, _REGISTRY_SYNCED_DB
|
|
289
|
+
from tradingcodex_service.application.runtime import tradingcodex_db_path
|
|
290
|
+
|
|
291
|
+
current_db = str(tradingcodex_db_path())
|
|
292
|
+
if _REGISTRY_SYNCED and (not current_db or _REGISTRY_SYNCED_DB == current_db):
|
|
293
|
+
return
|
|
294
|
+
try:
|
|
295
|
+
if workspace_root is not None:
|
|
296
|
+
from tradingcodex_service.application.runtime import ensure_runtime_database, workspace_file_lock
|
|
297
|
+
|
|
298
|
+
ensure_runtime_database(workspace_root)
|
|
299
|
+
with workspace_file_lock(workspace_root, "mcp-registry"):
|
|
300
|
+
sync_mcp_tool_definitions()
|
|
301
|
+
else:
|
|
302
|
+
sync_mcp_tool_definitions()
|
|
303
|
+
_REGISTRY_SYNCED = True
|
|
304
|
+
_REGISTRY_SYNCED_DB = current_db
|
|
305
|
+
except Exception:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def list_mcp_tools() -> list[dict[str, Any]]:
|
|
310
|
+
return [tool.public_definition() for tool in visible_tool_specs() if tool_enabled(tool.name)]
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def static_mcp_tools() -> list[dict[str, Any]]:
|
|
314
|
+
return [tool.public_definition() for tool in TOOL_SPECS]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def visible_tool_specs() -> tuple[McpToolSpec, ...]:
|
|
318
|
+
if safe_home_mcp_scope():
|
|
319
|
+
return tuple(tool for tool in TOOL_SPECS if tool.name in SAFE_HOME_TOOL_NAMES)
|
|
320
|
+
return TOOL_SPECS
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def safe_home_mcp_scope() -> bool:
|
|
324
|
+
import os
|
|
325
|
+
|
|
326
|
+
return os.environ.get("TRADINGCODEX_MCP_SAFE_TOOLS", "").lower() in {"1", "true", "yes", "on"}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def sync_mcp_tool_definitions() -> None:
|
|
330
|
+
from apps.mcp.models import McpToolDefinition
|
|
331
|
+
from apps.policy.services import sync_builtin_principals_and_capabilities
|
|
332
|
+
|
|
333
|
+
for tool in TOOL_SPECS:
|
|
334
|
+
existing = McpToolDefinition.objects.filter(name=tool.name).first()
|
|
335
|
+
McpToolDefinition.objects.update_or_create(
|
|
336
|
+
name=tool.name,
|
|
337
|
+
defaults={
|
|
338
|
+
"description": tool.description,
|
|
339
|
+
"category": tool.category,
|
|
340
|
+
"capability_required": tool.capability_required,
|
|
341
|
+
"input_schema": tool.input_schema,
|
|
342
|
+
"risk_level": tool.risk_level,
|
|
343
|
+
"allowed_roles": sorted(tool.allowed_roles),
|
|
344
|
+
"requires_approval": tool.requires_approval,
|
|
345
|
+
"audit_required": tool.audit_required,
|
|
346
|
+
"experimental": tool.experimental,
|
|
347
|
+
"enabled": existing.enabled if existing is not None else True,
|
|
348
|
+
},
|
|
349
|
+
)
|
|
350
|
+
sync_builtin_principals_and_capabilities(TOOL_SPECS)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def tool_enabled(name: str) -> bool:
|
|
354
|
+
try:
|
|
355
|
+
from apps.mcp.models import McpToolDefinition
|
|
356
|
+
|
|
357
|
+
configured = McpToolDefinition.objects.filter(name=name).first()
|
|
358
|
+
if configured is not None:
|
|
359
|
+
return configured.enabled
|
|
360
|
+
except Exception:
|
|
361
|
+
return True
|
|
362
|
+
return True
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def role_for_principal(principal_id: str) -> str:
|
|
366
|
+
try:
|
|
367
|
+
from apps.policy.services import role_for_principal_id
|
|
368
|
+
|
|
369
|
+
return role_for_principal_id(principal_id)
|
|
370
|
+
except Exception:
|
|
371
|
+
pass
|
|
372
|
+
return principal_id or "unknown"
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def call_mcp_tool(workspace_root: Path | str, name: str, args: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
376
|
+
prepare_mcp_runtime(workspace_root)
|
|
377
|
+
args = dict(args or {})
|
|
378
|
+
tool = TOOL_REGISTRY.get(name)
|
|
379
|
+
if tool is None:
|
|
380
|
+
raise ValueError(f"Unknown TradingCodex tool: {name}")
|
|
381
|
+
if safe_home_mcp_scope() and name not in SAFE_HOME_TOOL_NAMES:
|
|
382
|
+
raise PermissionError(f"MCP tool is not available in tradingcodex-home safe scope: {name}")
|
|
383
|
+
if not tool_enabled(name):
|
|
384
|
+
raise PermissionError(f"MCP tool is disabled: {name}")
|
|
385
|
+
principal_id = str(args.get("principal_id") or default_principal_for_tool(tool))
|
|
386
|
+
role = role_for_principal(principal_id)
|
|
387
|
+
if role not in tool.allowed_roles:
|
|
388
|
+
raise PermissionError(f"{principal_id} is not allowed to call {name}")
|
|
389
|
+
from apps.policy.services import capability_check, tool_capability_action
|
|
390
|
+
|
|
391
|
+
action = tool_capability_action(tool.name, tool.capability_required)
|
|
392
|
+
capability_allowed, capability_reasons = capability_check(principal_id, action, args.get("resource"))
|
|
393
|
+
if not capability_allowed:
|
|
394
|
+
raise PermissionError("; ".join(capability_reasons))
|
|
395
|
+
validate_input_schema(tool, args)
|
|
396
|
+
|
|
397
|
+
started = time.monotonic()
|
|
398
|
+
request_payload = {"tool_name": name, "arguments": args, "principal_id": principal_id}
|
|
399
|
+
try:
|
|
400
|
+
result = raw_call_tool(workspace_root, tool, args, principal_id)
|
|
401
|
+
if isinstance(result, dict):
|
|
402
|
+
from tradingcodex_service.application.runtime import persist_workspace_context_if_available
|
|
403
|
+
|
|
404
|
+
result = dict(result)
|
|
405
|
+
result.setdefault("db_canonical", True)
|
|
406
|
+
result.setdefault("workspace_context", persist_workspace_context_if_available(workspace_root))
|
|
407
|
+
record_tool_call(workspace_root, name, principal_id, "ok", request_payload, result, started)
|
|
408
|
+
return result
|
|
409
|
+
except Exception as exc:
|
|
410
|
+
error = {"status": "error", "error": str(exc), "tool_name": name}
|
|
411
|
+
record_tool_call(workspace_root, name, principal_id, "error", request_payload, error, started, str(exc))
|
|
412
|
+
raise
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def raw_call_tool(workspace_root: Path | str, tool: McpToolSpec, args: dict[str, Any], principal_id: str) -> dict[str, Any]:
|
|
416
|
+
from tradingcodex_service.application import audit, orders, policy, portfolio, research
|
|
417
|
+
|
|
418
|
+
name = tool.name
|
|
419
|
+
if name == "get_tradingcodex_status":
|
|
420
|
+
from tradingcodex_service.application.runtime import persist_workspace_context_if_available, tradingcodex_db_path
|
|
421
|
+
from tradingcodex_service.version import TRADINGCODEX_VERSION
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
"status": "ok",
|
|
425
|
+
"service": "tradingcodex",
|
|
426
|
+
"version": TRADINGCODEX_VERSION,
|
|
427
|
+
"db_path": str(tradingcodex_db_path()),
|
|
428
|
+
"workspace_context": persist_workspace_context_if_available(workspace_root),
|
|
429
|
+
"mcp_scope": "global-home" if safe_home_mcp_scope() else "project-scoped",
|
|
430
|
+
}
|
|
431
|
+
if name == "simulate_policy":
|
|
432
|
+
return policy.simulate_policy(workspace_root, args)
|
|
433
|
+
if name == "validate_order_intent":
|
|
434
|
+
return orders.validate_order_intent(workspace_root, args)
|
|
435
|
+
if name == "validate_approval_receipt":
|
|
436
|
+
return orders.validate_approval_receipt(workspace_root, args)
|
|
437
|
+
if name == "create_approval_receipt":
|
|
438
|
+
order = orders.resolve_order_intent(Path(workspace_root), args)
|
|
439
|
+
return orders.create_approval_receipt(workspace_root, order, args.get("approved_by") or principal_id, int(args.get("expires_hours") or 24))
|
|
440
|
+
if name == "submit_approved_order":
|
|
441
|
+
return orders.submit_approved_order(workspace_root, {**args, "principal_id": principal_id})
|
|
442
|
+
if name in {"get_positions", "get_portfolio_snapshot"}:
|
|
443
|
+
return portfolio.list_positions(workspace_root)
|
|
444
|
+
if name == "list_workflow_artifacts":
|
|
445
|
+
return research.list_workflow_artifacts(workspace_root)
|
|
446
|
+
if name == "create_research_artifact":
|
|
447
|
+
return research.create_research_artifact(workspace_root, {**args, "principal_id": principal_id})
|
|
448
|
+
if name == "append_research_artifact_version":
|
|
449
|
+
return research.append_research_artifact_version(workspace_root, {**args, "principal_id": principal_id})
|
|
450
|
+
if name == "get_research_artifact":
|
|
451
|
+
return research.get_research_artifact(workspace_root, args)
|
|
452
|
+
if name == "list_research_artifacts":
|
|
453
|
+
return research.list_research_artifacts(workspace_root, args)
|
|
454
|
+
if name == "search_research_artifacts":
|
|
455
|
+
return research.search_research_artifacts(workspace_root, args)
|
|
456
|
+
if name == "export_research_artifact_md":
|
|
457
|
+
return research.export_research_artifact_md(workspace_root, args)
|
|
458
|
+
if name == "record_source_snapshot":
|
|
459
|
+
return research.record_source_snapshot(workspace_root, {**args, "principal_id": principal_id})
|
|
460
|
+
if name == "record_audit_event":
|
|
461
|
+
return audit.write_audit_event(workspace_root, args.get("event") or args, principal_id, "mcp")
|
|
462
|
+
if name == "cancel_approved_order":
|
|
463
|
+
result = {"status": "not_supported", "order_id": args.get("order_id"), "reason": "cancel is a placeholder in the initial harness"}
|
|
464
|
+
audit.write_audit_event(workspace_root, {"type": "cancel_approved_order", "payload": result}, principal_id, "mcp")
|
|
465
|
+
return result
|
|
466
|
+
if name == "get_order_status":
|
|
467
|
+
return {"order_id": args.get("order_id"), "status": "local-only", "note": "Initial harness has no live broker order status."}
|
|
468
|
+
raise ValueError(f"Unknown TradingCodex tool: {name}")
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def default_principal_for_tool(tool: McpToolSpec) -> str:
|
|
472
|
+
if "execution-operator" in tool.allowed_roles and len(tool.allowed_roles) == 1:
|
|
473
|
+
return "execution-operator"
|
|
474
|
+
if "risk-manager" in tool.allowed_roles and tool.category == "approvals":
|
|
475
|
+
return "risk-manager"
|
|
476
|
+
if "head-manager" in tool.allowed_roles:
|
|
477
|
+
return "head-manager"
|
|
478
|
+
return sorted(tool.allowed_roles)[0]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def validate_input_schema(tool: McpToolSpec, args: dict[str, Any]) -> None:
|
|
482
|
+
for field in tool.input_schema.get("required", []):
|
|
483
|
+
if args.get(field) in (None, ""):
|
|
484
|
+
raise ValueError(f"{tool.name} requires {field}")
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def record_tool_call(
|
|
488
|
+
workspace_root: Path | str | None,
|
|
489
|
+
name: str,
|
|
490
|
+
principal_id: str,
|
|
491
|
+
status: str,
|
|
492
|
+
request_payload: dict[str, Any],
|
|
493
|
+
response_payload: dict[str, Any],
|
|
494
|
+
started: float,
|
|
495
|
+
error: str = "",
|
|
496
|
+
) -> None:
|
|
497
|
+
if _skip_db_tool_call_ledger(name):
|
|
498
|
+
return
|
|
499
|
+
try:
|
|
500
|
+
from apps.mcp.models import McpToolCall
|
|
501
|
+
from tradingcodex_service.application.common import stable_hash
|
|
502
|
+
from tradingcodex_service.application.runtime import workspace_context_payload
|
|
503
|
+
|
|
504
|
+
McpToolCall.objects.create(
|
|
505
|
+
tool_name=name,
|
|
506
|
+
principal_id=principal_id,
|
|
507
|
+
status=status,
|
|
508
|
+
request=request_payload,
|
|
509
|
+
response=response_payload,
|
|
510
|
+
workspace_context=workspace_context_payload(workspace_root),
|
|
511
|
+
request_hash=stable_hash(request_payload),
|
|
512
|
+
result_hash=stable_hash(response_payload),
|
|
513
|
+
error=error,
|
|
514
|
+
duration_ms=max(0, int((time.monotonic() - started) * 1000)),
|
|
515
|
+
)
|
|
516
|
+
except Exception:
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _skip_db_tool_call_ledger(name: str) -> bool:
|
|
521
|
+
tool = TOOL_REGISTRY.get(name)
|
|
522
|
+
if tool and tool.category == "research":
|
|
523
|
+
return True
|
|
524
|
+
return name == "list_workflow_artifacts"
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def handle_mcp_rpc(workspace_root: Path | str, message: dict[str, Any]) -> dict[str, Any] | None:
|
|
528
|
+
from tradingcodex_service.version import TRADINGCODEX_VERSION
|
|
529
|
+
|
|
530
|
+
prepare_mcp_runtime(workspace_root)
|
|
531
|
+
method = message.get("method")
|
|
532
|
+
if method == "notifications/initialized":
|
|
533
|
+
return None
|
|
534
|
+
if method == "initialize":
|
|
535
|
+
return {
|
|
536
|
+
"jsonrpc": "2.0",
|
|
537
|
+
"id": message.get("id"),
|
|
538
|
+
"result": {
|
|
539
|
+
"protocolVersion": "2025-06-18",
|
|
540
|
+
"capabilities": {"tools": {"listChanged": False}, "resources": {}, "prompts": {}},
|
|
541
|
+
"serverInfo": {"name": "tradingcodex-home" if safe_home_mcp_scope() else "tradingcodex", "version": TRADINGCODEX_VERSION},
|
|
542
|
+
"instructions": "TradingCodex MCP is a Django service-layer gateway backed by workspace files for agent/skill/research state and the central local TradingCodex DB for runtime ledgers. Codex projects are callers/provenance; research tools use workspace markdown; execution tools revalidate policy, approval, adapter, and audit.",
|
|
543
|
+
},
|
|
544
|
+
}
|
|
545
|
+
if method == "tools/list":
|
|
546
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"tools": list_mcp_tools()}}
|
|
547
|
+
if method == "resources/list":
|
|
548
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"resources": []}}
|
|
549
|
+
if method == "prompts/list":
|
|
550
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"prompts": []}}
|
|
551
|
+
if method == "tools/call":
|
|
552
|
+
try:
|
|
553
|
+
params = message.get("params") or {}
|
|
554
|
+
result = call_mcp_tool(workspace_root, params.get("name"), params.get("arguments") or {})
|
|
555
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "result": {"content": [{"type": "text", "text": json.dumps(result, indent=2, ensure_ascii=False)}]}}
|
|
556
|
+
except Exception as exc:
|
|
557
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "error": {"code": -32000, "message": str(exc)}}
|
|
558
|
+
return {"jsonrpc": "2.0", "id": message.get("id"), "error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def handle_mcp_batch(workspace_root: Path | str, payload: Any) -> Any:
|
|
562
|
+
if isinstance(payload, list):
|
|
563
|
+
responses = [handle_mcp_rpc(workspace_root, item) for item in payload]
|
|
564
|
+
return [response for response in responses if response is not None]
|
|
565
|
+
return handle_mcp_rpc(workspace_root, payload)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def default_db_name() -> str:
|
|
10
|
+
configured = os.environ.get("TRADINGCODEX_DB_NAME")
|
|
11
|
+
if configured:
|
|
12
|
+
path = Path(configured).expanduser().resolve()
|
|
13
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
return str(path)
|
|
15
|
+
home = Path(os.environ.get("TRADINGCODEX_HOME", "~/.tradingcodex")).expanduser().resolve()
|
|
16
|
+
path = home / "state" / "tradingcodex.sqlite3"
|
|
17
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
return str(path)
|
|
19
|
+
|
|
20
|
+
SECRET_KEY = os.environ.get("TRADINGCODEX_SECRET_KEY", "tradingcodex-local-dev-key")
|
|
21
|
+
DEBUG = os.environ.get("TRADINGCODEX_DEBUG", "1") == "1"
|
|
22
|
+
ALLOWED_HOSTS = os.environ.get("TRADINGCODEX_ALLOWED_HOSTS", "127.0.0.1,localhost,testserver").split(",")
|
|
23
|
+
ROOT_URLCONF = "tradingcodex_service.urls"
|
|
24
|
+
ASGI_APPLICATION = "tradingcodex_service.asgi.application"
|
|
25
|
+
WSGI_APPLICATION = "tradingcodex_service.wsgi.application"
|
|
26
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
27
|
+
|
|
28
|
+
INSTALLED_APPS = [
|
|
29
|
+
"django.contrib.admin",
|
|
30
|
+
"django.contrib.auth",
|
|
31
|
+
"django.contrib.contenttypes",
|
|
32
|
+
"django.contrib.sessions",
|
|
33
|
+
"django.contrib.messages",
|
|
34
|
+
"django.contrib.staticfiles",
|
|
35
|
+
"ninja",
|
|
36
|
+
"apps.audit",
|
|
37
|
+
"apps.harness",
|
|
38
|
+
"apps.integrations",
|
|
39
|
+
"apps.mcp",
|
|
40
|
+
"apps.orders",
|
|
41
|
+
"apps.policy",
|
|
42
|
+
"apps.portfolio",
|
|
43
|
+
"apps.research",
|
|
44
|
+
"apps.workflows",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
MIDDLEWARE = [
|
|
48
|
+
"django.middleware.security.SecurityMiddleware",
|
|
49
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
50
|
+
"django.middleware.common.CommonMiddleware",
|
|
51
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
52
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
53
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
|
54
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
DATABASES = {
|
|
58
|
+
"default": {
|
|
59
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
60
|
+
"NAME": default_db_name(),
|
|
61
|
+
"OPTIONS": {"timeout": int(os.environ.get("TRADINGCODEX_SQLITE_TIMEOUT", "30"))},
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
LANGUAGE_CODE = "en-us"
|
|
66
|
+
TIME_ZONE = "UTC"
|
|
67
|
+
USE_I18N = True
|
|
68
|
+
USE_TZ = True
|
|
69
|
+
STATIC_URL = "static/"
|
|
70
|
+
STATICFILES_DIRS = [BASE_DIR / "static"]
|
|
71
|
+
|
|
72
|
+
TEMPLATES = [
|
|
73
|
+
{
|
|
74
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
75
|
+
"DIRS": [BASE_DIR / "templates", BASE_DIR / "tradingcodex_service" / "templates"],
|
|
76
|
+
"APP_DIRS": True,
|
|
77
|
+
"OPTIONS": {
|
|
78
|
+
"context_processors": [
|
|
79
|
+
"django.template.context_processors.request",
|
|
80
|
+
"django.contrib.auth.context_processors.auth",
|
|
81
|
+
"django.contrib.messages.context_processors.messages",
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
TRADINGCODEX = {
|
|
88
|
+
"max_single_order_krw": int(os.environ.get("TRADINGCODEX_MAX_SINGLE_ORDER_KRW", "100000000")),
|
|
89
|
+
"allowed_adapters": ["stub-execution", "paper-trading"],
|
|
90
|
+
"enabled_live_execution": False,
|
|
91
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% extends "web/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Activity | tcx{% endblock %}
|
|
4
|
+
{% block page_label %}Activity{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<section class="tc-panel">
|
|
8
|
+
<div class="tc-panel-head">
|
|
9
|
+
<span class="tc-eyebrow">Recent activity</span>
|
|
10
|
+
<h2>{{ activity|length }} records</h2>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="tc-activity-list tc-activity-list--wide">
|
|
13
|
+
{% for item in activity %}
|
|
14
|
+
<div class="tc-activity-row">
|
|
15
|
+
<span class="tc-pill tc-pill--{{ item.status_class }}">{{ item.kind }}</span>
|
|
16
|
+
<strong>{{ item.title }}</strong>
|
|
17
|
+
<small>{{ item.subtitle }} · {{ item.status }} · {{ item.created_at|date:"Y-m-d H:i" }}</small>
|
|
18
|
+
</div>
|
|
19
|
+
{% empty %}
|
|
20
|
+
<p class="tc-empty">No activity has been recorded yet.</p>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
24
|
+
{% endblock %}
|