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.
- foundry_mcp/__init__.py +13 -0
- foundry_mcp/cli/__init__.py +67 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +640 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +667 -0
- foundry_mcp/cli/commands/session.py +472 -0
- foundry_mcp/cli/commands/specs.py +686 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +298 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +1454 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1773 -0
- foundry_mcp/core/batch_operations.py +1202 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/journal.py +700 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1376 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +146 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +387 -0
- foundry_mcp/core/prometheus.py +564 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +691 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
- foundry_mcp/core/prompts/plan_review.py +627 -0
- foundry_mcp/core/providers/__init__.py +237 -0
- foundry_mcp/core/providers/base.py +515 -0
- foundry_mcp/core/providers/claude.py +472 -0
- foundry_mcp/core/providers/codex.py +637 -0
- foundry_mcp/core/providers/cursor_agent.py +630 -0
- foundry_mcp/core/providers/detectors.py +515 -0
- foundry_mcp/core/providers/gemini.py +426 -0
- foundry_mcp/core/providers/opencode.py +718 -0
- foundry_mcp/core/providers/opencode_wrapper.js +308 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +857 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1234 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4142 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +1624 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +4119 -0
- foundry_mcp/core/task.py +2463 -0
- foundry_mcp/core/testing.py +839 -0
- foundry_mcp/core/validation.py +2357 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +177 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +300 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +164 -0
- foundry_mcp/dashboard/views/overview.py +96 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +414 -0
- foundry_mcp/server.py +150 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +92 -0
- foundry_mcp/tools/unified/authoring.py +3620 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +268 -0
- foundry_mcp/tools/unified/environment.py +1341 -0
- foundry_mcp/tools/unified/error.py +479 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +640 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +876 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +589 -0
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +1042 -0
- foundry_mcp/tools/unified/review_helpers.py +314 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +565 -0
- foundry_mcp/tools/unified/spec.py +1283 -0
- foundry_mcp/tools/unified/task.py +3846 -0
- foundry_mcp/tools/unified/test.py +431 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.8.22.dist-info/METADATA +344 -0
- foundry_mcp-0.8.22.dist-info/RECORD +153 -0
- foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
- foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
- 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
|