Buffaly Logo
Reference

Attaching JSON web services to Buffaly tools

Model an external JSON web service as a ProtoScript service, then expose small typed actions that call named JSON routes through a shared helper.

Core idea

Use this pattern when a product already exposes JSON web service routes and Buffaly needs a typed, discoverable tool surface instead of ad hoc HTTP calls. The service binding owns transport details, the actions own semantic tool names and typed parameters, and JsonWsHelper owns HTTP mechanics and secret resolution.

Service owns configuration

Keep BaseUrl, API prefixes, token keys, and helper methods on a ProtoScript Service.

Actions stay thin

Expose one safe remote operation per OpsAction wrapper and pass a service binding parameter.

Secrets stay secret

Store only logical keys such as FeedingFrenzy.ApiKey in the binding; resolve values from UserSecrets at call time.

Reference implementation

The Feeding Frenzy JSONWS integration is the reference pattern. It turns route-based endpoints under https://ff.medek.ai into Buffaly tools while keeping endpoint configuration, authentication, and route execution reusable.

Artifacts

  • Skills/FeedingFrenzyJsonWs/index.pts
  • Skills/FeedingFrenzyJsonWs/Leads.pts
  • Skills/FeedingFrenzyJsonWs/LeadStatuses.pts

Core symbols

FeedingFrenzyJsonWsSkill, FeedingFrenzyJsonWsSkillAction, FeedingFrenzyJsonWsService, FeedingFrenzyJsonWsService#Remote, FeedingFrenzyJsonWsService#Local, and LeadStatuses_GetLeadStatuses.

Overall shape

LayerPurposeFeeding Frenzy example
Base actionGroups every discoverable tool in the skill.FeedingFrenzyJsonWsSkillAction
Skill entityPoints Buffaly discovery at the action root.FeedingFrenzyJsonWsSkill
Service contractCentralizes URL, prefixes, token key, initialization, and call helpers.FeedingFrenzyJsonWsService
ActionsValidate inputs, build route arguments, and call the service helper.LeadStatuses_GetLeadStatuses

Service-owned binding and helper methods

Actions should not assemble full URLs or read secrets. They pass a route path and a JsonObject to the service. The service passes its BaseUrl, API prefix, and TokenKey to JsonWsHelper.CallJsonRouteSecure(...).

prototype FeedingFrenzyJsonWsService : Service
{
    String BaseUrl = new String();
    String BusinessApiPrefix = "api/feedingfrenzy.admin.business";
    String UiApiPrefix = "api/feedingfrenzy.admin.ui";
    String TokenKey = "FeedingFrenzy.ApiKey";

    function Initialize() : string
    {
        if (StringUtil.IsEmpty(this.BaseUrl))
            return "Error: BaseUrl must be set on the binding.";
        return "initialized";
    }

    function CallBusinessRoute(string routePath, JsonObject args) : string
    {
        JsonObject normalizedArgs = args == null ? new JsonObject() : args;
        return JsonWsHelper.CallJsonRouteSecure(this.BaseUrl, this.BusinessApiPrefix, routePath, this.TokenKey, normalizedArgs);
    }
}

Multiple service instances

Represent environments as partial prototypes of the same service type. Add staging, QA, customer-specific, or regional bindings by changing the instance name, BaseUrl, and TokenKey; do not duplicate every action.

#Remote

https://ff.medek.ai with FeedingFrenzy.ApiKey.

#Local

http://feedingfrenzy.local with FeedingFrenzy.Local.ApiKey.

Each action accepts the base service type, for example Execute(FeedingFrenzyJsonWsService service), so the caller chooses the binding at runtime.

Action wrappers

Each wrapper should validate that a service binding was supplied, build a JsonObject with exactly the parameter names the JSONWS route expects, and call either CallBusinessRoute(...) or CallUiRoute(...).

No-argument route

LeadStatuses_GetLeadStatuses checks the binding and calls lead-statuses/get-lead-statuses with an empty argument object.

Parameterized route

Leads_GetLeadsByEmail adds Email to the argument object and calls leads/get-leads-by-email.

Adaptations should be explicit. If an action such as Leads_GetPotentialDuplicates must call leads/get-lead first and pass the returned row to another route, document that dependency in the action description.

Route naming conventions

The route path should match the published JSONWS route segment, not the C# method name. Keep route strings literal and local to the action unless an authoritative generator owns them.

lead-statuses/get-lead-statuses leads/get-lead leads/get-leads-by-email leads/get-leads-by-phone leads/leads-_-get-unassigned-_-sp-_-paging-sp

Authentication and secret handling

A JSONWS binding should store only a logical secret key. The secret value belongs in UserSecrets, not in .pts, .json, .cs, wiki, markdown history, logs, or prompt output.

  • Use keys such as FeedingFrenzy.ApiKey and FeedingFrenzy.Local.ApiKey.
  • Import legacy environment variable values into canonical UserSecrets without printing them.
  • Smoke tests should report that a key resolves and a route returns data, never the bearer token itself.

Validation checklist

  1. Compile the ProtoScript project after adding the service and actions.
  2. Use semantic search or skill listing to confirm the new actions are discoverable.
  3. Load one action through ToLoadProtoScriptActionTool.
  4. Call the action with the intended service binding.
  5. Confirm missing credentials fail fast with a clear logical secret key.
  6. Configure the canonical user secret without exposing its value.
  7. Retry and confirm the remote service returns the expected data shape.
  8. Add focused regression tests when the helper or projection layer changes.
Safe smoke test: load LeadStatuses_GetLeadStatuses, call it with FeedingFrenzyJsonWsService#Remote, resolve FeedingFrenzy.ApiKey through UserSecrets, and confirm rows such as Not Contacted, Contacted, In the Pipeline, Sold, Lost, and Defunct.

Scaling the pattern to a larger API

For a small API, a few actions in index.pts may be enough. For a larger API, split actions by remote class or route family and include them from index.pts. This mirrors upstream JSONWS classes and makes route comparison or regeneration easier.

Leads.pts
LeadAutomation.pts
LeadStatuses.pts
LeadSubStatuses.pts
Sources.pts
Tags.pts

Common mistakes

  • Do not put full URLs in every action; put BaseUrl and API prefixes on the service binding.
  • Do not read UserSecrets in every action; store TokenKey on the binding and call the secure helper.
  • Do not create separate action copies for local and remote; create separate service instances and pass the selected binding.
  • Do not silently default a missing service binding; fail fast with a clear error.
  • Do not expose secret values while testing.
  • Do not mirror destructive or unbounded routes blindly; start with safe read-only routes and add safeguards.
Building ProtoScript services Registering MCP services Secrets and credentials handling Semantic data and ontology