DEV Community

Cover image for AWS re:Invent 2025 - Coding an MCP server for Amazon Aurora (DAT429)
Kazuya
Kazuya

Posted on • Edited on

AWS re:Invent 2025 - Coding an MCP server for Amazon Aurora (DAT429)

🦄 Making great presentations more accessible.
This project enhances multilingual accessibility and discoverability while preserving the original content. Detailed transcriptions and keyframes capture the nuances and technical insights that convey the full value of each session.

Note: A comprehensive list of re:Invent 2025 transcribed articles is available in this Spreadsheet!

Overview

📖 AWS re:Invent 2025 - Coding an MCP server for Amazon Aurora (DAT429)

In this video, Jim and Vlad demonstrate building an MCP (Model Context Protocol) server for Amazon Aurora PostgreSQL. They create a Streamlit entertainment hub application that uses Bedrock LLMs to query movie data through custom MCP tools: get_movie_characters and get_actor_roles. The session covers writing SQL queries with Kiro's assistance, implementing a normalized IMDb data model with joins, and wrapping queries into MCP server tools using Python and psycopg3. A key focus is securing database access through PostgreSQL row-level security (RLS) policies that restrict data visibility based on user roles and title types, ensuring deterministic security controls rather than relying on LLM behavior. The demonstration includes practical debugging, handling messy real-world data, using PostgreSQL comments for schema documentation, and the trade-offs between flexible schema exploration versus pre-defined canned queries for production applications. Code will be shared via GitHub repository.


; This article is entirely auto-generated while preserving the original presentation content as much as possible. Please note that there may be typos or inaccuracies.

Main Part

Thumbnail 0

Introduction: Building an MCP Server for Amazon Aurora with Live Coding

Hi, everybody. I am Jim and I'm Vlad. So today we're going to talk about coding an MCP server for Amazon Aurora. For those of you that have never been to a code talk, this is going to be a little different. You're going to sit here and watch me code for the next hour. I'm a bad typist, so I'm giving you that heads up right from the beginning. I'm going to use Kiro to do a lot of this stuff for me, otherwise you'd be watching me having typos constantly.

Let's keep this interactive and turn this into a giant paired programming exercise. If you see me making typos, shout it out. It's okay, I won't be offended. If I see me making a typo to make things go faster, and if you have questions, let's have them as they're coming along. Vlad's going to be walking around with the microphone. So let's just keep it interactive while we're doing things.

Thumbnail 70

So what we're going to talk about today is MCP, the Model Context Protocol. This is a universal framework in order to be able to expose your backend data to LLMs for Gen AI. It is still a new and maturing specification. It's only a year old, got released about a year ago. The latest spec just came out last week. I haven't had a chance to deep dive on it, but there's a lot of cool security stuff in there that I'm excited about because I thought that was something that was missing before as you were thinking about how do we push this into production and expose things externally outside of your internal infrastructure.

We're going to leverage one of the AWS MCP servers, the Aurora PostgreSQL MCP server. There's a bunch of them around for a lot of different services. They're all on GitHub, so feel free to download and play with them. MCP really is made up of a bunch of tools. You're exposing a bunch of services out to the LLM and it gives you an API in order to be able to do that, and it's more in free text, right?

Thumbnail 130

Thumbnail 140

Thumbnail 160

Really, how the LLMs determine what those tools do is by the description that you type there in human readable format. What you type there really matters in a lot of cases. We're going to use Aurora PostgreSQL. So when we talk about Amazon Aurora, we have Aurora PostgreSQL, Aurora MySQL, and Aurora DSQL. We're going to focus on Aurora PostgreSQL, but the stuff we're going to do today really would work across any of the PostgreSQL variants, even open source community PostgreSQL. We're not doing anything specifically Aurora. Some of the MCP server stuff uses the Data API for it, but the code we're writing is really PostgreSQL code, not necessarily Aurora code.

Thumbnail 190

Thumbnail 200

We're going to leverage Bedrock. Again, this is probably something that you're seeing in a lot of sessions this week, but we're going to use Bedrock to be able to host our models. So what we're building, we're going to start with a real basic Streamlit application. It's going to be an entertainment hub, and then we're going to build an MCP server that has two tools in it. It's going to have a tool called get_movie_characters and get_actor_roles. Those are going to pull data from an Aurora PostgreSQL database, and then Streamlit is going to use Bedrock for the LLM in order to be able to use that data.

Thumbnail 240

The MCP client we're leveraging from Streamlit, right, we're going to start with a simple time MCP server so we can tell what time it is. Those are one of those sample MCP servers that actually end up becoming really, really useful because LLMs don't know what time it is. So if you start asking questions about time, it needs to know what the current time is for different things. If you wanted to say show me all the log history for the last hour, it kind of needs to know what time it is. So it needs a source of truth for that, and it ends up being fairly useful for a lot of different things.

Thumbnail 280

We're going to use a data model. This is from the IMDb dataset. I created a data model that's more of a traditional normalized data model with foreign keys, something that you would expect in your old school legacy OLTP databases. So there's going to be some joins, right? So this way it's not just a simple single table. It needs to think a little bit in order to figure out how do I get this data out, and some of it's ugly data, right?

Thumbnail 330

Thumbnail 360

We're dealing with some messy data here. When certain things are null, what does that mean? That's real life with data models that have evolved over time. Instead of rolling out new columns, sometimes people change the meaning of existing columns as they put in new data types. We're going to focus on securing our database and making sure we do this in a secure way, because opening up your database to an LLM scares me as an old school database person. I want to make sure we're doing this securely and implementing different safeguards along the way to ensure the LLM only does what you intend. You don't want it to be able to query everything and post it out to the internet. If somebody has some prompt injection attacks, we need to protect from those sorts of things.

Thumbnail 370

Thumbnail 380

Thumbnail 420

Thumbnail 430

Thumbnail 440

Thumbnail 450

Exploring the Data Model and Writing SQL Queries with Kiro

So let's start writing some code. This is our application. Just a heads up, at the end of the session I'm going to put a QR code to a GitHub repo that's going to have all the code we're going to write today. This way you can go download it. I'll upload it when I get back home after re:Invent, probably on Monday. But I'll post it all there so everybody will have the link for it. Let me show you a little bit what it is. This is a simple Streamlit app with a simple login page. We have a list of movies, and then we have our MCP server there. If we have a decent internet connection, everything's going to sit there and return the time, so that all seems to be working just fine.

Thumbnail 460

Thumbnail 470

Thumbnail 480

Thumbnail 490

Now let's look at our data model. This is our list of tables. Primarily what we're going to be looking at is the titles table that has really all the information in there. It has a handful of columns including a primary key, a title type, the title, start year, end year, a bunch of indexes, and foreign keys. The other thing that we have here is that I added descriptions for everything that's there inside PostgreSQL using the comment command. We can put in a description that's there. The MCP servers can use that in order to have the right context of what's in there. That's something that when you're working with developers or DBAs, everybody kind of knows what's in all the different columns. So we need to be able to share that knowledge with the LLMs. Using the native tools inside of PostgreSQL that allows you to put comments lets you really be able to jump in and do that.

Thumbnail 550

Thumbnail 560

You have things like your end year, which only TV series is going to have. It's null for everything else. Having that sort of information in there so the LLMs know about it is important. Or this one here, the TS vector for full text search for PostgreSQL. Putting a description of what it is and even how to use it means the LLM knows how to use that sort of column. Outside of just for LLMs, that's a great way for sharing that knowledge with developers. If you need to tell everybody what's in your schema, use comments. We put it inside our source code and on your database objects too. You can put them on tables, you can put them inside PostgreSQL. You can put them on basically every type of object. Use it.

Thumbnail 570

Thumbnail 590

Thumbnail 600

So let's start by creating something. There's a little lag here with the internet. We're going to use Kiro to do all our heavy lifting today. We have a couple of MCP servers. There are some built-in ones. We have the PostgreSQL MCP server and we have the weather MCP server just as something to be able to do it.

Thumbnail 620

Thumbnail 630

Thumbnail 650

Thumbnail 660

Thumbnail 670

I set up a server to verify that the Model Context Protocol (MCP) is working. The cool thing about Kiro is that it actually asks you whether or not you want to perform operations based on whether or not it's trusted. So it returns back the weather. The MCP allows you to have the weather and PostgreSQL all in the same thing, and it will determine what you want to do. I could also ask what tables can I query. It's going to think for a second and then ask if I can run this using the information schema to get the list of tables. I'm also doing this to build up context for Kiro so it knows what tables are available. These are the same tables you see inside of PostgreSQL.

Thumbnail 720

Thumbnail 730

For me as a database person, whenever I'm writing a function that has to pull data from the database, I start with the query. When I'm using tools like Kiro or other ones, I tend to use it the way I code, in the same order and same chunks. I start with writing the queries and then build around that. I want a query that returns all of the characters and the actors who played them for a given movie title. Hopefully the LLM can handle that, and if not, my OCD would drive me crazy for some of these things. Now it's going to ask if you can see the schema. I'm going to use one of the built-in MCP tools to get the table schema and the table definition. I'm going to trust that because it's a query that I know and trust versus just running a random thing.

Thumbnail 750

Is that actually calling an MCP server from Kiro? Yes, it is. That is not related to our effort of pulling data from movies and weather. Weather and movies are part of our build, and then what you're doing is the database portion that's reaching out to the database through a database-specific MCP server. It's using the Aurora PostgreSQL MCP server that I have preconfigured, connecting to my database. I gave it the connection string, user ID, and password so it's all set to connect to a database in my development environment. It's pulling it live. It produced a four-table join here using the full-text search that we're talking about.

Thumbnail 800

Thumbnail 810

Thumbnail 820

Thumbnail 830

Thumbnail 840

Thumbnail 850

Thumbnail 880

Thumbnail 900

We could test the query. Let's run it. You notice there's a little latency as it's going there. If you're thinking about doing text-to-SQL, keep that in mind. The query worked. Here's an improved version that's going to pull out some of the JSON elements. We don't really want that. But the other thing to think about is it's doing a full-text search on the name, and The Godfather has multiple things. They got The Godfather, Godfather 2, and we can forget about Godfather 3. We'll pretend that never happened. But it will return multiple things. It's coming along. Let's make it so it only pulls one particular movie. They're going to do an explain analyze and show you the results. A lot of times, if it's running slow, I'll ask it to do that and say suggest ways to improve the query.

Thumbnail 910

Thumbnail 940

Thumbnail 950

Thumbnail 980

Thumbnail 1000

Thumbnail 1010

Thumbnail 1030

It's pretty good at analyzing query execution plans. It shows that it's using a GIN index and a primary key index. Let's change the query to only return the most popular title if there are multiple matches. So it wants to check the ratings table and put it in there as a subquery. I don't like the fact that it's doing a full-text search twice. Let's have it only use full-text search once. So now it's going to do it as a CTE, which allows it to create pretty sophisticated queries. You need to work with it a little bit to massage it to get what you want, but that's the same way I would write a query. You write something to get the data and then you switch it around and optimize it. Let's just test it again. I never trust when it's going to run SQL directly if I don't know what it will do, so I always wait and check to make sure because sometimes it does weird things. Right, so it works. What I'm doing is saving off that context so we could use it later. One of the things about using these tools is that if something happens and you lose your session, it loses all the context that's there. As you're doing things, you want to build things up, save it off for a while, and be able to use that again later. As I finish one little piece of it, I want to save off that file so I could use it as we're building up the bigger solution.

The Canned Query Pattern: Building Deterministic MCP Tools

There are a lot of tools we saw in the keynote this morning about being able to do this in a much broader way, but as you're learning and starting to use these tools, it's often easier to use it the way you would normally code. That makes it a lot easier to start learning how to do those sorts of things. I was thinking it would take a really long time to be able to just write out a simple query. This happens sometimes, probably if we have a whole bunch of people also using Kiro in one of the workshops or something like that. We have 60,000 people here, probably all playing around with Kiro all at the same time.

Thumbnail 1110

Thumbnail 1140

Where is this MCP server hosted right now? It's just in a subdirectory for my application. It's local. The MCP server does not call on an LLM by itself. Absolutely. How do you preserve the sessions from day one when you come back on day two? You want to preserve the session. That's what you were saying, right? How do you remember the session in Kiro itself? What did you do specifically to save that? This one I just told it to write the query to a file. The prompt I gave it was to write the query to a file called query1.sql. This way it's just saving off the query that we built. So the plan here is that if you're building it over multiple days, you just leave the Kiro IDE open. For the way I'm using it now with the CLI, there are other more sophisticated tools that have longer-term memory. Maybe Vlad knows some of the ones he tends to use those things a little bit more than I do. I'm going beyond VI right now, so this is sophisticated for me.

Thumbnail 1200

You're using the Kiro IDE, so you do have access to multiple sessions, threads, and are able to switch between them and keep different contexts for different tasks. So we're going to write our other query now. Write a query that returns all the roles and the movie titles for a given actor.

Thumbnail 1210

Thumbnail 1230

Thumbnail 1240

Thumbnail 1260

It had its context before, so it's writing a query a lot faster using that same sort of thing, but we also put that in the comment there so this way it knows that I'm thinking it should use that. Except test it again and make sure that's all working. Things usually work, but sometimes it makes some tactical changes, little typos, or sometimes it goes off into some weird loop. It all depends really on what the context is. Right now we're trying to figure some things out because it's trying to run it multiple times. I ask another question.

Thumbnail 1270

On the MCP server that we are building, your plan is actually to write a SQL query and then wrap it in the MCP as a named function, right? And give a description, and that's how the LLM is going to know: "Hey, if I want to have movies, movie list is your MCP server's target definition or something like that, and then you have a SQL query behind it." Yeah, exactly. So we're going to write those two tools and we need two SQL queries. Each one of those tools is going to pull data from the database. I've always done database stuff, so I think in terms of writing the query first and then wrapping it. Other people start with the API and then figure out the other direction. That's just the way that I tend to code. Other people think about it differently, but you could do it either way. That's just the way I'd rather do it.

Thumbnail 1330

Thumbnail 1340

Thumbnail 1350

Thumbnail 1380

When you have the next break, I'll ask my next question and follow-up question as well. So what seems like there's something wrong here with the connection, so I'm just going to write the context out. What Jim is trying to do here is essentially called the canned query pattern. So we're essentially taking queries, wrapping them into tools, and they become deterministic tools. With this MCP server, you can always ask it by hand: "Hey, give me this data from here, here, and here." But what's happening behind the scenes is the MCP agent is going to think about trying to discover the schema, figuring out what tables to ask, what joins you might need to make, and then come up with a query. The query may work or may not work. It'll test it. So you're going through a lot of agent loop cycles just to find the right query that works if you're just doing data exploration.

Thumbnail 1390

Thumbnail 1400

Thumbnail 1410

But if you're writing it for a front-end application, you probably don't want that type of complexity, let alone latency. The application is going to call predictably a certain set of deterministic queries. So let's take those queries and wrap them into tools. Then expose them to any end users. That's what we're trying to do here. But we're starting with the queries. So one way to do it is to expose it as tools, right? So each and every query that you need, you want to expose it as tools.

Thumbnail 1420

Thumbnail 1430

Thumbnail 1440

Thumbnail 1450

The other way is: why can't you basically say these are the tables that are available along with the metadata that he already had, right? And then let the LLM decide how to join those. You absolutely can, but there are two practical implications there. Who are the users of that tool? If it's you or Jim in front of there, then he can afford to go in and let the LLM figure out the query. That's perfectly fine. That's the purpose here. I imagine there's ten thousand Jims sitting there and doing that at that scale. Does that make sense anymore? No, because then you're consuming a lot of tokens. It's going to be very costly for you to offer that capability. But if you know that the people are always going to ask the question about the actors, go ahead and take that query, wrap it into its own tool, and it's always going to call that single tool.

It doesn't need to go through multiple loops of the LLM. It doesn't need to consume extra tokens. It doesn't need to wait and have extra latency. So there's a trade-off there. But what Jim is doing there is giving him flexibility to design the right tools. But if this would be an agent that I would be using as a consumer, that wouldn't be cost effective for whoever is operating that service. So Jim is building the tools for me.

Thumbnail 1510

That service, essentially as a consumer, you can basically say with the default local MCP server, you can let it query the schema and say, "Write an MCP server because that's also an LLM, right? Write an MCP server, look at these tables, but have the tools defined as one for movie and one for actors. Write an MCP server with two tools, right?" Yeah, you can absolutely design that way, or it can be 1000 queries or tools. It doesn't matter, right? It's use case optimal. Correct, yeah.

Thumbnail 1600

Creating and Testing the Movie MCP Server

So writing a prompt now to really write the MCP server. I'm telling it I want to have two tools. I gave it a reference to the weather MCP server as a template. It does a lot better if you point it at something of what you basically want to look like in order to do that. I'm telling it to connect to the database using psycopg3, use the queries that we generate as templates, and then we want to load the connection information from my file called .env. Now I'm going to think about that for a second and it's going to want to go check that tool, that other MCP server so we could see what's there. It's going to go find those queries.

Thumbnail 1620

Thumbnail 1630

Thumbnail 1640

Thumbnail 1650

Thumbnail 1690

Thumbnail 1700

Thumbnail 1720

Thumbnail 1730

Now it's going to build that MCP server writing a lot of Python code a lot faster than I could do it, right? Then I can type it. So this is what it's given us, right. It's creating a movie server with a database connection, right? It's only getting the database URL. We need to change that, right. Here it's pulling in that query that we had, right. Here's the other one pulling in the other query, right, and then that's the main thing. So let's tell it to change the get_db_connection. Like user and password instead of full URL. I'd much rather have distinct things so this way you have to go change your password, right? Or you should do this thing instead of having the full connection string. It's just the way I'd prefer it. So let's see if it did that right. Right, there we go, right, pulling that all together that way. We say, yeah, let's save that off, right. Now we have this movie server. We'll copy the env file here, right. Let's get out of here and let's test that out. Yeah, I got to go up one more.

Thumbnail 1740

Thumbnail 1750

Thumbnail 1760

Thumbnail 1770

Thumbnail 1780

Thumbnail 1790

Now if we could go through this whole session with this thing producing no errors, it would be the first time ever I've run through this thing, right? So I'm anticipating something going wrong, right. Especially since it's, you know, things are non-deterministic as a database person that drives me a little crazy, right, because you don't know what you're going to get. So let's see Star Wars. Right, and because it knows what Star Wars is, it just pulled it off the internet. Right, so we didn't fix. You need to be able to tell the user app to be able to use it. Let's go back to Kiro here. Change directory.

Thumbnail 1820

Thumbnail 1850

Thumbnail 1860

Thumbnail 1870

The way I tend to interact with these things with the prompts is just the same way as if I was talking to somebody next to me and trying to have them code for me, right, assuming my fingers weren't working. If I was going to have somebody do it that knows how to code a little bit, it tends to work. So you're a backseat coder basically, yeah, that's definitely what it is. I'm really embracing the two person coding rule there and having somebody else do all the typing. I'm a terrible typer, as everybody's seeing, right? So now we're adding that in there, added that second MCP server. Right. Let's go try that again. But that's kind of how we're using it too, even inside of RDS and how everybody's starting to use these tools. It is kind of a paired programming thing for a lot of people in order to really accelerate your development program.

Thumbnail 1890

Thumbnail 1900

Thumbnail 1920

Thumbnail 1940

Thumbnail 1960

Thumbnail 1970

Thumbnail 1990

Thumbnail 2000

Thumbnail 2020

Thumbnail 2030

Thumbnail 2040

Thumbnail 2050

Thumbnail 2070

Now let's see if we can find that tool. There we go. Found the tool, get movie characters, so it's running the query in the background. Right, gets back the list of them there and then it displays it. So that's something there that is out on the internet. It's not really showing anything because it is kind of public. So let's go create something there that's not there, right. So I have this simple app here called admin. Instead of you watching me write a bunch of stuff inside of SQL, just this basic thing over top of it. So let's create a new movie called 4:29 Coding and MCP Server. And we'll make this a TV movie. Had a person. It's Jim. Come on. Yeah. No, there we go. And that's Ed Flat. Now, let's put them in that movie. Jim. Everybody in the postgrad community calls me Jimbo, so that's the character name. Let's go, Vlad. And we got Mr. Vlad over there in the back with the microphone. So now we have a movie there that's not out on the internet. Right. That's one. And I just killed my session there. Right. You know, Streamlit's still running. It is. Let's kill this sucker. Let's see if we could go find that movie that we just created because that's only going to be in our database. Right, same as if you had your own internal stuff that the models haven't been trained on, right? Who is in the movie to that 4:29. Boom. I was able to find that. So this way it had enough reference there to be able to know, hey, it's a movie. Let the movie tool so this way you could get that doesn't have to go out the internet to get it. It's pulling off my local database.

Implementing Row-Level Security with PostgreSQL

So even though we're using public information there, a lot of times you want to build these tools, these MCP tools using your own internal stuff that you're not training public models on, but you still want people to interact with, right. But how do we secure this stuff, right? This is still if you're putting this out on the internet, right, if you want to be able to do that you want to be able to secure that a little bit better.

Thumbnail 2110

One comment I would make is that the LLM is not necessarily going out on the Internet and doing a search. When Jim searched for Star Wars, Star Wars was part of a training data set for the model itself. The LLM got invoked, figured that it could answer that question directly without invoking any tools.

Thumbnail 2140

Thumbnail 2150

The first time when Jim asked for Star Wars characters, the LLM actually didn't go through any sort of tools. It simply generated the answer because it had it. Most likely, since Star Wars is such a popular thing, it was most likely in the training data set for that particular model that is used underneath Kiro. But in this case, that wasn't the situation, so that's when it decided that this is potentially an obscure enough movie that it needs to use the tool to access it.

Thumbnail 2200

Thumbnail 2210

It's nondeterministic, so there is no rule that says first ask your tools and then do something if you can't do the other thing. When the agent sends that particular prompt to the LLM, the LLM is going to decide what is the best course of action. You have no control over that at that point. You might have control using steering documents and using the prompt itself. You could potentially tell it to always look for this information in the tools before answering. But even then you don't have a full guarantee that it will do that. Fundamentally, what the LLM responds back is going to be relatively nondeterministic.

This is an interesting question. When doing stuff with MCP, is it possible to create sort of a shadow of knowledge by having the agent select a specific MCP server to ask for a solution that it already would have access to in the model, and therefore you could make it not aware of certain things? Maybe, yes, that's the answer. You can tell the MCP server to use certain tools, and within certain frameworks of building agents, you can define what some of the steps are. For example, if you were using something like LangGraph, you can define nodes that need to be transitioned through before it goes to the LLM for the decision.

In those situations, you could force a step at the beginning to always look into this data set before you invoke the LLM. But that's not MCP that's enforcing that. That's not your tool that's enforcing that. That's the way you're building the agent itself that does that. A lot of that stuff is about how do you force some determinism into something that's inherently nondeterministic. You have to force it in some way, and that's kind of what we're going to show here a little bit too, which is how do we lock down some of this data based off the user that's logged in.

If we look here at this titles table, there are 11 million entries in there. But for this one, we want to lock it down by the title type. Jim could only see title type of one, which is movies. Vlad could see them all, and test two could see one and five. It's like we're using that for titles. A lot of times you might have a tenant ID or some other determining factor of who could see what data based on some roles.

Thumbnail 2380

Using that, we could see if we have this sort of WHERE clause where title type equals any, because this is a PostgreSQL array . We have 700,000 titles in there. Movie titles of one and five, we have 8,872,000. So that's kind of the WHERE clause that we want to inherently have all the time. Putting that something inside of the MCP server and being able to pass that there, it's like having the LLM put the right thing in there. We can never trust that. So we want to make it in a more deterministic way. Tools like PostgreSQL have that capability.

Thumbnail 2420

Thumbnail 2440

Thumbnail 2470

Thumbnail 2490

Thumbnail 2510

Thumbnail 2520

Thumbnail 2530

Thumbnail 2540

Thumbnail 2560

Thumbnail 2590

Thumbnail 2600

Thumbnail 2660

Thumbnail 2670

Thumbnail 2680

Thumbnail 2690

Thumbnail 2700

We want to make this in a more deterministic way. Tools like PostgreSQL have that ability. What we want to do is use low-level security in order to lock that down. I'm going to set an environment variable inside of PostgreSQL. This way we can start building what we want for our policy to be. There's a function called set_config that will set that. So now instead of having that string, we'll get the current setting. Now we set that to just having one, so that should match our 719,000 rows. This is really the WHERE clause that we want to set. So based off what we set that RLS typed environment variable to, that WHERE clause automatically gets appended to it. Great policy RLS types on titles. Let's copy and paste this WHERE clause. What's your table titles. Enable row level security. Now we are currently running as the table's owner, so row level security won't apply to that. Let's connect as the MCP user, not as the MCP owner. When we run that, you get nothing because we have nothing set. When you create your policy for something like row level security, you want to make it so that if things aren't set, you get no data. So now we want to explicitly allow to be able to see some of that. We'll go back and set the config here. Now we could do that again without putting that WHERE clause in there. And we wait that and now we get the right answer there. So row level security with PostgreSQL automatically adds that in there. When we're thinking about things like context like MCP or things where you're not quite sure what's going to happen as things get passed in there, we want to add that determinism in there, especially around securing your data. Things like if you're in the Oracle world, virtual private database, there's things like that. There's other ways of doing that inside the database that we could be sure that's there. Let's use those tools that are there. So let's go into the movie server. We'll go back to and change the. What I'm going to do is take in a command line parameter because that's how this MCP server is taking things in through standard IO. I'm telling you what library to use. Now I get to call it config. Right after making a database connection, use the following as a template. You notice I'm always telling it to use a template because I usually try these things and then once I know it's going to work, I don't let it try to figure it out a second time. I just tell it how to do it. That ends up being a lot faster that way instead of trying to figure it out. I pulled that import in there and set that to none. Right after the connection, we're going to call that with the value that gets passed in. Yes, we're going to allow that merge that patch. Right. And now it's parsing, getting titles type. Yes, we want that.

Thumbnail 2710

Thumbnail 2760

Thumbnail 2770

Thumbnail 2780

Thumbnail 2810

Debugging Security Parameters and Session Wrap-Up

Let's go change the parent directory to pass title types to the MCP server and load entitled types from the users table when logging in. Let's see what it does with that. It's always an adventure with this, seeing what kind of code it's going to spit out. You need to be able to look at things along the way. We'll trust this. We want to be able to see that. So I added that into the select statement, and that's getting there. But that's coming back as a PostgreSQL array, and Python doesn't handle that very well. So I need to change the query to cast titled types as text. We want that coming back as a string so it's easier to pass around. I've run through a bunch of times and encountered a bunch of errors, so I'm just saving everybody the time because I know we have to cast that as text.

Thumbnail 2820

Thumbnail 2860

Thumbnail 2880

Right now it's putting it in the session. Now it's passing in the args that are there. This is another thing I noticed because this thing is in a different memory context, so it won't have access to that session. This is another one of those things I keep bumping into. So entitled types is added to the args as a local variable. I haven't tried this prompt yet. We'll see what happens. I just know that the code it spit out is going to be a problem. There we go. We put it there, and that looks like it'll work a little bit better.

Thumbnail 2890

Thumbnail 2900

Thumbnail 2910

Thumbnail 2920

Thumbnail 2930

Thumbnail 2940

Thumbnail 2950

Thumbnail 2960

Thumbnail 2970

Thumbnail 2980

Now we've got to go up one level. This is taking a lot longer. It's not spitting out any movies or anything there because we have the row level security. We didn't change user_app.py in order to handle the row level security. We're just passing it to the MCP server. So we call the tool, but it couldn't find it. That's because when we created DAT429, that movie, we set it as a type of TV movie, not a regular movie, which has a code of five instead of one. Jim only has access to titles of type one. So if you log in as Vlad, I'm glad I stole your password. Now Vlad has access to all the types, but it still couldn't find it. Maybe that's a session thing. This tells me this isn't a pre-canned demo because the cool stuff didn't run. Let's try this other user, test2, and see if that one works.

Thumbnail 3000

Thumbnail 3010

Thumbnail 3020

Thumbnail 3030

Thumbnail 3050

Thumbnail 3060

Thumbnail 3070

Thumbnail 3080

I still couldn't find it, so the security is locked down pretty good. I'm not quite sure where that's going, but being able to pass in the determinism here is something that the LLM isn't going to be able to do. That's why we're passing it in here through logging it in. So getting it there and then passing it in down here, it's probably that I'm not passing it in through the entitled_types properly. Right, it's probably this sort of thing. Let me just put it here and that's probably going to give us an error, but let's try that. This is the point where I would honestly try logging to see what the variables are, or try mocking. Yeah, we could, but we only have a few minutes left, so going ahead and trying to go through that and having to do all that is a little finicky about how you pass some of that through.

Thumbnail 3090

Thumbnail 3110

There's the error, so that was the error that I was trying to avoid. I can't find that key, the missing key here. That comes down to Python Streamlit stuff that I don't really want to debug in front of everybody here because I don't know where that's going to go and we ask Kiro, it's going to go off into little tangents. But the main idea there is using low-level security in order to lock down your data because that's the deterministic part.

Thumbnail 3120

If the LLM is passing something that's there, it has policies that, as you can see, you get no data if it's sending things in the wrong way. That's what you want the default to be: don't expose your data in some sort of way. You'd rather get no data than put out the wrong data. As we're using these tools and it's moving faster and faster, we have to remember good security practices around our data and the privacy aspect of that. It means leveraging the features inside the database in order to do that, because those are the deterministic parts. If we added another parameter to the tools that's passed in like the user ID or the entitled_types, maybe the LLM will put it in there right. Maybe it won't. It might just say, "Hey, I want access to all the movies," so then it'll try to log in as Vlad. It'll pass in a random string that I'll give you all the things that are there. But that's the main thing that's there.

Thumbnail 3220

Let me flip back over here. That QR code goes to the GitHub repo where I'll post this code that's up there. I will get it working right with that last little bit, and I'll leave comments in the code of what I changed from what we have here in our session, so this way everybody can see what I did quite wrong with passing that thing and the entitled_types parameter across. For folks doing that, you'll be able to see it and you'll be able to run everything. I also have scripts on how to create the database, downloading the files, and being able to recreate everything that we did. It'll be in that GitHub repo probably Monday morning.

When you're doing it in a development environment, we saw a lot of the loops as we were going here, but if that was your main production application, you have basically the internet doing there, and all of a sudden if they ask questions, all of a sudden your bill goes through the roof. You have no way of determining that. It becomes a problem. So like Vlad said, if you're sure you're going to need that context every time, just add it. Vlad and I did a code talk last year about RAG where it was more deterministic of doing some of that stuff where we took movies, created a JSON document, put vectors on it, and then used that for semantics for nearest neighbor searches and semantic searches. That was more deterministic than this, but it was less flexible because we had to pre-compute all the vectors ahead of time. So if we were going to add a movie, we had to go create the vector of that new movie, put it inside the database in order for it to find it. This is just using standard SQL.

Thank you everybody. Please fill out the survey. It really helps us in order to make everything better for you. So please fill out the survey and leave some comments for us, even if it just says hi. That would be cool too. I appreciate it. Thanks everybody. Thank you all.


; This article is entirely auto-generated using Amazon Bedrock.

Top comments (0)