Scout Plugin Hybrid Routing
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Scout Plugin Hybrid Routingroute this confidential task to the local on-device model"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Hybrid Contextual Inference — Microsoft Scout Plugin
Route each task to the right model across a hybrid stack — Scout's hosted cloud models, on-device runtimes, and org-hosted endpoints — and keep sensitive content inside the trust boundary it belongs in.
A Scout port of smfworks/hermes-plugin-hybrid-routing, rebuilt for
Scout's extension model and hardened around the thing that actually makes
hybrid inference worth doing: provable egress control.
The three-signal design, the difficulty heuristics and the config schema originate upstream (MIT, © 2026 SMF Works). See ATTRIBUTION.md for exactly what was carried over and what is new here.
What it does
Classifies every task on three signals, then picks a model:
Signal | Values | Drives |
sensitivity |
| which trust boundary the content may cross |
role | coding, research, creative, strategy, vision, general | model specialization |
difficulty | simple / standard / hard | fast / balanced / strong tier |
The primary session model never changes, so prompt caching survives. Tasks that need a different model are delegated to a Scout subagent, or executed against a local / org-hosted endpoint.
Related MCP server: Router-MCP
Egress classes
This is the core concept, and the main departure from the Hermes original.
There, "local" is a naming convention inside an opaque provider/model string.
Here every model reference declares where inference physically happens:
Reference | Egress class | Executed via |
|
| Scout's |
|
| OpenAI-compatible HTTP |
|
| OpenAI-compatible HTTP |
Sensitivity sets a ceiling, and both model selection and the fallback chain are filtered by it:
normal → cloud-public permitted
confidential → org-tenant or tighter
restricted → on-device onlyIf no configured model satisfies the ceiling, the router blocks. It never degrades to a less-trusted model.
Differences from the Hermes original
Three defects were found by running the upstream engine before porting. Each
has a named regression test in tests/test_hybrid_routing.py.
# | Severity | Bug | Fix |
1 | critical | Sensitive content with no local model configured falls through to the balanced cloud tier ( | Fails closed — returns |
2 | high | All tier models are appended to the fallback chain after the sensitivity branch ( | Fallback chains are egress-filtered; a restricted decision contains only on-device candidates. |
3 | medium | Role cues match as bare substrings, so the cue | Cues match on word boundaries. |
4 | medium |
| All cue sets are case-insensitive. |
Line references are pinned to upstream commit 88198cf. All four were reported
upstream and are being addressed — see ATTRIBUTION.md.
Plus: the upstream run_tests() evaluates against whichever config is loaded,
so its expectations depend on the user's own model choices — with a plausible
hybrid config it self-reports 6/9. Here the self-test pins its own reference
config, so a failure always means the engine changed.
Second-pass findings (v1.1)
A self-review after v1.0.0 found five more bugs — in this code, not upstream. All 86 v1.0.0 tests passed while every one of them was live, because the tests were written by the author of the code and inherited its blind spots.
# | Severity | Bug | Fix |
1 | critical | A reference's egress came only from its scheme prefix, while the actual network destination came from the backend's |
|
2 | critical | A | an on-device claim now requires a loopback address, else it degrades to |
3 | high | Audit signals truncated a match to 12 characters — but the SSN pattern matches exactly 11, so SSNs and short MRNs were echoed verbatim into the decision record. The v1 test used a long secret and passed. | signals now report the match's shape ( |
4 | medium |
| normalized case/whitespace; unrecognized values fail closed to on-device and are reported by |
5 | medium | A model reference naming an undefined backend validated clean and routed | undefined backends resolve to |
The lesson generalizes: bug 1 is the same class of failure as the upstream fail-open — a trust decision made from a value that does not actually control where the bytes go. Fixing someone else's version of a bug is no protection against writing your own.
Each fix has a regression test, verified by mutation testing: reverting
resolve_egress to the v1 behaviour fails 5 tests, and reverting _redact
fails the short-secret cases.
Third-pass findings (v1.2)
An independent reviewer found eight more. The theme this time: routing itself was sound, but nothing enforced it at the point of transmission, and several ways of silently deleting a control went unreported.
# | Severity | Bug | Fix |
1 | high |
|
|
2 | high | On-device traffic could escape through an HTTP proxy: | loopback requests use an empty |
3 | medium | A backend with no | a missing or unreadable egress resolves to |
4 | medium | Confidential prompts and | non-loopback cleartext degrades to |
5 | medium |
| compile failures are collected and surfaced by |
6 | medium | Unknown config keys were ignored, so | known-key checking on top-level and nested sections |
7 | medium | Signal masking replaced alphanumerics but kept punctuation verbatim, so a symbol-only credential ( | every non-space character is masked |
8 | low | Malformed YAML, bad UTF-8 and non-numeric timeouts escaped as tracebacks. | translated into |
Fourth-pass findings (v1.3.1)
Both defects sit in the one payment-card rule carried over from the Hermes
original, and both were still present upstream at the time of writing
(hybrid_contextual_routing/data/routing_config.yaml:131). The old rule was
"\b(?:\d[ -]?){13,16}\b".
# | Severity | Bug | Fix |
1 | high | The | range widened to 13–19 via |
2 | medium | No value check, so any 13–16 digit run was classified restricted: ISBNs, order references, and columns of figures. Because a separator is allowed after every digit, nine two-digit numbers read as one run. Restricted content is blocked when no on-device model is configured, so ordinary documents stopped work. | the rule declares |
Pattern entries therefore accept a mapping as well as a plain string, so the value check stays visible in the config rather than hidden in Python:
restricted_patterns:
- "\\b\\d{3}-\\d{2}-\\d{4}\\b" # plain regex, unchanged
- pattern: "\\b(?:\\d[ -]?){12,18}\\d\\b"
validator: luhn # only match if Luhn-validAn unknown validator name is reported by validate() and the rule is kept
matching rather than dropped, consistent with third-pass finding 5: a
protection rule that silently vanishes is worse than one that over-fires.
Luhn is a checksum, not proof of a card: roughly one in ten random digit runs of a given length passes it. It removes the bulk of the false positives, not all of them.
Provenance
Content read from an M365 surface is treated as business content by default, even when the text carries no marker. How much that should matter is deployment-specific, so it is configurable:
provenance:
mode: floor # floor (default) | advisory | off
default_level: confidential
sources: # optional per-source overrides
transcript: restricted
calendar: normalMode | Behaviour |
| Anything from an M365 surface is raised to |
| The source is recorded in the decision signals but does not raise the level. Labels and content patterns still apply in full. |
| Provenance is ignored entirely. |
sources entries win over mode in both directions — they can exempt a
surface the mode would floor, or floor one it would ignore. Set a source to
normal to exempt it.
Which to pick. floor is right for regulated environments where any
tenant-sourced content needs containment. advisory suits an individual whose
M365 traffic is mostly routine: a blanket floor sends every email summary to
the sensitivity model — typically the smallest one available — and the quality
drop tends to make people disable the feature outright, which protects nothing.
Under advisory the label and pattern signals are untouched, so a document
labelled Highly Confidential or an email containing a key still routes to the
restricted model.
An unreadable mode, default_level or source override fails closed to
floor / confidential and is reported by status.
Limitations — read this before trusting it
Classification happens after the session model has already read the content.
This plugin routes execution. If Scout's cloud session model has already
received a confidential email in its context, calling route_classify on it
afterwards cannot un-disclose it — the decision only governs where the
follow-on work runs. Genuine prevention would require classification as
host-side middleware ahead of the first model invocation, which is outside what
a plugin can reach. Treat this as a control over delegation and execution, not
as a guarantee that the primary model never sees sensitive text.
org-tenant is asserted, not verified. Nothing proves an endpoint you
declare as tenant-hosted actually is. The router checks the URL is HTTPS and
non-loopback, and that on-device claims resolve to loopback — it cannot verify
tenancy. That declaration is yours to get right.
Detection is pattern-based. It will miss sensitive content that carries no
marker, no label and no M365 provenance. Pass --label and --source whenever
you have them; they are far stronger signals than the text scan.
Added for Scout
MIP sensitivity labels — pass
--label "Highly Confidential"and the label drives routing directly. Checked most-restrictive-first, so "Highly Confidential" is not swallowed by the "Confidential" substring.M365 provenance —
--source email|teams|sharepoint|calendar|transcriptapplies aconfidentialfloor by default. Content read out of the tenant is business content even when the text looks benign. Tunable via theprovenanceconfig section (floor/advisory/off, plus per-source overrides) — see Provenance.Real local execution — Hermes delegates model execution to its own provider layer. Scout's
tasktool hosts only Scout's cloud models, so this plugin ships an OpenAI-compatible client covering Ollama, LM Studio, Foundry Local, llama.cpp, vLLM and Azure AI Foundry.Backend discovery —
probereports what is reachable, including known local runtime ports that are running but unconfigured.reasoning_effortpassed through per tier and role.Config validation —
statusflags arestricted_modelthat is not actually on-device, and any reference it cannot parse.Global policy cap —
policy.max_egresscaps all routing regardless of content, for an air-gapped or high-compliance profile.Graded sensitivity — Hermes has binary normal/sensitive, which cannot express "internal business content, tenant-hosted model is fine". That distinction comes up constantly in Microsoft 365 work.
Install
pip install -r requirements.txt
python -m hybrid_routing init # writes ~/.scout/hybrid_routing/routing_config.yaml
python -m hybrid_routing probe # see which local runtimes are already upEdit the config to fill in your models, then:
python -m hybrid_routing status # validates and reports problems
python -m hybrid_routing test # engine self-testSee setup.md for the skill and MCP server registration.
Usage
python -m hybrid_routing classify "Debug the import logic in this module"
python -m hybrid_routing classify --label "Highly Confidential" --source email "Summarize this thread"
python -m hybrid_routing infer --backend ollama --model qwen3:8b --prompt-file task.txtAdd --json for machine-readable output. Exit codes: 0 routed, 1 blocked,
2 not configured, 3 error.
Example — sensitive M365 content
$ python -m hybrid_routing classify --label "Highly Confidential" --source email \
"Summarize this thread and list open questions"
sensitivity : restricted
egress ceiling: on-device
MODEL : local/ollama/qwen3:8b
channel : openai-http
delegate : YES
reason: Content classified 'restricted' -> routed to 'local/ollama/qwen3:8b'
(on-device); egress ceiling 'on-device' enforced; 5 cloud/less-trusted
model(s) excluded
signals:
- MIP label 'Highly Confidential' -> restricted
- M365 provenance (email) -> confidential floor
run: python -m hybrid_routing infer --backend ollama --model qwen3:8b --prompt-file <file>Example — the same content with a cloud-only config
ROUTING BLOCKED
MODEL : (none selected)
reason: BLOCKED: content classified 'restricted' requires a model at or inside
the 'on-device' boundary, and none is configured. 5 configured model(s)
were rejected because their egress class exceeds the 'on-device'
boundary. Routing to an available cloud model would send this content
outside the required boundary.
excluded (outside ceiling):
x scout/cloud-balanced — egress 'cloud-public' exceeds ceiling 'on-device'Exit code 1. This is the case the upstream plugin silently sends to the cloud.
Tools (via MCP)
Registering the MCP server puts these in Scout's tool list on every turn:
Tool | Purpose |
| classify a task and get a routing decision |
| show configuration and validation problems |
| run the engine self-test |
| report reachable local / org backends |
| run a prompt on a local or org backend |
Architecture
hybrid_routing/
├── egress.py egress classes, model reference parsing ← the trust model
├── classify.py sensitivity / role / difficulty, MIP labels, provenance
├── router.py selection + egress filtering + fail-closed blocking
├── backends.py OpenAI-compatible client, discovery (stdlib only)
├── config.py config resolution and validation
├── selftest.py engine self-test against a pinned reference config
├── cli.py command line interface
└── mcp_server.py MCP stdio server (JSON-RPC, no dependencies)
data/routing_config.yaml shipped default — every model field blank
skill/ Scout skill package
tests/ 164 testsDesign notes
Why blank by default. A router that guesses is a router that eventually sends something somewhere you did not intend. Nothing routes until you name a model. Unconfigured is reported, not silently defaulted.
Why unknown schemes raise. An unrecognized reference could be local or cloud. Guessing wrong on that single question is the entire failure mode this plugin exists to prevent, so it refuses to guess.
Why local models always delegate. skip_for_tier: [fast] can only apply to
Scout-hosted models. A local model is unreachable from the primary session, so
it is always executed out of band regardless of tier.
Why signals are redacted. Classification signals name the pattern that matched and echo at most 12 characters of it, so a decision record is auditable without reprinting the secret that triggered it.
Testing
python -m pytest # 164 testsLicense
MIT — © 2026 Michael Gannotti. Derivative of smfworks/hermes-plugin-hybrid-routing,
MIT © 2026 SMF Works. See LICENSE and ATTRIBUTION.md.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- -license-quality-maintenanceIntelligent routing service that selects optimal AI models based on capability requirements and normalizes input/output formats across multiple providers like OpenAI, Anthropic, Google, and others.Last updated
- FlicenseCqualityDmaintenanceRoutes natural language queries to appropriate MCP tools and planners with high-precision semantic matching and safety guardrails. Supports multi-intent detection, task planning, and strict role-based filtering to prevent misexecution or unauthorized access.Last updated7
- Alicense-qualityAmaintenanceA local-first LLM routing MCP server that keeps sensitive data on your own models, with fail-closed privacy and manager-worker delegation, exposing route and complete tools to any MCP client.Last updatedMIT
- Flicense-qualityDmaintenanceAutomatically routes queries to the most suitable AI model based on task type, cost constraints, and performance needs, supporting multiple providers and customizable priorities.Last updated
Related MCP Connectors
Enterprise AI Control Plane: governance, guardrails, spend tracking, compliance & smart routing.
Intent execution engine for autonomous agent task routing
Responsible-AI guardrails for agents: scoring with policy, injection & PII detection, DPDP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/mgannotti/scout-plugin-hybrid-routing'
If you have feedback or need assistance with the MCP directory API, please join our Discord server