Skip to content

Commit f6e8514

Browse files
committed
feat: Added an optional Alpha Vantage MCP.
chore: Made Ollama MCP optional. chore: Minor improvements and updated README.
1 parent ee985fd commit f6e8514

File tree

7 files changed

+67
-14
lines changed

7 files changed

+67
-14
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@ There are Dapr related configuration files too.
3838
- Dapr telemetry configuration at `.dapr/config.yaml`.
3939
- Dapr hot-swappable component configuration files at `.dapr/components/`.
4040

41-
The following environment variables are also relevant but not essential, except for `OLLAMA_API_KEY`.
42-
- `OLLAMA_API_KEY`: The Ollama API key is required for MCP-based web-search and cloud hosted models on Ollama.
41+
42+
43+
The following API keys are optional but maybe provided for additional functionality. If not provided, the corresponding functionality will not be available.
44+
45+
- `OLLAMA_API_KEY`: The Ollama API key is required for MCP-based web search, web fetch and cloud hosted models on Ollama. MCP features include 2 tools. Create one from [your Ollama account](https://ollama.com/settings/keys).
46+
- `ALPHAVANTAGE_API_KEY`: The API key to MCP-based Alpha Vantage finance related functions. MCP features include 118 tools. Obtain your [free API key from Alpha Vantage](https://www.alphavantage.co/support/#api-key). Note that basic finance tools are available through the [yfinance MCP](https://github.com/narumiruna/yfinance-mcp) even if Alpha Vantage MCP is not loaded.
47+
48+
The following environment variables are all optional.
4349
- `APP_LOG_LEVEL`: The general log level of the DQA app. Defaults to `INFO`.
4450
- `DQA_MCP_SERVER_TRANSPORT`, `FASTMCP_HOST` and `FASTMCP_PORT`: These specify the transport type, the host and port for the built-in MCP server of DQA. The default values are `stdio`, `localhost` and `8000` respectively.
4551
- `LLM_CONFIG_FILE` and `MCP_CONFIG_FILE`: These specify where the LLM and MCP configurations These default to `conf/llm.json` and `conf/mcp.json` respectively.
@@ -48,6 +54,7 @@ The following environment variables are also relevant but not essential, except
4854
- `BROWSER_STATE_CHAT_HISTORIES`: This is the key in browser state used by Gradio to store the chat histories (local values). The default value is `a2a_dapr_chat_histories`.
4955
- `APP_DAPR_SVC_HOST` and `APP_DAPR_SVC_PORT`: The host and port at which Dapr actor service will listen on. These default to `127.0.0.1` and `32768`. Should you change these, you must change the corresponding information in `dapr.yaml`.
5056
- `APP_DAPR_PUBSUB_STALE_MSG_SECS`: This specifies how old a message should be on the Dapr publish-subscribe topic queue before it will be considered too old, and dropped. The default value is 60 seconds.
57+
- `APP_DAPR_ACTOR_RETRY_ATTEMPTS`: This specifies the number of times an agent executor will try to invoke a method on an actor, if it fails to succeed. The default value is 3.
5158
- `DAPR_PUBSUB_NAME`: The configured name of the publish-subscribe component at `.dapr/components/pubsub.yaml`. Change this environment variable only if you change the corresponding pub-sub component configuration.
5259
- `APP_A2A_SRV_HOST` and `APP_MHQA_A2A_SRV_PORT`: The host and port at which A2A endpoint will be available. These default to `127.0.0.1` and `32770`. Should you change these, you must change the corresponding information in `dapr.yaml`.
5360
- `APP_MHQA_A2A_REMOTE_URL`: This environment variable can be used to specify the full remote URL including the protocol, i.e., `http` or `https` where the MHQA A2A endpoint is available. This is useful in a scenario where the web app is deployed on a machine that is different from where the MHQA A2A endpoint and Dapr service are. Default value is `None`.

src/dqa/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class ParsedEnvVars:
3333
APP_DAPR_PUBSUB_STALE_MSG_SECS: int = env.int(
3434
"APP_DAPR_PUBSUB_STALE_MSG_SECS", default=60
3535
)
36+
APP_DAPR_ACTOR_RETRY_ATTEMPTS: int = env.int(
37+
"APP_DAPR_ACTOR_RETRY_ATTEMPTS", default=3
38+
)
3639
APP_A2A_SRV_HOST: str = env.str("APP_A2A_SRV_HOST", default="127.0.0.1")
3740
APP_MHQA_A2A_SRV_PORT: int = env.int("APP_MHQA_A2A_SRV_PORT", default=32770)
3841
APP_MHQA_A2A_REMOTE_URL: str = env.str("APP_MHQA_A2A_REMOTE_URL", default=None)
@@ -47,6 +50,9 @@ class ParsedEnvVars:
4750
"BROWSER_STATE_CHAT_HISTORIES", default="a2a_dapr_chat_histories"
4851
)
4952

53+
API_KEY_ALPHAVANTAGE: str = env.str("ALPHAVANTAGE_API_KEY", default=None)
54+
API_KEY_OLLAMA: str = env.str("OLLAMA_API_KEY", default=None)
55+
5056
_instance: ClassVar = None
5157

5258
def __new__(cls: type["ParsedEnvVars"]) -> "ParsedEnvVars":

src/dqa/actor/mhqa.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,13 @@ async def _on_activate(self) -> None:
158158
logger.error(f"Error parsing MCP config. {e}")
159159
logger.exception(e)
160160

161+
# logger.info(
162+
# f"MCP tools available to {self.__class__.__name__} ({self.id}): {
163+
# ','.join([f.metadata.name for f in self.mcp_features])
164+
# }"
165+
# )
161166
logger.info(
162-
f"MCP tools available to {self.__class__.__name__} ({self.id}): {
163-
','.join([f.metadata.name for f in self.mcp_features])
164-
}"
167+
f"MCP tools available to {self.__class__.__name__} ({self.id}): {len(self.mcp_features)}"
165168
)
166169

167170
if not hasattr(self, "workflow"):

src/dqa/executor/mhqa.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
class MHQAAgentExecutor(AgentExecutor):
3535
def __init__(self):
3636
self._actor_mhqa = MHQAActor.__name__
37-
self._factory = ActorProxyFactory(retry_policy=RetryPolicy(max_attempts=1))
37+
self._factory = ActorProxyFactory(
38+
retry_policy=RetryPolicy(
39+
max_attempts=ParsedEnvVars().APP_DAPR_ACTOR_RETRY_ATTEMPTS
40+
)
41+
)
3842

3943
async def do_mhqa_respond(self, data: MHQAInput):
4044
# TODO: Potential memory leak without closing the streams?

src/dqa/mcp/primary.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import signal
33
import sys
44

5-
from fastmcp import FastMCP
5+
from fastmcp import FastMCP, Client
66
from dqa import ParsedEnvVars
77

88
from dqa.mcp.ollama import app as ollama_mcp
@@ -25,16 +25,36 @@ def server():
2525

2626
# See: https://gofastmcp.com/servers/composition
2727
# Live linking
28-
logger.info("Mounting Ollama web search and fetch MCP...")
29-
server.mount(ollama_mcp(), prefix="ollama")
30-
logger.info("Mounting Frankfurter currency conversion MCP...")
3128
server.mount(frankfurter_mcp(), prefix="currency")
32-
logger.info("Mounting DateTime MCP...")
29+
logger.info("Mounted Frankfurter currency conversion MCP.")
3330
server.mount(datetime_mcp(), prefix="datetime")
34-
logger.info("Mounting Basic Arithmetic MCP...")
31+
logger.info("Mounted DateTime MCP.")
3532
server.mount(basic_arithmetic_mcp(), prefix="arithmetic")
36-
logger.info("Mounting YFMCP...")
33+
logger.info("Mounted Basic Arithmetic MCP.")
3734
server.mount(yfmcp_mcp, prefix="yfmcp", as_proxy=True)
35+
logger.info("Mounted YFMCP.")
36+
37+
if ParsedEnvVars().API_KEY_ALPHAVANTAGE:
38+
# See: https://github.com/alphavantage/alpha_vantage_mcp
39+
alphavantage_remote_url = f"https://mcp.alphavantage.co/mcp?apikey={ParsedEnvVars().API_KEY_ALPHAVANTAGE}"
40+
alphavantage_remote_proxy = FastMCP.as_proxy(Client(alphavantage_remote_url))
41+
server.mount(alphavantage_remote_proxy, prefix="alphavantage")
42+
logger.info("Mounted AlphaVantage remote MCP.")
43+
logger.warning(
44+
"AlphaVantage MCP is a rate-limited remote service, expect higher latency."
45+
)
46+
else:
47+
logger.warning(
48+
"No AlphaVantage API key found in environment variable 'API_KEY_ALPHAVANTAGE'. Skipping mounting AlphaVantage remote MCP."
49+
)
50+
51+
if ParsedEnvVars().API_KEY_OLLAMA:
52+
server.mount(ollama_mcp(), prefix="ollama")
53+
logger.info("Mounted Ollama MCP.")
54+
else:
55+
logger.warning(
56+
"No Ollama API key found in environment variable 'API_KEY_OLLAMA'. Skipping mounting Ollama MCP."
57+
)
3858

3959
return server
4060

src/dqa/web/gradio.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ async def btn_echo_clicked(
391391
MHQAResponse(
392392
thread_id=selected_chat_id,
393393
user_input=user_query,
394+
agent_output="Attempting to find an answer, 🤔 please wait...",
394395
)
395396
)
396397

tests/test_primary_mcp.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,17 @@ def test_composition(self, mcp_client):
3434
"""
3535
Test that the primary MCP server has composed the necessary MCPs correctly.
3636
"""
37+
n_base_tools = 22 # Base tools from local MCPs
38+
n_ollama_tools = 2
39+
n_alphavantage_tools = 118
3740
tools = asyncio.run(self.list_tools(mcp_client))
38-
assert len(tools) == 22
41+
assert len(tools) in [
42+
# Base
43+
n_base_tools,
44+
# With Ollama only
45+
n_base_tools + n_ollama_tools,
46+
# With AlphaVantage only
47+
n_base_tools + n_alphavantage_tools,
48+
# With both Ollama and AlphaVantage
49+
n_base_tools + n_ollama_tools + n_alphavantage_tools,
50+
]

0 commit comments

Comments
 (0)