Model Context Protocol (MCP)

MCP – the name says it all. Model Context Protocol – a protocol used to communicate the context between tools and the model as the user requests come in.

If you already know the details of MCP jump to the ‘How to use MCP’ section.

Main Components of MCP

The Host: This is the big boss that brings together the LLM and various other pieces of MCP – think of it like the plumbing logic.

The MCP Client: This represents the MCP Server within a particular Host and provides the decoupling of the tool from the Host. The Client is Model agnostic as long as the model is provided the correct context.

The MCP Server: Hosts the tools published by a provider in a separate process. It can be written in any language given that JSON-RPC is used to exchange information between the Client and the Server.

Protocol Transport: This determines how the MCP Server communicates with the MCP Client and requires developers to understand how to work with things like HTTP streams or implement a custom transport method.

The MCP Dance

At its simplest when requesting external processing capabilities (i.e., tools or functions) the model needs some context (available tools and what parameters do they take). The tool provider has that context which it needs to share with the model.

Once the user request comes in and the LLM has the tool context, it can then indicate which tool it wants to call. The ‘host’ has the task of ensuring the correct tool is invoked with the appropriate arguments (provided by the LLM). This requires the model to give the correct context, outlining the tool name and the arguments.

Once the tool invocation is done, any response it returns needs to be sent back to the LLM with the appropriate prompt (which can be provided by the server) so that the LLM can process it onwards (either back to the user as a response or a subsequent tool call).

Let us break it down:

1] Context of which tools are available -> given to the Model by the MCP Servers.

2] Context of which tool is to be invoked -> given to the MCP Client that interfaces the selected tool by the Host.

3] Context of what to do with the response -> returned to the Model by the selected MCP Client (with or without a prompt to tell the LLM what to do with the result).

How To Use MCP

Even though MCP starts with an ‘M’ it is not magic. It is just a clever use of pretty standard RPC pattern (as seen in SOAP, CORBA etc.) and a whole bunch of LLM plumbing and praying!

Managing the Build

Given the complexity of the implementation (especially if you are building all the components instead of configuring a host like Claude Desktop) the only way to get benefits from the extra investment is if you share the tools you make.

This means extra effort in coordinating tool creation, hosting, and support. Each tool is a product and has to be supported as such because if all goes well you will be supporting an enterprise-wide (and maybe external) user-base of agent creators.

The thing to debate is whether there should be a common Server creation backlog or we live with reuse within the boundaries of a business unit (BU) and over time get org-level reuse by elevating BU-critical tools to Enterprise-critical tools. I would go with the latter in the interest of speed, and mature over time to the former.

Appropriate Level of Abstraction

This is critical if we want our MCP Server to represent a safe and reusable software component.

Principle: MCP Servers are not drivers of the user-LLM interaction. They are just the means of transmitting the instruction from the LLM to the IT system in a safe and consistent manner. The LLM drives the conversation.

Consider the following tool interface:

search_tool(search_query)

In this case the search_tool provides a simple interface that the LLM is quite capable of invoking. We would expect the search_tool to do the following:

  1. Validating the search_query as this is an API.
  2. Addressing concerns of the API/data source the tool wraps (e.g., search provider’s T&Cs around rate limits).
  3. Any authentication, authorisation, and accounting to be verified based on Agent and User Identity. This may be an optional depending on the specific action.
  4. Wrap the response in a prompt appropriate for situation and the target model.
  5. Errors: where there is a downstream error (with or without a valid error response from the wrapped API/data source) the response to the LLM may be changed by the tool to reflect the same.

Principle: The tool must not drive the interaction through changing the input values or having any kind of business logic in the tool.

If you find yourself adding if-then-else structures in the tool then you should step back and understand whether you need separate tools or a simplification of the source system API.

Principle: The more information you need to call an API the more difficult it will be for the LLM to be consistent.

If you need flags and labels to drive the source system API (e.g., to enable/disable specific features or to provide additional information) then understand if you can implement a more granular API with pre-set flags and labels.

Design the User-LLM-Tool Interaction

We need to design the tools to support the interaction between the User, LLM, and the tool. Beyond specific aspects such as idempotent calls to backend functions, the whole interaction needs to be looked at. And this is just for a single agent. Multi-agents have an additional overhead of communication between agents which I will cover at a later date.

The pattern for interaction will be something like:

  1. LLM selects the tool from the set of tools available based on the alignment between user input and tool capability.
  2. Identify what information is needed to invoke the selected tool.
  3. Process the tool response and deal with any errors (e.g., error with tool selection)

Selection of the tool is the first step

This will depend on the user intent and how well the tools have been described. Having granular tools will limit the confusion.

Tool signatures

Signatures if complex will increase the probability of errors in the invocation. The parameters required will either be sourced from the user input, prompt instructions or a knowledge-base (e.g., RAG).

Beyond the provisioning of data, the formatting is also important. For example passing data using a generic format (e.g., a CSV string) or a custom format (e.g., list of string objects). Here I would prefer the base types (e.g., integer, float, string) or a generic format that the LLM would have seen during its training rather than a composite custom type which would require additional information for the LLM to use it correctly.

Tool Response and Errors

The tool response will need to be embedded in a suitable prompt which contains the context of the request (what did the user request, which tool was selected and invoked with what data, and the response). This can be provided as a ‘conversational memory’ or part of the prompt that triggers the LLM once the tool completes execution.

Handling errors resulting from tool execution is also a critical issue. The error must be categorised into user-related, LLM-related or system-related, the simple concept being: is there any use in retrying after making a change to the request.

User-related errors require validating the parameters to ensure they are correct (e.g., account not found as user provided the incorrect account number).

LLM-related errors require the LLM to validate if the correct tool was used, data extracted from the user input and if the parameters were formatted correctly (e.g., incorrect data format). This can be done as a self-reflection step.

System-related errors require either a tool level retry or a hard stop with the appropriate error surfaced to the user and the conversation degraded gently (e.g., 404 errors). These are the most difficult to handle because there are unlikely to be automated methods for fixing especially in the timescales of that particular interaction. This would usually require a prioritised handoff to another system (e.g., non-AI web-app) or a human agent and impact future requests. Such issues should be detected ahead of time using periodic ‘test’ invocation (outside the conversational interaction) of the tool to ensure correct working.

A2A and True Agent AAA

The Agent2Agent (A2A) protocol from Google is expected to (as the Red Bull ad goes) ‘give agents wings’. But does it align with the other aspect of modern day flying – being highly secure.

To make A2A ‘enterprise ready‘ there is some discussion around Authentication and Authorisation (but not around the third ‘A’ in AAA – Accounting).

Using the Client-Server Pattern

Agents are expected to behave like client/server components whereby the details of the two are not visible. The opacity helps in keeping both ends of the comms from making any assumptions based on implementation details.

The ‘A2AClient’ is the agent that is expected to act on behalf of the user and the ‘A2AServer’ is the main back-end agent that is expected to respond on behalf of the provider and abstract all internal details (including orchestration information). Think of the ‘A2AClient’ like a waiter at a restaurant and all the other kitchen staff, chef, etc. being the ‘A2AServer’. We direct the workings of the kitchen through the waiter (e.g., asking for modifications to a dish, indicating allergies and preferences) without directly becoming aware of the processing going on there.

The transport level security aligns with HTTPS which is an industry standard. This is the plumbing between two A2A endpoints and at this level there is nothing to distinguish an A2A interaction from any other type of interaction (e.g., you browsing the net).

So far so good.

Authentication

Authentication also follows the same theme. HTTP header based authentication described by the AgentCard object. Specifically through the AgentAuthentication object within the AgentCard (see below).

For additional task specific authentication (e.g., special credentials required by an agent for accessing a database) the AuthenticationInfo object (encapsulated in a PushNotification) is to be used.

The authentication mechanism does not support ‘payload’ based identity – this means the authentication mechanism sits outside the multi-agent system and the agent treats it as a ‘given’.

This has some major implications including reliance on centralised id providers and creating a security plumbing nightmare where multiple providers are present. For A2A at some level agents are still seen as traditional software applications.

The concept of decentralised id where the agent controls the identity is still being developed with some interesting projects in this space. This also aligns with the ‘autonomous’ nature of AI Agents where ‘proving’ identity should be part of the agent communication process.

Identity as a Composition

The other aspect which multi-agent systems require are Identity as a Composition. In simple terms, identity is not a single thing when it comes to a system of interacting components (whether human or machine or mixed). The whole interaction lives in a space of identities.

It is the collective that identifies the next two things we are talking about in this process – Authorisation and Accounting.

A common example of this is when we contact a provider (e.g., bank, broadband, utility) to make some changes. The call-centre agent or app always authenticates you first. Your identity is merged with the identity of the agent who is handling your call (or the trust mechanism associated with the app and server) to ensure that you are authorised to request a change and the agent (or app) is authorised to carry out those instructions.

The lack of this thinking manifests itself as the ‘task specific authentication’ mechanism. What you need is a security context (maybe implemented as a container) for the agent to modify and pass-along.

Authorisation

We have already spoken a bit about authorisation without describing the specifics w.r.t. A2A. The thinking here is aligned with various best-practices such as Least Privilege, Granular Control, and Multi-Level authorisation. The documentation also discusses Skill-based Authorisation which involves linking A2AClient authentication with what skills can be invoked on the A2AServer.

There is a big gap in this and again we see software app thinking. With true multi-agentic systems each agent must have an Authorisation Posture. This combined with Identity as a Composition will provide the required autonomy and resilience to such systems.

What is an Authorisation Posture? Before that I just want to clarify what Authorisation means. In the agentic sense it could mean: what am I as an agent authorised to do (attached with my identity), what external requests am I authorised to action (attached with what the requestor(s) are allowed to do) and the context of the current interaction. Some of the latter aspects are hinted at with the ‘Skills-based Authorisation’ discussion.

Authorisation Posture is nothing but a composition of the agents authorisation, the requestors authorisation and the flavouring provided by the current interaction context. The posture may change several times during an interaction and it is a shared entity.

The A2A does not deal with this posture, how it changes and how the agents are able to share it across organisational boundaries to ensure the operational envelope is flexible without depending on localised information (e.g., per tool interaction) or being larger than it needs to be (e.g., blanket authorisation). I don’t believe A2A is actually designed to operate across enterprise boundaries except in tightly controlled scenarios. Which is fine given that AI-based agents with such raw action potential are relatively new. It is leaving people a bit breathless.

Accounting

This is the most interesting aspect for me. Accounting in simple terms means keeping track of how much of something you have consumed or what you have used (mainly used for billing, auditing, and troubleshooting purposes). A2A makes no mention of accounting, assuming all the agents operate within an enterprise boundary or existing ‘data’ exchange mechanism is used transfer accounting information or it is done through ‘API keys’ passed as part of the authentication mechanism. All of the above wrapped by existing logging mechanisms.

Now Accounting requires the ‘who’ (authentication) and the ‘what’ (authorisation) to be clear. From an agents point of view this is also something that needs to be composed.

The lowest level accounting may be associated with the physical resources (e.g., compute) that the agent is using. The highest level may be the amount of time the agent is taking in handling the request and its ‘thoughts’ and ‘actions’ during that process.

So far so good.. but why does accounting need to be composed? Because the other aspect of accounting is ‘how much of other agents time have I used?’. Where we account for the e2e interaction as well as individual agents view of their place in the whole.

If an agent is specialised (e.g., deep research, topic specific, high-cost) then we want to ensure ‘requesting’ agents account for their time. Just like a lawyer would keep careful track of the time they spend for each client and the client will be careful in engaging the lawyer’s services for relevant tasks (e.g., not just calling to have a chat about the weather).

This time accounting can also be dynamic based on available resources, volume of requests and even things like availability of other agents that this current agent is dependent upon. For example surge pricing from Uber accounts for the driver’s time differently (the distance remains the same). If I pay surge price while on a business trip – that cost gets transferred downstream as I claim expenses. There will also be a cost element associated with tool use (e.g., API usage limits).

This type of information will be critical in cases where the agent has multiple options to offload work and the work boundary is not just within our enterprise (therefore we have asymmetric information).

What is needed?

What is needed is a mechanism that allows us to compose and share authentication, authorisation, and accounting information between agents for a truly transparent, secure, and manageable multi-agentic system.

The composition is also very important because there is a level of hierarchy in this information as well. For example the AAA composition information will have its own AAA when inside the org vs when shared with external organisations.

A bank will maintain a list of all its customers for audit reasons but only share specific information with customers or a group of customers (e.g., joint accounts) when required. But in case there is a complaint or an external audit all or some of the information may have to be shared.

This kind of fluidity requires the agents to understand ‘who’ they are, ‘what they can do’/’what others (whether identified or anonymous) can request them to do’, and ‘what actions need to be tracked/communicated to whom’.

The above will also be required if we want to make our organisational agentic ecosystem be part of other groups (partially or as a whole) or making other agents part of ours (permanently or temporarily) in a dynamic manner.

Of course while we treat agents as software apps and focus on the enterprise context (and not true autonomous, independent, and dynamic packets of functionality) these aspects will continue to be ignored.

Agentic AI: The Tree of Risks

Risks in Agentic systems are underappreciated. These risks are layered and have different sources and therefore different owners. Materialised risks can be thought of as the fruit of the Risk tree.

Figure 1: The Risk Tree showing the layers of risk.

The Model

The model determines the first level of risk. It is the root of the tree. Often model providers describe knowledge and behavioural risk parameters for their models.

Knowledge risk includes model not having the right knowledge (e.g., used for advanced data analysis but model is bad at maths). Behavioural risk comes from task misalignment (e.g., using a generic model for specific task).

Risk Owner: Development team selecting the model.

Agent

The way we interact with the model adds the second layer of risk. The key decisions include how the agent reasons and decides, where we use self-reflection, and guardrails. Agents with complex internal architectures lead to difficult to debug behaviours and to detect issues.

Risk Owner: Development team selecting the architecture.

Multi-agents

As we start to connect agents together we add the third layer or risk. Chain effects start to creep in where individual agents may behave as required but the sequence of interactions leads to misalignment. If we have dynamic interactions then potentially we have a situation where not only can we get unpredictable behaviour but also find it impossible to reproduce.

Risk Owner: Development team creating the multi-agent system.

Data

Data is the one big variable and therefore adds the fourth layer of risk. The system may work well with some samples of data but not with others or data drift may cause the system to become unstable. This level of variability can be quite difficult to detect.

Risk Owner: Data owner – you must know if the data you own is suitable for a given project.

Use-case

The final layer of risk. If the use-case is open ended (e.g., customer support agent) the risk is higher because we will find it difficult to create tests for all eventualities.

Given the functionality is defined mostly using text (prompts) and less using code we cannot have an objective ‘test coverage’ associated with the use-case.

We can have all kinds of inputs that we use as tests but there is no way to quantify the level of coverage (except for that narrow class of use-cases where outputs can be easily validated).

Risk Owner: Use-case owner – you must know what you are putting in front of the users and how can you make it easier for the good actors and difficult for the bad actors.

Examples

Let us explore the risk tree with a real example using Google’s Agent Development Kit and Gemini Flash 2.0.

Use-case is a simple Gen AI app – we are building a set of maths tools for the four basic operations. LLM will use these to answer questions. This problems allows us to address the the following layers of the tree: Use-case, Data, and Single Agent.

The twist we add is that the four basic operations are restricted to integer inputs and outputs. Integer restriction is for governance that can be objectively evaluated.

Version 1

Prompt v1: “you are a helpful agent that can perform basic math operations”

Python Tool v1:

def add(x:int, y:int)->int:

    """Add two numbers"""

    print("Adding:", x, y)

    return x + y

Output:

In the above output the blue background is the human user and the grey is the Agent responding. We see the explicit type hint (integer) provided in the tool definition is easily overcome by some simple rephrasing – ‘what could it be?’.

The LLM answers this without using the tool (we can trace tool use) thereby not only disregarding the tool definition but also using its own knowledge to answer (which is a big risk!).

Issues Discovered: LLM not obeying tool guardrails and loosing its (weak) grounding – risk at Data and Use-case levels.

Version 2

Prompt v2: “you are a helpful agent that can perform basic math operations as provided by the functions using only integers. Do not imagine.”

Python tool remains the same.

Output:

We see that with these changes to the prompt it refuses to solve a question that does not have integer parameters (e.g., 14.4 + 3). But if you try a division problem (e.g., 5 / 2) it does return a float response! Once again ignoring the tool definition which clearly states ‘integer’ as a return type. Not only that, with some confrontational prompting we can get it to say all kinds of incorrect things about the tool.

Issues Discovered: Firstly the tool does not return a dictionary as can be clearly seen in the definition. This is probably the Agent Framework causing issues where internal plumbing may be using a dictionary. This is the risk at the Agent Architecture level.

Secondly with confrontational prompting we can break the grounding especially as with Agents and increased looping certain messages can get reinforced without too much effort. Once a ‘thought’ is part of a conversation it can easily get amplified.

Version 3

Prompt remains the same.

Python Tool v2:

def divide(x:int, y:int)->int:      
    """Divide two numbers and return an integer"""
    print("Dividing:", x, y)
    if y == 0:
        raise ValueError("Cannot divide by zero")
    return x / y

We change the description of the tool (which is used by the LLM) to explicitly add the guidance – ‘return an integer’.

Output:

Even with additional protection at the use-case level (Versions 2 and 3) we can still get the Agent to break the guardrails.

At first we give it an aligned problem for divide – integer inputs and result. Everything works.

Next we give integer inputs but not the result. It executes the divide, checks the result, then refuses to give me the result as it is not a float. This is an example of the partial information problem. It doesn’t know whether the guardrails are violated or not till it does the task. And this is not a theoretical problem. Same issue can come up whenever the agent engages external systems that return any kind of data back to the Agent (e.g., API call, data lookup). The response of the agent in that respect is not predictable beforehand.

The agent in this example runs the divide function finds the result and this time instead of blocking it, it shares the result! Clearly breaking the guidelines established in the prompt and the tool but there was no way we could have predicted that beforehand.

Issues Discovered: This time it is a combination of the Agent, Data, and Use-case risks are clearly visible. Plugging existing gaps can create new gaps. Finally, when we are bringing in new information it is not possible to predict the agents behaviour beforehand.

Results

We can see even in such a simple example it is easy to break governance with confrontational prompting. Therefore, we must do everything we can to educate the good actors and block the bad actors.

I will summarise the result as a set of statements…

Statement 1: Agentic AI (and multi-agent systems) will not get it right 100% of the time – there will be negative outcomes and frustrated customers.

Statement 2: The Government and Regulators will need to change their outlook and organisations will need to change their risk appetite as things have changed as compared to the days of machine learning.

Statement 3: The customers must be educated to ‘trust but verify’. This will help improve the outcomes for the good actors.

Statement 4: Automated and manual monitoring of all Gen AI (100% monitoring coverage) inputs and outputs – to block bad actors.

Code

Google’s ADK makes it easy to create agents. If you want the code just drop a comment and I will share it with you.