Skip to content

Commit

Permalink
feat: add roots
Browse files Browse the repository at this point in the history
  • Loading branch information
punkpeye committed Dec 31, 2024
1 parent 431d0ac commit e983ee9
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 3 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) serv
- [Error handling](#errors)
- [SSE](#sse)
- [Progress notifications](#progress)
- [Typed events](#typed-events)
- [Typed server events](#typed-server-events)
- Roots
- CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)

Expand Down Expand Up @@ -402,7 +402,7 @@ server.sessions;

We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.

### Typed events
### Typed server events

You can listen to events emitted by the server using the `on` method:

Expand Down Expand Up @@ -454,6 +454,16 @@ The `server` property contains an instance of MCP server that is associated with
session.server;
```

### Typed session events

You can listen to events emitted by the session using the `on` method:

```ts
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
```

## Running Your Server

### Test with `mcp-cli`
Expand Down
88 changes: 88 additions & 0 deletions src/FastMCP.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ListRootsRequestSchema,
LoggingMessageNotificationSchema,
McpError,
Root,
} from "@modelcontextprotocol/sdk/types.js";

const runWithTestServer = async ({
Expand Down Expand Up @@ -755,3 +756,90 @@ test("session knows about roots", async () => {
},
});
});

test("session listens to roots changes", async () => {
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {
roots: {
listChanged: true,
},
},
},
);

let clientRoots: Root[] = [
{
uri: "file:///home/user/projects/frontend",
name: "Frontend Repository",
},
];

client.setRequestHandler(ListRootsRequestSchema, () => {
return {
roots: clientRoots,
};
});

await runWithTestServer({
client,
start: async () => {
const server = new FastMCP({
name: "Test",
version: "1.0.0",
});

return server;
},
run: async ({ session }) => {
expect(session.roots).toEqual([
{
uri: "file:///home/user/projects/frontend",
name: "Frontend Repository",
},
]);

clientRoots.push({
uri: "file:///home/user/projects/backend",
name: "Backend Repository",
});

await client.sendRootsListChanged();

const onRootsChanged = vi.fn();

session.on("rootsChanged", onRootsChanged);

await delay(100);

expect(session.roots).toEqual([
{
uri: "file:///home/user/projects/frontend",
name: "Frontend Repository",
},
{
uri: "file:///home/user/projects/backend",
name: "Backend Repository",
},
]);

expect(onRootsChanged).toHaveBeenCalledTimes(1);
expect(onRootsChanged).toHaveBeenCalledWith({
roots: [
{
uri: "file:///home/user/projects/frontend",
name: "Frontend Repository",
},
{
uri: "file:///home/user/projects/backend",
name: "Backend Repository",
},
],
});
},
});
});
31 changes: 30 additions & 1 deletion src/FastMCP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
McpError,
ReadResourceRequestSchema,
Root,
RootsListChangedNotificationSchema,
ServerCapabilities,
SetLevelRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
Expand All @@ -33,6 +34,10 @@ type FastMCPEvents = {
disconnect: (event: { session: FastMCPSession }) => void;
};

type FastMCPSessionEvents = {
rootsChanged: (event: { roots: Root[] }) => void;
};

/**
* Generates an image content object from a URL, file path, or buffer.
*/
Expand Down Expand Up @@ -238,7 +243,13 @@ type LoggingLevel =
| "alert"
| "emergency";

export class FastMCPSession {
const FastMCPSessionEventEmitterBase: {
new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
} = EventEmitter;

class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}

export class FastMCPSession extends FastMCPSessionEventEmitter {
#capabilities: ServerCapabilities = {};
#loggingLevel: LoggingLevel = "info";
#server: Server;
Expand All @@ -258,6 +269,8 @@ export class FastMCPSession {
resources: Resource[];
prompts: Prompt[];
}) {
super();

if (tools.length) {
this.#capabilities.tools = {};
}
Expand All @@ -279,6 +292,7 @@ export class FastMCPSession {

this.setupErrorHandling();
this.setupLoggingHandlers();
this.setupRootsHandlers();

if (tools.length) {
this.setupToolHandlers(tools);
Expand Down Expand Up @@ -351,6 +365,21 @@ export class FastMCPSession {
return this.#loggingLevel;
}

private setupRootsHandlers() {
this.#server.setNotificationHandler(
RootsListChangedNotificationSchema,
() => {
this.#server.listRoots().then((roots) => {
this.#roots = roots.roots;

this.emit("rootsChanged", {
roots: roots.roots,
});
});
},
);
}

private setupLoggingHandlers() {
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
this.#loggingLevel = request.params.level;
Expand Down

0 comments on commit e983ee9

Please sign in to comment.