agent-portal-2 0.1.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.
Files changed (120) hide show
  1. package/.continue/agents/new-config.yaml +22 -0
  2. package/AGENT_STEERING.md +36 -0
  3. package/ARCHITECTURE.md +13 -0
  4. package/CHANGELOG.md +97 -0
  5. package/CLI.md +38 -0
  6. package/CONTRIBUTING.md +55 -0
  7. package/INSTALLATION.md +58 -0
  8. package/LICENSE +60 -0
  9. package/PLUGIN_SYSTEM.md +33 -0
  10. package/PYTHON_SDK.md +22 -0
  11. package/QUICKSTART.md +19 -0
  12. package/README.md +385 -0
  13. package/RELEASE_NOTES_v0.1.0.md +281 -0
  14. package/ROADMAP.md +3 -0
  15. package/RUNTIME.md +44 -0
  16. package/SAFETY_MODEL.md +24 -0
  17. package/TESTING.md +35 -0
  18. package/TROUBLESHOOTING.md +30 -0
  19. package/UPGRADE_GUIDE.md +288 -0
  20. package/VS_CODE_EXTENSION.md +47 -0
  21. package/agent-portal.config.json +20 -0
  22. package/apps/desktop/agent-portal-desktop.zip +0 -0
  23. package/apps/desktop/fixtures/local-workflow.html +151 -0
  24. package/apps/desktop/package.json +18 -0
  25. package/apps/desktop/src/main.ts +117 -0
  26. package/apps/desktop/tsconfig.json +8 -0
  27. package/apps/vscode-extension/LICENSE +60 -0
  28. package/apps/vscode-extension/README.md +20 -0
  29. package/apps/vscode-extension/media/agent-portal-logo.png +0 -0
  30. package/apps/vscode-extension/package.json +149 -0
  31. package/apps/vscode-extension/src/extension.ts +614 -0
  32. package/apps/vscode-extension/tsconfig.json +12 -0
  33. package/assets/branding/agent-portal-logo.png +0 -0
  34. package/connectors/chatgpt-tools/README.md +9 -0
  35. package/connectors/claude-mcp-server/README.md +9 -0
  36. package/connectors/gemini-connector/README.md +9 -0
  37. package/connectors/rest-websocket-api/README.md +9 -0
  38. package/docs/MCP_SERVER.md +68 -0
  39. package/docs/architecture.md +214 -0
  40. package/docs/roadmap.md +125 -0
  41. package/package.json +21 -0
  42. package/packages/agent-portal-mcp/README.md +12 -0
  43. package/packages/agent-portal-mcp/agent_portal_mcp/__init__.py +3 -0
  44. package/packages/agent-portal-mcp/agent_portal_mcp/bridge/__init__.py +1 -0
  45. package/packages/agent-portal-mcp/agent_portal_mcp/bridge/runtime_client.py +180 -0
  46. package/packages/agent-portal-mcp/agent_portal_mcp/cli.py +32 -0
  47. package/packages/agent-portal-mcp/agent_portal_mcp/doctor.py +71 -0
  48. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/__init__.py +1 -0
  49. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/actions.py +17 -0
  50. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/results.py +24 -0
  51. package/packages/agent-portal-mcp/agent_portal_mcp/schemas/risk.py +20 -0
  52. package/packages/agent-portal-mcp/agent_portal_mcp/security/__init__.py +1 -0
  53. package/packages/agent-portal-mcp/agent_portal_mcp/security/policy.py +27 -0
  54. package/packages/agent-portal-mcp/agent_portal_mcp/server.py +148 -0
  55. package/packages/agent-portal-mcp/agent_portal_mcp/tool_registry.py +58 -0
  56. package/packages/agent-portal-mcp/agent_portal_mcp/tools/__init__.py +1 -0
  57. package/packages/agent-portal-mcp/agent_portal_mcp/tools/browser.py +89 -0
  58. package/packages/agent-portal-mcp/agent_portal_mcp/tools/common.py +98 -0
  59. package/packages/agent-portal-mcp/agent_portal_mcp/tools/inspection.py +93 -0
  60. package/packages/agent-portal-mcp/agent_portal_mcp/tools/navigation.py +93 -0
  61. package/packages/agent-portal-mcp/agent_portal_mcp/tools/reports.py +34 -0
  62. package/packages/agent-portal-mcp/agent_portal_mcp/tools/steering.py +93 -0
  63. package/packages/agent-portal-mcp/pyproject.toml +20 -0
  64. package/packages/agent-portal-mcp/tests/test_doctor.py +20 -0
  65. package/packages/agent-portal-mcp/tests/test_mcp_server.py +161 -0
  66. package/packages/core/package.json +15 -0
  67. package/packages/core/src/index.ts +1842 -0
  68. package/packages/core/tsconfig.json +8 -0
  69. package/packages/mcp-server/package.json +15 -0
  70. package/packages/mcp-server/src/index.ts +73 -0
  71. package/packages/mcp-server/tsconfig.json +8 -0
  72. package/packages/sdk/package.json +15 -0
  73. package/packages/sdk/src/index.ts +544 -0
  74. package/packages/sdk/tsconfig.json +8 -0
  75. package/plugins/README.md +16 -0
  76. package/plugins/agent-portal-browser/plugin.json +19 -0
  77. package/plugins/agent-portal-python/plugin.json +16 -0
  78. package/plugins/agent-portal-skills/plugin.json +19 -0
  79. package/plugins/agent-portal-vscode/plugin.json +27 -0
  80. package/plugins/example-runtime-plugin/README.md +3 -0
  81. package/plugins/example-runtime-plugin/plugin.json +20 -0
  82. package/plugins/plugin.schema.json +53 -0
  83. package/python/README.md +18 -0
  84. package/python/agent_portal/__init__.py +5 -0
  85. package/python/agent_portal/__main__.py +5 -0
  86. package/python/agent_portal/browser.py +393 -0
  87. package/python/agent_portal/cli.py +164 -0
  88. package/python/agent_portal/config.py +31 -0
  89. package/python/agent_portal/doctor.py +165 -0
  90. package/python/agent_portal/exceptions.py +39 -0
  91. package/python/agent_portal/logging_utils.py +33 -0
  92. package/python/agent_portal/metrics.py +309 -0
  93. package/python/agent_portal/models.py +160 -0
  94. package/python/agent_portal/plugin_system.py +42 -0
  95. package/python/agent_portal/rate_limit.py +253 -0
  96. package/python/agent_portal/runtime.py +739 -0
  97. package/python/agent_portal/server.py +351 -0
  98. package/python/agent_portal/validation.py +299 -0
  99. package/python/pyproject.toml +29 -0
  100. package/python/tests/test_config.py +24 -0
  101. package/python/tests/test_doctor.py +19 -0
  102. package/python/tests/test_metrics.py +180 -0
  103. package/python/tests/test_rate_limit.py +237 -0
  104. package/python/tests/test_runtime.py +122 -0
  105. package/python/tests/test_server.py +53 -0
  106. package/python/tests/test_validation.py +170 -0
  107. package/releases/desktop/agent-portal-desktop/README.md +378 -0
  108. package/releases/desktop/agent-portal-desktop/RELEASE_NOTES.md +14 -0
  109. package/releases/desktop/agent-portal-desktop/assets/branding/agent-portal-logo.png +0 -0
  110. package/releases/desktop/agent-portal-desktop/fixtures/local-workflow.html +151 -0
  111. package/releases/desktop/agent-portal-desktop/launch-agent-portal.bat +4 -0
  112. package/releases/desktop/agent-portal-desktop.zip +0 -0
  113. package/releases/python/agent_portal-0.0.2-py3-none-any.whl +0 -0
  114. package/releases/python/agent_portal-0.0.2.tar.gz +0 -0
  115. package/scripts/package_desktop.mjs +117 -0
  116. package/scripts/release_python.py +46 -0
  117. package/tests/plugin-manifest.test.mjs +26 -0
  118. package/tests/runtime.test.mjs +41 -0
  119. package/tests/vscode-extension.test.mjs +22 -0
  120. package/tsconfig.base.json +16 -0
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from agent_portal_mcp.bridge.runtime_client import AgentPortalRuntimeClient
6
+ from agent_portal_mcp.schemas.actions import McpAction
7
+ from agent_portal_mcp.schemas.results import ToolResult
8
+ from agent_portal_mcp.tools.common import execute_or_queue
9
+
10
+
11
+ def navigate_to_url(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
12
+ return execute_or_queue(
13
+ client,
14
+ "navigate_to_url",
15
+ McpAction(
16
+ tool="navigate_to_url",
17
+ runtime_action_type="open_url",
18
+ reason=str(args.get("reason", "Navigate to URL")),
19
+ target=str(args.get("url", "")),
20
+ risk_hint="low",
21
+ ),
22
+ )
23
+
24
+
25
+ def click_element(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
26
+ return execute_or_queue(
27
+ client,
28
+ "click_element",
29
+ McpAction(
30
+ tool="click_element",
31
+ runtime_action_type="click",
32
+ reason=str(args.get("reason", "Click element")),
33
+ target=str(args.get("selector", "")),
34
+ risk_hint="low",
35
+ ),
36
+ )
37
+
38
+
39
+ def type_text(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
40
+ return execute_or_queue(
41
+ client,
42
+ "type_text",
43
+ McpAction(
44
+ tool="type_text",
45
+ runtime_action_type="type",
46
+ reason=str(args.get("reason", "Type text into element")),
47
+ target=str(args.get("selector", "")),
48
+ payload=str(args.get("text", "")),
49
+ risk_hint="medium",
50
+ ),
51
+ )
52
+
53
+
54
+ def scroll_page(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
55
+ return execute_or_queue(
56
+ client,
57
+ "scroll_page",
58
+ McpAction(
59
+ tool="scroll_page",
60
+ runtime_action_type="scroll",
61
+ reason=str(args.get("reason", "Scroll page")),
62
+ target=str(args["selector"]) if "selector" in args and args["selector"] is not None else None,
63
+ risk_hint="safe",
64
+ ),
65
+ )
66
+
67
+
68
+ def hover_element(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
69
+ return execute_or_queue(
70
+ client,
71
+ "hover_element",
72
+ McpAction(
73
+ tool="hover_element",
74
+ runtime_action_type="hover",
75
+ reason=str(args.get("reason", "Hover over element")),
76
+ target=str(args.get("selector", "")),
77
+ risk_hint="safe",
78
+ ),
79
+ )
80
+
81
+
82
+ def wait_for_page(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
83
+ return execute_or_queue(
84
+ client,
85
+ "wait_for_page",
86
+ McpAction(
87
+ tool="wait_for_page",
88
+ runtime_action_type="wait",
89
+ reason=str(args.get("reason", "Wait for page element")),
90
+ target=str(args.get("selector", "")),
91
+ risk_hint="safe",
92
+ ),
93
+ )
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from agent_portal_mcp.bridge.runtime_client import AgentPortalRuntimeClient
6
+ from agent_portal_mcp.schemas.results import ToolResult
7
+ from agent_portal_mcp.tools.common import build_result
8
+
9
+
10
+ def generate_report(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
11
+ status = client.status()
12
+ payload = client.generate_report()
13
+ return build_result("generate_report", status, None, "completed", "safe", "Generated report.", payload)
14
+
15
+
16
+ def list_reports(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
17
+ status = client.status()
18
+ payload = client.list_reports()
19
+ return build_result("list_reports", status, None, "completed", "safe", "Listed reports.", payload)
20
+
21
+
22
+ def read_report(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
23
+ status = client.status()
24
+ payload = client.read_report(str(args.get("report", "")))
25
+ return build_result("read_report", status, None, "completed", "safe", "Read report.", payload)
26
+
27
+
28
+ def export_report(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
29
+ status = client.status()
30
+ payload = client.export_report(
31
+ str(args.get("report", "")),
32
+ str(args["destination"]) if "destination" in args and args["destination"] is not None else None,
33
+ )
34
+ return build_result("export_report", status, None, "completed", "safe", "Exported report.", payload)
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from agent_portal_mcp.bridge.runtime_client import AgentPortalRuntimeClient
6
+ from agent_portal_mcp.schemas.results import ToolResult
7
+ from agent_portal_mcp.tools.common import build_result
8
+
9
+
10
+ def propose_action(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
11
+ status = client.status()
12
+ action = client.propose_action(
13
+ str(args.get("action_type", "")),
14
+ str(args.get("reason", "Propose action")),
15
+ target=str(args["target"]) if "target" in args and args["target"] is not None else None,
16
+ payload=str(args["payload"]) if "payload" in args and args["payload"] is not None else None,
17
+ risk_level=str(args.get("risk", "low")),
18
+ )
19
+ mapped_status = "pending_approval" if action.get("status") == "pending" else str(action.get("status", "failed"))
20
+ return build_result(
21
+ "propose_action",
22
+ status,
23
+ action,
24
+ mapped_status, # type: ignore[arg-type]
25
+ str(action.get("risk_level", "low")), # type: ignore[arg-type]
26
+ "Proposed action through the runtime policy engine.",
27
+ {"action": action},
28
+ )
29
+
30
+
31
+ def approve_action(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
32
+ status = client.status()
33
+ action = client.approve_action(str(args.get("action_id", "")), execute=bool(args.get("execute", True)))
34
+ return build_result(
35
+ "approve_action",
36
+ status,
37
+ action,
38
+ "completed" if action.get("status") == "completed" else "pending_approval",
39
+ str(action.get("risk_level", "low")), # type: ignore[arg-type]
40
+ "Approved action through Agent Portal.",
41
+ {"action": action},
42
+ )
43
+
44
+
45
+ def reject_action(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
46
+ status = client.status()
47
+ action = client.reject_action(str(args.get("action_id", "")), str(args.get("reason", "Rejected by user")))
48
+ return build_result(
49
+ "reject_action",
50
+ status,
51
+ action,
52
+ "rejected",
53
+ str(action.get("risk_level", "low")), # type: ignore[arg-type]
54
+ "Rejected action through Agent Portal.",
55
+ {"action": action},
56
+ )
57
+
58
+
59
+ def pause_agent(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
60
+ status = client.pause()
61
+ return build_result("pause_agent", status, None, "completed", "safe", "Paused agent runtime.", status)
62
+
63
+
64
+ def resume_agent(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
65
+ status = client.resume()
66
+ return build_result("resume_agent", status, None, "completed", "safe", "Resumed agent runtime.", status)
67
+
68
+
69
+ def stop_agent(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
70
+ status = client.stop()
71
+ return build_result("stop_agent", {"session": {}}, None, "completed", "medium", "Stopped agent runtime.", status)
72
+
73
+
74
+ def set_goal(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
75
+ status = client.set_goal(str(args.get("goal", "")))
76
+ return build_result("set_goal", status, None, "completed", "safe", "Set current goal.", status)
77
+
78
+
79
+ def get_current_goal(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
80
+ status = client.status()
81
+ goal = client.current_goal()
82
+ return build_result("get_current_goal", status, None, "completed", "safe", "Returned current goal.", goal)
83
+
84
+
85
+ def get_action_queue(client: AgentPortalRuntimeClient, _args: dict[str, Any]) -> ToolResult:
86
+ status = client.status()
87
+ queue = client.action_queue()
88
+ return build_result("get_action_queue", status, None, "completed", "safe", "Returned action queue.", queue)
89
+
90
+
91
+ def set_action_mode(client: AgentPortalRuntimeClient, args: dict[str, Any]) -> ToolResult:
92
+ status = client.set_action_mode(str(args.get("mode", "assisted")))
93
+ return build_result("set_action_mode", status, None, "completed", "medium", "Updated action mode.", status)
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "agent-portal-mcp"
3
+ version = "0.0.2"
4
+ description = "MCP server bridge for Agent Portal."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Magnexis" }
10
+ ]
11
+
12
+ [project.scripts]
13
+ agent-portal-mcp = "agent_portal_mcp.cli:main"
14
+
15
+ [tool.flit.module]
16
+ name = "agent_portal_mcp"
17
+
18
+ [build-system]
19
+ requires = ["flit_core>=3.12.0,<4"]
20
+ build-backend = "flit_core.buildapi"
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+
9
+ from agent_portal_mcp.doctor import run_doctor
10
+
11
+
12
+ class McpDoctorTests(unittest.TestCase):
13
+ def test_doctor_returns_checks(self) -> None:
14
+ result = run_doctor("http://127.0.0.1:9")
15
+ self.assertGreater(len(result.checks), 0)
16
+ self.assertTrue(any(check.name == "tool-registry" for check in result.checks))
17
+
18
+
19
+ if __name__ == "__main__":
20
+ unittest.main()
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+
9
+ from agent_portal_mcp.bridge.runtime_client import RuntimeClientError
10
+ from agent_portal_mcp.schemas.results import ToolResult
11
+ from agent_portal_mcp.server import AgentPortalMcpServer
12
+ from agent_portal_mcp.tool_registry import build_tool_registry
13
+ from agent_portal_mcp.tools import browser, inspection, navigation, reports
14
+
15
+
16
+ class FakeClient:
17
+ def __init__(self, risk: str = "low", blocked: bool = False, auto_complete: bool = True) -> None:
18
+ self.risk = risk
19
+ self.blocked = blocked
20
+ self.auto_complete = auto_complete
21
+ self.token = "super-secret-token"
22
+
23
+ def status(self) -> dict[str, object]:
24
+ return {
25
+ "session": {"session_id": "session-1", "runtime_status": "idle"},
26
+ "policy": {"mode": "assisted", "approval_threshold": "medium", "read_only": False},
27
+ }
28
+
29
+ def propose_action(self, action_type: str, reason: str, target=None, payload=None, risk_level="low"):
30
+ if self.blocked:
31
+ return {
32
+ "action_id": "action-1",
33
+ "action_type": action_type,
34
+ "status": "blocked",
35
+ "reason": reason,
36
+ "target": target,
37
+ "payload": payload,
38
+ "risk_level": "blocked",
39
+ "result": "blocked",
40
+ }
41
+ return {
42
+ "action_id": "action-1",
43
+ "action_type": action_type,
44
+ "status": "pending",
45
+ "reason": reason,
46
+ "target": target,
47
+ "payload": payload,
48
+ "risk_level": self.risk,
49
+ }
50
+
51
+ def approve_action(self, action_id: str, execute: bool = False):
52
+ return {
53
+ "action_id": action_id,
54
+ "status": "completed" if execute and self.auto_complete else "approved",
55
+ "risk_level": self.risk,
56
+ "after_screenshot": "after.png",
57
+ }
58
+
59
+ def browser_status(self):
60
+ return {"connected": True}
61
+
62
+ def capture(self, label: str = "capture"):
63
+ return {"action": {"action_id": "capture-1", "after_screenshot": f"{label}.png"}, "screenshotPath": f"{label}.png", "inspection": {"url": "http://localhost", "title": "Fixture", "dom": "<html></html>", "consoleErrors": [], "networkErrors": []}}
64
+
65
+ def read_text(self, selector: str, reason: str):
66
+ return {"action": {"action_id": "read-1"}, "selector": selector, "text": "hello"}
67
+
68
+ def read_dom(self):
69
+ return {"dom": "<html></html>"}
70
+
71
+ def read_accessibility_tree(self):
72
+ return {"accessibilityTree": {"role": "document"}}
73
+
74
+ def read_console_errors(self):
75
+ return {"consoleErrors": ["error"]}
76
+
77
+ def read_network_failures(self):
78
+ return {"networkFailures": ["GET /api - failed"]}
79
+
80
+ def inspect_element(self, selector: str):
81
+ return {"selector": selector, "visible": True}
82
+
83
+ def generate_report(self):
84
+ return {"reportPath": "report.json"}
85
+
86
+ def list_reports(self):
87
+ return {"reports": [{"name": "report.json"}]}
88
+
89
+ def read_report(self, report_name: str):
90
+ return {"name": report_name}
91
+
92
+ def export_report(self, report_name: str, destination=None):
93
+ return {"exportPath": destination or report_name}
94
+
95
+ def health(self):
96
+ return {"ok": True, "runtimeVersion": "0.0.2"}
97
+
98
+
99
+ class McpServerTests(unittest.TestCase):
100
+ def test_tools_are_registered(self) -> None:
101
+ registry = build_tool_registry()
102
+ self.assertIn("browser_open", registry)
103
+ self.assertIn("capture_screenshot", registry)
104
+ self.assertIn("approve_action", registry)
105
+
106
+ def test_server_initialize_and_list_tools(self) -> None:
107
+ server = AgentPortalMcpServer()
108
+ initialize = server.handle_request({"jsonrpc": "2.0", "id": 1, "method": "initialize"})
109
+ tools = server.handle_request({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
110
+ self.assertEqual(initialize["result"]["serverInfo"]["name"], "agent-portal-mcp")
111
+ self.assertGreater(len(tools["result"]["tools"]), 5)
112
+
113
+ def test_browser_open_tool_executes(self) -> None:
114
+ result = browser.browser_open(FakeClient(), {"url": "http://localhost:3000"})
115
+ self.assertTrue(result.ok)
116
+ self.assertEqual(result.status, "completed")
117
+
118
+ def test_capture_screenshot_tool_executes(self) -> None:
119
+ result = inspection.capture_screenshot(FakeClient(), {"label": "home"})
120
+ self.assertTrue(result.ok)
121
+ self.assertEqual(result.data["screenshotPath"], "home.png")
122
+
123
+ def test_click_element_can_enter_pending_approval(self) -> None:
124
+ result = navigation.click_element(FakeClient(risk="high"), {"selector": "#danger"})
125
+ self.assertFalse(result.ok)
126
+ self.assertEqual(result.status, "pending_approval")
127
+
128
+ def test_high_risk_action_can_be_blocked(self) -> None:
129
+ result = navigation.type_text(
130
+ FakeClient(blocked=True),
131
+ {"selector": "#password", "text": "secret", "reason": "Enter password"},
132
+ )
133
+ self.assertEqual(result.status, "blocked")
134
+
135
+ def test_report_generation_tool(self) -> None:
136
+ result = reports.generate_report(FakeClient(), {})
137
+ self.assertEqual(result.data["reportPath"], "report.json")
138
+
139
+ def test_token_redaction(self) -> None:
140
+ server = AgentPortalMcpServer()
141
+ server.client = FakeClient() # type: ignore[assignment]
142
+ redacted = server._redact("token super-secret-token should not leak")
143
+ self.assertNotIn("super-secret-token", redacted)
144
+
145
+ def test_runtime_offline_error(self) -> None:
146
+ server = AgentPortalMcpServer()
147
+
148
+ class OfflineClient:
149
+ token = None
150
+
151
+ def status(self):
152
+ raise RuntimeClientError("Agent Portal runtime is not running. Start it with: agent-portal start")
153
+
154
+ server.client = OfflineClient() # type: ignore[assignment]
155
+ result = server.call_tool("browser_status", {})
156
+ self.assertFalse(result.ok)
157
+ self.assertIn("agent-portal start", result.message)
158
+
159
+
160
+ if __name__ == "__main__":
161
+ unittest.main()
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@agent-portal/core",
3
+ "version": "0.0.2",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json",
10
+ "typecheck": "tsc -p tsconfig.json --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "playwright": "^1.54.1"
14
+ }
15
+ }