foundry-mcp 0.8.22__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.

Potentially problematic release.


This version of foundry-mcp might be problematic. Click here for more details.

Files changed (153) hide show
  1. foundry_mcp/__init__.py +13 -0
  2. foundry_mcp/cli/__init__.py +67 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +640 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +667 -0
  15. foundry_mcp/cli/commands/session.py +472 -0
  16. foundry_mcp/cli/commands/specs.py +686 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +298 -0
  22. foundry_mcp/cli/logging.py +212 -0
  23. foundry_mcp/cli/main.py +44 -0
  24. foundry_mcp/cli/output.py +122 -0
  25. foundry_mcp/cli/registry.py +110 -0
  26. foundry_mcp/cli/resilience.py +178 -0
  27. foundry_mcp/cli/transcript.py +217 -0
  28. foundry_mcp/config.py +1454 -0
  29. foundry_mcp/core/__init__.py +144 -0
  30. foundry_mcp/core/ai_consultation.py +1773 -0
  31. foundry_mcp/core/batch_operations.py +1202 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/health.py +749 -0
  40. foundry_mcp/core/intake.py +933 -0
  41. foundry_mcp/core/journal.py +700 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1376 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +146 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +387 -0
  57. foundry_mcp/core/prometheus.py +564 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +691 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
  61. foundry_mcp/core/prompts/plan_review.py +627 -0
  62. foundry_mcp/core/providers/__init__.py +237 -0
  63. foundry_mcp/core/providers/base.py +515 -0
  64. foundry_mcp/core/providers/claude.py +472 -0
  65. foundry_mcp/core/providers/codex.py +637 -0
  66. foundry_mcp/core/providers/cursor_agent.py +630 -0
  67. foundry_mcp/core/providers/detectors.py +515 -0
  68. foundry_mcp/core/providers/gemini.py +426 -0
  69. foundry_mcp/core/providers/opencode.py +718 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +308 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +857 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/research/__init__.py +68 -0
  78. foundry_mcp/core/research/memory.py +528 -0
  79. foundry_mcp/core/research/models.py +1234 -0
  80. foundry_mcp/core/research/providers/__init__.py +40 -0
  81. foundry_mcp/core/research/providers/base.py +242 -0
  82. foundry_mcp/core/research/providers/google.py +507 -0
  83. foundry_mcp/core/research/providers/perplexity.py +442 -0
  84. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  85. foundry_mcp/core/research/providers/tavily.py +383 -0
  86. foundry_mcp/core/research/workflows/__init__.py +25 -0
  87. foundry_mcp/core/research/workflows/base.py +298 -0
  88. foundry_mcp/core/research/workflows/chat.py +271 -0
  89. foundry_mcp/core/research/workflows/consensus.py +539 -0
  90. foundry_mcp/core/research/workflows/deep_research.py +4142 -0
  91. foundry_mcp/core/research/workflows/ideate.py +682 -0
  92. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  93. foundry_mcp/core/resilience.py +600 -0
  94. foundry_mcp/core/responses.py +1624 -0
  95. foundry_mcp/core/review.py +366 -0
  96. foundry_mcp/core/security.py +438 -0
  97. foundry_mcp/core/spec.py +4119 -0
  98. foundry_mcp/core/task.py +2463 -0
  99. foundry_mcp/core/testing.py +839 -0
  100. foundry_mcp/core/validation.py +2357 -0
  101. foundry_mcp/dashboard/__init__.py +32 -0
  102. foundry_mcp/dashboard/app.py +119 -0
  103. foundry_mcp/dashboard/components/__init__.py +17 -0
  104. foundry_mcp/dashboard/components/cards.py +88 -0
  105. foundry_mcp/dashboard/components/charts.py +177 -0
  106. foundry_mcp/dashboard/components/filters.py +136 -0
  107. foundry_mcp/dashboard/components/tables.py +195 -0
  108. foundry_mcp/dashboard/data/__init__.py +11 -0
  109. foundry_mcp/dashboard/data/stores.py +433 -0
  110. foundry_mcp/dashboard/launcher.py +300 -0
  111. foundry_mcp/dashboard/views/__init__.py +12 -0
  112. foundry_mcp/dashboard/views/errors.py +217 -0
  113. foundry_mcp/dashboard/views/metrics.py +164 -0
  114. foundry_mcp/dashboard/views/overview.py +96 -0
  115. foundry_mcp/dashboard/views/providers.py +83 -0
  116. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  117. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  118. foundry_mcp/prompts/__init__.py +9 -0
  119. foundry_mcp/prompts/workflows.py +525 -0
  120. foundry_mcp/resources/__init__.py +9 -0
  121. foundry_mcp/resources/specs.py +591 -0
  122. foundry_mcp/schemas/__init__.py +38 -0
  123. foundry_mcp/schemas/intake-schema.json +89 -0
  124. foundry_mcp/schemas/sdd-spec-schema.json +414 -0
  125. foundry_mcp/server.py +150 -0
  126. foundry_mcp/tools/__init__.py +10 -0
  127. foundry_mcp/tools/unified/__init__.py +92 -0
  128. foundry_mcp/tools/unified/authoring.py +3620 -0
  129. foundry_mcp/tools/unified/context_helpers.py +98 -0
  130. foundry_mcp/tools/unified/documentation_helpers.py +268 -0
  131. foundry_mcp/tools/unified/environment.py +1341 -0
  132. foundry_mcp/tools/unified/error.py +479 -0
  133. foundry_mcp/tools/unified/health.py +225 -0
  134. foundry_mcp/tools/unified/journal.py +841 -0
  135. foundry_mcp/tools/unified/lifecycle.py +640 -0
  136. foundry_mcp/tools/unified/metrics.py +777 -0
  137. foundry_mcp/tools/unified/plan.py +876 -0
  138. foundry_mcp/tools/unified/pr.py +294 -0
  139. foundry_mcp/tools/unified/provider.py +589 -0
  140. foundry_mcp/tools/unified/research.py +1283 -0
  141. foundry_mcp/tools/unified/review.py +1042 -0
  142. foundry_mcp/tools/unified/review_helpers.py +314 -0
  143. foundry_mcp/tools/unified/router.py +102 -0
  144. foundry_mcp/tools/unified/server.py +565 -0
  145. foundry_mcp/tools/unified/spec.py +1283 -0
  146. foundry_mcp/tools/unified/task.py +3846 -0
  147. foundry_mcp/tools/unified/test.py +431 -0
  148. foundry_mcp/tools/unified/verification.py +520 -0
  149. foundry_mcp-0.8.22.dist-info/METADATA +344 -0
  150. foundry_mcp-0.8.22.dist-info/RECORD +153 -0
  151. foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
  152. foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
  153. foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,271 @@
1
+ """CHAT workflow for single-model conversation with thread persistence.
2
+
3
+ Provides conversational interaction with context preservation across messages,
4
+ supporting thread creation, continuation, and message history management.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Optional
9
+
10
+ from foundry_mcp.config import ResearchConfig
11
+ from foundry_mcp.core.research.memory import ResearchMemory
12
+ from foundry_mcp.core.research.models import (
13
+ ConversationMessage,
14
+ ConversationThread,
15
+ ThreadStatus,
16
+ )
17
+ from foundry_mcp.core.research.workflows.base import ResearchWorkflowBase, WorkflowResult
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ChatWorkflow(ResearchWorkflowBase):
23
+ """Single-model conversation workflow with thread persistence.
24
+
25
+ Features:
26
+ - Create new conversation threads
27
+ - Continue existing threads with full context
28
+ - Token-aware context window management
29
+ - Message persistence across invocations
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ config: ResearchConfig,
35
+ memory: Optional[ResearchMemory] = None,
36
+ ) -> None:
37
+ """Initialize chat workflow.
38
+
39
+ Args:
40
+ config: Research configuration
41
+ memory: Optional memory instance
42
+ """
43
+ super().__init__(config, memory)
44
+
45
+ def execute(
46
+ self,
47
+ prompt: str,
48
+ thread_id: Optional[str] = None,
49
+ system_prompt: Optional[str] = None,
50
+ provider_id: Optional[str] = None,
51
+ model: Optional[str] = None,
52
+ temperature: Optional[float] = None,
53
+ max_tokens: Optional[int] = None,
54
+ title: Optional[str] = None,
55
+ **kwargs: Any,
56
+ ) -> WorkflowResult:
57
+ """Execute a chat turn.
58
+
59
+ Creates a new thread or continues an existing one, sends the prompt
60
+ to the provider, and persists the conversation.
61
+
62
+ Args:
63
+ prompt: User message
64
+ thread_id: Existing thread to continue (creates new if None)
65
+ system_prompt: System prompt (only used for new threads)
66
+ provider_id: Provider to use (uses config default if None)
67
+ model: Optional model override
68
+ temperature: Optional temperature setting
69
+ max_tokens: Optional max tokens
70
+ title: Optional title for new threads
71
+
72
+ Returns:
73
+ WorkflowResult with assistant response and thread metadata
74
+ """
75
+ # Get or create thread
76
+ thread = self._get_or_create_thread(
77
+ thread_id=thread_id,
78
+ system_prompt=system_prompt,
79
+ provider_id=provider_id,
80
+ title=title,
81
+ )
82
+
83
+ # Add user message
84
+ thread.add_message(role="user", content=prompt)
85
+
86
+ # Build context for provider
87
+ context = self._build_context(thread)
88
+
89
+ # Execute provider
90
+ result = self._execute_provider(
91
+ prompt=context,
92
+ provider_id=thread.provider_id or provider_id,
93
+ system_prompt=thread.system_prompt,
94
+ model=model,
95
+ temperature=temperature,
96
+ max_tokens=max_tokens,
97
+ )
98
+
99
+ if result.success:
100
+ # Add assistant message
101
+ thread.add_message(
102
+ role="assistant",
103
+ content=result.content,
104
+ provider_id=result.provider_id,
105
+ model_used=result.model_used,
106
+ tokens_used=result.tokens_used,
107
+ )
108
+
109
+ # Persist thread
110
+ self.memory.save_thread(thread)
111
+
112
+ # Add thread info to result metadata
113
+ result.metadata["thread_id"] = thread.id
114
+ result.metadata["message_count"] = len(thread.messages)
115
+ result.metadata["thread_title"] = thread.title
116
+
117
+ return result
118
+
119
+ def _get_or_create_thread(
120
+ self,
121
+ thread_id: Optional[str] = None,
122
+ system_prompt: Optional[str] = None,
123
+ provider_id: Optional[str] = None,
124
+ title: Optional[str] = None,
125
+ ) -> ConversationThread:
126
+ """Get existing thread or create a new one.
127
+
128
+ Args:
129
+ thread_id: Existing thread ID to load
130
+ system_prompt: System prompt for new threads
131
+ provider_id: Provider ID for new threads
132
+ title: Title for new threads
133
+
134
+ Returns:
135
+ ConversationThread instance
136
+ """
137
+ if thread_id:
138
+ thread = self.memory.load_thread(thread_id)
139
+ if thread:
140
+ return thread
141
+ logger.warning("Thread %s not found, creating new thread", thread_id)
142
+
143
+ # Create new thread
144
+ return ConversationThread(
145
+ title=title,
146
+ system_prompt=system_prompt,
147
+ provider_id=provider_id or self.config.default_provider,
148
+ )
149
+
150
+ def _build_context(self, thread: ConversationThread) -> str:
151
+ """Build conversation context for the provider.
152
+
153
+ Formats message history with token-aware truncation to fit
154
+ within context window limits.
155
+
156
+ Args:
157
+ thread: Conversation thread
158
+
159
+ Returns:
160
+ Formatted context string
161
+ """
162
+ # Get recent messages (respecting max_messages config)
163
+ messages = thread.get_context_messages(
164
+ max_messages=self.config.max_messages_per_thread
165
+ )
166
+
167
+ # Format messages for context
168
+ parts = []
169
+ for msg in messages:
170
+ role_label = "User" if msg.role == "user" else "Assistant"
171
+ parts.append(f"{role_label}: {msg.content}")
172
+
173
+ return "\n\n".join(parts)
174
+
175
+ def list_threads(
176
+ self,
177
+ status: Optional[ThreadStatus] = None,
178
+ limit: Optional[int] = 50,
179
+ ) -> list[dict[str, Any]]:
180
+ """List conversation threads.
181
+
182
+ Args:
183
+ status: Filter by thread status
184
+ limit: Maximum threads to return
185
+
186
+ Returns:
187
+ List of thread summaries
188
+ """
189
+ threads = self.memory.list_threads(status=status, limit=limit)
190
+
191
+ return [
192
+ {
193
+ "id": t.id,
194
+ "title": t.title,
195
+ "status": t.status.value,
196
+ "message_count": len(t.messages),
197
+ "created_at": t.created_at.isoformat(),
198
+ "updated_at": t.updated_at.isoformat(),
199
+ "provider_id": t.provider_id,
200
+ }
201
+ for t in threads
202
+ ]
203
+
204
+ def get_thread(self, thread_id: str) -> Optional[dict[str, Any]]:
205
+ """Get full thread details including messages.
206
+
207
+ Args:
208
+ thread_id: Thread identifier
209
+
210
+ Returns:
211
+ Thread data with messages or None if not found
212
+ """
213
+ thread = self.memory.load_thread(thread_id)
214
+ if not thread:
215
+ return None
216
+
217
+ return {
218
+ "id": thread.id,
219
+ "title": thread.title,
220
+ "status": thread.status.value,
221
+ "system_prompt": thread.system_prompt,
222
+ "provider_id": thread.provider_id,
223
+ "created_at": thread.created_at.isoformat(),
224
+ "updated_at": thread.updated_at.isoformat(),
225
+ "messages": [
226
+ {
227
+ "id": m.id,
228
+ "role": m.role,
229
+ "content": m.content,
230
+ "timestamp": m.timestamp.isoformat(),
231
+ "provider_id": m.provider_id,
232
+ "model_used": m.model_used,
233
+ "tokens_used": m.tokens_used,
234
+ }
235
+ for m in thread.messages
236
+ ],
237
+ "metadata": thread.metadata,
238
+ }
239
+
240
+ def delete_thread(self, thread_id: str) -> bool:
241
+ """Delete a conversation thread.
242
+
243
+ Args:
244
+ thread_id: Thread identifier
245
+
246
+ Returns:
247
+ True if deleted, False if not found
248
+ """
249
+ return self.memory.delete_thread(thread_id)
250
+
251
+ def update_thread_status(
252
+ self,
253
+ thread_id: str,
254
+ status: ThreadStatus,
255
+ ) -> bool:
256
+ """Update thread status.
257
+
258
+ Args:
259
+ thread_id: Thread identifier
260
+ status: New status
261
+
262
+ Returns:
263
+ True if updated, False if not found
264
+ """
265
+ thread = self.memory.load_thread(thread_id)
266
+ if not thread:
267
+ return False
268
+
269
+ thread.status = status
270
+ self.memory.save_thread(thread)
271
+ return True