Skip to content

Server and Python SDK 3.2.1

DeepIntShield Server 3.2.1 and Python SDK 3.2.1 - 16 October 2026.

This is a correction release on 3.2.0. It aligns the SDK, gateway and documentation reference at 3.2.1; every 3.2.0 client keeps working unchanged. Check the version and enabled capabilities of your own gateway: a documentation release does not establish what is deployed.

MCP tool schemas only for callers that can use them

Section titled “MCP tool schemas only for callers that can use them”

A virtual key can be bound to MCP servers while the workspace has Agentic Security on. Until now the gateway attached those servers’ tool schemas to every chat and Responses request made with such a key. Tool execution, however, needs an enabled agent associated with the key, so a plain application call - no agent, a model that decided to use one of the offered tools - ended with 503 mcp_tool_authorization_unavailable at the model’s first tool call, and frameworks that read the content of that turn saw an empty answer.

The governed catalog now asks the authorizer first whether the caller could be authorized at all. A key with no enabled agent association (or an invalid X-Agent-Subject, or an active organization that does not own the workspace) is shown no MCP schema; the request completes with the tools it brought. Keys with an enabled agent see and execute their tools exactly as before, and the per-call authorization boundary is unchanged: nothing is granted, something is no longer offered. With Agentic Security off, the key’s own MCP allow-list remains the boundary and the catalog is unchanged. The answer is reused for five seconds per caller, so a key that carries MCP bindings pays no extra lookup on its ordinary requests.

Operators who want a plain application key to execute MCP tools under Agentic Security associate it with an approved agent, as before; the difference is that until they do, the key’s requests succeed without tools instead of failing.

An unknown /api/…, /v1/… or /scim/… path now answers the standard JSON 404 envelope (Route not found: <path>) instead of a 200 carrying the dashboard’s HTML. Scripts that probed a route a build does not have - or mistyped one - get a status they can act on. Dashboard pages are unaffected.

The cost-optimization cascade and the model policy

Section titled “The cost-optimization cascade and the model policy”

With Cost Optimization’s cascade routing on, the gateway may answer a request with a cheaper tier’s model. The substitution happens before the guardrail stage, which judges the model it finds on the request. When the caller’s guardrail policies include a model-inventory check (the OWASP LLM03 supply-chain card, a “whitelist” of models), a tier outside that list was dispatched and then refused: a gpt-4o-mini request - a model the policy allows - ended with 403 guardrail_blocked: Model gemini-2.5-flash violated the configured whitelist policy, naming a model the caller never asked for.

The cascade now asks the guardrails plugin first. The question runs on the plugin’s in-process fast path with the caller’s own policy selection and no content, so only model-dependent checks can answer it; it adds no round-trip, no external adapter call and no evidence record. A refused tier is simply not used - the request keeps the model its caller asked for and the ordinary evaluation decides as before. Permitted tiers, keys without guardrails and deployments without the embedded runtime behave exactly as they did.

Two things the release does not change, because they are the operator’s configuration: a tier the policy permits is still dispatched in place of the requested model (the response’s model names what answered), and a requested model outside the policy’s list is still refused when the cascade leaves the request untouched. A workspace whose tests or applications must reach a specific model keeps the cascade’s virtual-key scope away from those keys.

Gemini requests with a forced function call

Section titled “Gemini requests with a forced function call”

A request in Gemini’s own format (/v1beta/models/{model}:generateContent through the gateway’s Gemini integration) can carry toolConfig.functionCallingConfig with mode ANY and one allowedFunctionNames entry: the model must call that function. The gateway converted this to a tool_choice of type function without the function’s name, and a provider that validates the choice - any OpenAI-compatible provider, including the fallback a workspace may route Gemini models to - answered 400 Missing required parameter: 'tool_choice.name'.

The conversion is now complete in both directions: one forced function becomes a tool_choice that names it; several allowed names become an allowed_tools choice (required for ANY and VALIDATED, auto otherwise); NONE becomes "none"; ANY without names stays "required". The reverse conversion, used when a Responses-format request reaches a Gemini model, produces the same toolConfig again. Requests that name no function are unchanged.

Added 18 October: a function declared with the google-genai SDK’s parameters_json_schema (a plain JSON Schema in place of the OpenAPI-style parameters) reached the provider with no parameters at all, so the model called it with empty arguments. The JSON Schema object now becomes the function’s parameters when no OpenAPI-style schema is given; a non-object value is ignored as before.

  • Redis 8. Dropping a semantic-cache index that does not exist is accepted on Redis 8’s query engine (SEARCH_INDEX_NOT_FOUND Index not found) as it is on Redis Stack 7 (Unknown Index name). Namespace deletion no longer fails on a Redis 8 store whose index was never created.
  • Plugin modules build standalone. plugins/jsonparser, plugins/mocker and plugins/mcpcache resolve their sibling core and framework modules from the checkout, and the go.mod/go.sum of logging, response_consistency, semanticcache, governance and mcpcache are complete, so go test ./... runs in every plugin directory without network access to a private module path.
  • Handlers log through a default logger. The HTTP handlers package starts with a no-op logger and ignores SetLogger(nil), so no request path can dereference a nil logger before the server installs the real one; the request-logging step of the CORS middleware panicked on a nil logger, which showed only when a handler test ran on its own.
  • Server, UI, MCP handshake (DeepIntShieldMCPVersion), Helm chart (version and appVersion) and npm package metadata read 3.2.1. Container and npm publication are separate release steps.
  • Cloud Run tier: one guard shared secret, webhook outbox on (18 October, deployment/tiers/cloud-cheap). The gateway’s fine-tune (LoRA) page and the models sidecar’s runtime authentication both need DEEPINTSHIELD_GUARD_SHARED_SECRET, and the tier’s service template mounted it into neither container (“Configure the same DEEPINTSHIELD_GUARD_SHARED_SECRET on the gateway and models sidecar”). deploy_cloudrun.sh now creates deepintshield-cheap-guard-shared-secret once in Secret Manager (seeded from DEEPINTSHIELD_GUARD_SHARED_SECRET when given, otherwise generated; never rotated on its own) and the template mounts its latest version into the server and models containers. The same template renders the durable webhook outbox on (DEEPINTSHIELD_WEBHOOKS_ENABLED, default true) with its object-store settings: naming DEEPINTSHIELD_WEBHOOK_S3_BUCKET selects the S3-compatible payload store, otherwise the file backend keeps payloads on the instance’s ephemeral disk and the deploy warns. The CD workflow, the GitHub sync scripts, env.example, the smoke test and the contract tests carry the variables; a deploy creates a new revision and so restarts both containers together. The GKE Helm tier already wired the secret and is unchanged.
  • Aligned with Server 3.2.1; the inference, guardrail, RAG, MCP and Agentic APIs are unchanged from 3.2.0.
  • A program whose server run ended because its identity cannot use one runs locally with the fingerprint the administrator approved: when the gateway’s capabilities answer carries report_program_bundle, local discovery reports the program bundle the submission reported, without a second review.
  • shield.mcp.connect(identity=True) names the reason when the gateway refuses to issue workload credentials (agent_not_registered, for example). The refusal happens inside the MCP transport’s request hook, which the transport answered with a bare CancelledError: Cancelled via cancel scope; the session now raises the recorded refusal as its connection error, and as the operation’s error when a credential renewal fails mid-session. A real cancellation without such a refusal still propagates unchanged.
  • A LangGraph node built from Runnables - a RunnableParallel, a chain such as itemgetter("messages") | model, a RunnableBranch, a ToolNode or a compiled subgraph - is covered in the blueprint. The SDK already gated such nodes at run time but described them as a local tool with no source, so the gateway refused every graph of that shape as blueprint_coverage_incomplete and quarantined the reporting agent. The node’s source is now the composition it dispatches (class names and keys only, never arguments or configuration) with the source of every Python callable and application class inside it, which is what the gateway scans, in the blueprint manifest and in the discovery report alike; the node becomes a governed tool keyed by its node name. A callable whose source cannot be read still leaves the node uncovered, as a plain function without source does.
  • A program with no application code of its own (an agent made of instructions, a model and an output type, with no tools, hooks or callbacks) reports its definition as the code to scan. The gateway refuses a discovery report with no code artifact at all as blueprint_coverage_incomplete and quarantines the reporter, so such programs could never be enrolled; the definition artifact (framework, agent, model, bounded instructions, output type, handoffs) is scanned like any source. A program whose only tools are remote MCP tools is unchanged.
  • An autogen_agentchat (or AG2) agent run without a team is discovered as its own one-participant AutoGen program, with its model and, when it has no tools, its definition (the system message) as the code to scan. The generic path had captured the framework’s run method or nothing at all, so the report read as blueprint_coverage_incomplete and quarantined the reporting agent.

The internal test catalogue that exercises every gateway route was corrected alongside this release: management-token requests name the QA workspace (QA_ORG_ID, QA_WORKSPACE_ID shared settings, sent as X-Active-Tenant-Id and X-Active-Workspace-Id); a route behind a plan gate reports Blocked with the locked feature instead of Fail when the workspace’s plan lacks it; unknown-ID probes use an ID of the shape the handler validates; and a source test that can skip itself is no longer a case that expects it to pass. The forced-tool-call fidelity cases now tell the model that the JSON object is the tool’s complete arguments object (the earlier “return this exact JSON object” made gpt-4o-mini place the whole object inside the single string argument), and the large-identifier payload is a 20-digit string: a 13-19 digit run is payment-card data to the guard’s PII detector by design (demo numbers without a checksum must still be caught), so the earlier 16-digit value was refused by any workspace with a card policy before it reached a model. A Chat Completions case that forces one named function expects finish_reason stop, which is what OpenAI returns for a forced specific function (tool_calls is the answer to auto and required); the earlier expectation held only while another provider answered in OpenAI’s place. And a source-test case whose Go or pytest test skips itself on the tester’s machine (a fixture to build, a store to start, a key to set) prints Status: BLOCKED with the skip reason instead of FAIL; a Go test that fails while one of its subtests skips still reads FAIL, since the skip is a precondition only when the named test did not fail. Every chat case of the catalogue now names gpt-4o-mini, including the cases written against the Anthropic Messages, Gemini generation and Bedrock Converse client libraries, which the gateway converts to the provider that serves the model: one OpenAI provider key, one model-inventory entry and one model configuration carry the whole catalogue, and a provider’s own quota or a per-model cap no longer stops a run. Only the settings with no gpt-4o-mini equivalent keep their model (embeddings, speech-to-text, the realtime session, document OCR, Gemini’s named content caching). Note for anyone running the catalogue: a QA key inside the cost-optimization cascade’s scope has its short requests answered by the cheapest tier, and a model-inventory check that does not list a model refuses its requests. The setup script checks both and, with --apply-model-policy, takes the QA keys out of the cascade’s virtual-key scope and adds every model the cases name to the enabled policies’ model-inventory checks; without the flag it reports the exact change to make.

Corrected on 18 October, after the deployment of the release: fourteen sample-application cases (and the 72 that share their check in the other sample applications) called json.loads on the model’s raw answer and failed when gpt-4o-mini wrapped the JSON in a Markdown code fence or prefaced it; they now read the JSON object in the answer, with their value checks unchanged. The API contract cases of a feature the gateway mounts only with a server flag (the webhook outbox, DEEPINTSHIELD_WEBHOOKS_ENABLED) read Blocked with the flag’s name, like a plan gate, when the deployment does not mount it. And a note for anyone running the agentic cases: one agent identity holds one blueprint at a time, and with the workspace’s blueprint policy on runtime enforcement every distinct program needs its own approval while its scan is the agent’s latest, so the cases run one at a time (run, approve, run again) unless the policy admits clean deltas.

Terminal window
python -m pip install --upgrade "deepintshield==3.2.1"
# with gateway runtime authentication and LangGraph:
python -m pip install --upgrade "deepintshield[runtime,langgraph]==3.2.1"

The console ships inside the server image, so Server 3.2.1 and its UI upgrade together. No configuration changes, no new variables and no migration are required by the server itself; the Cloud Run (cloud-cheap) deployment tier’s deploy script creates the guard shared secret and renders the webhook outbox settings on its own (see “Deployment and operations”). A workspace whose plain application keys carry MCP bindings will see those keys’ requests complete without MCP tools until the keys are associated with an approved agent; nothing else changes for existing clients. For the 3.2.0 changes (two-run registration, deferred activation, registration e-mails), see Server and Python SDK 3.2.0.