Skip to content

Commit 65b9e78

Browse files
authored
Merge pull request #596 from bpartridge83/mcp-server
MCP Server template
2 parents 7450d55 + d58dca2 commit 65b9e78

File tree

14 files changed

+926
-675
lines changed

14 files changed

+926
-675
lines changed

mcp-server/.env.example

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# description: Your Twilio Account SID
2+
# format: text
3+
# link: https://www.twilio.com/console
4+
# required: true
5+
ACCOUNT_SID=ACxxx
6+
7+
# description: Your Twilio Auth Token
8+
# format: text
9+
# link: https://www.twilio.com/console
10+
# required: true
11+
AUTH_TOKEN=abc
12+
13+
# description: Your Twilio API Key
14+
# format: text
15+
# link: https://www.twilio.com/console/project/api-keys
16+
# required: true
17+
API_KEY=SKxxx
18+
19+
# description: Your Twilio API Secret
20+
# format: text
21+
# link: https://www.twilio.com/console/project/api-keys
22+
# required: true
23+
API_SECRET=abc

mcp-server/.owners

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ktalebian
2+
vingiarrusso
3+
bpartridge
4+
# Insert your Github username here

mcp-server/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
## [Unreleased]
4+
5+
## [1.0.0]
6+
### Added
7+
- Initial release.
8+

mcp-server/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# mcp-server
2+
3+
Functions to run a remote MCP server for Twilio API tools
4+
5+
## Pre-requisites
6+
7+
### Environment variables
8+
9+
This project requires some environment variables to be set. A file named `.env` is used to store the values for those environment variables. To keep your tokens and secrets secure, make sure to not commit the `.env` file in git. When setting up the project with `twilio serverless:init ...` the Twilio CLI will create a `.gitignore` file that excludes `.env` from the version history.
10+
11+
In your `.env` file, set the following values:
12+
13+
* ACCOUNT_SID
14+
* AUTH_TOKEN
15+
* API_KEY
16+
* API_SECRET
17+
18+
## Create a new project with the template
19+
20+
1. Install the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart#install-twilio-cli)
21+
2. Install the [serverless toolkit](https://www.twilio.com/docs/labs/serverless-toolkit/getting-started)
22+
23+
```shell
24+
twilio plugins:install @twilio-labs/plugin-serverless
25+
```
26+
27+
3. Initiate a new project
28+
29+
```
30+
twilio serverless:init example --template=mcp-server && cd example
31+
```
32+
33+
4. Start the server with the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):
34+
35+
```
36+
twilio serverless:start
37+
```
38+
39+
ℹ️ Check the developer console and terminal for any errors, make sure you've set your environment variables.
40+
41+
## Deploying
42+
43+
Deploy your functions and assets with either of the following commands. Note: you must run these commands from inside your project folder. [More details in the docs.](https://www.twilio.com/docs/labs/serverless-toolkit)
44+
45+
With the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):
46+
47+
```
48+
twilio serverless:deploy
49+
```
50+
51+
## Integration with MCP clients
52+
53+
`https://<functions-domain>.twil.io/mcp?services=`
54+
55+
Header: x-twilio-signature
56+
57+
@TODO: Code samples to generate x-twilio-signature
58+
59+
Available services
60+
* Messaging (default)
61+
* Voice
62+
* VoiceAddOns
63+
* Conversations
64+
* Studio
65+
* TaskRouter
66+
* Serverless
67+
* Account
68+
* PhoneNumbers
69+
* Applications
70+
* Auth
71+
* AddOns
72+
* Usage
73+
74+
## Example prompts
75+
76+
@TODO
77+
78+
## Security recommendations
79+
80+
This remote MCP server function will provide Tools to your LLM that provide access to your Twilio account. We recommend the following considerations when giving clients access to your server:
81+
82+
- Always set the `requires_approval` field to ensure that there are no unintended actions taken within your account.
83+
- Use scoped permissions for your Twilio API Key. Not all endpoints support scoped permissions, but some do. See https://www.twilio.com/docs/iam/api-keys/restricted-api-keys for more information about which actions are supported per API Service.
84+
- To ensure privacy, do not use other MCP servers in conjunction with your Twilio MCP server.

mcp-server/assets/index.html

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="x-ua-compatible" content="ie=edge">
7+
<title>Get started with your Twilio Functions!</title>
8+
9+
<link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico">
10+
<link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css">
11+
<script src="https://twilio-labs.github.io/function-templates/static/v1/ce-helpers.js" defer></script>
12+
<script>
13+
window.addEventListener('DOMContentLoaded', (_event) => {
14+
inputPrependBaseURL();
15+
});
16+
</script>
17+
</head>
18+
<body>
19+
<div class="page-top">
20+
<header>
21+
<div id="twilio-logo">
22+
<a href="https://www.twilio.com/" target="_blank" rel="noopener">
23+
<svg class="logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 60 60">
24+
<title>Twilio Logo</title><path class="cls-1" d="M30,15A15,15,0,1,0,45,30,15,15,0,0,0,30,15Zm0,26A11,11,0,1,1,41,30,11,11,0,0,1,30,41Zm6.8-14.7a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,26.3Zm0,7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,33.7Zm-7.4,0a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,33.7Zm0-7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,26.3Z"/></svg>
25+
</a>
26+
</div>
27+
<nav>
28+
<span>Your Twilio application</span>
29+
<aside>
30+
<svg class="icon" role="img" aria-hidden="true" width="100%" height="100%" viewBox="0 0 20 20" aria-labelledby="NewIcon-1577"><path fill="currentColor" fill-rule="evenodd" d="M6.991 7.507c.003-.679 1.021-.675 1.019.004-.012 2.956 1.388 4.41 4.492 4.48.673.016.66 1.021-.013 1.019-2.898-.011-4.327 1.446-4.48 4.506-.033.658-1.01.639-1.018-.02-.03-3.027-1.382-4.49-4.481-4.486-.675 0-.682-1.009-.008-1.019 3.02-.042 4.478-1.452 4.49-4.484zm.505 2.757l-.115.242c-.459.9-1.166 1.558-2.115 1.976l.176.08c.973.465 1.664 1.211 2.083 2.22l.02.05.088-.192c.464-.973 1.173-1.685 2.123-2.124l.039-.018-.118-.05c-.963-.435-1.667-1.117-2.113-2.034l-.068-.15zm10.357-8.12c.174.17.194.434.058.625l-.058.068-1.954 1.905 1.954 1.908a.482.482 0 010 .694.512.512 0 01-.641.056l-.07-.056-1.954-1.908-1.954 1.908a.511.511 0 01-.71 0 .482.482 0 01-.058-.626l.058-.068 1.954-1.908-1.954-1.905a.482.482 0 010-.693.512.512 0 01.64-.057l.07.057 1.954 1.905 1.954-1.905a.511.511 0 01.71 0z"></path></svg>
31+
Live
32+
</aside>
33+
</nav>
34+
</header>
35+
</div>
36+
<main>
37+
<div class="content">
38+
<h1>
39+
<img src="https://twilio-labs.github.io/function-templates/static/v1/success.svg" />
40+
<div>
41+
<p>Welcome!</p>
42+
<p>Your remote MCP server!</p>
43+
</div>
44+
</h1>
45+
<section>
46+
<h2>Get started with your application</h2>
47+
<p>
48+
Follow these steps to try out your new app:
49+
</p>
50+
<ol class="steps">
51+
<li>Configure your MCP client application to use the /mcp endpoint</li>
52+
<li>Make sure you're generating a signature to pass with your requests using the `x-twilio-signature` header.</li>
53+
<li>Filter the MCP tools by passing a `?service` query parameter, using one or more of the services below.</li>
54+
</ol>
55+
</section>
56+
<section>
57+
<h3>Available services</h3>
58+
<ul style="list-style-type: disc; padding-left: 40px;">
59+
<li>Messaging (default)</li>
60+
<li>Voice</li>
61+
<li>VoiceAddOns</li>
62+
<li>Conversations</li>
63+
<li>Studio</li>
64+
<li>TaskRouter</li>
65+
<li>Serverless</li>
66+
<li>Account</li>
67+
<li>PhoneNumbers</li>
68+
<li>Applications</li>
69+
<li>Auth</li>
70+
<li>AddOns</li>
71+
<li>Usage</li>
72+
</ul>
73+
<!-- APP_INFO_V2 -->
74+
</section>
75+
</div>
76+
</main>
77+
<footer>
78+
<span class="statement">We can't wait to see what you build.</span>
79+
</footer>
80+
</body>
81+
</html>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/* eslint-disable callback-return */
2+
3+
const { randomUUID } = require('crypto');
4+
5+
const modules = Runtime.getFunctions();
6+
const createServer = require(modules.server.path);
7+
const createReq = require(modules.req.path);
8+
const createRes = require(modules.res.path);
9+
10+
const defaultMessaging = [
11+
'Api20100401Message',
12+
'Api20100401IncomingPhoneNumber',
13+
];
14+
15+
const TWILIO_TAG_MAP = {
16+
Messaging: defaultMessaging,
17+
Voice: ['Api20100401Call'],
18+
VoiceAddOns: [
19+
'Api20100401Recording',
20+
'Api20100401Transcription',
21+
'Api20100401Conference',
22+
],
23+
Conversations: [
24+
'ConversationsV1Conversation',
25+
'ConversationsV1Message',
26+
'ConversationsV1Participant',
27+
'ConversationsV1Service',
28+
'ConversationsV1User',
29+
],
30+
Studio: [
31+
'StudioV2Execution',
32+
'StudioV2ExecutionContext',
33+
'StudioV2ExecutionStep',
34+
'StudioV2Flow',
35+
'StudioV2FlowRevision',
36+
'StudioV2FlowValidate',
37+
],
38+
TaskRouter: [
39+
'TaskrouterV1Activity',
40+
'TaskrouterV1Event',
41+
'TaskrouterV1Task',
42+
'TaskrouterV1TaskChannel',
43+
'TaskrouterV1TaskQueue',
44+
'TaskrouterV1TaskReservation',
45+
'TaskrouterV1Worker',
46+
'TaskrouterV1WorkerChannel',
47+
'TaskrouterV1WorkerReservation',
48+
'TaskrouterV1Workflow',
49+
'TaskrouterV1Workspace',
50+
'TaskrouterV1WorkspaceStatistics',
51+
],
52+
Serverless: [
53+
'ServerlessV1Asset',
54+
'ServerlessV1AssetVersion',
55+
'ServerlessV1Build',
56+
'ServerlessV1Deployment',
57+
'ServerlessV1Environment',
58+
'ServerlessV1Function',
59+
'ServerlessV1Service',
60+
'ServerlessV1Variable',
61+
],
62+
Account: ['Api20100401Account'],
63+
PhoneNumbers: ['Api20100401IncomingPhoneNumber', 'Api20100401Address'],
64+
Applications: ['Api20100401Application'],
65+
Auth: ['Api20100401Token'],
66+
AddOns: ['Api20100401AddOnResult'],
67+
Usage: ['Api20100401Usage'],
68+
};
69+
70+
const validateContext = (context, callback) => {
71+
if (!context.ACCOUNT_SID || !context.API_KEY || !context.API_SECRET) {
72+
const response = new Twilio.Response();
73+
response.setStatusCode(400);
74+
response.setBody({
75+
error:
76+
'required context variables ACCOUNT_SID or API_KEY or API_SECRET not found',
77+
});
78+
79+
callback(null, response);
80+
81+
return false;
82+
}
83+
84+
return true;
85+
};
86+
87+
const getTags = (event) => {
88+
if (!event.services) {
89+
return defaultMessaging;
90+
}
91+
92+
const services =
93+
typeof event.services === 'string' ? [event.services] : event.services;
94+
// MCP does not like additoinal keys
95+
delete event.services;
96+
97+
const tags = [];
98+
services.forEach((service) => {
99+
if (TWILIO_TAG_MAP[service]) {
100+
tags.push(...TWILIO_TAG_MAP[service]);
101+
}
102+
});
103+
104+
if (tags.length === 0) {
105+
return ['Api20100401Message'];
106+
}
107+
108+
return tags;
109+
};
110+
111+
exports.handler = async function (context, event, callback) {
112+
const id = randomUUID().substring(0, 6);
113+
console.log(`[${id}]`, 'STARTED MCP called with method', event.method);
114+
if (event.method === 'tools/call') {
115+
console.log(
116+
`[${id}]`,
117+
'Calling tool',
118+
event.params.name,
119+
'with arguments',
120+
event.params.arguments
121+
);
122+
}
123+
124+
if (!validateContext(context, callback)) {
125+
return;
126+
}
127+
128+
try {
129+
const tags = getTags(event);
130+
const { transport } = await createServer(context, event, tags);
131+
const req = createReq(event);
132+
const res = createRes(callback);
133+
134+
await transport.handleRequest(req, res);
135+
} catch (error) {
136+
console.log(`[${id}]`, 'FAILED MCP request with method', event.method);
137+
console.log(error);
138+
139+
const response = new Twilio.Response();
140+
response.setStatusCode(500);
141+
response.setBody({ error: error.message });
142+
callback(null, response);
143+
} finally {
144+
console.log(`[${id}]`, 'COMPLETED MCP request with method', event.method);
145+
}
146+
};

0 commit comments

Comments
 (0)