OpenAI Agents SDK
Connect an agent built with the OpenAI Agents SDK to Felspar, and gate its acting tools with tool guardrails driven by the released pack.
An agent built with OpenAI’s Agents SDK (JavaScript) connects to Felspar as a Streamable HTTP MCP server with its own agent key. The pre-action gate plugs in as tool guardrails. The example on this page screens job applicants for a recruiting team.
Connect
import { Agent, MCPServerStreamableHttp, run } from "@openai/agents";
const felspar = new MCPServerStreamableHttp({
name: "felspar",
url: "https://mcp.felspar.ai/mcp",
requestInit: { headers: { Authorization: `Bearer ${process.env.FELSPAR_AGENT_KEY}` } },
});
await felspar.connect();
const agent = new Agent({
name: "Applicant screener",
instructions: "Run the company's Felspar jobs. If no job is named, pick one with list_jobs. Never decide yourself.",
mcpServers: [felspar],
});
const result = await run(agent, "Screen application APP-2291 for requisition REQ-311.");
In this runtime, tool names are the tools’ own names: list_jobs, get_job_requirements, get_job_decision and check_output. Put the full Felspar method in the instructions: npx @felspar/sdk skill <dir> writes the SDK’s skill to <dir>/skills/felspar/SKILL.md, and its text works as instructions in any runtime.
Add the pre-action gate
Here the applicant-tracking system’s email tool may send only the output the released pack lists under the decided outcome, only for the application Felspar decided on, and only with text Felspar passed:
import {
createActionGate,
felsparJobDescriber,
felsparOutputCheckVerifier,
felsparVerifier,
} from "@felspar/sdk/draft/action-gate";
import {
felsparDecisionOutputGuardrail,
felsparToolInputGuardrail,
} from "@felspar/sdk/draft/action-gate/openai-agents";
const felsparApi = { origin: "https://api.felspar.ai", agentKey: process.env.FELSPAR_AGENT_KEY };
const gate = createActionGate({
job: "screen-candidate",
decisionTool: "get_job_decision",
outputCheckTool: "check_output",
tools: {
send_candidate_email: {
// The output this tool writes, by its id in the released pack.
writes: "candidate-update",
// The case is the application in this field.
entity: { field: "applicationId" },
fields: ["applicationId", "to", "subject", "body"],
outputCheck: { fields: ["body"] },
},
},
verify: felsparVerifier(felsparApi),
describe: felsparJobDescriber(felsparApi),
verifyOutputCheck: felsparOutputCheckVerifier(felsparApi),
});
// Each run's context carries a conversation ID, unique per conversation.
const sessionOf = (runContext) => runContext.context.conversationId;
Then attach the two guardrails:
felsparToolInputGuardrail(gate, { sessionOf })goes intoolInputGuardrailson the MCP server whose tools act (orinputGuardrailson a function tool). It runs the gate before a gated call. A denial rejects the call, and the model sees the reason and, when the policy gives one,Next step:with the company’s own next step.felsparDecisionOutputGuardrail(gate, { sessionOf })goes intoolOutputGuardrailson Felspar’s server. It records eachget_job_decisionresult with theentity_idit asked about, and eachcheck_outputresult, and never changes the output.
Pass the conversation ID when you run:
await run(agent, "Screen application APP-2291 and draft the update to the candidate.", {
context: { conversationId: "conv-7f3a" },
});
sessionOf matters
The gate only uses decisions recorded under the same session ID. sessionOf(runContext) must return an ID unique to the conversation, never shared between users or conversations. Otherwise one user’s decision could authorize another user’s action.
Notes
- Because tool names here are short, make sure no two servers expose a gated tool with the same name.
- The case field (
applicationIdhere) must hold the decision’sentity_idexactly as sent, trimmed, case included. Withentity, recipients aren’t tied to the case, so limit who a tool can address withtestRecipientsor the tool’s own settings. - The adapters use structural types and add no dependencies. They were type-checked against
@openai/agents-core0.20. - The gate needs
@felspar/sdk0.2.0 or later, and 0.2.1 or later for jobs whose case IDs Felspar issues or derives (identity modes). Itsdraft/modules may change between minor versions until 1.0.