polyrob 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
@@ -0,0 +1,899 @@
1
+ """
2
+ Service for managing per-user MCP server configurations.
3
+
4
+ Provides high-level operations for adding, configuring, and managing
5
+ user-specific MCP servers with validation and security checks.
6
+ """
7
+
8
+ import time
9
+ from typing import List, Optional, Dict, Any
10
+ from dataclasses import dataclass
11
+
12
+ from modules.database.user_mcp_servers import (
13
+ UserMCPServersHandler,
14
+ UserMCPServer,
15
+ UserMCPSettings
16
+ )
17
+ from tools.mcp.security import MCPEncryption, MCPURLValidator, get_encryption, get_url_validator
18
+ from tools.mcp.config import MCPServerConfig, MCPServerType
19
+
20
+ from core.logging import get_component_logger
21
+
22
+ # Module-level logger for static methods
23
+ logger = get_component_logger("UserMCPService")
24
+
25
+
26
+ @dataclass
27
+ class AddServerResult:
28
+ """Result of adding a server."""
29
+
30
+ success: bool
31
+ server: Optional[UserMCPServer] = None
32
+ error: Optional[str] = None
33
+ ready: bool = False
34
+
35
+
36
+ @dataclass
37
+ class TestConnectionResult:
38
+ """Result of testing server connection."""
39
+
40
+ success: bool
41
+ latency_ms: Optional[float] = None
42
+ error: Optional[str] = None
43
+ tools_discovered: Optional[int] = None
44
+ tools: Optional[List[str]] = None # List of tool names discovered
45
+
46
+
47
+ class RateLimiter:
48
+ """Simple in-memory rate limiter (FIX #11)."""
49
+
50
+ def __init__(self, max_requests: int = 10, window_seconds: int = 60):
51
+ self.max_requests = max_requests
52
+ self.window_seconds = window_seconds
53
+ self._requests: Dict[str, List[float]] = {} # user_id -> list of timestamps
54
+
55
+ def check(self, user_id: str) -> bool:
56
+ """Check if request is allowed. Returns True if allowed."""
57
+ import time
58
+ now = time.time()
59
+
60
+ if user_id not in self._requests:
61
+ self._requests[user_id] = []
62
+
63
+ # Remove old requests outside window
64
+ cutoff = now - self.window_seconds
65
+ self._requests[user_id] = [t for t in self._requests[user_id] if t > cutoff]
66
+
67
+ if len(self._requests[user_id]) >= self.max_requests:
68
+ return False
69
+
70
+ self._requests[user_id].append(now)
71
+ return True
72
+
73
+ def remaining(self, user_id: str) -> int:
74
+ """Get remaining requests in window."""
75
+ import time
76
+ now = time.time()
77
+ cutoff = now - self.window_seconds
78
+
79
+ if user_id not in self._requests:
80
+ return self.max_requests
81
+
82
+ recent = [t for t in self._requests[user_id] if t > cutoff]
83
+ return max(0, self.max_requests - len(recent))
84
+
85
+
86
+ class UserMCPService:
87
+ """Service for user MCP server management."""
88
+
89
+ def __init__(
90
+ self,
91
+ db_handler: UserMCPServersHandler,
92
+ encryption: Optional[MCPEncryption] = None,
93
+ url_validator: Optional[MCPURLValidator] = None,
94
+ allow_http: bool = False,
95
+ rate_limit_requests: int = 20,
96
+ rate_limit_window: int = 60
97
+ ):
98
+ """
99
+ Initialize service.
100
+
101
+ Args:
102
+ db_handler: Database handler for user MCP servers
103
+ encryption: Encryption instance (uses default if not provided)
104
+ url_validator: URL validator (uses default if not provided)
105
+ allow_http: Allow HTTP URLs (development only)
106
+ rate_limit_requests: Max requests per window (FIX #11)
107
+ rate_limit_window: Rate limit window in seconds (FIX #11)
108
+ """
109
+ self.db = db_handler
110
+ self.encryption = encryption or get_encryption()
111
+ self.validator = url_validator or get_url_validator(allow_http=allow_http)
112
+ self.logger = get_component_logger("UserMCPService")
113
+ # FIX #11: Rate limiting
114
+ self._rate_limiter = RateLimiter(rate_limit_requests, rate_limit_window)
115
+
116
+ def _check_rate_limit(self, user_id: str) -> None:
117
+ """Check rate limit and raise if exceeded (FIX #11)."""
118
+ if not self._rate_limiter.check(user_id):
119
+ remaining = self._rate_limiter.remaining(user_id)
120
+ raise ValueError(
121
+ f"Rate limit exceeded. Please wait before making more requests. "
122
+ f"({remaining} requests remaining)"
123
+ )
124
+
125
+ @staticmethod
126
+ def _sanitize_error(error: str) -> str:
127
+ """Sanitize error message to hide internal details (FIX #14)."""
128
+ import re
129
+ # Remove file paths
130
+ error = re.sub(r'/[^\s:]+\.py', '[internal]', error)
131
+ error = re.sub(r'line \d+', '', error)
132
+ # Remove stack trace indicators
133
+ error = re.sub(r'Traceback.*?:', '', error, flags=re.DOTALL)
134
+ error = re.sub(r'File "[^"]+",', '', error)
135
+ # Remove internal module names
136
+ error = re.sub(r'\b(tools|modules|agents)\.[a-z_.]+', '[internal]', error)
137
+ # Limit length
138
+ if len(error) > 200:
139
+ error = error[:200] + '...'
140
+ return error.strip()
141
+
142
+ async def add_server(
143
+ self,
144
+ user_id: str,
145
+ server_name: str,
146
+ server_url: str,
147
+ server_type: str = "sse",
148
+ api_key: Optional[str] = None,
149
+ headers: Optional[Dict[str, str]] = None,
150
+ auth_method: str = "api_key",
151
+ display_name: Optional[str] = None,
152
+ verify_connection: bool = False,
153
+ **options
154
+ ) -> AddServerResult:
155
+ """
156
+ Add a new MCP server for a user.
157
+
158
+ Performs validation and security checks before adding.
159
+
160
+ Args:
161
+ user_id: Owner's user ID
162
+ server_name: Unique name for the server (lowercase, alphanumeric, hyphens)
163
+ server_url: Server endpoint URL (must be HTTPS)
164
+ server_type: 'sse' or 'http' (NOT 'stdio' - blocked for security)
165
+ api_key: API key for authentication
166
+ headers: Custom headers for requests
167
+ auth_method: 'api_key', 'bearer', or 'none'
168
+ display_name: Friendly display name
169
+ verify_connection: If True, verify MCP protocol before saving (FIX #6)
170
+ **options: Additional options (timeout, retry_attempts, etc.)
171
+
172
+ Returns:
173
+ AddServerResult with success status and server or error
174
+ """
175
+ # FIX #11: Check rate limit
176
+ try:
177
+ self._check_rate_limit(user_id)
178
+ except ValueError as e:
179
+ return AddServerResult(success=False, error=str(e))
180
+
181
+ # Validate server type (NO STDIO for users)
182
+ type_valid, type_error = self.validator.validate_server_type(server_type)
183
+ if not type_valid:
184
+ return AddServerResult(
185
+ success=False,
186
+ error=type_error
187
+ )
188
+
189
+ # Validate URL (HTTPS only, no internal IPs)
190
+ url_valid, url_error = self.validator.validate(server_url)
191
+ if not url_valid:
192
+ return AddServerResult(
193
+ success=False,
194
+ error=f"Invalid URL: {url_error}"
195
+ )
196
+
197
+ # Validate server name format
198
+ if not self._validate_server_name(server_name):
199
+ return AddServerResult(
200
+ success=False,
201
+ error="Server name must be lowercase, alphanumeric with hyphens only (3-50 chars)"
202
+ )
203
+
204
+ # Check user's server limit
205
+ settings = await self.db.get_user_settings(user_id)
206
+ current_count = await self.db.server_count(user_id)
207
+
208
+ if current_count >= settings.max_servers:
209
+ return AddServerResult(
210
+ success=False,
211
+ error=f"Maximum server limit ({settings.max_servers}) reached"
212
+ )
213
+
214
+ # Check for duplicate name
215
+ existing = await self.db.get_server(user_id, server_name)
216
+ if existing:
217
+ return AddServerResult(
218
+ success=False,
219
+ error=f"Server '{server_name}' already exists"
220
+ )
221
+
222
+ # FIX #6: Optionally verify connection before saving
223
+ tools_discovered = 0
224
+ if verify_connection and (api_key is not None or auth_method == 'none'):
225
+ # Build temporary config for testing
226
+ temp_config = MCPServerConfig(
227
+ type=MCPServerType(server_type),
228
+ url=server_url,
229
+ headers=self._build_auth_headers(headers, api_key, auth_method),
230
+ enabled=True,
231
+ timeout=options.get('timeout', 30)
232
+ )
233
+
234
+ # Test MCP protocol
235
+ test_result = await self._test_mcp_protocol_with_config(temp_config)
236
+
237
+ if not test_result.success:
238
+ return AddServerResult(
239
+ success=False,
240
+ error=f"Server verification failed: {test_result.error}"
241
+ )
242
+
243
+ tools_discovered = test_result.tools_discovered or 0
244
+ self.logger.info(f"Server {server_name} verified: {tools_discovered} tools discovered")
245
+
246
+ # Add server
247
+ try:
248
+ server = await self.db.add_server(
249
+ user_id=user_id,
250
+ server_name=server_name,
251
+ server_url=server_url,
252
+ server_type=server_type,
253
+ auth_method=auth_method,
254
+ api_key=api_key,
255
+ headers=headers,
256
+ display_name=display_name,
257
+ **options
258
+ )
259
+
260
+ # Update tools_discovered if we verified
261
+ if tools_discovered > 0:
262
+ await self.db.update_connection_status(
263
+ user_id, server_name,
264
+ connected=True,
265
+ tools_count=tools_discovered
266
+ )
267
+
268
+ return AddServerResult(
269
+ success=True,
270
+ server=server,
271
+ ready=api_key is not None or auth_method == 'none'
272
+ )
273
+
274
+ except Exception as e:
275
+ self.logger.error(f"Failed to add server '{server_name}' for user {user_id}: {e}")
276
+ return AddServerResult(
277
+ success=False,
278
+ error=f"Database error: {str(e)}"
279
+ )
280
+
281
+ def _build_auth_headers(
282
+ self,
283
+ headers: Optional[Dict[str, str]],
284
+ api_key: Optional[str],
285
+ auth_method: str
286
+ ) -> Optional[Dict[str, str]]:
287
+ """Build headers dict with authentication."""
288
+ final_headers = dict(headers) if headers else {}
289
+ if api_key:
290
+ if auth_method == 'bearer':
291
+ final_headers['Authorization'] = f'Bearer {api_key}'
292
+ elif auth_method == 'api_key':
293
+ final_headers['Authorization'] = f'Bearer {api_key}'
294
+ return final_headers or None
295
+
296
+ async def _test_mcp_protocol_with_config(
297
+ self,
298
+ config: MCPServerConfig
299
+ ) -> TestConnectionResult:
300
+ """Test MCP protocol with a config object (for verification before saving)."""
301
+ import time
302
+ import asyncio
303
+ from tools.mcp.protocol import MCPClient, MCPSSETransport, MCPHTTPTransport
304
+
305
+ client = None
306
+ start = time.time()
307
+
308
+ try:
309
+ if config.type == MCPServerType.SSE:
310
+ transport = MCPSSETransport(
311
+ url=config.url,
312
+ headers=config.headers,
313
+ timeout=min(config.timeout, 30),
314
+ validate_ssrf=True # SSRF: user-supplied URL
315
+ )
316
+ elif config.type == MCPServerType.HTTP:
317
+ transport = MCPHTTPTransport(
318
+ url=config.url,
319
+ headers=config.headers,
320
+ timeout=min(config.timeout, 30),
321
+ validate_ssrf=True # SSRF: user-supplied URL
322
+ )
323
+ else:
324
+ return TestConnectionResult(
325
+ success=False,
326
+ error=f"Unsupported server type: {config.type}"
327
+ )
328
+
329
+ client = MCPClient(transport)
330
+
331
+ try:
332
+ await asyncio.wait_for(client.connect(), timeout=30)
333
+ except asyncio.TimeoutError:
334
+ return TestConnectionResult(
335
+ success=False,
336
+ error="MCP handshake timed out (30s)"
337
+ )
338
+
339
+ latency = (time.time() - start) * 1000
340
+ tools = client.tools
341
+ tool_names = [t.get('name', 'unknown') for t in tools]
342
+
343
+ return TestConnectionResult(
344
+ success=True,
345
+ latency_ms=round(latency, 2),
346
+ tools_discovered=len(tools),
347
+ tools=tool_names[:20]
348
+ )
349
+
350
+ except Exception as e:
351
+ return TestConnectionResult(
352
+ success=False,
353
+ error=f"MCP protocol error: {str(e)}"
354
+ )
355
+ finally:
356
+ if client:
357
+ try:
358
+ await client.close()
359
+ except Exception:
360
+ pass
361
+
362
+ async def get_user_servers(
363
+ self,
364
+ user_id: str,
365
+ enabled_only: bool = True
366
+ ) -> List[UserMCPServer]:
367
+ """
368
+ Get all MCP servers for a user.
369
+
370
+ Args:
371
+ user_id: Owner's user ID
372
+ enabled_only: Only return enabled servers
373
+
374
+ Returns:
375
+ List of UserMCPServer records
376
+ """
377
+ return await self.db.get_user_servers(user_id, enabled_only)
378
+
379
+ async def get_server(
380
+ self,
381
+ user_id: str,
382
+ server_name: str
383
+ ) -> Optional[UserMCPServer]:
384
+ """
385
+ Get a specific server.
386
+
387
+ Args:
388
+ user_id: Owner's user ID
389
+ server_name: Server name
390
+
391
+ Returns:
392
+ UserMCPServer or None
393
+ """
394
+ return await self.db.get_server(user_id, server_name)
395
+
396
+ async def get_server_config(
397
+ self,
398
+ user_id: str,
399
+ server_name: str
400
+ ) -> Optional[MCPServerConfig]:
401
+ """
402
+ Get MCPServerConfig for a user's server (for use by MCPServerManager).
403
+
404
+ Decrypts credentials and builds config object.
405
+
406
+ Args:
407
+ user_id: Owner's user ID
408
+ server_name: Server name
409
+
410
+ Returns:
411
+ MCPServerConfig ready for connection, or None
412
+ """
413
+ server = await self.db.get_server(user_id, server_name)
414
+ if not server:
415
+ return None
416
+
417
+ # Get decrypted credentials
418
+ api_key, headers = await self.db.get_decrypted_credentials(user_id, server_name)
419
+
420
+ # Build headers with auth
421
+ final_headers = headers or {}
422
+ if api_key:
423
+ if server.auth_method == 'bearer':
424
+ final_headers['Authorization'] = f'Bearer {api_key}'
425
+ elif server.auth_method == 'api_key':
426
+ # Default to Bearer, but could be customized per server
427
+ final_headers['Authorization'] = f'Bearer {api_key}'
428
+
429
+ return MCPServerConfig(
430
+ type=MCPServerType(server.server_type),
431
+ url=server.server_url,
432
+ headers=final_headers or None,
433
+ enabled=server.enabled,
434
+ timeout=server.timeout,
435
+ retry_attempts=server.retry_attempts,
436
+ retry_delay=server.retry_delay,
437
+ auto_reconnect=server.auto_reconnect,
438
+ max_concurrent_requests=server.max_concurrent_requests,
439
+ message_endpoint=server.message_endpoint # For SSE servers
440
+ )
441
+
442
+ async def update_server(
443
+ self,
444
+ user_id: str,
445
+ server_name: str,
446
+ **updates
447
+ ) -> bool:
448
+ """
449
+ Update server configuration.
450
+
451
+ Args:
452
+ user_id: Owner's user ID
453
+ server_name: Server name
454
+ **updates: Fields to update
455
+
456
+ Returns:
457
+ True if updated successfully
458
+ """
459
+ # Validate URL if being updated
460
+ if 'server_url' in updates:
461
+ url_valid, url_error = self.validator.validate(updates['server_url'])
462
+ if not url_valid:
463
+ raise ValueError(f"Invalid URL: {url_error}")
464
+
465
+ # Validate server_type if being updated
466
+ if 'server_type' in updates:
467
+ type_valid, type_error = self.validator.validate_server_type(updates['server_type'])
468
+ if not type_valid:
469
+ raise ValueError(type_error)
470
+
471
+ return await self.db.update_server(user_id, server_name, **updates)
472
+
473
+ async def delete_server(self, user_id: str, server_name: str) -> bool:
474
+ """
475
+ Delete a user's MCP server.
476
+
477
+ Args:
478
+ user_id: Owner's user ID
479
+ server_name: Server name
480
+
481
+ Returns:
482
+ True if deleted successfully
483
+ """
484
+ # FIX #11: Check rate limit
485
+ self._check_rate_limit(user_id)
486
+
487
+ # FIX #12: Audit log (uses private method)
488
+ await self.db._audit_log(
489
+ user_id=user_id,
490
+ action="server_deleted",
491
+ server_name=server_name,
492
+ details=None
493
+ )
494
+
495
+ return await self.db.delete_server(user_id, server_name)
496
+
497
+ async def test_connection(
498
+ self,
499
+ user_id: str,
500
+ server_name: str,
501
+ verify_protocol: bool = True
502
+ ) -> TestConnectionResult:
503
+ """
504
+ Test connectivity to a user's MCP server.
505
+
506
+ Performs actual MCP protocol handshake to verify the server speaks MCP.
507
+
508
+ Args:
509
+ user_id: Owner's user ID
510
+ server_name: Server name
511
+ verify_protocol: If True, performs full MCP handshake (default).
512
+ If False, only checks HTTP connectivity.
513
+
514
+ Returns:
515
+ TestConnectionResult with status and discovered tools
516
+ """
517
+ # FIX #11: Check rate limit
518
+ try:
519
+ self._check_rate_limit(user_id)
520
+ except ValueError as e:
521
+ return TestConnectionResult(success=False, error=str(e))
522
+
523
+ config = await self.get_server_config(user_id, server_name)
524
+ if not config:
525
+ return TestConnectionResult(
526
+ success=False,
527
+ error="Server not found"
528
+ )
529
+
530
+ try:
531
+ start = time.time()
532
+
533
+ if verify_protocol:
534
+ # FIX #5: Perform actual MCP protocol handshake
535
+ result = await self._test_mcp_protocol(user_id, server_name, config, start)
536
+ else:
537
+ # Legacy: Just check HTTP connectivity
538
+ result = await self._test_http_connectivity(user_id, server_name, config, start)
539
+
540
+ # FIX #12: Audit log test results (uses private method)
541
+ await self.db._audit_log(
542
+ user_id=user_id,
543
+ action="server_tested",
544
+ server_name=server_name,
545
+ details={"success": result.success, "tools": result.tools_discovered}
546
+ )
547
+
548
+ return result
549
+
550
+ except Exception as e:
551
+ # FIX #14: Sanitize error message
552
+ error = self._sanitize_error(f"Unexpected error: {str(e)}")
553
+ await self.db.update_connection_status(
554
+ user_id, server_name,
555
+ connected=False,
556
+ error=error
557
+ )
558
+ return TestConnectionResult(
559
+ success=False,
560
+ error=error
561
+ )
562
+
563
+ async def _test_http_connectivity(
564
+ self,
565
+ user_id: str,
566
+ server_name: str,
567
+ config: MCPServerConfig,
568
+ start_time: float
569
+ ) -> TestConnectionResult:
570
+ """Test basic HTTP connectivity (legacy method)."""
571
+ import aiohttp
572
+ import time
573
+
574
+ try:
575
+ async with aiohttp.ClientSession() as session:
576
+ async with session.get(
577
+ config.url,
578
+ headers=config.headers,
579
+ timeout=aiohttp.ClientTimeout(total=10)
580
+ ) as resp:
581
+ latency = (time.time() - start_time) * 1000
582
+
583
+ if resp.status < 400:
584
+ await self.db.update_connection_status(
585
+ user_id, server_name, connected=True
586
+ )
587
+ return TestConnectionResult(
588
+ success=True,
589
+ latency_ms=round(latency, 2)
590
+ )
591
+ else:
592
+ error = f"Server returned {resp.status}"
593
+ await self.db.update_connection_status(
594
+ user_id, server_name, connected=False, error=error
595
+ )
596
+ return TestConnectionResult(success=False, error=error)
597
+
598
+ except aiohttp.ClientError as e:
599
+ error = f"Connection error: {str(e)}"
600
+ await self.db.update_connection_status(
601
+ user_id, server_name, connected=False, error=error
602
+ )
603
+ return TestConnectionResult(success=False, error=error)
604
+
605
+ async def _test_mcp_protocol(
606
+ self,
607
+ user_id: str,
608
+ server_name: str,
609
+ config: MCPServerConfig,
610
+ start_time: float
611
+ ) -> TestConnectionResult:
612
+ """
613
+ Test MCP protocol by performing actual handshake.
614
+
615
+ Connects, sends initialize request, and attempts to list tools.
616
+ """
617
+ import time
618
+ import asyncio
619
+ from tools.mcp.protocol import MCPClient, MCPSSETransport, MCPHTTPTransport
620
+
621
+ client = None
622
+ try:
623
+ # Create appropriate transport based on server type
624
+ if config.type == MCPServerType.SSE:
625
+ transport = MCPSSETransport(
626
+ url=config.url,
627
+ headers=config.headers,
628
+ timeout=min(config.timeout, 30), # Cap at 30s for test
629
+ validate_ssrf=True # SSRF: user-supplied URL
630
+ )
631
+ elif config.type == MCPServerType.HTTP:
632
+ transport = MCPHTTPTransport(
633
+ url=config.url,
634
+ headers=config.headers,
635
+ timeout=min(config.timeout, 30),
636
+ validate_ssrf=True # SSRF: user-supplied URL
637
+ )
638
+ else:
639
+ return TestConnectionResult(
640
+ success=False,
641
+ error=f"Unsupported server type for testing: {config.type}"
642
+ )
643
+
644
+ # Create client and connect with timeout
645
+ client = MCPClient(transport)
646
+
647
+ try:
648
+ await asyncio.wait_for(client.connect(), timeout=30)
649
+ except asyncio.TimeoutError:
650
+ return TestConnectionResult(
651
+ success=False,
652
+ error="MCP handshake timed out (30s)"
653
+ )
654
+
655
+ latency = (time.time() - start_time) * 1000
656
+
657
+ # Get discovered tools
658
+ tools = client.tools
659
+ tool_names = [t.get('name', 'unknown') for t in tools]
660
+ tools_count = len(tools)
661
+
662
+ # Update database with results
663
+ await self.db.update_connection_status(
664
+ user_id, server_name,
665
+ connected=True,
666
+ tools_count=tools_count
667
+ )
668
+
669
+ self.logger.info(
670
+ f"MCP protocol test passed for {server_name}: "
671
+ f"{tools_count} tools, {latency:.0f}ms"
672
+ )
673
+
674
+ return TestConnectionResult(
675
+ success=True,
676
+ latency_ms=round(latency, 2),
677
+ tools_discovered=tools_count,
678
+ tools=tool_names[:20] # Limit to first 20 tool names
679
+ )
680
+
681
+ except Exception as e:
682
+ error = f"MCP protocol error: {str(e)}"
683
+ await self.db.update_connection_status(
684
+ user_id, server_name,
685
+ connected=False,
686
+ error=error
687
+ )
688
+ return TestConnectionResult(success=False, error=error)
689
+
690
+ finally:
691
+ # Clean up client
692
+ if client:
693
+ try:
694
+ await client.close()
695
+ except Exception:
696
+ pass
697
+
698
+ async def get_user_settings(self, user_id: str) -> UserMCPSettings:
699
+ """
700
+ Get user's MCP settings.
701
+
702
+ Args:
703
+ user_id: User ID
704
+
705
+ Returns:
706
+ UserMCPSettings record
707
+ """
708
+ return await self.db.get_user_settings(user_id)
709
+
710
+ async def update_user_settings(self, user_id: str, **updates) -> bool:
711
+ """
712
+ Update user's MCP settings.
713
+
714
+ Args:
715
+ user_id: User ID
716
+ **updates: Settings to update
717
+
718
+ Returns:
719
+ True if updated
720
+ """
721
+ return await self.db.update_user_settings(user_id, **updates)
722
+
723
+ async def get_available_servers_for_session(
724
+ self,
725
+ user_id: str,
726
+ include_global: bool = True
727
+ ) -> Dict[str, Any]:
728
+ """
729
+ Get all available MCP servers for a session.
730
+
731
+ Returns both global (platform) servers and user's custom servers.
732
+
733
+ Args:
734
+ user_id: User ID
735
+ include_global: Whether to include global servers
736
+
737
+ Returns:
738
+ Dict with 'global' and 'user' server lists
739
+ """
740
+ settings = await self.db.get_user_settings(user_id)
741
+
742
+ result = {
743
+ 'global': [],
744
+ 'user': []
745
+ }
746
+
747
+ # Get user's custom servers
748
+ user_servers = await self.db.get_user_servers(user_id, enabled_only=True)
749
+ for server in user_servers:
750
+ result['user'].append({
751
+ 'name': server.server_name,
752
+ 'tool_id': f"mcp:user:{server.server_name}",
753
+ 'display_name': server.display_name or server.server_name,
754
+ 'type': server.server_type,
755
+ 'status': server.auth_status,
756
+ 'tools_count': server.tools_discovered
757
+ })
758
+
759
+ # Include global servers if enabled
760
+ if include_global and settings.include_global_servers:
761
+ # Global servers are loaded from mcp_config.json at startup
762
+ # This will be populated by MCPTool when it initializes
763
+ result['include_global'] = True
764
+ else:
765
+ result['include_global'] = False
766
+
767
+ return result
768
+
769
+ async def health_check_user_servers(
770
+ self,
771
+ user_id: str,
772
+ update_status: bool = True
773
+ ) -> Dict[str, Any]:
774
+ """
775
+ Perform health check on all user's MCP servers (FIX #15).
776
+
777
+ Args:
778
+ user_id: User ID to check
779
+ update_status: If True, update connection status in database
780
+
781
+ Returns:
782
+ Dict with health status for each server
783
+ """
784
+ servers = await self.get_user_servers(user_id, enabled_only=True)
785
+ results = {
786
+ "user_id": user_id,
787
+ "checked_at": time.strftime("%Y-%m-%d %H:%M:%S"),
788
+ "total_servers": len(servers),
789
+ "healthy": 0,
790
+ "unhealthy": 0,
791
+ "servers": {}
792
+ }
793
+
794
+ for server in servers:
795
+ try:
796
+ # Use fast HTTP check for health (not full MCP protocol)
797
+ test_result = await self.test_connection(
798
+ user_id,
799
+ server.server_name,
800
+ verify_protocol=False # Faster check
801
+ )
802
+
803
+ if test_result.success:
804
+ results["healthy"] += 1
805
+ results["servers"][server.server_name] = {
806
+ "status": "healthy",
807
+ "latency_ms": test_result.latency_ms
808
+ }
809
+ else:
810
+ results["unhealthy"] += 1
811
+ results["servers"][server.server_name] = {
812
+ "status": "unhealthy",
813
+ "error": self._sanitize_error(test_result.error or "Unknown error")
814
+ }
815
+
816
+ except Exception as e:
817
+ results["unhealthy"] += 1
818
+ results["servers"][server.server_name] = {
819
+ "status": "error",
820
+ "error": self._sanitize_error(str(e))
821
+ }
822
+
823
+ return results
824
+
825
+ def _validate_server_name(self, name: str) -> bool:
826
+ """
827
+ Validate server name format.
828
+
829
+ Must be:
830
+ - 3-50 characters
831
+ - Lowercase
832
+ - Alphanumeric and hyphens only
833
+ - No leading/trailing hyphens
834
+
835
+ Args:
836
+ name: Server name to validate
837
+
838
+ Returns:
839
+ True if valid
840
+ """
841
+ import re
842
+
843
+ if not name or len(name) < 3 or len(name) > 50:
844
+ return False
845
+
846
+ if name != name.lower():
847
+ return False
848
+
849
+ if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', name) and len(name) > 2:
850
+ return False
851
+
852
+ if len(name) <= 2:
853
+ return re.match(r'^[a-z0-9]+$', name) is not None
854
+
855
+ return True
856
+
857
+
858
+ # Singleton instance
859
+ _user_mcp_service: Optional[UserMCPService] = None
860
+
861
+
862
+ def get_user_mcp_service() -> Optional[UserMCPService]:
863
+ """Get the singleton UserMCPService instance (may be None)."""
864
+ return _user_mcp_service
865
+
866
+
867
+ def require_user_mcp_service() -> UserMCPService:
868
+ """
869
+ Get the singleton UserMCPService instance.
870
+
871
+ FIX #10: Raises error if service not initialized, preventing None checks everywhere.
872
+
873
+ Raises:
874
+ RuntimeError: If service not initialized
875
+
876
+ Returns:
877
+ Initialized UserMCPService
878
+ """
879
+ if _user_mcp_service is None:
880
+ raise RuntimeError(
881
+ "UserMCPService not initialized. Call init_user_mcp_service() first."
882
+ )
883
+ return _user_mcp_service
884
+
885
+
886
+ def init_user_mcp_service(db_handler: UserMCPServersHandler, **kwargs) -> UserMCPService:
887
+ """
888
+ Initialize the singleton UserMCPService.
889
+
890
+ Args:
891
+ db_handler: Database handler
892
+ **kwargs: Additional options (encryption, url_validator, allow_http)
893
+
894
+ Returns:
895
+ Initialized UserMCPService
896
+ """
897
+ global _user_mcp_service
898
+ _user_mcp_service = UserMCPService(db_handler, **kwargs)
899
+ return _user_mcp_service