Connecting AI Payment Agents to RisingWave via MCP

Connecting AI Payment Agents to RisingWave via MCP

AI payment agents are no longer a slide deck. Stripe shipped the Agent Toolkit. Mastercard announced agent integrations across its network. Visa published its agentic commerce framework. Builders are wiring their own agents on top of LangChain, the OpenAI Agents SDK, and Anthropic's tool-use primitives. The agent layer is real, and it is starting to move money.

The hard part is not getting an agent to call a payment API. The hard part is getting it to make a defensible decision before it does. A responsible payment agent should know how much the user has spent in the last twenty-four hours, which mandates are still active, what the current fraud risk score looks like, and whether the merchant is in line with the user's history. None of that lives in a static prompt or a vector database. It lives in event streams that update every second.

This post walks through how an AI payment agent gets that real-time context from RisingWave through the Model Context Protocol (MCP). RisingWave is a streaming database that maintains materialized views incrementally and speaks PostgreSQL on the wire, which means any PostgreSQL MCP server works against it without modification. Each materialized view becomes a discoverable tool. The agent calls get_user_spending and gets sub-second-fresh aggregates. It calls get_active_mandates and gets remaining budget per mandate. The full pipeline is SQL plus a config file. We will build all four "tool" views, run them against real data, and show the JSON config that turns RisingWave into a tool source for any MCP-aware agent.

Why AI Payment Agents Need a Real-Time Data Layer

A payment agent stands at a decision point that is fundamentally different from a chat agent. A chat agent that hallucinates loses trust. A payment agent that hallucinates loses money. The blast radius is direct, irreversible, and visible on the user's statement.

To make a defensible authorize-or-decline decision, an agent needs four classes of context. None of them tolerate stale data.

Recent spending. Did the user already spend $400 today through this agent or another agent? A daily mandate of $500 is meaningless if the agent cannot see what the agent next door already authorized fifteen minutes ago. Hourly batch refreshes are too slow because users transact faster than that.

Mandate status. Mandates are the agentic-commerce primitive that says "this agent may spend up to $X for this user, scoped to this category, until this date." Mandates can be paused, revoked, or expired in real time by the user, by a fraud system, or by an issuing bank. An agent that operates on a mandate snapshot from this morning will happily authorize a payment under a mandate that was revoked an hour ago.

Risk signal. Fraud risk is not a property of a transaction. It is a property of the user's recent stream of behavior. Velocity, merchant diversity, geography, time-of-day distribution. A risk score that only updates every fifteen minutes is operationally blind during exactly the window when fraud rings move fastest.

Peer behavior. "Is this purchase normal for this user?" is a question that must compare a single proposed transaction to the user's last few hundred actions plus a peer cohort. The reference distribution shifts hourly as new payments arrive.

The architecture mistake teams make is to treat this context as something to fetch from a feature store or a data warehouse. Both options serve data that was correct at compute time, not at decision time. A streaming database closes that gap. It maintains the answer continuously and serves it on read with the freshness of the most recent event. For a deeper treatment of why agents in general need streaming context, see our piece on why AI agents need real-time data.

What Is MCP (Model Context Protocol)?

MCP is an open standard from Anthropic for connecting AI agents to external tools and data. The full specification lives at modelcontextprotocol.io. The mental model is simple: rather than hard-coding a list of functions into the agent's prompt, the agent connects to one or more MCP servers, asks each one "what tools do you have?", receives a typed schema for each tool, and then calls tools on demand during a reasoning loop.

The protocol distinguishes three primitives:

  • Tools are callable functions, model-controlled. The agent decides when to invoke them. get_user_spending(user_id) is a tool.
  • Resources are static or streamable data sources, app-controlled. A document the user attached is a resource.
  • Prompts are templates, user-controlled. A "summarize this thread" workflow is a prompt.

For a payment agent, tools are the load-bearing primitive. The agent's job is to gather facts, reason about whether to authorize, and call the payment API only if everything checks out. Each fact-gathering step is a tool call.

The reason MCP matters specifically for payment agents is that it decouples the data layer from the agent layer. You can swap the agent (Stripe Agent Toolkit today, a custom OpenAI Agents SDK pipeline tomorrow) without rewriting tool plumbing. You can swap the data source (Postgres for development, RisingWave for production) without touching the agent. The contract is the tool schema. Both sides target the schema, not each other.

The Stripe team has published Stripe Agent Toolkit which exposes Stripe operations to agents, and Anthropic, OpenAI, and Mastercard have all built MCP-compatible surfaces. The ecosystem is converging on this protocol fast, which means the work you do to expose your data through MCP today will keep paying off as new agent frameworks ship.

How Materialized Views Become Agent Tools

The pattern that makes RisingWave a clean fit for payment agents is this: write one materialized view per "thing the agent should be able to ask," give the view a name that reads like a tool name, and let the MCP server discover it.

A PostgreSQL MCP server typically does two things at startup. It introspects information_schema to enumerate tables and views in the connected database, and it exposes each one as a callable tool whose input is a SQL query and whose output is the result set. More sophisticated servers expose each view as a typed tool whose name is the view name and whose arguments are the columns the agent should filter on.

Either way, the practical consequence is that adding a new tool is a CREATE MATERIALIZED VIEW statement. No separate tool registry. No Python wrapper. No API gateway in the path. You write SQL, and the agent gains a new capability.

This matters more than it sounds. In most agent stacks, "give the agent a new piece of context" is a multi-day project: define a feature, train it, deploy it, register it with the orchestrator, version it. With RisingWave plus an MCP server, the same change is one DDL statement that the database materializes incrementally from the same upstream events you already have flowing through Kafka. We have written about this pattern more broadly in MCP streaming database: connect AI agents to live data with RisingWave.

A naming convention helps the agent discover the right tool. We prefix view names with the verb the agent will use: get_user_spending, get_active_mandates, get_user_risk_score, get_agent_recent_payments. The agent's tool selection step picks the view by name. The agent's tool call step issues a SELECT against it.

Setup: PostgreSQL MCP Server + RisingWave

RisingWave speaks the PostgreSQL wire protocol on port 4566. Any MCP server that connects to PostgreSQL works against RisingWave with no code changes. Here is the configuration snippet you drop into your MCP client (Claude Desktop, Cursor, Continue, or any other MCP-aware host) to wire it up.

{
  "mcpServers": {
    "risingwave-payment-context": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://root@localhost:4566/dev"
      ]
    }
  }
}

Three things to notice. First, the connection string is just a Postgres URL with port 4566 and database dev. Second, the user is root with no password, which is the default for a local RisingWave instance; in production you will use a scoped read-only role. Third, there is nothing RisingWave-specific in this config. The MCP server has no idea it is talking to a streaming database. It introspects schemas, finds tables and views, and exposes them.

Once this server is registered, the agent host lists all materialized views in the dev database as discoverable tools. Every view we create in the next section appears automatically. To remove a tool, you DROP MATERIALIZED VIEW. To add one, you CREATE MATERIALIZED VIEW. The agent's available toolset and the database schema are the same artifact.

A quick note on safety. The connection above gives the agent the same access as the root user. For a real deployment, create a role that has SELECT only on the views the agent should use, and connect the MCP server as that role. RisingWave's role and grant model is the standard PostgreSQL one, so this works the same way it does for application Postgres.

Building Agent-Tool Materialized Views

We will build four views that together give a payment agent enough context to make authorize-or-decline decisions. The schema below uses a aap18_ prefix purely so the example does not collide with anything else in the database; in your own deployment you would drop the prefix and let the view names read directly as tool names.

Step 1: Source Tables

The agent does not write to these tables. It reads from views built on top of them. In production these tables would themselves be CDC sources, Kafka tables, or webhook sinks. For the example we use plain tables and INSERT a realistic batch.

CREATE TABLE aap18_payments (
  payment_id INT PRIMARY KEY,
  agent_id VARCHAR,
  user_id VARCHAR,
  merchant VARCHAR,
  amount DECIMAL,
  payment_time TIMESTAMPTZ
);

CREATE TABLE aap18_mandates (
  mandate_id INT PRIMARY KEY,
  agent_id VARCHAR,
  user_id VARCHAR,
  scope_max_amount DECIMAL,
  valid_to TIMESTAMPTZ,
  status VARCHAR
);

We seed the tables with eight mandates spanning three agents (stripe_agent_a, mc_agent_x, custom_agent_1) and five users, plus twenty-five payments distributed across the last forty-eight hours. The seed data covers expired mandates, paused mandates, mandates that have been overspent, and bursty in-the-last-five-minutes activity. Full INSERT statements are below.

INSERT INTO aap18_mandates VALUES
  (1, 'stripe_agent_a', 'user_alice',   500.00, NOW() + INTERVAL '30 days', 'active'),
  (2, 'stripe_agent_a', 'user_bob',     200.00, NOW() + INTERVAL '7 days',  'active'),
  (3, 'mc_agent_x',     'user_carol',  1500.00, NOW() + INTERVAL '60 days', 'active'),
  (4, 'mc_agent_x',     'user_dave',    300.00, NOW() - INTERVAL '1 day',   'expired'),
  (5, 'custom_agent_1', 'user_eve',     800.00, NOW() + INTERVAL '14 days', 'active'),
  (6, 'stripe_agent_a', 'user_alice',   100.00, NOW() + INTERVAL '90 days', 'active'),
  (7, 'mc_agent_x',     'user_bob',     250.00, NOW() + INTERVAL '15 days', 'paused'),
  (8, 'custom_agent_1', 'user_carol',   600.00, NOW() + INTERVAL '21 days', 'active');

INSERT INTO aap18_payments VALUES
  (101, 'stripe_agent_a', 'user_alice',   'Amazon',         42.99,  NOW() - INTERVAL '2 hours'),
  (102, 'stripe_agent_a', 'user_alice',   'Uber',           18.50,  NOW() - INTERVAL '5 hours'),
  (103, 'stripe_agent_a', 'user_alice',   'Whole Foods',    87.32,  NOW() - INTERVAL '12 hours'),
  (104, 'stripe_agent_a', 'user_bob',     'Spotify',         9.99,  NOW() - INTERVAL '3 hours'),
  (105, 'stripe_agent_a', 'user_bob',     'Netflix',        15.49,  NOW() - INTERVAL '20 hours'),
  (106, 'mc_agent_x',     'user_carol',   'Delta Airlines',412.00,  NOW() - INTERVAL '6 hours'),
  (107, 'mc_agent_x',     'user_carol',   'Marriott',      289.50,  NOW() - INTERVAL '8 hours'),
  (108, 'mc_agent_x',     'user_carol',   'OpenTable',      72.40,  NOW() - INTERVAL '14 hours'),
  (109, 'mc_agent_x',     'user_dave',    'Best Buy',      149.99,  NOW() - INTERVAL '30 hours'),
  (110, 'custom_agent_1', 'user_eve',     'Etsy',           34.00,  NOW() - INTERVAL '1 hour'),
  (111, 'custom_agent_1', 'user_eve',     'Apple Store',   199.00,  NOW() - INTERVAL '4 hours'),
  (112, 'custom_agent_1', 'user_eve',     'GrubHub',        28.75,  NOW() - INTERVAL '7 hours'),
  (113, 'stripe_agent_a', 'user_alice',   'Target',         63.20,  NOW() - INTERVAL '18 hours'),
  (114, 'mc_agent_x',     'user_carol',   'Lyft',           22.10,  NOW() - INTERVAL '22 hours'),
  (115, 'stripe_agent_a', 'user_bob',     'DoorDash',       31.85,  NOW() - INTERVAL '9 hours'),
  (116, 'custom_agent_1', 'user_carol',   'Sephora',        88.00,  NOW() - INTERVAL '2 hours'),
  (117, 'stripe_agent_a', 'user_alice',   'Starbucks',       6.45,  NOW() - INTERVAL '30 minutes'),
  (118, 'stripe_agent_a', 'user_alice',   'Starbucks',       7.10,  NOW() - INTERVAL '15 minutes'),
  (119, 'stripe_agent_a', 'user_alice',   'Starbucks',       6.85,  NOW() - INTERVAL '5 minutes'),
  (120, 'mc_agent_x',     'user_carol',   'Hertz',         145.00,  NOW() - INTERVAL '11 hours'),
  (121, 'stripe_agent_a', 'user_bob',     'Walmart',        52.75,  NOW() - INTERVAL '13 hours'),
  (122, 'custom_agent_1', 'user_eve',     'Amazon',         71.30,  NOW() - INTERVAL '16 hours'),
  (123, 'mc_agent_x',     'user_dave',    'Costco',        178.45,  NOW() - INTERVAL '40 hours'),
  (124, 'stripe_agent_a', 'user_alice',   'Trader Joes',    54.20,  NOW() - INTERVAL '21 hours'),
  (125, 'custom_agent_1', 'user_carol',   'Nordstrom',     267.00,  NOW() - INTERVAL '3 hours');

Tool 1: get_user_spending

This view returns per-user aggregates over the last twenty-four hours. The agent calls it with a WHERE user_id = ? filter to ask "how much has this user spent in the last day, across how many transactions, with what average and max ticket size?"

CREATE MATERIALIZED VIEW aap18_get_user_spending AS
SELECT
  user_id,
  COUNT(*) AS payment_count_24h,
  ROUND(SUM(amount), 2) AS total_spent_24h,
  ROUND(AVG(amount), 2) AS avg_amount_24h,
  ROUND(MAX(amount), 2) AS max_amount_24h
FROM aap18_payments
WHERE payment_time >= NOW() - INTERVAL '24 hours'
GROUP BY user_id;

Real output from our seed data:

  user_id   | payment_count_24h | total_spent_24h | avg_amount_24h | max_amount_24h
------------+-------------------+-----------------+----------------+----------------
 user_alice |                 8 |          286.61 |          35.83 |          87.32
 user_bob   |                 4 |          110.08 |          27.52 |          52.75
 user_carol |                 7 |         1296.00 |         185.14 |         412.00
 user_eve   |                 4 |          333.05 |          83.26 |         199.00

Notice that user_carol has spent over $1,296 in the past day across travel-related merchants. The agent gets that fact in one tool call.

Tool 2: get_active_mandates

The mandate view is the agent's "am I allowed to spend this much, right now, for this user?" check. We join active mandates against the user's payments to compute remaining budget per mandate. Expired and paused mandates are filtered out at the view level so the agent cannot accidentally spend against them.

CREATE MATERIALIZED VIEW aap18_get_active_mandates AS
SELECT
  m.user_id,
  m.agent_id,
  m.mandate_id,
  m.scope_max_amount,
  m.valid_to,
  COALESCE(ROUND(SUM(p.amount), 2), 0) AS spent_under_mandate,
  ROUND(m.scope_max_amount - COALESCE(SUM(p.amount), 0), 2) AS remaining_budget
FROM aap18_mandates m
LEFT JOIN aap18_payments p
  ON m.user_id = p.user_id AND m.agent_id = p.agent_id
WHERE m.status = 'active' AND m.valid_to > NOW()
GROUP BY m.user_id, m.agent_id, m.mandate_id, m.scope_max_amount, m.valid_to;

Real output:

  user_id   |    agent_id    | mandate_id | scope_max_amount | spent_under_mandate | remaining_budget
------------+----------------+------------+------------------+---------------------+------------------
 user_alice | stripe_agent_a |          1 |              500 |              286.61 |           213.39
 user_alice | stripe_agent_a |          6 |              100 |              286.61 |          -186.61
 user_bob   | stripe_agent_a |          2 |              200 |              110.08 |            89.92
 user_carol | mc_agent_x     |          3 |             1500 |              941.00 |           559.00
 user_carol | custom_agent_1 |          8 |              600 |              355.00 |           245.00
 user_eve   | custom_agent_1 |          5 |              800 |              333.05 |           466.95

The negative remaining budget on user_alice's smaller $100 mandate is informative. The user has two overlapping mandates with the same agent and the agent has already spent past the smaller one's cap. A well-built agent reasoning step would notice that and either decline or escalate, depending on how the mandates are scoped.

Tool 3: get_user_risk_score

This is a deliberately simple risk model. Real risk models combine velocity, geography, merchant category mismatches, device signals, and a calibrated scoring backbone. For the purpose of showing how risk surfaces as a tool, we approximate with a transaction-count and amount blend.

CREATE MATERIALIZED VIEW aap18_get_user_risk_score AS
SELECT
  user_id,
  COUNT(*) AS tx_count_24h,
  ROUND(SUM(amount), 2) AS total_amount_24h,
  COUNT(DISTINCT merchant) AS unique_merchants_24h,
  CASE
    WHEN COUNT(*) >= 8 OR SUM(amount) >= 500 THEN 'high'
    WHEN COUNT(*) >= 4 OR SUM(amount) >= 200 THEN 'medium'
    ELSE 'low'
  END AS risk_band,
  LEAST(100, (COUNT(*) * 5 + (SUM(amount) / 10)::INT)) AS risk_score
FROM aap18_payments
WHERE payment_time >= NOW() - INTERVAL '24 hours'
GROUP BY user_id;

Real output:

  user_id   | tx_count_24h | total_amount_24h | unique_merchants_24h | risk_band | risk_score
------------+--------------+------------------+----------------------+-----------+------------
 user_carol |            7 |          1296.00 |                    7 | high      |        100
 user_alice |            8 |           286.61 |                    6 | high      |         69
 user_eve   |            4 |           333.05 |                    4 | medium    |         53
 user_bob   |            4 |           110.08 |                    4 | medium    |         31

The agent does not need to know how the score is computed. It calls the tool, reads the band, and feeds the answer into its decision step.

Tool 4: get_agent_recent_payments

The previous three views are user-centric. This one is agent-centric. It tells you what an agent has been doing, across all users, in the last twenty-four hours. This is useful when a fraud investigator or a higher-level supervisor agent wants to ask "is stripe_agent_a behaving normally right now, or is it suddenly authorizing five times its usual volume?"

CREATE MATERIALIZED VIEW aap18_get_agent_recent_payments AS
SELECT
  agent_id,
  COUNT(*) AS recent_payment_count,
  ROUND(SUM(amount), 2) AS recent_total,
  COUNT(DISTINCT user_id) AS unique_users,
  MAX(payment_time) AS last_payment_at
FROM aap18_payments
WHERE payment_time >= NOW() - INTERVAL '24 hours'
GROUP BY agent_id;

Real output:

    agent_id    | recent_payment_count | recent_total | unique_users
----------------+----------------------+--------------+--------------
 mc_agent_x     |                    5 |       941.00 |            1
 custom_agent_1 |                    6 |       688.05 |            2
 stripe_agent_a |                   12 |       396.69 |            2

stripe_agent_a ran twelve transactions across two users in the past day. mc_agent_x ran five transactions all for the same user. Either pattern can be normal or anomalous depending on the historical baseline. The supervisor agent is the one that decides; the streaming database is the one that surfaces the data.

Example Agent Conversation Using These Tools

To make the integration concrete, here is what a turn-by-turn interaction looks like. The user is asking a payment agent to authorize a charge. The agent has access to the four tools above through the PostgreSQL MCP server.

User: "Buy me the $89 ticket to the Sunday concert."

The agent does not call the payment API yet. It reasons about what it needs to know first.

Agent (internal): I need to confirm the user has an active mandate that covers $89, that they have not already exceeded their daily spend, and that their current risk profile is acceptable. I will call three tools in parallel.

The agent issues three tool calls.

Tool call 1: get_active_mandates(user_id='user_alice') Result: Two active mandates with stripe_agent_a. Mandate 1 has $213.39 remaining; mandate 6 is over budget at -$186.61. There is enough room under mandate 1 to authorize $89.

Tool call 2: get_user_spending(user_id='user_alice') Result: 8 payments in the last 24 hours, total $286.61, average $35.83, max $87.32. Adding $89 brings the day total to $375.61.

Tool call 3: get_user_risk_score(user_id='user_alice') Result: Risk band high, risk score 69. The high band is driven by transaction count, not amount, and the merchants are routine retail.

The agent now has enough to decide.

Agent (internal): The user has $213.39 remaining under an active mandate. Adding $89 keeps them below the cap. Daily spend is moderate. Risk band is high but driven by count, not amount, and the proposed merchant is a standard event venue, not a flagged category. I will authorize.

Agent (to user): "Buying the $89 ticket. Confirming with the merchant now."

What makes this flow workable is that the tool results are sub-second-fresh. Mandate 6 was overspent five minutes ago. The eighth Starbucks charge happened five minutes ago. If the agent had been reading from a fifteen-minute-stale aggregate, mandate 6 might have looked fine, the day total might have been $200 lower than it actually is, and the risk band might have been medium. The agent would have authorized on the basis of stale facts.

This is the substantive payoff of putting RisingWave behind an MCP server. Every tool call is grounded in events that arrived seconds ago, not the last batch refresh window. For more on building the underlying agentic data architecture, we have a companion post that goes deeper on the SQL patterns.

FAQ

What is MCP and why does it matter for AI payment agents?

MCP (Model Context Protocol) is an open standard from Anthropic that lets AI agents discover and call external tools through a single, uniform interface. For AI payment agents, MCP matters because it replaces bespoke integrations with a contract: the agent asks the MCP server what tools exist, the server lists them with schemas, and the agent calls them on demand. When the MCP server points at a real-time database like RisingWave, every materialized view becomes a discoverable tool that returns live data, so the agent can check spending limits, mandate status, and risk scores before authorizing a payment.

How do AI payment agents get real-time data?

AI payment agents need three things to make trustworthy decisions: the user's recent activity, the agent's current spending mandate, and a fresh risk signal. They get this data through MCP tool calls into a live data layer. RisingWave ingests payment events, mandate updates, and fraud signals from Kafka, CDC, or HTTP sources, then maintains materialized views that update incrementally as new events arrive. A PostgreSQL MCP server connects to RisingWave on port 4566 and exposes those views as tools. When the agent fires a tool call, it gets sub-second-fresh data instead of a snapshot from a nightly batch job.

Can AI agents query a streaming database directly?

Yes. RisingWave speaks the PostgreSQL wire protocol, so any PostgreSQL-compatible MCP server connects to it the same way it connects to a regular Postgres instance. The agent uses standard SELECT statements against materialized views. The difference from querying batch Postgres is that the views in RisingWave are always current. They are recomputed incrementally as new payment events arrive on Kafka or via CDC, so the agent reads sub-second-fresh aggregates without ever touching raw event streams or running window functions itself.

How does RisingWave become a tool source for AI agents?

Each materialized view in RisingWave becomes a tool the agent can call. A view named get_user_spending acts as a tool that returns spending; a view named get_active_mandates acts as a tool that returns mandate status. The PostgreSQL MCP server reads RisingWave's information_schema, lists the views, and exposes each one as a callable tool to the agent. To add a new tool, you write one CREATE MATERIALIZED VIEW statement. There is no separate tool registry, no Python wrapper to deploy, and no API gateway in the path.

Conclusion

AI payment agents are at the moment in their lifecycle when the bottleneck has shifted. The agent layer works. The tool-calling protocol works. What is still missing on most teams is the data layer: a place where mandate state, user activity, and risk signals are simultaneously fresh, queryable, and standardized.

RisingWave plugs into that gap by pretending to be Postgres on the wire while behaving like a streaming engine underneath. Any PostgreSQL MCP server works against it. Every materialized view becomes a tool. The agent gets sub-second-fresh context with no extra plumbing, and adding a new tool to the agent's repertoire is a CREATE MATERIALIZED VIEW statement away.

If your agent is going to move money, it should be reading from a data layer that updates as fast as the transactions it is deciding on.

Ready to connect your payment agent to real-time data? Try RisingWave Cloud free.

Join our Slack community to talk with other teams building agentic payment infrastructure on streaming data.

Best-in-Class Event Streaming
for Agents, Apps, and Analytics
GitHubXLinkedInSlackYouTube
Sign up for our to stay updated.