MCP vs REST API: Choosing the Right Agent Interface
MCP and REST are not competitors at the same layer. REST with OpenAPI describes a service's endpoints for a programmer; MCP describes tools for a model, so the client can discover them at runtime and call one without a code change. Use REST where a human writes the call, MCP where the agent chooses it — and put identity, limits and audit in front of both.
Short answer: MCP and REST are not competitors at the same layer. REST with OpenAPI describes a service's endpoints for a programmer; MCP describes tools for a model, so the client can discover them at runtime and call one without a code change. Use REST where a human writes the call, MCP where the agent chooses it — and put identity, limits and audit in front of both.
Key takeaways
- The interface is the variable, not the implementation. Both surfaces resolve the same registered modules, so the question is which one your caller can actually use.
- Discovery is the real difference. A REST client needs docs and a hand-written path; an MCP client asks what exists and gets a list with annotations.
- Credentials are protocol-independent. One API key, one team, one budget — hashed lookup and revocation behave the same for a REST request and a tool call.
- Chaining moves. REST pushes orchestration into your client; MCP can keep step resolution on the server, where the audit trail is complete.
- Pick by caller, not by fashion, then enforce the decision in the gateway so a second surface does not silently become a second policy.
The two-minute version for whoever owns the integration
"MCP vs API" is usually asked as an either/or question, and it is really two questions stacked. The first is an interface question: does the caller know the endpoint before it runs, or does it ask what is available? The second is an operations question: whichever interface you expose, who is the caller, what may they spend, and where is the record of the call?
REST answers the first question for humans and hand-written clients — a documented API, an OpenAPI document, a generated client are all designed for someone who reads docs once and writes the call. MCP answers it for agents: the client connects, lists tools, and calls one by name with arguments the server described. Neither interface answers the second question. That is the gateway's job, it looks the same for both surfaces, and it is why most of this page is code rather than a feature matrix.
The measured demand fits that reading. The head term this page targets carries roughly 1,900 US
searches a month at a keyword difficulty of 12 — the easy band — and the section phrases that
pinned the excerpts below run from mcp sampling at 260 a month down to mcp vs openapi at
20. The smaller phrases are the leaf-level comparisons engineers type while deciding.
When MCP is the right interface, and when REST is
| Situation | Interface that fits | Why |
|---|---|---|
| A model must choose which capability to use at runtime | MCP | Tools are discoverable and described in the response that lists them |
| A scheduler, cron job or webhook calls one known operation | REST | The path is fixed, the payload is typed, nothing has to be discovered |
| The output feeds another service in a fixed pipeline | REST | Request and response are exactly the contract, and OpenAPI generates the client |
| A long agent loop with several tools, some chosen mid-run | MCP | Discovery and invocation in one session, with step inputs resolved server-side |
| Human approval before a state-changing call | MCP | Tool annotations tell the host which calls may run without asking |
Read the table as a caller test, not a protocol ranking. Both columns can sit on one backend, share one credential and read one policy object — the excerpts below show exactly that.
mcp vs function calling: one registry behind both
Function calling is how a model emits a call; MCP is how the callable things are published and invoked. Here the tools are declared once, on a server object:
# backend/smartgate/api/mcp.py — source lines 106–126 (register_mcp_tools)
def register_mcp_tools(server: FastMCP) -> None:
"""Register all 7 smart_* tools on a FastMCP instance."""
@server.tool(
name="smart_fetch",
description=TOOL_DESCRIPTIONS["smart_fetch"],
annotations=tool_annotations("smart_fetch"),
)
async def smart_fetch(
url: str = Field(description="Full HTTP or HTTPS URL to fetch."),
timeout: int = Field(default=30, description="HTTP timeout in seconds."),
) -> str:
_, registry = _app_state()
module = registry.get("fetch")
ctx = _tool_ctx()
return await _run_with_audit(
"fetch",
ctx,
module.process(ctx, url=url, timeout=timeout),
{"url": url},
)
# backend/smartgate/api/mcp.py — source lines 302–309 (register_mcp_tools)
@server.tool(
name="smart_pipe",
description=TOOL_DESCRIPTIONS["smart_pipe"],
annotations=ToolAnnotations(
title="Pipeline orchestrator",
readOnlyHint=False,
),
)
Each tool is registered with a name, a description read from a shared table, and annotations derived from the tool name, so the listing response and the server's own tool bodies cannot disagree about what exists. One tool is registered with an explicit annotation block instead of a derived one — the pipeline orchestrator, the call that can change state, so its read-only hint is written out rather than computed.
That is the difference worth holding on to when someone says MCP versus function calling. A function-calling schema tells the model how to shape a call, and every client has its own dialect for it. MCP fixes the wire format, so a tool declared here is the same tool to every host that speaks the protocol — and the model still sees a function-calling-shaped interface, because that is how the host presents it.
mcp vs rest: the registry that serves both surfaces
Neither interface owns the implementation. Both resolve the same object:
# backend/smartgate/core/registry.py — source lines 11–31 (ModuleRegistry)
class ModuleRegistry:
"""管理所有算法模块的注册、初始化、查询。"""
def __init__(self):
self._modules: Dict[str, SmartModule] = {}
@property
def modules(self) -> Dict[str, SmartModule]:
return self._modules
def register(self, module: SmartModule) -> None:
if module.name in self._modules:
logger.warning(f"Module '{module.name}' already registered, overwriting.")
self._modules[module.name] = module
logger.info(f"Registered module: {module.name} v{module.version}")
def get(self, name: str) -> SmartModule:
module = self._modules.get(name)
if module is None:
raise KeyError(f"Module '{name}' not found. Available: {list(self._modules.keys())}")
return module
Modules register under a name, and the REST routes and the MCP tool bodies look them up the same
way. Registration is forgiving — a duplicate name overwrites with a warning rather than throwing,
so a reload does not take the process down — but the lookup is not: get raises a KeyError
listing every available module, which is the message an integrator needs when a tool name is
misspelled.
The practical consequence is why this is not a coin flip. If both interfaces sit in front of one registry, exposing MCP is an addition rather than a rewrite: the tool body resolves a module, calls it, returns the result. Teams that treat MCP as a separate product to build from scratch end up maintaining two implementations of one algorithm, and two places for them to drift.
function calling vs mcp: parsing the arguments either way
Arguments arrive in more than one shape, and the boundary that accepts both is small:
# lib/smartgate/audit-logs.ts — source lines 48–63 (parseParams)
function parseParams(raw: unknown): Record<string, unknown> {
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
return raw as Record<string, unknown>;
}
if (typeof raw === "string") {
try {
const parsed = JSON.parse(raw) as unknown;
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
} catch {
/* ignore */
}
}
return {};
}
An object passes through, a JSON string is parsed and passes through if the parse yields an object, anything else returns an empty record. Arrays are rejected on purpose — tool arguments are an object, and keeping a list would produce a call the server cannot interpret.
The two interfaces disagree about shape without disagreeing about meaning. A REST client sends a JSON body; an agent host may hand the server the already-decoded object from a function-call payload; a third host sends the same object as a string because that is what its transport carried. Parsing at the boundary rather than at each call site is what keeps an audit row's parameters readable across all three.
mcp api key validation in one lookup
Credentials are where the two interfaces converge most cleanly, because a key is a key:
# lib/api-keys/index.ts — source lines 92–118 (validateApiKey)
async function validateApiKey(
rawKey: string
): Promise<{ valid: boolean; teamId?: string; keyId?: string }> {
const hash = hashKey(rawKey);
const key = await prisma.apiKey.findUnique({
where: { keyHash: hash },
select: {
id: true,
teamId: true,
revokedAt: true,
expiresAt: true,
},
});
if (!key || key.revokedAt) return { valid: false };
if (key.expiresAt && key.expiresAt < new Date()) return { valid: false };
await prisma.apiKey.update({
where: { id: key.id },
data: {
lastUsedAt: new Date(),
usageCount: { increment: 1 },
},
});
return { valid: true, teamId: key.teamId, keyId: key.id };
}
One hash, one indexed lookup, two rejection conditions — revoked, or past its expiry — and a last-used update that increments a counter in the same round trip. The plaintext key never reaches the database, so a database read cannot produce a working credential.
The useful property for this comparison is that the credential model does not change with the transport. A REST caller typically sends a bearer token; an MCP client sends a header in its config block. Both resolve to the same key id and team id, so quota, budget and audit attach to one identity rather than two — and revoking a key kills the REST path and the tool path at the same moment, which is what an incident response depends on.
mcp vs openapi: how a request is labelled for audit
Once both interfaces are live, the audit trail has to tell them apart:
# backend/smartgate/core/audit_enrichment.py — source lines 35–40 (infer_route)
def infer_route(path: str, route_hdr: str) -> str:
if route_hdr:
return route_hdr
if path.startswith("/mcp"):
return "mcp"
return "rest"
A header wins if present, otherwise the path decides: /mcp is labelled mcp and everything else
rest. Two branches, and the difference between "we had 40,000 calls last month" and "we had
12,000 tool calls from agents and 28,000 REST writes".
OpenAPI solves a related but different problem: it describes the REST surface in a document a generator can consume, which is why it produces good SDKs and good documentation. It has no equivalent for a tool call an agent chose at runtime, and nothing in the document says which client made a call or why. Labelling has to happen where the request arrives, which is why this function exists in the gateway.
api gateway for ai: the policy both paths share
One object holds the limits, and both surfaces read it:
# lib/settings/load-gateway-policy.ts — source lines 10–12 (loadGatewayPolicy)
function loadGatewayPolicy(team: TeamPolicySource): GatewayPolicyV1 {
return parseGatewayPolicy(team.gatewayPolicy);
}
Three lines, and the point is which request reads them. The policy is fetched from the team the authenticated key belongs to, on the request path, so the same per-key rate, daily cap and governance switches apply whether the call arrived as a REST request or a tool call. There is no second policy for the second interface.
That is the property to ask about in any gateway you evaluate. If REST traffic and agent traffic resolve different limit objects, every plan question has two answers and every incident has two timelines. Here it is one object per team, clamped to the plan and read on both paths — which is also why adding the agent surface did not require re-deriving the REST limits.
mcp sampling: a pipeline step that resolves its own inputs
Chaining is where the interfaces diverge most, because someone has to decide what step two receives:
# backend/smartgate/core/pipeline.py — source lines 183–209 (resolve_pipeline_step_params)
def resolve_pipeline_step_params(
module_name: str,
params: Dict[str, Any],
pipeline_ctx: PipelineContext,
run_inputs: Optional[Dict[str, Any]] = None,
pipeline_template: str = "",
) -> Dict[str, Any]:
"""Inject query/url/text across template steps from run inputs and prior results."""
resolved = dict(params)
inputs = run_inputs or {}
if module_name == "search" and not (resolved.get("query") or "").strip():
query = (inputs.get("query") or "").strip()
if pipeline_template == "research":
query = _research_search_query(query)
resolved["query"] = query
if module_name == "fetch" and not (resolved.get("url") or "").strip():
url = (inputs.get("url") or "").strip()
if not url:
for prev in reversed(pipeline_ctx.prior_results()):
if prev.success and prev.data:
url = _first_search_url(prev.data) or ""
if url:
break
if url:
resolved["url"] = url
The rules are per module: a search step with no query takes the run's query, rewritten first under the research template; a fetch step with no URL takes the run's URL or, failing that, the first URL from a prior successful search; a context-gate step with no text takes the run's text or the text of a prior fetch. Each branch checks whether the input is already set, then looks backwards.
Server-side resolution is what MCP buys you here. A REST client would read step one's response, decide what step two needs and issue the second request itself — fine for a service, awkward for a model already several turns deep. MCP also has protocol-level affordances REST has no wire format for, including sampling, where the server asks the client for a model call (MCP spec). The excerpt is the server-side half: whichever surface triggered the run, inputs are resolved once, in one place, and every step is on the audit trail.
windsurf mcp: the config file a client actually ships
The client-side cost of each interface is not symmetric:
# lib/connect/mcp-config-templates.ts — source lines 99–116 (buildWindsurfMcpConfigJson)
function buildWindsurfMcpConfigJson(
mcpUrl: string,
apiKeyPlaceholder: string = API_KEY_PLACEHOLDER,
): string {
return JSON.stringify(
{
mcpServers: {
smartgate: {
type: "streamable-http",
serverUrl: mcpUrl,
headers: buildMcpAuthHeaders(apiKeyPlaceholder, PLATFORM_AGENT_ID.Windsurf),
},
},
},
null,
2,
);
}
A transport name, a server URL and a header block — three fields, serialized into the config file the host expects. Nothing in it describes the tools, because the client will ask for them, so adding a tool on the server needs no client change.
The REST side of the same integration is equally small for the base URL and credential, but the caller also has to know the path, the method and the payload shape, which is why REST integrations ship with generated clients and versioned documents. Neither approach is better; they place the knowledge in different repositories. The MCP version keeps it on the server, where it can change without asking every host to recompile — the trade made when a client config is this short.
token optimization: the policy gate that bounds it
Saving tokens is a permission before it is an algorithm, and the permission is enforced at the policy boundary:
# lib/settings/assert-plan-allows.ts — source lines 40–44 (assertGatewayPolicyEditable)
function assertGatewayPolicyEditable(caps: PlanCapabilities) {
if (!caps.teamGatewayPolicyEditable) {
throw new PlanFeatureDisabledError("PRO", "gateway_policy");
}
}
One capability check, one typed error naming the missing capability and the protected object. If the plan does not grant an editable team policy the caller cannot write limits — and that matters for token optimization specifically, because the levers that reduce spend (a tighter daily cap, a compression ratio, a smaller context window, tool-level governance) are all values in the policy object.
The alternative design is worse in a way that is easy to ship by accident: let anyone set limits and the team with the biggest invoice can raise its own ceiling. Gating the write path makes an optimization a decision with an audit trail, and a downgrade cannot be reversed by editing a field. The check also runs before the mutation, so a rejected edit leaves the previous policy in force.
mcp authorization: HMAC signing for a tool call
Both interfaces have to prove a request was not tampered with, and both use the same primitive:
# lib/crypto.ts — source lines 57–69 (hmacSha256Hex)
async function hmacSha256Hex(key: string, data: string): Promise<string> {
const crypto = ensureCrypto();
const keyBytes = textEncoder.encode(key);
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const sig = await crypto.subtle.sign("HMAC", cryptoKey, textEncoder.encode(data));
return bytesToHex(new Uint8Array(sig));
}
Subtle import, sign, hex-encode. The key is imported as raw bytes with a SHA-256 HMAC and marked non-extractable, so the signing key cannot be read back out of the crypto context afterwards. The signature is deterministic, which is what makes it usable in a header the receiver recomputes.
The MCP specification defines an authorization model for HTTP transports, and its building blocks are the ones every REST service already uses: bearer credentials over TLS, plus a signature where a payload has to be verifiable end to end (MCP authorization). An integrator that needs a budget signed by a party the gateway does not fully trust uses exactly this function. Nothing in it is protocol-specific, which is the honest answer to "does MCP need a different security stack": no, it needs the same one enforced on one more surface.
prompt compression: a tool call that shrinks the payload
The clearest interface difference is a capability the model chooses to use:
# backend/smartgate/api/mcp.py — source lines 150–174 (smart_context_gate)
@server.tool(
name="smart_context_gate",
description=TOOL_DESCRIPTIONS["smart_context_gate"],
annotations=tool_annotations("smart_context_gate"),
)
async def smart_context_gate(
text: str = Field(description="Long text to compress before the host LLM call."),
ratio: float = Field(
default=0.5,
description="Target compression ratio (e.g. 0.3–0.7).",
),
purpose: str | None = Field(
default=None,
description="Optional goal to pre-filter paragraphs (step intent, user query).",
),
) -> str:
_, registry = _app_state()
module = registry.get("context_gate")
ctx = _tool_ctx()
return await _run_with_audit(
"compress",
ctx,
module.process(ctx, text=text, ratio=ratio, purpose=purpose),
{"ratio": ratio, "purpose": purpose},
)
The tool takes long text, a target ratio and an optional purpose, resolves the module from the registry, and returns through the audited path with the arguments recorded. Because it is described in the listing response, a model can decide to compress before it continues — no client code names this operation anywhere.
That is the practical difference between a tool and an endpoint. A REST route for compression runs the same computation behind a path, and calling it requires someone to have written the call. The MCP version makes the capability discoverable and therefore choosable mid-run, which is what the purpose argument is for: the caller pre-filters paragraphs against the current step's intent instead of compressing blindly. The honest limit is that a model can also choose not to use it, which is why the budget cap is enforced in the gateway rather than left to the caller's judgement.
mcp security best practices: masking values before they reach a log
The one control that has to run on every surface is redaction, and recursion is the part people skip:
# backend/smartgate/shared/utils.py — source lines 9–21 (mask_sensitive)
def mask_sensitive(data: Dict[str, Any], keys: set = {"api_key", "secret", "token"}) -> Dict[str, Any]:
"""脱敏敏感字段."""
result = {}
for k, v in data.items():
if k in keys:
result[k] = "***"
elif isinstance(v, dict):
result[k] = mask_sensitive(v, keys)
elif isinstance(v, list):
result[k] = [mask_sensitive(i, keys) if isinstance(i, dict) else i for i in v]
else:
result[k] = v
return result
A set of sensitive key names, a replacement constant, and three cases: the key is sensitive, the value is a nested object, or the value is a list of objects. The list branch calls the same function per element, so a credential nested three levels deep in a tool result is still masked.
Best practice here is a boundary decision rather than a protocol feature. Tool results are arbitrary JSON produced by whatever the tool fetched, and audit rows record parameters. If redaction lives at each caller, the one caller that forgets writes a key into a log with a 180-day retention window. Running it once, where values are serialized for storage, covers the REST path and the tool path with one implementation — and the recursive shape is what makes that claim true rather than aspirational.
How this differs from AI gateway vs API gateway
These two pages sit next to each other and answer different questions, so it is worth saying which is which before either is used to decide anything.
AI gateway vs API gateway asks about layers: both terms describe a gateway, and that page maps which responsibilities a classic API gateway already covers and which ones an AI-facing one adds. This page asks about interfaces: MCP and REST are two ways to expose the same capability, and a gateway is assumed on both sides. If you are deciding where a limit belongs, read the layer page. If you are deciding which surface an agent should call, this is the one.
They share a conclusion, reached from different directions: the policy that decides whether a call is allowed is not a property of the protocol. Both pages end in the same place because both start from shipped code in which one identity, one policy and one audit path serve every surface the gateway terminates.
A model context protocol call and an HTTP call reach the gateway carrying a credential, and every later check reads the identity that one credential produced. The comparison narrows further when both ends are agents rather than a client and a server: the a2a protocol adds a second identity to the same policy path.
How SmartGate compares
Each alternative is described the way its own documentation describes it; the links are sources, not claims from this page.
| What it exposes | How a model discovers it | Where the limits live | |
|---|---|---|---|
| A hand-written REST client per agent | Whatever paths the author wired in | It does not — the client names each call | In the client, per integration, usually not at all |
| REST with an OpenAPI document and a generated client (OpenAPI) | The documented surface, typed | It does not — the generator produced fixed methods | In the service or in front of it, per route |
| An MCP server without a gateway | Tools, discoverable at runtime | From the listing response, with annotations | Nowhere: the server trusts its caller |
| SmartGate — an MCP-native algorithm gateway | Tools over Streamable HTTP, alongside the REST surface | From the listing response, per key | In one policy object, clamped to the plan, enforced per key and per team |
The billing model is the part worth testing against your own invoices: pay for the platform, share only when you save. Free is $0 with 2M tokens a month, the full tool surface, 120 MCP requests a minute per key and 7 days of logs. Pro starts at $18 a month with 20M tokens and 30 days; Teams from $55 with a 100M-token pool and 90 days; Enterprise is contract pricing with a 180-day window. The share applies only once measured savings pass the threshold, and it is capped — pricing page.
How to get started
- Decide by caller, then write it down. If a model chooses the operation at runtime, publish it as a tool; if a human or a scheduler calls it on a schedule, a REST route is cheaper to operate and easier to version.
- Read the listing before you write a client. Call
tools/listonce and check the titles and read-only hints — they are the contract a host acts on, and they are catalogued in the tool reference. - Keep one credential model. The same key should authenticate the REST path and the tool path, or revocation stops being a single action. The details are on the security page.
- Point a host at the endpoint. The per-client blocks, including the transport and header fields above, are on the connect page and the MCP endpoint page.
- Read the neighbours. Gateway mechanics are in MCP gateway, hosting questions in MCP server, and transport or version questions in protocol versions and transports.
- Start on Free. 2M tokens a month and the full tool surface on start free; the API and tooling docs start at the tools docs.
Frequently Asked Questions
Limitations and what this does not do
- This is not a feature matrix. Vendor pages and SDKs move faster than any table, so the links above are the sources; treat this page as a decision procedure instead.
- The excerpts are the gateway's side of the wire. What a given client does with a tool listing, or how a specific SDK shapes function calls, is out of scope and not quoted.
- Server-side chaining concentrates logic. Resolving step inputs in one place is a benefit until that one place needs changing.
- Annotations are hints, not enforcement. A read-only hint shapes what a host will run without asking; the controls that actually stop a call are the plan, the rate limit and the budget cap.
- Redaction is keyed by name. Masking covers the field names in the shared set, so a credential stored under an unexpected key is not masked by that rule.
- MCP is still moving. Version negotiation and the authorization model have changed across revisions; the specification links above are the source of record, not this page.
Sources
- Model Context Protocol — specification (2026-07-28): https://modelcontextprotocol.io/specification/2026-07-28
- Model Context Protocol — server tools (
tools/list,tools/call, annotations): https://modelcontextprotocol.io/specification/2026-07-28/server/tools - Model Context Protocol — transports, including stateless Streamable HTTP: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
- Model Context Protocol — authorization: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization
- Model Context Protocol — the official MCP registry: https://modelcontextprotocol.io/registry/about
- OpenAPI Specification: https://spec.openapis.org/oas/latest.html
- OpenAPI Initiative: https://www.openapis.org/
- SmartGate — docs, tools, connect, pricing: https://smartgate.network/docs · https://smartgate.network/docs/tools · https://smartgate.network/docs/connect · https://smartgate.network/pricing
Method note
The code in this article is not transcribed. Each block was cut directly out of the slice body
returned by the SmartGate slice API and re-asserted byte-for-byte as a substring of that body
before publication; the first line inside every fence records the file and the exact source lines.
Symbols were pinned by whole-name containment (rule A level 2) and confirmed by the service's
slot-proof endpoint before any prose was written. Ten of the twelve excerpts are whole slice
bodies; register_mcp_tools uses two windows into a longer file — the tool declarations at the
top and the pipeline orchestrator's explicit annotation block — and resolve_pipeline_step_params
is windowed to the input-resolution branches, with the helpers around it described rather than
quoted.
Demand figures come from this project's own keyword run, recorded in research_brief.md and
search_volume.json: the section phrases mcp sampling (260), windsurf mcp (170),
token optimization (170), mcp authorization (170), prompt compression (140),
mcp security best practices (90), mcp vs function calling (50), mcp vs rest (50),
function calling vs mcp (30), mcp api key (20), mcp vs openapi (20) and api gateway for ai
(20), each with its competition band. The head term and its difficulty come from the same research
pass.
Slice provenance
| # | SERP keyword | Symbol | File | Source lines | How it was pinned | sha256(12) |
|---|---|---|---|---|---|---|
| 1 | mcp vs function calling | register_mcp_tools |
backend/smartgate/api/mcp.py |
106–126, 302–309 | rule A L2 → slot-proof | 9d4a1623b28c |
| 2 | mcp vs rest | ModuleRegistry |
backend/smartgate/core/registry.py |
11–31 | rule A L2 → slot-proof | de7a246bcdbe |
| 3 | function calling vs mcp | parseParams |
lib/smartgate/audit-logs.ts |
48–63 | rule A L2 → slot-proof | 747608709d38 |
| 4 | mcp api key | validateApiKey |
lib/api-keys/index.ts |
92–118 | rule A L2 → slot-proof | eea805141bb6 |
| 5 | mcp vs openapi | infer_route |
backend/smartgate/core/audit_enrichment.py |
35–40 | rule A L2 → slot-proof | 8cb200979cc6 |
| 6 | api gateway for ai | loadGatewayPolicy |
lib/settings/load-gateway-policy.ts |
10–12 | rule A L2 → slot-proof | 82918a7047cc |
| 7 | mcp sampling | resolve_pipeline_step_params |
backend/smartgate/core/pipeline.py |
183–209 | rule A L2 → slot-proof | 9049b657cb1f |
| 8 | windsurf mcp | buildWindsurfMcpConfigJson |
lib/connect/mcp-config-templates.ts |
99–116 | rule A L2 → slot-proof | 82aabcf99dd1 |
| 9 | token optimization | assertGatewayPolicyEditable |
lib/settings/assert-plan-allows.ts |
40–44 | rule A L2 → slot-proof | 5bf979730866 |
| 10 | mcp authorization | hmacSha256Hex |
lib/crypto.ts |
57–69 | rule A L2 → slot-proof | 0f086a9df54e |
| 11 | prompt compression | smart_context_gate |
backend/smartgate/api/mcp.py |
150–174 | rule A L2 → slot-proof | 39c700a39edd |
| 12 | mcp security best practices | mask_sensitive |
backend/smartgate/shared/utils.py |
9–21 | rule A L2 → slot-proof | 1776f5449b8d |
Every fenced block above was cut from the slice body and re-asserted against it byte-for-byte before publication. 12 of 12 sections pinned, 0 abstentions, 0 misses.