Skip to content

MCP Server Example on MCP SDK#534

Open
MehakBindra wants to merge 3 commits intomainfrom
mehak/mcp-server
Open

MCP Server Example on MCP SDK#534
MehakBindra wants to merge 3 commits intomainfrom
mehak/mcp-server

Conversation

@MehakBindra
Copy link
Copy Markdown
Contributor

@MehakBindra MehakBindra commented Apr 20, 2026

This pull request migrates and refactors the MCP (Model Context Protocol) Teams bot example from @examples/mcp to @examples/mcp-server, significantly expanding its functionality and improving its documentation and code structure. The new implementation provides a robust MCP server that exposes human-in-the-loop tools for Teams, including notification, question/answer, and approval flows.

Key changes include:

1. Migration and Renaming

  • The example is moved from @examples/mcp to @examples/mcp-server, with all related files and documentation updated to reflect the new name. (README.md, CHANGELOG.md, package.json, directory and file renames)

2. Major Feature Expansion

  • Implements a full-featured MCP server for Teams, supporting five tools: notify, ask, getReply, requestApproval, and getApproval. These tools allow agents or clients to interact with real users via Teams for notifications, Q&A, and approval workflows.

4. Removal of Old Implementation

  • Deletes the previous minimal MCP bot implementation and its associated files, including the old README.md, manifest.json, and TypeScript entry point, which are replaced with the new, more capable server.

Copilot AI review requested due to automatic review settings April 20, 2026 21:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the MCP Teams bot example from @examples/mcp to a new @examples/mcp-server workspace and expands it into a more complete MCP server that can message Teams users and support Q&A + approval flows via MCP tools.

Changes:

  • Replace the old minimal MCP bot example (examples/mcp) with a new MCP server implementation (examples/mcp-server) using @modelcontextprotocol/sdk’s McpServer + Streamable HTTP transport.
  • Add in-memory state + Teams handlers to support notify, ask/getReply, and requestApproval/getApproval tools.
  • Update repo docs/lockfile to reflect the new example package and dependency set.

Reviewed changes

Copilot reviewed 11 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
package-lock.json Updates workspace entries and adds new deps for the MCP server example (including MCP inspector).
examples/mcp/src/index.ts Removes the previous minimal MCP bot implementation.
examples/mcp/appPackage/manifest.json Removes the old Teams app manifest for the prior example.
examples/mcp/appPackage/outline.png Removes old app package asset (example app icon).
examples/mcp/appPackage/color.png Removes old app package asset (example app icon).
examples/mcp/README.md Removes the old example documentation.
examples/mcp-server/turbo.json Adds Turbo build config for the new example workspace.
examples/mcp-server/tsconfig.json Adds TypeScript config for building the new example.
examples/mcp-server/src/state.ts Adds in-memory state maps for conversation IDs, asks, and approvals.
examples/mcp-server/src/mcpTools.ts Implements MCP tool registrations and proactive Teams messaging/approval cards.
examples/mcp-server/src/index.ts Boots the app and mounts the MCP Streamable HTTP transport at /mcp.
examples/mcp-server/src/app.ts Adds Teams message and card.action handlers to capture replies and approvals.
examples/mcp-server/package.json Renames package and updates deps/devDeps/scripts for the new MCP server example.
examples/mcp-server/eslint.config.js Adds ESLint configuration for the new workspace.
examples/mcp-server/README.md Adds documentation for tools, layout, and run/inspector flow.
examples/mcp-server/CHANGELOG.md Updates changelog header for the renamed example package.
examples/mcp-server/.gitignore Adds workspace-local ignores (env, infra, node_modules, etc.).
README.md Updates the root examples list to point to @examples/mcp-server.
.gitignore Adds .claude/settings.local.json to ignored local files.
Comments suppressed due to low confidence (1)

examples/mcp-server/package.json:39

  • @modelcontextprotocol/inspector@^0.21.2 requires Node >=22.7.5 (per the lockfile), but the repo’s root package.json declares engines.node >=20. This can cause install-time engine warnings or failures in CI/dev environments pinned to Node 20. Consider pinning the inspector to a version that supports Node 20, or updating the repo/tooling Node requirement consistently.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +34
adapter.post(MCP_PATH, handle);
adapter.get(MCP_PATH, handle);
adapter.delete(MCP_PATH, handle);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /mcp endpoint is mounted with no authentication/authorization, but the exposed tools can proactively message arbitrary userIds and record approvals. Even for an example, leaving this open on the network makes it easy to abuse (spam users, force approval state changes) if the port is reachable. Consider adding a simple guard (e.g., require a bearer token/shared secret in headers for /mcp, or restrict to localhost in dev) and document it in the README.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a real gap that at least needs to be addressed in the README as non-production code.

Comment thread examples/mcp-server/src/mcpTools.ts
Comment on lines +29 to +31
const handle = async (req: any, res: any) => {
await transport.handleRequest(req, res, req.body);
};
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle uses req: any, res: any, which loses type safety and makes it easy to miss issues like missing body parsing. Since this file already depends on Express via ExpressAdapter, consider typing these as express.Request / express.Response (or at least unknown with narrowing) to catch integration mistakes at compile time.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +34
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
await mcpServer.connect(transport);

const handle = async (req: any, res: any) => {
await transport.handleRequest(req, res, req.body);
};
adapter.post(MCP_PATH, handle);
adapter.get(MCP_PATH, handle);
adapter.delete(MCP_PATH, handle);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adapter.post(MCP_PATH, handle) is mounting the MCP endpoint without any JSON body parsing middleware. ExpressAdapter.registerRoute() adds express.json() for the Teams messaging endpoint, but routes added via adapter.post() here won’t get that automatically, so req.body will usually be undefined and transport.handleRequest(req, res, req.body) can fail to parse requests. Add express.json() (or equivalent) for the /mcp routes (e.g., adapter.use(express.json()) before registering, or pass the middleware just for these routes).

Copilot uses AI. Check for mistakes.
await send('Hi! Will let you know if I need anything.');
});

app.on('card.action', async ({ activity }) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.on('card.action.submit_form', async ({ activity, send }) => {
can you use this pattern?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.on('card.action.approval_response', ...)

return {
statusCode: 200,
type: 'application/vnd.microsoft.activity.message',
value: 'Response recorded',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe just returning 'Response recorded' should be enough

// Initialize first so /api/messages is registered before we mount /mcp.
await app.initialize();

const adapter = app.server.adapter;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend managing express outside yourself.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the http adapter example on how that works

Copy link
Copy Markdown
Collaborator

@corinagum corinagum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sample won't start without TENANT_ID in env vars; I think in another sample either in TS or PY we started getting more explicit about tenant id. Here it should be added to the README as a pre-req.

await app.send(conversationId, question);
state.pendingAsks.set(requestId, { userId, status: 'pending' });
state.userPendingAsk.set(userId, requestId);
return { requestId };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably note in the README that there's only one ask per user.

Comment on lines +32 to +34
adapter.post(MCP_PATH, handle);
adapter.get(MCP_PATH, handle);
adapter.delete(MCP_PATH, handle);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a real gap that at least needs to be addressed in the README as non-production code.

Copy link
Copy Markdown
Member

@rido-min rido-min left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still trying to get the sample running

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rajan-chari this is another proof of why we should have State in the SDK, so samples like these can benefit of that abstraction

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the .gitignore at the root?

@@ -0,0 +1,164 @@
import { randomUUID } from 'crypto';

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the .js? I think we can avoid it by customizing the tsconfig

@@ -16,22 +16,22 @@
"build": "npx tsc",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if tsc is installed from package.json we dont need npx

@@ -16,22 +16,22 @@
"build": "npx tsc",
"start": "node -r dotenv/config .",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use --env-file instead of dotenv/config ?

"@modelcontextprotocol/sdk": "^1.25.2",
"zod": "^3.24.3"
},
"devDependencies": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add typescript

"inspect": "npx cross-env SERVER_PORT=9000 npx @modelcontextprotocol/inspector -e NODE_NO_WARNINGS=1 -e PORT=3978 node -r dotenv/config .",
"inspect": "npx cross-env SERVER_PORT=9000 npx @modelcontextprotocol/inspector",
"dev:teamsfx": "NODE_OPTIONS='--inspect=9239' npx env-cmd -f .env npm run dev",
"dev:teamsfx:testtool": "NODE_OPTIONS='--inspect=9239' npx env-cmd -f .env npm run dev",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why teamsfx?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing a note on how to configure this sample

Comment thread package-lock.json
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are some vulns, we should update the lock

17 vulnerabilities (7 low, 6 moderate, 3 high, 1 critical)

{
"name": "@examples/mcp",
"name": "@examples/mcp-server",
"version": "0.0.6",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what means this vesion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants