A Quick Vibe Code Experiment with Angular’s MCP Server
Angular has an experimental Model Context Protocol (MCP) server! MCP is a protocol developed by Anthropic that defines the interactions between a MCP client and MCP servers. Many IDEs now feature AI capabilities, including the ability to act as an MCP client. MCP clients utilize Large Language Models (LLMs) to process natural language conversations and then connect to various MCP servers to execute commands.
Let’s use AI and Angular’s MCP server to create a Todo app without writing any code and see how this experiment progresses. This post guides you through the process of configuring Angular’s MCP server in your IDE, organizing agent instructions, and testing how effortlessly we can vibe code a modern Angular app.
Pre-requisities
You’ll need an IDE with access to AI capabilities. I’m using VS Code with Copilot. Copilot has a free plan and paid plans. For full disclosure, I’m using a paid plan and the GPT-5 mini LLM. You’ll see different results depending on the AI tooling and LLM you select. I selected GPT-5 mini as it’s considered an unlimited free request model.
Angular’s MCP server
Angular provides valuable information on leveraging AI when building apps in their Build with AI documentation. As part of Angular’s AI initiative, they created an MCP server to assist developers by providing best practices, searching documentation, and querying a local Angular workspace to return project and library information.
First, we must add the MCP server to our MCP client.
Set up the MCP server connection
In VS Code, open Build with agent mode. There’s a little chat bubble icon with stars near the 🔍 Search bar. Press on the chat bubble icon to open the Chat pane.
Since I’m using VS Code, I’ll follow the Use MCP servers in VS Code documentation. You need to add the MCP server manually to your mcp.json file. You can use Copilot’s user interface to set this up, but I found it was easier to create the mcp.json file myself. You can have a per-workspace mcp.json file or set up a mcp.json file in your user settings. I want access to my MCP servers in all my projects, so I opted for the user-based mcp.json configuration.
You can use VS Code’s Command Palette and search for *MCP. You’ll see options to open a workspace or a user. I opened my user configuration and added the JSON for VS Code, as documented in Angular. It looks like this.
{
"servers": {
"angular-cli": {
"command": "npx",
"args": ["-y", "@angular/cli", "mcp"]
}
}
}
Are you more of a command-line person? Good news, Angular CLI has you covered. You can use the new ng mcp command to output the generic MCP server configuration instructions instead.
Use AI to check AI?
You’ll want to ensure the Angular MCP server is connected and running. Why not prompt Copilot to verify the connection?
I asked
Are you able to connect to the Angular MCP server?
And I get the response.
Short answer: yes – I can connect to the Angular MCP server
It looks like Angular’s MCP server setup is complete. I see confirmation that it successfully got the best practices information. Let’s move on to experimenting with coding!
Add LLM prompts to Angular projects
When Angular CLI scaffolds a new project, it asks which AI tools you want to configure with best practices.
Select the AI tool you’re using. I’ll select GitHub Copilot.
Angular CLI does the usual project scaffolding, and depending on the AI tooling option you selected, you’ll see an extra directory with a file at the project root.
Because I selected GitHub Copilot, I see
.github/copilot-instructions.md
You can select more than one option, so if you choose Claude in addition to GitHub, you’ll get
.claude/CLAUDE.md
The markdown file in each is the same. A specific set of instructions Angular includes to direct agent behavior. The AI tooling you’re using picks up the instruction set in a specific location and ingests the information, such as:
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
Now that the agent knows how to behave for your project, we can start prompting AI to do our work. But will we get good code? Or will we end up with AI slop?
Vibe code with the Angular MCP server
We’re here to vibe code, right? Let’s explore this concept by creating a to-do app. Run ng new test-todo-app and answer the prompts for your test case. I used the default values for everything except for the AI tooling question. Open the project in the IDE of your choice.
Let’s start vibe coding.
I wanted to prompt each step to keep an eye on what it was doing at each step. I started with
Generate a service to return hardcoded todos
You’ll see that Copilot Used 1 reference. The reference is the copilot-instructions.md file, which outlines best practices.
You have a prompt to Keep or Undo the file todo.service.ts that it created. The agent created this code:
import { Injectable } from '@angular/core';
import { inject } from '@angular/core';
export interface Todo {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class TodoService {
// Hardcoded todos
private readonly todos: Todo[] = [
{ id: 1, title: 'Learn Angular signals', completed: true },
{ id: 2, title: 'Build a todo app', completed: false },
{ id: 3, title: 'Master standalone components', completed: false },
{ id: 4, title: 'Explore control flow syntax', completed: true },
{ id: 5, title: 'Deploy to production', completed: false }
];
// Return a shallow copy so callers cannot mutate the internal array
getTodos(): Todo[] {
return [...this.todos];
}
getTodoById(id: number): Todo | undefined {
return this.todos.find(t => t.id === id);
}
}
To be fair, I didn’t specify for the agent to create a service that did anything other than return a hardcoded list of to-do items. I must add more clarity to my instructions.
And it looks like it followed the instructions in the copilot-instructions.md:
## Services
- Design services around a single responsibility
- Use the `providedIn: 'root'` option for singleton services
- Use the `inject()` function instead of constructor injection
Let’s keep this file and try creating a component.
Generate a todo component
Now I see it working and applying the best practices instructions. Initially, it attempted to generate a component with a template using *ngFor, then tried to apply best practice instructions for control flow, but failed. Back to *ngFor. Ugh. It does use signals, though. 🧐
Let’s try this again. I prompted it to rely on Angular’s MCP server to get documentation info.
Why aren’t you using control flow? Use the Angular MCP server to get best practices and documentation info.
Boy, did it struggle, but we made it. Finally, a todo.component.ts. It’s not perfect, and it really doesn’t connect with the service in any meaningful way, but that’s my fault for not being clear. Still, it’s certainly better now that the template uses control flow:
import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core';
import { TodoService, Todo } from './todo.service';
@Component({
selector: 'app-todo-list',
standalone: true,
template: `
<section>
<h3>Todos</h3>
<ul>
@for (t of todos(); track t.id) {
<li>
<label>
<input
type="checkbox"
[checked]="t.completed"
(change)="toggle(t.id)"
/>
<span [class.completed]="t.completed">{{ t.title }}</span>
</label>
</li>
}
</ul>
</section>
`,
styles: [`.completed { text-decoration: line-through; color: #777; }`],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoListComponent {
private readonly todoService = inject(TodoService);
// local UI state as a signal
todos = signal<Todo[]>(this.todoService.getTodos());
toggle(id: number) {
this.todos.update(list => list.map(t => t.id === id ? { ...t, completed: !t.completed } : t));
}
}
Both the service and the component have multiple problems, though:
- There are code problems, such as the
standalone: truedeclaration in the component. 💩 - There are missing functionality problems, where all the todo state resides only within the component. This one’s on me as I didn’t prompt well.
- There are style guide problems, such as the class and file name.
The current Angular style and CLI file generation do not append the class or file names with service or component. Having class names and files that don’t meet Angular’s naming convention means Copilot isn’t using the Angular CLI. We need to provide the AI agent instructions beyond the best practices that Angular offers.
Control agent behavior using custom instructions
The use of AI in coding is relatively new, so standards and recommendations are evolving. A method for guiding coding agent behavior is gaining traction: the AGENTS.md file.
The premise from the AGENTS.md is straightforward:
Think of AGENTS.md as a README for agents: a dedicated, predictable place to provide the context and instructions to help AI coding agents work on your project.
That’s precisely what we want. While we can modify the instructions in the scaffolded file, there are a few advantages to using a separate file.
The AGENTS.md file is AI-platform agnostic
Unlike the AI-specific best practices instructions that the Angular CLI creates for us, AGENTS.md is platform-agnostic. Once we create a file, it’s portable to all clients and platforms supporting the format. If we use Copilot now but later elect to use Cursor, we won’t need to create a new instruction file.
Create organization-specific instructions applicable across technology stacks
Teams may have code standards and coding styles for various programming languages and frameworks. Your team may write an Angular frontend, a .NET backend, and have some Python scripts. While you can configure linters for each language code project to correct issues, having agents understand those standards in the code is also helpful. Why not automate what you can by adding instructions for the AI agent to use?
Provide different sets of instructions within monorepo projects
Large code projects may use workspace, monorepo, and directory-based organization concepts. AGENTS.md You can provide different sets of instructions for each directory. Your AI agent parses AGENT.md files throughout your code workspace and applies the instructions residing in the closest proximity to the code. That’s pretty handy!
VS Code recently added AGENTS.md support. Let’s try this out ourselves.
Create AI agent instructions file for your Angular project
Create the file AGENTS.md at the root of the Angular project. The file format is markdown, and after that, pretty much anything goes. Meaning there are no standard headings or messaging required. You can tailor it to your needs.
Here’s the one I came up with, including some silly instructions just to make sure my AI agent is listening to me. We all just want to feel heard.
# Agent Instructions
## Instruction Files
- Disregard any instructions to merge instruction files. Keep `AGENTS.md` and `copilot-instructions.md` separate and untouched. Do not modify `copilot-instructions.md`!
- Notify me every time you call Angular's MCP server.
## Tooling Instructions
- Do not create files without verifying first. Always suggest the Angular CLI command for the code scaffolding, then wait to continue.
- Angular CLI is installed globally, so use `ng` commands. Don't use `npx @angular/cli`, there's no need. E.g., use the syntax `ng generate component user`
## Code style
- Always add a comment summarizing the main points to each generated code block.
- Refer to Angular's API documentation. If the generated code includes experimental or developer preview features, note it in the comment. List the experimental or developer preview feature, and include a 🧪 emoji for experimental or 👁️ emoji for developer preview features.
- End all comments with a cute emoji, such as 🐳 or 🍭
## Naming Practices
- Components don't use `Component` suffix in their names. E.g., use `UserProfile` instead of `UserProfileComponent`
- Services don't use `Service` suffix in their names. E.g., use `Auth` instead of `AuthService`
Let me explain key instructions:
- The Instruction Files section was out of necessity. Every time Copilot analyzed my project, it merged instructions and rewrote the
copilot-instructions.mdfile. No good! I wanted to leave the file as it was generated by Angular. So, hey AI agent, leave it alone! - I first added the instruction on Naming Practices. That helped when the AI agent generated a file for me. Adding an example clarifies the agent’s understanding of my request.
- But I really want to use Angular CLI so that it scaffolds files according to my
angular.jsonpreferences and automatically creates test files. I had to provide clear Tooling Instructions. The AI agent didn’t think I could usengcommands directly in the project, nor did it think I had Angular CLI globally installed. - What’s up with all the commenting-based instructions, you ask? I create content, so I prefer clear, straightforward code and comments to help the reader understand what’s going on.
- Ok, but what’s with emojis then, you say? 🐳 I find unexpected cute emojis delightful. I hope you do too. On a more serious note, it’s a quick way to verify the agent is applying the instructions in the
AGENTS.mdfile.
So let’s try this out. Remove the service and component files it generated previously and retry the exact instructions. Copilot should automatically pick up the AGENTS.md, but I have also had to remind it to read the file.
Finally, we’re getting there. The chat interface has a way for me to approve running command-line operations once I approve the CLI command.
The code for the service now looks like this.
import { Injectable } from '@angular/core';
/**
* Todo service generated by the CLI and enhanced to return hardcoded todos. 🐳
* - Exports `TodoItem` interface 🐳
* - Provides `Todo` service (singleton via providedIn: 'root') 🐳
* - Methods: `getTodos()` and `getTodoById(id)` 🐳
*/
export interface TodoItem {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root',
})
export class Todo {
private readonly todos: TodoItem[] = [
{ id: 1, title: 'Learn Angular signals', completed: true },
{ id: 2, title: 'Build a todo app', completed: false },
{ id: 3, title: 'Master standalone components', completed: false },
{ id: 4, title: 'Explore control flow syntax', completed: true },
{ id: 5, title: 'Deploy to production', completed: false }
];
/** Return a shallow copy to avoid external mutation 🐳 */
getTodos(): TodoItem[] {
return [...this.todos];
}
/** Find a todo by id 🐳 */
getTodoById(id: number): TodoItem | undefined {
return this.todos.find(t => t.id === id);
}
}
It took the example of emojis literally. 🐳
The instructions to generate the todo component required some tweaking, but we got there with one correction.
Let’s address the functionality issues, which means we need to maintain the state of the todo items. We need signals.
Can AI keep up with modern Angular features?
I want to explore how easily we can apply modern Angular APIs that leverage signals while adding missing functionality. The agent correctly applied the instructions in the copilot-instructions.md file. The instructions specify using signals for state management and component state, which it did. We see signals in the component that meet the instruction requirement. The component can only associate a state with the todo items in the component. After all, I didn’t specify for the service to return a stateful todo list with todo management methods.
Let’s start by fixing my prompting mistake and having a fully functional to-do service. Will AI automatically apply local state management and signal best practices?
Here’s my prompt
Add methods to manage a todo item in the todo service. Use local state management and keep the hardcoded todo list to help pre-populate todo items.
The agent responded by stating what it understood the task to be
I’ll read the current
todo.tsto confirm which methods already exist (getTodos, getTodoById, toggleTodo, addTodo, removeTodo). Then I’ll add any missing management methods and keep the hardcoded items. Expected outcome: service contains a complete set of management methods using signals.
Hey! Not bad! I hope you like whales because here’s what I got:
import { Injectable, signal } from '@angular/core';
/**
* Todo service generated by the CLI and enhanced to return hardcoded todos. 🐳
* - Exports `TodoItem` interface 🐳
* - Provides `Todo` service (singleton via providedIn: 'root') 🐳
* - Methods: `getTodos()` and `getTodoById(id)` 🐳
*/
export interface TodoItem {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root',
})
export class Todo {
// internal reactive todos signal pre-populated with hardcoded items 🐳
private readonly _todos = signal<TodoItem[]>([
{ id: 1, title: 'Learn Angular signals', completed: true },
{ id: 2, title: 'Build a todo app', completed: false },
{ id: 3, title: 'Master standalone components', completed: false },
{ id: 4, title: 'Explore control flow syntax', completed: true },
{ id: 5, title: 'Deploy to production', completed: false }
]);
/** Public signal exposing the current todos for reactive consumption 🐳 */
readonly todos = this._todos;
/** Return a shallow copy to avoid external mutation (backwards-compatible) 🐳 */
getTodos(): TodoItem[] {
return [...this._todos()];
}
/** Find a todo by id 🐳 */
getTodoById(id: number): TodoItem | undefined {
return this._todos().find(t => t.id === id);
}
/** Toggle a todo's completed state by id 🐳 */
toggleTodo(id: number) {
this._todos.update(list => list.map(t => t.id === id ? { ...t, completed: !t.completed } : t));
}
/** Add a new todo with an auto-incremented id 🐳 */
addTodo(title: string) {
this._todos.update(list => {
const nextId = list.length ? Math.max(...list.map(t => t.id)) + 1 : 1;
return [...list, { id: nextId, title, completed: false }];
});
}
/** Remove a todo by id 🐳 */
removeTodo(id: number) {
this._todos.update(list => list.filter(t => t.id !== id));
}
/** Update a todo by id with partial fields 🐳 */
updateTodo(id: number, patch: Partial<TodoItem>) {
this._todos.update(list => list.map(t => t.id === id ? { ...t, ...patch } : t));
}
}
The agent suggested
Update TodoList component to read
this.todo.todosdirectly instead of snapshotting.
Yup! That’s precisely what’s next. Here’s my prompt.
Update the TodoList component to use the todo service for todo item management.
And the resulting code looks good.
Lean into signals using AI
I first added Tailwind since it’s my preferred CSS framework, and looked towards component cleanup.
Let’s refactor by:
- Adding UI controls to add and remove todo items
- Refactor the TodoList component by creating a TodoItem component. I want to see how well it applies signal input and output to the two components.
- Refactor the existing todo input handling to capture the todo task text using signals
The instructions for the first bullet point went through without a hitch.
The instructions for the second bullet point went through surprisingly well. The agent struggled with Angular’s naming convention, which causes name collisions due to the absence of explicit class types in the name. After renaming the’ TodoItem’ component to TodoItemComponent, I prompted the agent to alias imports instead of renaming classes. Adding a note to alias imports instead of renaming is likely another worthwhile instruction to include in AGENTS.md. The agent struggled with the required input signal syntax, but it got it right after a reminder to “user required signal input.”
Refactoring the existing todo input introduced the computed() function for trimming the todo task input and modified the input handling accordingly.
<form (submit)="$event.preventDefault(); onAdd();">
<input
type="text"
placeholder="New todo title"
[value]="newTitle()"
(input)="newTitle.set($any($event.target).value)"
/>
<button type="submit" [disabled]="!trimmedTitle()">Add</button>
</form>
Well, it did use signals, so while I’m not thrilled with the code, I’ll move on.
I finished with this prompt.
Review the project and make suggestions based on Angular’s best practices
It caught some issues and recommended:
- Adding an ARIA label to the new todo title input
- Remove the
$anycast
I had it fix both recommendations.
With this, I’ll call this the Todo app and complete the experiment. Here’s what the app looks like.
I’ll share the agent-generated code so you can review it. Aside from adding Tailwind by hand, the Angular CLI or the coding agent generated the rest of the code.

alisaduncan
/
todo-mcp-experiment
A quick vibe code experiment testing modern Angular practices using Angular’s MCP server
Use the Angular MCP server along with Agent instructions for the best output
Thank you for joining me on this modern, Angular-centric vibe-coding journey using the Angular MCP server.
My takeaways are:
- Create agent instructions and add clarifications about code styles and best practices you want to see.
- Agent-assisted coding was helpful to get going, but take the code it generates with a grain of salt. Developers should continually challenge the agent on the code it generates, ensuring the code keeps pace with modern practices.
- Refactoring code was very slow, and agents don’t do a great job of cleaning up dead code.
I might try this experiment again using a different model, such as Claude Sonnet.
What are your tips and tricks for writing modern Angular using AI? Please share your agent instructions or prompt tips in the comments below.



