From 2bae7ef74649607de88fc6ad81e7346d13b78d42 Mon Sep 17 00:00:00 2001 From: Esther-MacMini Date: Sat, 12 Sep 2026 18:53:03 -0400 Subject: [PATCH 1/2] Add ParlayAPI read-only MCP integration guide --- integrations/parlayapi-mcp.md | 156 ++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 integrations/parlayapi-mcp.md diff --git a/integrations/parlayapi-mcp.md b/integrations/parlayapi-mcp.md new file mode 100644 index 00000000..7fe2b657 --- /dev/null +++ b/integrations/parlayapi-mcp.md @@ -0,0 +1,156 @@ +--- +layout: integration +name: ParlayAPI MCP +description: "Sports, sportsbook coverage and private odds research through ParlayAPI's MCP server." +authors: + - name: ParlayAPI + socials: + github: JacobiusMakes +pypi: https://pypi.org/project/parlayapi-mcp/ +repo: https://github.com/JacobiusMakes/parlay-api-mcp +type: Tool Integration +report_issue: https://github.com/JacobiusMakes/parlay-api-mcp/issues +version: Haystack 2.0 +toc: true +mcp: true +--- + +## Overview + +[ParlayAPI](https://parlay-api.com/docs) provides sports-data tools through a +published MCP server. Discover sport keys and event counts, inspect sportsbook +coverage metadata, or connect private account data to a Haystack workflow. +This integration uses `mcp-haystack`'s `MCPToolset` without a separate wrapper. +The examples expose only selected read-only tools, excluding signup, email, +billing and preference changes. + +## Installation + +Use Python 3.10 or newer in a private virtual environment: + +```bash +pip install "haystack-ai==2.31.0" "mcp-haystack==1.5.1" "parlayapi-mcp==0.3.7" +``` + +## Discover tools without an account or model + +Save this complete example as `parlayapi_discovery.py` and run it with Python. +It starts the installed server locally, lists only the allowed tool names, and +closes the connection. It calls no ParlayAPI endpoint or model, even if account +keys already exist in your environment. Haystack telemetry is disabled. + +```python +import os +import sys + + +def make_toolset(): + os.environ["HAYSTACK_TELEMETRY_ENABLED"] = "false" + from haystack_integrations.tools.mcp import MCPToolset, StdioServerInfo + + allowed = [ + "parlayapi_live_sports", + "parlayapi_source_quality", + "parlayapi_book_coverage", + ] + server = StdioServerInfo( + command=sys.executable, + args=["-m", "parlayapi_mcp"], + env={ + "PARLAYAPI_BASE_URL": "https://parlay-api.com", + "PARLAYAPI_KEY": "", + "PARLAY_API_KEY": "", + }, + max_retries=0, + ) + return MCPToolset( + server_info=server, + tool_names=allowed, + connection_timeout=20, + invocation_timeout=20, + ) + + +if __name__ == "__main__": + toolset = make_toolset() + try: + toolset.warm_up() + print( + "Available read-only tools:", + ", ".join(sorted(tool.name for tool in toolset.tools)), + ) + print("Discovery only: no API data request or model call was made.") + finally: + toolset.close() +``` + +## Read public sport counts with one explicit call + +After saving the first example, save the following alongside it and run it when +you want current public metadata. It makes one no-key call, prints only sport +keys and reported event counts, and closes the server. No model is involved. + +```python +import json + +from parlayapi_discovery import make_toolset + +toolset = make_toolset() +try: + toolset.warm_up() + tool = next(tool for tool in toolset.tools if tool.name == "parlayapi_live_sports") + result = json.loads(tool.invoke()) + if result.get("isError") is not False or not isinstance( + result.get("content"), list + ): + raise RuntimeError("Public metadata request failed") + rows = [] + for block in result["content"]: + if block.get("type") != "text": + raise RuntimeError("Unexpected metadata content") + row = json.loads(block["text"]) + if not isinstance(row, dict) or not isinstance(row.get("key"), str): + raise RuntimeError("Unexpected sport metadata") + if type(row.get("event_count")) is not int or row["event_count"] < 0: + raise RuntimeError("Unknown event count") + rows.append((row["key"], row["event_count"])) + for sport, count in rows: + print(f"{sport}: {count} reported events") +finally: + toolset.close() +``` + +The public metadata call was tested through the published MCP server. Counts +describe that endpoint's response, not exhaustive coverage or price freshness. +Discovery alone does not validate API access. Tested versions: Python 3.12, +Haystack 2.31.0, `mcp-haystack` 1.5.1 and `parlayapi-mcp` 0.3.7. + +## Use the toolset in private research + +Once you choose to make API calls, use the initialized toolset with a Haystack +[Agent](https://docs.haystack.deepset.ai/docs/agent) or +[ToolInvoker](https://docs.haystack.deepset.ai/docs/toolinvoker) before closing it. +Each tool exposes its input schema; only invoke the specific operation your +workflow needs. + +For account data, create your own [account](https://parlay-api.com/signup), store +its key privately, and explicitly configure `Secret.from_env_var("PARLAYAPI_KEY")` +from `haystack.utils` as the server's `PARLAYAPI_KEY` value. Keep the alternate +`PARLAY_API_KEY` value empty. Add only the data tools you need, such as +`parlayapi_list_sports` and `parlayapi_get_odds`, to `tool_names`; never expose the +whole server by omitting the allowlist. Account calls consume the applicable +[plan allowance](https://parlay-api.com/pricing). + +Keep credentials out of tool arguments, prompts and source code. Keep account +results and pipeline storage private. Connecting a model may send raw tool +results to that provider; choose one appropriate for your data-use agreement. +Review the allowlist when upgrading the server. This example does not place bets. + +## License + +The [ParlayAPI MCP server](https://github.com/JacobiusMakes/parlay-api-mcp) is MIT +licensed. `mcp-haystack` uses the +[Apache-2.0 license](https://github.com/deepset-ai/haystack-core-integrations/blob/main/LICENSE). +Software licensing is separate from [API service terms](https://parlay-api.com/terms). +An API account does not grant public odds redisplay, redistribution or white-label +data rights. From 2f4bdd6847eff36810db7ae571870a92de71dd42 Mon Sep 17 00:00:00 2001 From: Esther-MacMini Date: Thu, 17 Sep 2026 14:30:33 -0400 Subject: [PATCH 2/2] docs: show ParlayAPI metadata tools inside a Haystack Agent --- integrations/parlayapi-mcp.md | 72 ++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/integrations/parlayapi-mcp.md b/integrations/parlayapi-mcp.md index 7fe2b657..02e6f0a1 100644 --- a/integrations/parlayapi-mcp.md +++ b/integrations/parlayapi-mcp.md @@ -44,11 +44,11 @@ import os import sys -def make_toolset(): +def make_toolset(tool_names=None): os.environ["HAYSTACK_TELEMETRY_ENABLED"] = "false" from haystack_integrations.tools.mcp import MCPToolset, StdioServerInfo - allowed = [ + allowed = tool_names if tool_names is not None else [ "parlayapi_live_sports", "parlayapi_source_quality", "parlayapi_book_coverage", @@ -60,6 +60,7 @@ def make_toolset(): "PARLAYAPI_BASE_URL": "https://parlay-api.com", "PARLAYAPI_KEY": "", "PARLAY_API_KEY": "", + "OPENAI_API_KEY": "", }, max_retries=0, ) @@ -84,7 +85,69 @@ if __name__ == "__main__": toolset.close() ``` -## Read public sport counts with one explicit call +## Use public sport metadata in a Haystack agent + +Save this example as `parlayapi_agent.py` alongside `parlayapi_discovery.py`. +Set `OPENAI_API_KEY` in your local environment, then run `python parlayapi_agent.py`. +The public sport metadata tool needs no ParlayAPI key. The model requires an +OpenAI API key and may incur usage charges; its key is not passed to the MCP +subprocess. + +The [Agent](https://docs.haystack.deepset.ai/docs/2.31/agent) receives an +`MCPToolset` containing only `parlayapi_live_sports`. It can request the metadata, +read the tool result and produce a short answer. The step limit bounds the loop, +and `finally` closes the MCP connection even if the model or tool fails. + +```python +import os + +os.environ["HAYSTACK_TELEMETRY_ENABLED"] = "false" + +from haystack.components.agents import Agent +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.dataclasses import ChatMessage +from haystack.utils import Secret + +from parlayapi_discovery import make_toolset + +toolset = make_toolset(tool_names=["parlayapi_live_sports"]) +try: + agent = Agent( + chat_generator=OpenAIChatGenerator( + model="gpt-5-mini", + api_key=Secret.from_env_var("OPENAI_API_KEY"), + timeout=30, + max_retries=0, + ), + tools=toolset, + system_prompt=( + "Use parlayapi_live_sports once to answer the question. " + "Report only sport keys and their reported event_count values, " + "for at most five sports. If the tool fails or returns no sports, " + "say so; do not invent counts. These counts do not establish " + "exhaustive coverage or odds freshness." + ), + exit_conditions=["text"], + max_agent_steps=3, + raise_on_tool_invocation_failure=True, + ) + agent.warm_up() + result = agent.run( + messages=[ChatMessage.from_user("Which sports currently report events?")] + ) + answer = result["last_message"].text + if not answer: + raise RuntimeError("Agent stopped before returning a text answer") + print(answer) +finally: + toolset.close() +``` + +The agent flow was checked locally with a deterministic test generator and +mocked metadata through the installed MCP server. No paid model call was made +during that check; a live model's tool choices and wording may vary. + +## Read public sport counts without a model After saving the first example, save the following alongside it and run it when you want current public metadata. It makes one no-key call, prints only sport @@ -127,8 +190,7 @@ Haystack 2.31.0, `mcp-haystack` 1.5.1 and `parlayapi-mcp` 0.3.7. ## Use the toolset in private research -Once you choose to make API calls, use the initialized toolset with a Haystack -[Agent](https://docs.haystack.deepset.ai/docs/agent) or +Adapt the agent above or use the initialized toolset with a Haystack [ToolInvoker](https://docs.haystack.deepset.ai/docs/toolinvoker) before closing it. Each tool exposes its input schema; only invoke the specific operation your workflow needs.