{"id":112464,"date":"2026-10-05T12:00:00","date_gmt":"2026-10-05T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=112464"},"modified":"2026-09-29T15:09:46","modified_gmt":"2026-09-29T15:09:46","slug":"how-to-secure-mcp-servers-database-access-control","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/how-to-secure-mcp-servers-database-access-control\/","title":{"rendered":"How to secure MCP (Model Context Protocol) servers connected to your database &#8211; a practical guide"},"content":{"rendered":"\n<p><strong>AI agents don&#8217;t ask permission before every query. Instead, they themselves decide <em>which<\/em> tools to call and chain together. That&#8217;s a fundamentally different risk model than traditional access control &#8211; and it&#8217;s exactly why <a href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/local-vs-remote-mcp-servers-which-should-you-choose\/\" target=\"_blank\" rel=\"noreferrer noopener\">MCP (Model Context Protocol)<\/a> servers connected to databases need their own security playbook. <\/strong><\/p>\n\n\n\n<p><strong>This guide covers the failure modes to watch for: confused deputy, token passthrough, prompt injection, over-scoped credentials, and session hijacking. Then, how to <em>prevent<\/em> those failure modes &#8211; using authentication, authorization, and least-privilege controls.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-is-mcp-database-access-control-different-to-conventional-access-control\">How is MCP database access control different to conventional access control?<\/h2>\n\n\n\n<p><strong>A <a href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/local-vs-remote-mcp-servers-which-should-you-choose\/\" target=\"_blank\" rel=\"noreferrer noopener\">Model Context Protocol (MCP)<\/a> server that connects to a database is the link between <a href=\"https:\/\/www.ibm.com\/think\/topics\/ai-agents\" target=\"_blank\" rel=\"noreferrer noopener\">AI agents<\/a> and your data. Deploying one requires precise control of who gets which records. <\/strong><\/p>\n\n\n\n<p>Conventional <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-access-control-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">access control<\/a> revolves around monitoring a person. Someone clicks a button, a system performs a permission check, and the action proceeds or fails.<\/p>\n\n\n\n<p>In MCP, on the other hand, an AI agent decides which tools to call, and in what order &#8211; <em>without<\/em> human approval of each step. The agent can chain several tool calls and query a production database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-can-go-wrong-without-database-access-control-the-failure-modes-explained\">What can go wrong <em>without<\/em> database access control (the failure modes explained)<\/h2>\n\n\n\n<p>Let&#8217;s see what failure modes happen <em>without<\/em> access control. Having these in mind allows us to follow access control far more easily.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-the-confused-deputy-problem\">The &#8216;confused deputy&#8217; problem<\/h4>\n\n\n\n<p>The server runs queries with its own <a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/sql-server-privilege-escalation-via-replication-jobs\/\" target=\"_blank\" rel=\"noreferrer noopener\">privileges<\/a> instead of the initiator&#8217;s. The account <em>on<\/em> the server can read entire tables, so a user who can only see their own rows can request everything. <a href=\"https:\/\/modelcontextprotocol.io\/docs\/2026-07-28\/tutorials\/security\/security_best_practices#confused-deputy-problem\" target=\"_blank\" rel=\"noreferrer noopener\">Click here<\/a> for more.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-token-passthrough\">Token passthrough<\/h4>\n\n\n\n<p>The server accepts a token and then forwards the unmodified one to the database. The MCP specification on authorization forbids this. The downstream system cannot confirm the token&#8217;s intended audience. <a href=\"https:\/\/modelcontextprotocol.io\/docs\/2026-07-28\/tutorials\/security\/security_best_practices#token-passthrough\" target=\"_blank\" rel=\"noreferrer noopener\">Passthrough<\/a> also breaks audience checks, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Rate_limiting\" target=\"_blank\" rel=\"noreferrer noopener\">rate limits<\/a>, and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/the-complete-guide-to-auditing-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">auditing<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-in-query-prompt-injection\">In-query prompt injection<\/h4>\n\n\n\n<p>Read-only mode is not a prevention for in-query <a href=\"https:\/\/www.ibm.com\/think\/topics\/prompt-injection\" target=\"_blank\" rel=\"noreferrer noopener\">prompt injection<\/a>. Malicious text stored in a row can instruct an agent to return out-of-reach data. The agent may comply even when it only runs read queries.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-over-scoped-credentials\">Over-scoped credentials<\/h4>\n\n\n\n<p>A single account with deep permissions shared by every user gives every caller deep access.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-session-hijacking\">Session hijacking<\/h4>\n\n\n\n<p>Performing <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/sql-server-authentication-methods\/\" target=\"_blank\" rel=\"noreferrer noopener\">authentication<\/a> on a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Session_ID\" target=\"_blank\" rel=\"noreferrer noopener\">session identifier<\/a> lets an attacker replay or resume a given session (with the identifier). The security guidance for MCP advises against using session-based authentication.<\/p>\n\n\n\n<div id=\"callout-block_4a99826b72a0aaee9fa8bd5c92a02a48\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>More essential reading on Simple Talk<\/strong>&#8230;<\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/\" target=\"_blank\" rel=\"noreferrer noopener\">Click here for Simple Talk&#8217;s full archive of security, privacy and compliance articles and guides.<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-does-mcp-authentication-work\">How does MCP authentication work?<\/h2>\n\n\n\n<p><strong>Authentication in MCP is transport-dependent. Remote servers over <a href=\"https:\/\/en.wikipedia.org\/wiki\/HTTP\" target=\"_blank\" rel=\"noreferrer noopener\">HTTP<\/a> act as <a href=\"https:\/\/oauth.net\/2.1\/\" target=\"_blank\" rel=\"noreferrer noopener\">OAuth 2.1<\/a> resource servers, and a separate authorization server handles login and issues tokens. MCP servers then validate them.<\/strong><\/p>\n\n\n\n<p>The specification requires <a href=\"https:\/\/oauth.net\/2\/pkce\/\" target=\"_blank\" rel=\"noreferrer noopener\">Proof Key for Code Exchange (PKCE)<\/a> with <a href=\"https:\/\/www.movable-type.co.uk\/scripts\/sha256.html\" target=\"_blank\" rel=\"noreferrer noopener\">SHA-256<\/a>, HTTPS on every authorization endpoint, and strict redirect <a href=\"https:\/\/en.wikipedia.org\/wiki\/Uniform_Resource_Identifier\" target=\"_blank\" rel=\"noreferrer noopener\">URI (Uniform Resource Identifier)<\/a> validation with two supported discovery mechanisms:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The server publishes <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc9728\" target=\"_blank\" rel=\"noreferrer noopener\">Protected Resource Metadata (RFC 9728)<\/a> at <code>\/.well-known\/oauth-protected-resource<\/code><br><br><\/li>\n\n\n\n<li>The server returns a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Reference\/Headers\/WWW-Authenticate\" target=\"_blank\" rel=\"noreferrer noopener\"><code>WWW-Authenticate<\/code><\/a> header on a 401 response<\/li>\n<\/ul>\n<\/div>\n\n\n<p>This way, a client knows where to authenticate. Clients like Claude Desktop, Cursor, and Claude Code use this method.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-additional-tips\">Additional tips<\/h3>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Always validate the audience. Reject tokens with the <a href=\"https:\/\/workos.com\/blog\/what-is-the-aud-claim-in-identity\" target=\"_blank\" rel=\"noreferrer noopener\"><code>aud<\/code> claim<\/a> that <em>do not<\/em> contain your server, even with signed and\/or unexpired tokens.<br><br><\/li>\n\n\n\n<li>Local servers over <code>stdio<\/code> (standard input\/output) work differently. Do <em>not<\/em> run the OAuth flow. Read <a href=\"https:\/\/en.wikipedia.org\/wiki\/Environment_variable\" target=\"_blank\" rel=\"noreferrer noopener\">environment variables<\/a> for credentials.<br><br><\/li>\n\n\n\n<li>Keep the clocks synchronized. Both the MCP server <em>and<\/em> the authorization server should agree on <code><a href=\"https:\/\/mojoauth.com\/blog\/understanding-jwt-expiration-time-claim-exp\" target=\"_blank\" rel=\"noreferrer noopener\">exp<\/a><\/code> and <code>nbf<\/code> claims.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-you-should-identity-propagation-for-users-agents-and-tools\">Why you should identity propagation for users, agents, and tools<\/h2>\n\n\n\n<p><strong>The user (or its role) should be forwarded all the way down through context to the database, so that <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-rls-setup\/\" target=\"_blank\" rel=\"noreferrer noopener\">row-level security (RLS)<\/a> and r<a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/security\/sql-server-security-providing-a-security-model-using-user-defined-roles\/\" target=\"_blank\" rel=\"noreferrer noopener\">ole-based access control (RBAC)<\/a> can act on real identities.<\/strong><\/p>\n\n\n\n<p>Quite often, the opposite of this is happening. The user is authenticated well but there&#8217;s then a wholly-opened and shared database connection for the whole server. The database only &#8220;sees&#8221; the server and can&#8217;t make a difference <em>between<\/em> users.<\/p>\n\n\n\n<p><strong>Agents and tools are <em>also<\/em> principals, so should have their own identities rather than using those of a person. This is called <a href=\"https:\/\/www.microsoft.com\/en-us\/security\/business\/security-101\/what-are-non-human-identities\" target=\"_blank\" rel=\"noreferrer noopener\">non-human identity (NHI)<\/a>.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-control-what-each-caller-can-do-with-authorization\">How to control what each caller can do with authorization<\/h2>\n\n\n\n<p><strong><em>Authentication<\/em> is a process to get a verified identity, whereas <em>authorization<\/em> decides what that identity can do with specific levels.<\/strong><\/p>\n\n\n\n<p>Scopes define boundaries; just one scope should <em>not<\/em> have both read query and a destructive write. They should be separated.<\/p>\n\n\n\n<p>Additionally, server-level access should be a minimum. Deciding who can connect does not prevent a connected user to call a tool beyond their limits. Scope each user or role to a specific set of tools or operations.<\/p>\n\n\n\n<p>Finally, a per-client consent registry records which applications each user approved, and the server denies requests that don&#8217;t match an approval. Start narrow and widen when necessary. This is also known as the <a href=\"https:\/\/www.red-gate.com\/hub\/product-learning\/flyway\/the-importance-of-access-checks-and-controls-in-database-development\/\" target=\"_blank\" rel=\"noreferrer noopener\">principle of least privilege (PoLP)<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-is-it-important-to-enforce-least-privilege\">Why is it important to enforce least privilege?<\/h2>\n\n\n\n<p><strong>The MCP server is not a security firewall. Enforce the same policies at the database; find out where your sensitive data is <em>before<\/em> scoping anything.<\/strong><\/p>\n\n\n\n<p>To help with this, you can use data classification tools such as <a href=\"https:\/\/www.red-gate.com\/products\/sql-data-catalog\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redgate SQL Data Catalog<\/a>, which tags any columns containing <a href=\"https:\/\/www.ibm.com\/think\/topics\/pii\" target=\"_blank\" rel=\"noreferrer noopener\">regulated or personal data<\/a>.<\/p>\n\n\n\n<p>Apply the following database controls:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Grant read-only access by default.<br><br><\/li>\n\n\n\n<li>Assign one narrow role per user.<br><br><\/li>\n\n\n\n<li>Combine per-user connections with RLS (row-level security).<br><br><\/li>\n\n\n\n<li>Set RLS (row-level security) to <em><a href=\"https:\/\/www.red-gate.com\/simple-talk\/podcasts\/security-vs-speed-in-databases\/\" target=\"_blank\" rel=\"noreferrer noopener\">deny-by-default<\/a><\/em>.<br><br><\/li>\n\n\n\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/style-guide\/a-z-word-list-term-collections\/a\/allow-list\" target=\"_blank\" rel=\"noreferrer noopener\">Allowlist<\/a> query types for the tool.<br><br><\/li>\n\n\n\n<li>Send query traffic to a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/designing-highly-scalable-database-architectures\/#database-read-replicas:~:text=the%20preferred%20approach.-,Database%20Read%20Replicas,-A%20read%20replica\" target=\"_blank\" rel=\"noreferrer noopener\">read replica<\/a>.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>To note, none of these controls stop prompt injection on their own. While read-only mode <em>does<\/em> block writes, it does <em>not<\/em> prevent an agent from returning data that has already been received.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-govern-the-write\">How to govern the write<\/h2>\n\n\n\n<p>The control mentioned above is what an agent can <em>read<\/em>, but doesn&#8217;t cover what <em>changes<\/em> an agent can make. When an agent can modify schema or data, those changes should be routed through governed <a href=\"https:\/\/en.wikipedia.org\/wiki\/Change_control\" target=\"_blank\" rel=\"noreferrer noopener\">change control<\/a> instead of letting it run ad-hoc <a href=\"https:\/\/www.red-gate.com\/simple-talk\/other\/what-is-sql\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL<\/a>.<\/p>\n\n\n\n<p><strong>Governed change control gives each change its own version, validation step, a record of who (or what) made it, and why it was made.<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/products\/flyway\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redgate Flyway Enterprise<\/a> approaches this at the database layer. It applies version-controlled, automated and &#8211; most importantly &#8211; <em>deterministic<\/em> changes, rather than manual edits.<\/p>\n\n\n\n<p>In addition, the <a href=\"https:\/\/www.red-gate.com\/blog\/introducing-the-flyway-mcp-server-governed-database-change-now-available-to-your-ai-coding-assistant\/\" target=\"_blank\" rel=\"noreferrer noopener\">Flyway Enterprise MCP server<\/a> extends that model to agents, so proposing agentic changes are routed through a governed pipeline used by developers.<\/p>\n\n\n\n<p>That pipeline captures, validates, and records everything. Put simply: compliance is contained <em>inside<\/em> the workflow, rather than being added afterwards.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-in-summary-here-s-how-you-should-govern-the-write\">In summary, here&#8217;s how you should govern the write:<\/h4>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Deny direct schema and data changes at the MCP tool layer &#8211; and only expose change operations through the governed pipeline. An agent should <em>not<\/em> be permitted to run raw <code>ALTER TABLE<\/code> or <code>UPDATE<\/code> commands against production.<br><br><\/li>\n\n\n\n<li>Connect change control to observability, ensuring that recorded changes line up with the effects on the system. For a wider view, you can connect Flyway Enterprise with <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redgate Monitor<\/a>.<\/li>\n<\/ul>\n<\/div>\n\n\n<section id=\"my-first-block-block_ac9a8c87a7a81f8feda4f41280ab7fa1\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Future-proof database monitoring with Redgate Monitor<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Multi-platform database observability for your entire estate. Optimize performance, ensure security, and mitigate potential risks with fast deep-dive analysis, intelligent alerting, and AI-powered insights.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Future-proof database monitoring with Redgate Monitor\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-defaults-you-should-use-to-secure-mcp-database-access\">The defaults you should use to secure MCP database access<\/h2>\n\n\n\n<p><strong>Here are the defaults I recommend to secure your MCP database access, with the aim of giving you as safe a start as possible:<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Ship read-only; only add write scopes when the workflow requires them.<br><br><\/li>\n\n\n\n<li>Validate the token audience.<br><br><\/li>\n\n\n\n<li>Refuse passthrough.<br><br><\/li>\n\n\n\n<li>Choose a scope naming convention.<br><br><\/li>\n\n\n\n<li>Enable RLS with <a href=\"https:\/\/csrc.nist.gov\/glossary\/term\/deny_by_default\" target=\"_blank\" rel=\"noreferrer noopener\">deny-by-default<\/a>.<br><br><\/li>\n\n\n\n<li>Do <em>not<\/em> use session-based authentication.<br><br><\/li>\n\n\n\n<li>Keep all server clocks synchronized. <\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-avoid-anti-patterns\">How to avoid anti-patterns<\/h2>\n\n\n\n<p><strong>Finally, to avoid anti-patterns, you should:<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Only have <em>one<\/em> shared account with broad rights for every caller.<br><br><\/li>\n\n\n\n<li>Omit the tenant filter in an agent-generated query.<br><br><\/li>\n\n\n\n<li>Assume read-only mode stops prompt injection.<br><br><\/li>\n\n\n\n<li>Authenticate on a session identifier.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-in-summary-mcp-access-control-the-key-takeaways\">In summary: MCP access control (the key takeaways)<\/h2>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Authenticate with OAuth for remote servers, and use environment credentials for local servers. Validate token audience on every request. Never forward a client token to the database.<br><br><\/li>\n\n\n\n<li>Authorize per tool &#8211; not only per-server. A user who can connect != a user who can delete stuff.<br><br><\/li>\n\n\n\n<li>Apply least privilege at the database. Grant read-only access by default.<br><br><\/li>\n\n\n\n<li>Log the <em>full<\/em> chain of command &#8211; including user, agent, tool, query, and row.<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Remember: authentication verifies <em>who<\/em> you are, while authorization determines <em>what<\/em> you&#8217;re allowed to access or do.<\/strong><\/p>\n\n\n\n<section id=\"my-first-block-block_697e2613a046dfa6025c2bb6e815145d\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the &quot;confused deputy&quot; problem in MCP?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">It happens when an MCP server runs database queries using its own privileges instead of the requesting user&#8217;s. Because the server&#8217;s account can often read entire tables, a user who should only see their own rows can end up requesting \u2014 and receiving \u2014 everything.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Does read-only mode prevent prompt injection?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">No. Read-only mode stops an agent from writing or modifying data, but it doesn&#8217;t stop malicious text stored in a database row from instructing the agent to return data it shouldn&#8217;t. Preventing this requires access control at the query and row level, not just blocking writes.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Why shouldn&#039;t an MCP server forward a client&#039;s token to the database?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">This is called token passthrough, and the MCP specification&#8217;s authorization guidance forbids it. The downstream system can&#8217;t verify who the token was actually intended for, which breaks audience checks, rate limiting, and audit logging.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How is authentication different for remote vs. local MCP servers?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Remote servers over HTTP act as OAuth 2.1 resource servers, using PKCE, HTTPS, and strict redirect URI validation. Local servers over stdio skip the OAuth flow entirely and instead read credentials from environment variables.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. What does &quot;least privilege&quot; look like in practice for MCP database access?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Grant read-only access by default, assign one narrow role per user, combine per-user connections with row-level security (RLS) set to deny-by-default, allowlist query types, and route production writes through governed change control rather than ad-hoc SQL.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to secure MCP database access: stop confused deputy attacks, token passthrough, and prompt injection with proper auth and least privilege.&hellip;<\/p>\n","protected":false},"author":346911,"featured_media":109395,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,46],"tags":[4168,4170,4619,5765],"coauthors":[159385],"class_list":["post-112464","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-data-security-privacy-compliance","tag-database","tag-database-administration","tag-security","tag-security-and-compliance"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112464","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/346911"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=112464"}],"version-history":[{"count":10,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112464\/revisions"}],"predecessor-version":[{"id":112581,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112464\/revisions\/112581"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/109395"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=112464"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=112464"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=112464"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=112464"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}