sokrates-mcp 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {sokrates_mcp-0.2.0/src/sokrates_mcp.egg-info → sokrates_mcp-0.3.0}/PKG-INFO +9 -2
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/README.md +8 -1
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/pyproject.toml +2 -2
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp/main.py +37 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp/mcp_config.py +88 -39
- sokrates_mcp-0.3.0/src/sokrates_mcp/utils.py +28 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp/workflow.py +53 -3
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0/src/sokrates_mcp.egg-info}/PKG-INFO +9 -2
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp.egg-info/SOURCES.txt +1 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/LICENSE +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/MANIFEST.in +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/config.yml.example +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/setup.cfg +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp/__init__.py +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp.egg-info/dependency_links.txt +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp.egg-info/entry_points.txt +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp.egg-info/requires.txt +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp.egg-info/top_level.txt +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp_client/__init__.py +0 -0
- {sokrates_mcp-0.2.0 → sokrates_mcp-0.3.0}/src/sokrates_mcp_client/mcp_client_example.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sokrates-mcp
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: A templated MCP server for demonstration and quick start.
|
5
5
|
Author-email: Julian Weber <julianweberdev@gmail.com>
|
6
6
|
License: MIT License
|
@@ -224,7 +224,7 @@ The server follows a modular design pattern:
|
|
224
224
|
### workflow.py
|
225
225
|
|
226
226
|
- **Workflow** class: Implements the business logic for prompt refinement and execution.
|
227
|
-
-
|
227
|
+
- e.g.:
|
228
228
|
- `refine_prompt`: Refines a given prompt
|
229
229
|
- `refine_and_execute_external_prompt`: Refines and executes a prompt with an external LLM
|
230
230
|
- `handover_prompt`: Hands over a prompt to an external LLM for processing
|
@@ -289,6 +289,13 @@ If you see "ModuleNotFoundError: fastmcp", ensure:
|
|
289
289
|
|
290
290
|
## Changelog
|
291
291
|
|
292
|
+
**0.3.0 (Aug 2025)**
|
293
|
+
- adds new tools:
|
294
|
+
- roll_dice
|
295
|
+
- read_from_file
|
296
|
+
- store_to_file
|
297
|
+
- refactorings - code quality - still ongoing
|
298
|
+
|
292
299
|
**0.2.0 (Aug 2025)**
|
293
300
|
- First published version
|
294
301
|
- Update to latest sokrates library version
|
@@ -186,7 +186,7 @@ The server follows a modular design pattern:
|
|
186
186
|
### workflow.py
|
187
187
|
|
188
188
|
- **Workflow** class: Implements the business logic for prompt refinement and execution.
|
189
|
-
-
|
189
|
+
- e.g.:
|
190
190
|
- `refine_prompt`: Refines a given prompt
|
191
191
|
- `refine_and_execute_external_prompt`: Refines and executes a prompt with an external LLM
|
192
192
|
- `handover_prompt`: Hands over a prompt to an external LLM for processing
|
@@ -251,6 +251,13 @@ If you see "ModuleNotFoundError: fastmcp", ensure:
|
|
251
251
|
|
252
252
|
## Changelog
|
253
253
|
|
254
|
+
**0.3.0 (Aug 2025)**
|
255
|
+
- adds new tools:
|
256
|
+
- roll_dice
|
257
|
+
- read_from_file
|
258
|
+
- store_to_file
|
259
|
+
- refactorings - code quality - still ongoing
|
260
|
+
|
254
261
|
**0.2.0 (Aug 2025)**
|
255
262
|
- First published version
|
256
263
|
- Update to latest sokrates library version
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "sokrates-mcp"
|
3
|
-
version = "0.
|
3
|
+
version = "0.3.0"
|
4
4
|
description = "A templated MCP server for demonstration and quick start."
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
@@ -41,4 +41,4 @@ explicit = true
|
|
41
41
|
name = "testpypi"
|
42
42
|
url = "https://test.pypi.org/simple/"
|
43
43
|
publish-url = "https://test.pypi.org/legacy/"
|
44
|
-
explicit = true
|
44
|
+
explicit = true
|
@@ -294,6 +294,43 @@ async def generate_code_review(
|
|
294
294
|
) -> str:
|
295
295
|
return await workflow.generate_code_review(ctx=ctx, provider=provider, model=model, review_type=review_type, source_directory=source_directory, source_file_paths=source_file_paths, target_directory=target_directory)
|
296
296
|
|
297
|
+
@mcp.tool(
|
298
|
+
name="read_from_file",
|
299
|
+
description="Read a file from the local disk at the given file path and return it's contents.",
|
300
|
+
tags={"file","read","load","local"}
|
301
|
+
)
|
302
|
+
async def read_from_file(
|
303
|
+
ctx: Context,
|
304
|
+
file_path: Annotated[str, Field(description="The source file path to use for reading the file. This should be an absolute file path on the disk.")],
|
305
|
+
) -> str:
|
306
|
+
return await workflow.read_from_file(ctx=ctx, file_path=file_path)
|
307
|
+
|
308
|
+
@mcp.tool(
|
309
|
+
name="store_to_file",
|
310
|
+
description="Store a file with the provided content to the local drive at the provided file path.",
|
311
|
+
tags={"file","store","save","local"}
|
312
|
+
)
|
313
|
+
async def store_to_file(
|
314
|
+
ctx: Context,
|
315
|
+
file_path: Annotated[str, Field(description="The target file path to use for storing the file. This should be an absolute file path on the disk.")],
|
316
|
+
file_content: Annotated[str, Field(description="The content that should be written to the target file.")],
|
317
|
+
) -> str:
|
318
|
+
return await workflow.store_to_file(ctx=ctx, file_path=file_path, file_content=file_content)
|
319
|
+
|
320
|
+
@mcp.tool(
|
321
|
+
name="roll_dice",
|
322
|
+
description="Rolls the given number of dice with the specified number of sides for the given number of times and returns the result. For example you can also instruct to throw a W12, which should then set the side_count to 12.",
|
323
|
+
tags={"dice","roll","random"}
|
324
|
+
)
|
325
|
+
async def roll_dice(
|
326
|
+
ctx: Context,
|
327
|
+
number_of_dice: Annotated[int, Field(description="The number of dice to to use for rolling.", default=1)],
|
328
|
+
side_count: Annotated[int, Field(description="The number of sides of the dice to use for rolling.", default=6)],
|
329
|
+
number_of_rolls: Annotated[int, Field(description="The count of dice rolls to execute.", default=1)]
|
330
|
+
) -> str:
|
331
|
+
return await workflow.roll_dice(ctx=ctx, number_of_dice=number_of_dice, side_count=side_count, number_of_rolls=number_of_rolls)
|
332
|
+
|
333
|
+
|
297
334
|
@mcp.tool(
|
298
335
|
name="list_available_models_for_provider",
|
299
336
|
description="Lists all available large language models and the target api endpoint configured as provider for the sokrates-mcp server.",
|
@@ -18,6 +18,7 @@ import logging
|
|
18
18
|
from urllib.parse import urlparse
|
19
19
|
from pathlib import Path
|
20
20
|
from sokrates import Config
|
21
|
+
from typing import Dict, List, Optional, Any
|
21
22
|
|
22
23
|
DEFAULT_API_ENDPOINT = "http://localhost:1234/v1"
|
23
24
|
DEFAULT_API_KEY = "mykey"
|
@@ -53,7 +54,7 @@ class MCPConfig:
|
|
53
54
|
"openai"
|
54
55
|
]
|
55
56
|
|
56
|
-
def __init__(self, config_file_path=CONFIG_FILE_PATH, api_endpoint = DEFAULT_API_ENDPOINT, api_key = DEFAULT_API_KEY, model= DEFAULT_MODEL, verbose=False):
|
57
|
+
def __init__(self, config_file_path: str = CONFIG_FILE_PATH, api_endpoint: str = DEFAULT_API_ENDPOINT, api_key: str = DEFAULT_API_KEY, model: str = DEFAULT_MODEL, verbose: bool = False):
|
57
58
|
"""Initialize MCP configuration.
|
58
59
|
|
59
60
|
Args:
|
@@ -64,15 +65,15 @@ class MCPConfig:
|
|
64
65
|
model (str): Model name to use. Defaults to DEFAULT_MODEL.
|
65
66
|
verbose (bool): Enable verbose logging. Defaults to False.
|
66
67
|
|
67
|
-
Returns:
|
68
|
-
None
|
69
|
-
|
70
68
|
Side Effects:
|
71
69
|
Initializes instance attributes with values from config file or defaults
|
72
70
|
Sets up logging based on verbose parameter
|
73
71
|
"""
|
74
72
|
self.logger = logging.getLogger(__name__)
|
75
73
|
self.config_file_path = config_file_path
|
74
|
+
# Validate config file path
|
75
|
+
if not self._validate_config_file_path(config_file_path):
|
76
|
+
raise ValueError(f"Invalid config file path: {config_file_path}")
|
76
77
|
config_data = self._load_config_from_file(self.config_file_path)
|
77
78
|
|
78
79
|
prompts_directory = config_data.get("prompts_directory", self.DEFAULT_PROMPTS_DIRECTORY)
|
@@ -80,14 +81,13 @@ class MCPConfig:
|
|
80
81
|
raise ValueError(f"Invalid prompts directory: {prompts_directory}")
|
81
82
|
self.prompts_directory = prompts_directory
|
82
83
|
|
84
|
+
# Validate prompt files using helper method
|
83
85
|
refinement_prompt_filename = config_data.get("refinement_prompt_filename", self.DEFAULT_REFINEMENT_PROMPT_FILENAME)
|
84
|
-
|
85
|
-
raise FileNotFoundError(f"Refinement prompt file not found: {refinement_prompt_filename}")
|
86
|
+
self._validate_prompt_file_exists(prompts_directory, refinement_prompt_filename)
|
86
87
|
self.refinement_prompt_filename = refinement_prompt_filename
|
87
88
|
|
88
89
|
refinement_coding_prompt_filename = config_data.get("refinement_coding_prompt_filename", self.DEFAULT_REFINEMENT_CODING_PROMPT_FILENAME)
|
89
|
-
|
90
|
-
raise FileNotFoundError(f"Refinement coding prompt file not found: {refinement_coding_prompt_filename}")
|
90
|
+
self._validate_prompt_file_exists(prompts_directory, refinement_coding_prompt_filename)
|
91
91
|
self.refinement_coding_prompt_filename = refinement_coding_prompt_filename
|
92
92
|
|
93
93
|
|
@@ -98,25 +98,74 @@ class MCPConfig:
|
|
98
98
|
self.logger.info(f" Refinement Coding Prompt Filename: {self.refinement_coding_prompt_filename}")
|
99
99
|
self.logger.info(f" Default Provider: {self.default_provider}")
|
100
100
|
for prov in self.providers:
|
101
|
-
self.logger.info(f"Configured provider name: {prov[
|
101
|
+
self.logger.info(f"Configured provider name: {prov['name']} , api_endpoint: {prov['api_endpoint']} , default_model: {prov['default_model']}")
|
102
|
+
|
103
|
+
def _validate_prompt_file_exists(self, prompts_directory: str, filename: str) -> None:
|
104
|
+
"""Validate that a prompt file exists in the specified directory.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
prompts_directory (str): Directory where prompt files are located
|
108
|
+
filename (str): Name of the prompt file to check
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
FileNotFoundError: If the prompt file does not exist
|
112
|
+
"""
|
113
|
+
if not os.path.exists(os.path.join(prompts_directory, filename)):
|
114
|
+
raise FileNotFoundError(f"Prompt file not found: {filename}")
|
115
|
+
|
116
|
+
def _validate_config_file_path(self, config_file_path: str) -> bool:
|
117
|
+
"""Validate that the configuration file path is valid and accessible.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
config_file_path (str): Path to the configuration file
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
bool: True if path is valid and accessible, False otherwise
|
124
|
+
"""
|
125
|
+
try:
|
126
|
+
# Check if we can write to the directory
|
127
|
+
dir_path = os.path.dirname(config_file_path) or "."
|
128
|
+
if not os.path.exists(dir_path):
|
129
|
+
os.makedirs(dir_path, exist_ok=True)
|
130
|
+
# Test that we can actually access the file path
|
131
|
+
Path(config_file_path).touch(exist_ok=True)
|
132
|
+
return True
|
133
|
+
except (OSError, IOError):
|
134
|
+
return False
|
102
135
|
|
103
|
-
def available_providers(self):
|
104
|
-
return
|
136
|
+
def available_providers(self) -> List[Dict[str, Any]]:
|
137
|
+
return [{'name': p['name'], 'api_endpoint': p['api_endpoint'], 'type': p['type']} for p in self.providers]
|
105
138
|
|
106
|
-
def get_provider_by_name(self, provider_name):
|
107
|
-
|
108
|
-
|
139
|
+
def get_provider_by_name(self, provider_name: str) -> Dict[str, Any]:
|
140
|
+
"""Get a provider by its name.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
provider_name (str): Name of the provider to find
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
dict: Provider configuration dictionary
|
147
|
+
|
148
|
+
Raises:
|
149
|
+
IndexError: If no provider with the given name is found
|
150
|
+
"""
|
151
|
+
for provider in self.providers:
|
152
|
+
if provider['name'] == provider_name:
|
153
|
+
return provider
|
154
|
+
raise IndexError(f"Provider '{provider_name}' not found")
|
109
155
|
|
110
|
-
def get_default_provider(self):
|
156
|
+
def get_default_provider(self) -> Dict[str, Any]:
|
111
157
|
return self.get_provider_by_name(self.default_provider)
|
112
158
|
|
113
|
-
def _configure_providers(self, config_data):
|
159
|
+
def _configure_providers(self, config_data: Dict[str, Any]) -> None:
|
114
160
|
# configure defaults if not config_data could be loaded
|
115
|
-
|
161
|
+
providers = config_data.get("providers", [])
|
162
|
+
if not isinstance(providers, list):
|
163
|
+
raise ValueError("'providers' must be a list in the configuration file")
|
164
|
+
self.providers = providers
|
116
165
|
if len(self.providers) < 1:
|
117
|
-
|
118
|
-
|
119
|
-
]
|
166
|
+
# Validate defaults before use
|
167
|
+
self._validate_provider(DEFAULT_PROVIDER_CONFIGURATION)
|
168
|
+
self.providers = [DEFAULT_PROVIDER_CONFIGURATION]
|
120
169
|
self.default_provider = DEFAULT_PROVIDER_NAME
|
121
170
|
return
|
122
171
|
|
@@ -127,41 +176,42 @@ class MCPConfig:
|
|
127
176
|
self._validate_provider(provider)
|
128
177
|
provider_names.append(provider['name'])
|
129
178
|
|
130
|
-
if not config_data
|
179
|
+
if not config_data.get('default_provider'):
|
131
180
|
raise ValueError(f"No default_provider was configured at the root level of the config file in {self.config_file_path}")
|
132
181
|
self.default_provider = config_data['default_provider']
|
133
182
|
|
134
|
-
def _validate_provider(self, provider):
|
183
|
+
def _validate_provider(self, provider: Dict[str, Any]) -> None:
|
135
184
|
self._validate_provider_name(provider.get("name", ""))
|
136
185
|
self._validate_provider_type(provider.get("type", ""))
|
137
186
|
self._validate_url(provider.get("api_endpoint", ""))
|
138
187
|
self._validate_api_key(provider.get("api_key", ""))
|
139
188
|
self._validate_model_name(provider.get("default_model", ""))
|
140
189
|
|
141
|
-
def _validate_provider_name(self, provider_name):
|
190
|
+
def _validate_provider_name(self, provider_name: str) -> None:
|
142
191
|
if len(provider_name) < 1:
|
143
192
|
raise ValueError(f"The provider name: {provider_name} is not a valid provider name")
|
144
193
|
|
145
|
-
def _validate_provider_type(self, provider_type):
|
194
|
+
def _validate_provider_type(self, provider_type: str) -> None:
|
146
195
|
if not provider_type in self.PROVIDER_TYPES:
|
147
196
|
raise ValueError(f"The provider type: {provider_type} is not supported by sokrates-mcp")
|
148
197
|
|
149
|
-
def _validate_url(self, url):
|
198
|
+
def _validate_url(self, url: str) -> None:
|
150
199
|
"""Validate URL format.
|
151
200
|
|
152
201
|
Args:
|
153
202
|
url (str): URL to validate
|
154
203
|
|
155
|
-
|
156
|
-
|
204
|
+
Raises:
|
205
|
+
ValueError: If the URL is invalid
|
157
206
|
"""
|
158
207
|
try:
|
159
208
|
result = urlparse(url)
|
160
|
-
|
161
|
-
|
162
|
-
|
209
|
+
if not (result.scheme in ['http', 'https'] and result.netloc):
|
210
|
+
raise ValueError(f"Invalid API endpoint: {url}")
|
211
|
+
except Exception as e:
|
212
|
+
raise ValueError(f"Invalid API endpoint format: {url}") from e
|
163
213
|
|
164
|
-
def _validate_api_key(self, api_key):
|
214
|
+
def _validate_api_key(self, api_key: str) -> None:
|
165
215
|
"""Validate API key format.
|
166
216
|
|
167
217
|
Args:
|
@@ -173,7 +223,7 @@ class MCPConfig:
|
|
173
223
|
if len(api_key) < 1:
|
174
224
|
raise ValueError("The api key is empty")
|
175
225
|
|
176
|
-
def _validate_model_name(self, model):
|
226
|
+
def _validate_model_name(self, model: str) -> None:
|
177
227
|
"""Validate model name format.
|
178
228
|
|
179
229
|
Args:
|
@@ -185,7 +235,7 @@ class MCPConfig:
|
|
185
235
|
if len(model) < 1:
|
186
236
|
raise ValueError("The model is empty")
|
187
237
|
|
188
|
-
def _ensure_directory_exists(self, directory_path):
|
238
|
+
def _ensure_directory_exists(self, directory_path: str) -> bool:
|
189
239
|
"""Ensure directory exists and is valid.
|
190
240
|
|
191
241
|
Args:
|
@@ -203,7 +253,7 @@ class MCPConfig:
|
|
203
253
|
self.logger.error(f"Error ensuring directory exists: {e}")
|
204
254
|
return False
|
205
255
|
|
206
|
-
def _load_config_from_file(self, config_file_path):
|
256
|
+
def _load_config_from_file(self, config_file_path: str) -> Dict[str, Any]:
|
207
257
|
"""Load configuration data from a YAML file.
|
208
258
|
|
209
259
|
Args:
|
@@ -224,13 +274,12 @@ class MCPConfig:
|
|
224
274
|
with open(config_file_path, 'r') as f:
|
225
275
|
return yaml.safe_load(f) or {}
|
226
276
|
else:
|
227
|
-
self.logger.warning(f"Config file not found at {config_file_path}. Using defaults.")
|
228
|
-
# Create empty config file
|
229
|
-
with open(config_file_path, 'w') as f:
|
230
|
-
yaml.dump({}, f)
|
277
|
+
self.logger.warning(f"Config file not found at {config_file_path}. Using defaults (no config created).")
|
231
278
|
return {}
|
232
279
|
except yaml.YAMLError as e:
|
233
280
|
self.logger.error(f"Error parsing YAML config file {config_file_path}: {e}")
|
281
|
+
except OSError as e:
|
282
|
+
self.logger.error(f"OS error reading config file {config_file_path}: {e}")
|
234
283
|
except Exception as e:
|
235
|
-
self.logger.error(f"
|
284
|
+
self.logger.error(f"Unexpected error reading config file {config_file_path}: {e}")
|
236
285
|
return {}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import secrets
|
2
|
+
|
3
|
+
class Utils:
|
4
|
+
|
5
|
+
@staticmethod
|
6
|
+
def rand_int_inclusive(min_val: int, max_val: int) -> int:
|
7
|
+
"""
|
8
|
+
Return a random integer N such that min_val <= N <= max_val.
|
9
|
+
Uses `secrets.randbelow` which is cryptographically secure.
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
min_val : int
|
14
|
+
Lower bound (inclusive).
|
15
|
+
max_val : int
|
16
|
+
Upper bound (inclusive).
|
17
|
+
|
18
|
+
Returns
|
19
|
+
-------
|
20
|
+
int
|
21
|
+
Random integer in the specified range.
|
22
|
+
"""
|
23
|
+
if min_val > max_val:
|
24
|
+
raise ValueError("min_val must be <= max_val")
|
25
|
+
|
26
|
+
# randbelow(n) returns 0 .. n-1. We need a window of size (max-min+1)
|
27
|
+
range_size = max_val - min_val + 1
|
28
|
+
return secrets.randbelow(range_size) + min_val
|
@@ -1,9 +1,14 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import List
|
3
|
+
|
1
4
|
from fastmcp import Context
|
2
|
-
|
5
|
+
|
3
6
|
from sokrates import FileHelper, RefinementWorkflow, LLMApi, PromptRefiner, IdeaGenerationWorkflow
|
4
7
|
from sokrates.coding.code_review_workflow import run_code_review
|
5
|
-
|
6
|
-
from
|
8
|
+
|
9
|
+
from .mcp_config import MCPConfig
|
10
|
+
from .utils import Utils
|
11
|
+
|
7
12
|
class Workflow:
|
8
13
|
|
9
14
|
WORKFLOW_COMPLETION_MESSAGE = "Workflow completed."
|
@@ -290,4 +295,49 @@ class Workflow:
|
|
290
295
|
model_list = "\n".join([f"- {model}" for model in models])
|
291
296
|
result = f"{api_headline}\n# List of available models\n{model_list}"
|
292
297
|
await ctx.info(self.WORKFLOW_COMPLETION_MESSAGE)
|
298
|
+
return result
|
299
|
+
|
300
|
+
async def store_to_file(self, ctx: Context, file_path: str, file_content: str) -> str:
|
301
|
+
"""Store the provided content to a file on disk
|
302
|
+
|
303
|
+
"""
|
304
|
+
await ctx.info(f"Storing file to: {file_path} ...")
|
305
|
+
if not file_path:
|
306
|
+
raise ValueError("No file_path provided.")
|
307
|
+
if not file_content:
|
308
|
+
raise ValueError("No file_content provided.")
|
309
|
+
|
310
|
+
FileHelper.write_to_file(file_path=file_path, content=file_content)
|
311
|
+
|
312
|
+
result = f"The file has been stored to {file_path}"
|
313
|
+
await ctx.info(self.WORKFLOW_COMPLETION_MESSAGE)
|
314
|
+
return result
|
315
|
+
|
316
|
+
async def read_from_file(self, ctx: Context, file_path: str) -> str:
|
317
|
+
"""Read content of the provided file path
|
318
|
+
|
319
|
+
"""
|
320
|
+
await ctx.info(f"Reading file from: {file_path} ...")
|
321
|
+
if not file_path:
|
322
|
+
raise ValueError("No file_path provided.")
|
323
|
+
if not Path(file_path).is_file():
|
324
|
+
raise ValueError("No file exists at the given file path.")
|
325
|
+
|
326
|
+
content = FileHelper.read_file(file_path=file_path)
|
327
|
+
result = f"<file source_file_path='{file_path}'>\n{content}\n</file>"
|
328
|
+
await ctx.info(self.WORKFLOW_COMPLETION_MESSAGE)
|
329
|
+
return result
|
330
|
+
|
331
|
+
async def roll_dice(self, ctx: Context, number_of_dice: int=1, side_count: int=6, number_of_rolls: int=1) -> str:
|
332
|
+
"""Roll a dice with the provided number of sides and return the result
|
333
|
+
|
334
|
+
"""
|
335
|
+
await ctx.info(f"Throwing {number_of_dice} dice with {side_count} sides {number_of_rolls} times ...")
|
336
|
+
result = ""
|
337
|
+
for throw_number in range(1,number_of_rolls):
|
338
|
+
result = f"{result}# Roll {throw_number}\n"
|
339
|
+
for dice_number in range(1, number_of_dice):
|
340
|
+
dice_result = Utils.rand_int_inclusive(1, side_count)
|
341
|
+
result = f"- Dice {dice_number} result: {dice_result}\n"
|
342
|
+
await ctx.info(self.WORKFLOW_COMPLETION_MESSAGE)
|
293
343
|
return result
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sokrates-mcp
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: A templated MCP server for demonstration and quick start.
|
5
5
|
Author-email: Julian Weber <julianweberdev@gmail.com>
|
6
6
|
License: MIT License
|
@@ -224,7 +224,7 @@ The server follows a modular design pattern:
|
|
224
224
|
### workflow.py
|
225
225
|
|
226
226
|
- **Workflow** class: Implements the business logic for prompt refinement and execution.
|
227
|
-
-
|
227
|
+
- e.g.:
|
228
228
|
- `refine_prompt`: Refines a given prompt
|
229
229
|
- `refine_and_execute_external_prompt`: Refines and executes a prompt with an external LLM
|
230
230
|
- `handover_prompt`: Hands over a prompt to an external LLM for processing
|
@@ -289,6 +289,13 @@ If you see "ModuleNotFoundError: fastmcp", ensure:
|
|
289
289
|
|
290
290
|
## Changelog
|
291
291
|
|
292
|
+
**0.3.0 (Aug 2025)**
|
293
|
+
- adds new tools:
|
294
|
+
- roll_dice
|
295
|
+
- read_from_file
|
296
|
+
- store_to_file
|
297
|
+
- refactorings - code quality - still ongoing
|
298
|
+
|
292
299
|
**0.2.0 (Aug 2025)**
|
293
300
|
- First published version
|
294
301
|
- Update to latest sokrates library version
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|