Skip to content

Add tempguru event staffing tool plugin#3252

Open
kissmyabs32 wants to merge 4 commits into
langgenius:mainfrom
kissmyabs32:add-tempguru-event-staffing
Open

Add tempguru event staffing tool plugin#3252
kissmyabs32 wants to merge 4 commits into
langgenius:mainfrom
kissmyabs32:add-tempguru-event-staffing

Conversation

@kissmyabs32
Copy link
Copy Markdown

Summary

Adds the tempguru tool plugin under tools/tempguru/. Wraps the public TempGuru MCP REST API as 5 read-only Dify tools so any Dify agent can answer event-staffing questions (coverage, rates, lead times, state compliance) for events across 300+ US and Canadian markets — no authentication, no API key, no setup.

Matches the no-auth precedent set by duckduckgo and wikipedia in the same plugins directory.

What it does

Tool Returns
get_cities Cities TempGuru staffs, with tier classification. Filter by state or tier.
get_roles Event staffing roles with descriptions and skill tiers.
check_availability Lead-time guidance for a city + date. Not real-time inventory.
get_role_pricing All-inclusive hourly rate range for a role in a city (W-2 wages, workers comp, payroll taxes, general liability all included).
get_compliance_by_state US state-level minimum wage, overtime rules, compliance summary. Not legal advice.

Each tool is a ~20-line Python class that makes one HTTPS GET to mcp.tempguru.co/api/v1/<endpoint> and yields the response JSON back to Dify. No business logic reimplemented; the TempGuru MCP REST mirror is the canonical source.

Trust profile

  • Read-only — every tool wraps a GET. No POST / PUT / DELETE exists in the plugin or the underlying API.
  • No authentication — public data only. No API keys, OAuth, credentials.
  • No data collection — the plugin does not persist user inputs, queries, or outputs.
  • No executable scripts — only the standard Dify plugin entrypoint.
  • Same data as the public site — every response comes from TempGuru's published rate cards, coverage maps, and compliance summaries at https://tempguru.co.

Bilingual labels

All label, human_description, and description.human fields ship in both en_US and zh_Hans (Simplified Chinese), matching the convention of the highest-quality existing plugins. TempGuru already serves a Chinese-facing agent page at https://tempguru.co/zh-cn/ai.

Already shipped on adjacent surfaces

Documentation

License

MIT

Wraps the public TempGuru MCP REST API as 5 read-only Dify tools:
  - get_cities                : list cities by state/tier
  - get_roles                 : list event staffing roles
  - check_availability        : lead-time guidance for a city + date
  - get_role_pricing          : all-inclusive hourly rate range
  - get_compliance_by_state   : state-level wage/overtime/compliance

Architecture: each tool is a 20-line Python class that makes one HTTPS
GET to mcp.tempguru.co/api/v1/<endpoint> and yields the response JSON
back to Dify. No business logic reimplemented; the MCP REST mirror is
the canonical source.

Trust profile:
  - Read-only (every tool is a GET)
  - No authentication required (public data)
  - No API keys, OAuth, credentials
  - No user data persisted
  - No executable scripts beyond the standard Dify entrypoint

Matches the no-auth precedent set by duckduckgo and wikipedia plugins
in the same .curated directory. Bilingual labels (en_US + zh_Hans)
throughout.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jun 4, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the TempGuru Event Staffing Dify plugin, which provides read-only access to event staffing data across US and Canadian markets through five tools. The feedback focuses on improving the robustness and safety of the plugin. Specifically, it is recommended to simplify credential validation by using a direct HTTP request rather than invoking internal SDK methods, and to clean up the resulting unused import. Additionally, the reviewer suggests implementing defensive parameter checks and wrapping API requests in try-except blocks across all tools to gracefully handle network errors and JSON parsing failures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +16 to +25
def _validate_credentials(self, credentials: dict) -> None:
try:
for _ in GetCitiesTool.from_credentials(credentials).invoke_from_executor(
tool_parameters={}
):
pass
except Exception as e:
raise ToolProviderCredentialValidationError(
f"Unable to reach TempGuru MCP REST API at mcp.tempguru.co: {str(e)}"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Instantiating a tool class and calling internal SDK methods like invoke_from_executor inside _validate_credentials is highly risky and prone to breaking if the SDK internals change. It is much safer and cleaner to perform a direct HTTP request to the public API endpoint to verify connectivity.

Suggested change
def _validate_credentials(self, credentials: dict) -> None:
try:
for _ in GetCitiesTool.from_credentials(credentials).invoke_from_executor(
tool_parameters={}
):
pass
except Exception as e:
raise ToolProviderCredentialValidationError(
f"Unable to reach TempGuru MCP REST API at mcp.tempguru.co: {str(e)}"
)
def _validate_credentials(self, credentials: dict) -> None:
try:
response = requests.get(
"https://mcp.tempguru.co/api/v1/cities",
headers={"User-Agent": "tempguru-dify-plugin/0.0.1", "Accept": "application/json"},
timeout=10,
)
response.raise_for_status()
except Exception as e:
raise ToolProviderCredentialValidationError(
f"Unable to reach TempGuru MCP REST API at mcp.tempguru.co: {str(e)}"
)

Comment thread tools/tempguru/provider/tempguru.py Outdated
Comment on lines +1 to +3
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from dify_plugin import ToolProvider
from tools.get_cities import GetCitiesTool
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import of GetCitiesTool is unused if we simplify the credential validation logic to use a direct HTTP request. Removing it keeps the imports clean and avoids potential import cycle or path resolution issues.

Suggested change
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from dify_plugin import ToolProvider
from tools.get_cities import GetCitiesTool
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from dify_plugin import ToolProvider
import requests

Comment on lines +15 to +34
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
params: dict[str, Any] = {
"city": tool_parameters["city"],
"date": tool_parameters["date"],
}
if tool_parameters.get("role"):
params["role"] = tool_parameters["role"]
if tool_parameters.get("count"):
params["count"] = tool_parameters["count"]

response = requests.get(
f"{API_BASE}/availability",
params=params,
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure robust error handling and defensive programming, we should safely extract parameters using .get() and check for their presence. Additionally, wrapping the HTTP request in a try-except block to catch requests.RequestException and ValueError prevents unhandled exceptions from crashing the tool execution, allowing us to return a clean error message instead.

    def _invoke(
        self, tool_parameters: dict[str, Any]
    ) -> Generator[ToolInvokeMessage, None, None]:
        city = tool_parameters.get("city")
        date = tool_parameters.get("date")
        if not city or not date:
            yield self.create_text_message("Both 'city' and 'date' parameters are required.")
            return

        params: dict[str, Any] = {
            "city": city,
            "date": date,
        }
        if tool_parameters.get("role"):
            params["role"] = tool_parameters["role"]
        if tool_parameters.get("count"):
            params["count"] = tool_parameters["count"]

        try:
            response = requests.get(
                f"{API_BASE}/availability",
                params=params,
                headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
                timeout=30,
            )
            response.raise_for_status()
            yield self.create_json_message(response.json())
        except requests.RequestException as e:
            yield self.create_text_message(f"API request failed: {str(e)}")
        except ValueError:
            yield self.create_text_message("Failed to parse response from TempGuru API.")

Comment thread tools/tempguru/tools/get_cities.py Outdated
Comment on lines +15 to +31
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
params: dict[str, Any] = {}
if tool_parameters.get("state"):
params["state"] = tool_parameters["state"]
if tool_parameters.get("tier"):
params["tier"] = tool_parameters["tier"]

response = requests.get(
f"{API_BASE}/cities",
params=params,
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Wrapping the network request in a try-except block ensures that any connection issues, timeouts, or invalid JSON responses are handled gracefully rather than raising unhandled exceptions.

    def _invoke(
        self, tool_parameters: dict[str, Any]
    ) -> Generator[ToolInvokeMessage, None, None]:
        params: dict[str, Any] = {}
        if tool_parameters.get("state"):
            params["state"] = tool_parameters["state"]
        if tool_parameters.get("tier"):
            params["tier"] = tool_parameters["tier"]

        try:
            response = requests.get(
                f"{API_BASE}/cities",
                params=params,
                headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
                timeout=30,
            )
            response.raise_for_status()
            yield self.create_json_message(response.json())
        except requests.RequestException as e:
            yield self.create_text_message(f"API request failed: {str(e)}")
        except ValueError:
            yield self.create_text_message("Failed to parse response from TempGuru API.")

Comment on lines +15 to +26
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
params = {"state": tool_parameters["state"]}
response = requests.get(
f"{API_BASE}/compliance",
params=params,
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Safely retrieve the required 'state' parameter and wrap the HTTP request in a try-except block to handle potential network errors or parsing failures gracefully.

    def _invoke(
        self, tool_parameters: dict[str, Any]
    ) -> Generator[ToolInvokeMessage, None, None]:
        state = tool_parameters.get("state")
        if not state:
            yield self.create_text_message("The 'state' parameter is required.")
            return

        params = {"state": state}
        try:
            response = requests.get(
                f"{API_BASE}/compliance",
                params=params,
                headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
                timeout=30,
            )
            response.raise_for_status()
            yield self.create_json_message(response.json())
        except requests.RequestException as e:
            yield self.create_text_message(f"API request failed: {str(e)}")
        except ValueError:
            yield self.create_text_message("Failed to parse response from TempGuru API.")

Comment on lines +15 to +29
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
params = {
"role": tool_parameters["role"],
"city": tool_parameters["city"],
}
response = requests.get(
f"{API_BASE}/pricing",
params=params,
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Perform defensive parameter checks and wrap the HTTP request in a try-except block to handle network errors or invalid JSON responses gracefully.

    def _invoke(
        self, tool_parameters: dict[str, Any]
    ) -> Generator[ToolInvokeMessage, None, None]:
        role = tool_parameters.get("role")
        city = tool_parameters.get("city")
        if not role or not city:
            yield self.create_text_message("Both 'role' and 'city' parameters are required.")
            return

        params = {
            "role": role,
            "city": city,
        }
        try:
            response = requests.get(
                f"{API_BASE}/pricing",
                params=params,
                headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
                timeout=30,
            )
            response.raise_for_status()
            yield self.create_json_message(response.json())
        except requests.RequestException as e:
            yield self.create_text_message(f"API request failed: {str(e)}")
        except ValueError:
            yield self.create_text_message("Failed to parse response from TempGuru API.")

Comment thread tools/tempguru/tools/get_roles.py Outdated
Comment on lines +15 to +24
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
response = requests.get(
f"{API_BASE}/roles",
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Wrap the HTTP request in a try-except block to handle network errors or invalid JSON responses gracefully.

Suggested change
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
response = requests.get(
f"{API_BASE}/roles",
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
def _invoke(
self, tool_parameters: dict[str, Any]
) -> Generator[ToolInvokeMessage, None, None]:
try:
response = requests.get(
f"{API_BASE}/roles",
headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
timeout=30,
)
response.raise_for_status()
yield self.create_json_message(response.json())
except requests.RequestException as e:
yield self.create_text_message(f"API request failed: {str(e)}")
except ValueError:
yield self.create_text_message("Failed to parse response from TempGuru API.")

…vider validation

Applies the 7 suggestions from the Gemini Code Assist bot on PR langgenius#3252:

provider/tempguru.py
  - Replace SDK-internal-method validation with a direct requests.get
    probe. Avoids coupling the validator to Tool subclass internals.
  - Remove unused GetCitiesTool import; add requests.

All 5 tool handlers
  - Wrap requests.get in try/except for requests.RequestException and
    ValueError. Yield create_text_message on errors instead of raising.

Tools with required parameters
  - Use tool_parameters.get() instead of subscript access on required
    params. Return early with a helpful error message if any required
    param is missing.
@kissmyabs32
Copy link
Copy Markdown
Author

Thanks for the thorough review. All 7 suggestions applied in 17a231d4:

provider/tempguru.py:

  • Switched from GetCitiesTool.from_credentials().invoke_from_executor() to a direct requests.get probe — agreed that internal-SDK coupling is the wrong dependency to take
  • Removed the now-unused GetCitiesTool import
  • Added import requests

All 5 tool handlers:

  • Wrapped requests.get in try/except for requests.RequestException and ValueError
  • Yield self.create_text_message(...) on errors so the agent sees a clean message instead of an unhandled exception

Tools with required params (check_availability, get_role_pricing, get_compliance_by_state):

  • Now use tool_parameters.get() with a None check + early-return helpful error message, rather than subscript access that would KeyError if a param is missing

Ready for re-review.

CI's astral-sh/setup-uv@v6 looks for uv.lock or requirements.txt to
invalidate the dependency cache. Without either, the cache key check
fails with 'No file matched to .../uv.lock,.../requirements.txt' and
the downstream test step exits 1.

Generated by 'uv lock' against pyproject.toml. Resolved 41 packages
including dify_plugin transitive deps. Lockfile is portable across
Python 3.12+ per requires-python in pyproject.toml.
The pre-pr-check-per-plugin workflow at .github/workflows/ line 120
hard-requires the top-level manifest.yaml author field to match
'langgenius' (the org maintaining the official-plugins monorepo).
Individual contributor attribution stays in provider/<name>.yaml
which retains 'author: tempguru' — same pattern as duckduckgo
(provider author: yash_parmar) and other community contributions.
@kissmyabs32 kissmyabs32 deployed to tools/tempguru June 4, 2026 15:01 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant