@modelcontextprotocol/sdk 1.18.2 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1010 -824
- package/dist/cjs/cli.js +35 -37
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/client/auth.d.ts +12 -12
- package/dist/cjs/client/auth.d.ts.map +1 -1
- package/dist/cjs/client/auth.js +81 -93
- package/dist/cjs/client/auth.js.map +1 -1
- package/dist/cjs/client/index.d.ts +186 -123
- package/dist/cjs/client/index.d.ts.map +1 -1
- package/dist/cjs/client/index.js +40 -41
- package/dist/cjs/client/index.js.map +1 -1
- package/dist/cjs/client/middleware.d.ts +2 -2
- package/dist/cjs/client/middleware.d.ts.map +1 -1
- package/dist/cjs/client/middleware.js +22 -27
- package/dist/cjs/client/middleware.js.map +1 -1
- package/dist/cjs/client/sse.d.ts +4 -4
- package/dist/cjs/client/sse.d.ts.map +1 -1
- package/dist/cjs/client/sse.js +34 -21
- package/dist/cjs/client/sse.js.map +1 -1
- package/dist/cjs/client/stdio.d.ts +4 -4
- package/dist/cjs/client/stdio.d.ts.map +1 -1
- package/dist/cjs/client/stdio.js +32 -32
- package/dist/cjs/client/stdio.js.map +1 -1
- package/dist/cjs/client/streamableHttp.d.ts +7 -6
- package/dist/cjs/client/streamableHttp.d.ts.map +1 -1
- package/dist/cjs/client/streamableHttp.js +55 -38
- package/dist/cjs/client/streamableHttp.js.map +1 -1
- package/dist/cjs/client/websocket.d.ts +2 -2
- package/dist/cjs/client/websocket.d.ts.map +1 -1
- package/dist/cjs/client/websocket.js +5 -7
- package/dist/cjs/client/websocket.js.map +1 -1
- package/dist/cjs/examples/client/multipleClientsParallel.js +2 -2
- package/dist/cjs/examples/client/multipleClientsParallel.js.map +1 -1
- package/dist/cjs/examples/client/parallelToolCallsClient.js +6 -5
- package/dist/cjs/examples/client/parallelToolCallsClient.js.map +1 -1
- package/dist/cjs/examples/client/simpleOAuthClient.js +15 -13
- package/dist/cjs/examples/client/simpleOAuthClient.js.map +1 -1
- package/dist/cjs/examples/client/simpleStreamableHttp.js +15 -11
- package/dist/cjs/examples/client/simpleStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/client/streamableHttpWithSseFallbackClient.js +2 -2
- package/dist/cjs/examples/client/streamableHttpWithSseFallbackClient.js.map +1 -1
- package/dist/cjs/examples/server/demoInMemoryOAuthProvider.d.ts +1 -1
- package/dist/cjs/examples/server/demoInMemoryOAuthProvider.d.ts.map +1 -1
- package/dist/cjs/examples/server/demoInMemoryOAuthProvider.js +18 -16
- package/dist/cjs/examples/server/demoInMemoryOAuthProvider.js.map +1 -1
- package/dist/cjs/examples/server/jsonResponseStreamableHttp.js +18 -18
- package/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/mcpServerOutputSchema.js +19 -17
- package/dist/cjs/examples/server/mcpServerOutputSchema.js.map +1 -1
- package/dist/cjs/examples/server/simpleSseServer.js +8 -8
- package/dist/cjs/examples/server/simpleSseServer.js.map +1 -1
- package/dist/cjs/examples/server/simpleStatelessStreamableHttp.js +22 -22
- package/dist/cjs/examples/server/simpleStatelessStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/simpleStreamableHttp.js +78 -78
- package/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +18 -18
- package/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +8 -8
- package/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/toolWithSampleServer.js +19 -19
- package/dist/cjs/examples/server/toolWithSampleServer.js.map +1 -1
- package/dist/cjs/examples/shared/inMemoryEventStore.d.ts.map +1 -1
- package/dist/cjs/examples/shared/inMemoryEventStore.js.map +1 -1
- package/dist/cjs/inMemory.d.ts +3 -3
- package/dist/cjs/inMemory.d.ts.map +1 -1
- package/dist/cjs/inMemory.js +1 -1
- package/dist/cjs/inMemory.js.map +1 -1
- package/dist/cjs/server/auth/clients.d.ts +2 -2
- package/dist/cjs/server/auth/clients.d.ts.map +1 -1
- package/dist/cjs/server/auth/errors.d.ts +1 -1
- package/dist/cjs/server/auth/errors.d.ts.map +1 -1
- package/dist/cjs/server/auth/errors.js +17 -17
- package/dist/cjs/server/auth/errors.js.map +1 -1
- package/dist/cjs/server/auth/handlers/authorize.d.ts +3 -3
- package/dist/cjs/server/auth/handlers/authorize.d.ts.map +1 -1
- package/dist/cjs/server/auth/handlers/authorize.js +21 -18
- package/dist/cjs/server/auth/handlers/authorize.js.map +1 -1
- package/dist/cjs/server/auth/handlers/metadata.d.ts +2 -2
- package/dist/cjs/server/auth/handlers/metadata.js +1 -1
- package/dist/cjs/server/auth/handlers/metadata.js.map +1 -1
- package/dist/cjs/server/auth/handlers/register.d.ts +4 -4
- package/dist/cjs/server/auth/handlers/register.d.ts.map +1 -1
- package/dist/cjs/server/auth/handlers/register.js +7 -9
- package/dist/cjs/server/auth/handlers/register.js.map +1 -1
- package/dist/cjs/server/auth/handlers/revoke.d.ts +4 -4
- package/dist/cjs/server/auth/handlers/revoke.d.ts.map +1 -1
- package/dist/cjs/server/auth/handlers/revoke.js +9 -9
- package/dist/cjs/server/auth/handlers/revoke.js.map +1 -1
- package/dist/cjs/server/auth/handlers/token.d.ts +3 -3
- package/dist/cjs/server/auth/handlers/token.d.ts.map +1 -1
- package/dist/cjs/server/auth/handlers/token.js +14 -14
- package/dist/cjs/server/auth/handlers/token.js.map +1 -1
- package/dist/cjs/server/auth/middleware/allowedMethods.d.ts +1 -1
- package/dist/cjs/server/auth/middleware/allowedMethods.d.ts.map +1 -1
- package/dist/cjs/server/auth/middleware/allowedMethods.js +1 -3
- package/dist/cjs/server/auth/middleware/allowedMethods.js.map +1 -1
- package/dist/cjs/server/auth/middleware/bearerAuth.d.ts +4 -4
- package/dist/cjs/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/dist/cjs/server/auth/middleware/bearerAuth.js +7 -7
- package/dist/cjs/server/auth/middleware/bearerAuth.js.map +1 -1
- package/dist/cjs/server/auth/middleware/clientAuth.d.ts +4 -4
- package/dist/cjs/server/auth/middleware/clientAuth.d.ts.map +1 -1
- package/dist/cjs/server/auth/middleware/clientAuth.js +6 -6
- package/dist/cjs/server/auth/middleware/clientAuth.js.map +1 -1
- package/dist/cjs/server/auth/provider.d.ts +4 -4
- package/dist/cjs/server/auth/provider.d.ts.map +1 -1
- package/dist/cjs/server/auth/providers/proxyProvider.d.ts +10 -10
- package/dist/cjs/server/auth/providers/proxyProvider.d.ts.map +1 -1
- package/dist/cjs/server/auth/providers/proxyProvider.js +34 -34
- package/dist/cjs/server/auth/providers/proxyProvider.js.map +1 -1
- package/dist/cjs/server/auth/router.d.ts +11 -11
- package/dist/cjs/server/auth/router.d.ts.map +1 -1
- package/dist/cjs/server/auth/router.js +16 -18
- package/dist/cjs/server/auth/router.js.map +1 -1
- package/dist/cjs/server/auth/types.d.ts +1 -1
- package/dist/cjs/server/auth/types.d.ts.map +1 -1
- package/dist/cjs/server/completable.d.ts +5 -5
- package/dist/cjs/server/completable.d.ts.map +1 -1
- package/dist/cjs/server/completable.js +5 -5
- package/dist/cjs/server/completable.js.map +1 -1
- package/dist/cjs/server/index.d.ts +9 -9
- package/dist/cjs/server/index.d.ts.map +1 -1
- package/dist/cjs/server/index.js +38 -42
- package/dist/cjs/server/index.js.map +1 -1
- package/dist/cjs/server/mcp.d.ts +8 -8
- package/dist/cjs/server/mcp.d.ts.map +1 -1
- package/dist/cjs/server/mcp.js +87 -82
- package/dist/cjs/server/mcp.js.map +1 -1
- package/dist/cjs/server/sse.d.ts +4 -4
- package/dist/cjs/server/sse.d.ts.map +1 -1
- package/dist/cjs/server/sse.js +16 -15
- package/dist/cjs/server/sse.js.map +1 -1
- package/dist/cjs/server/stdio.d.ts +3 -3
- package/dist/cjs/server/stdio.d.ts.map +1 -1
- package/dist/cjs/server/stdio.js +7 -7
- package/dist/cjs/server/stdio.js.map +1 -1
- package/dist/cjs/server/streamableHttp.d.ts +5 -5
- package/dist/cjs/server/streamableHttp.d.ts.map +1 -1
- package/dist/cjs/server/streamableHttp.js +63 -64
- package/dist/cjs/server/streamableHttp.js.map +1 -1
- package/dist/cjs/shared/auth-utils.d.ts.map +1 -1
- package/dist/cjs/shared/auth-utils.js +3 -3
- package/dist/cjs/shared/auth-utils.js.map +1 -1
- package/dist/cjs/shared/auth.d.ts +1 -1
- package/dist/cjs/shared/auth.d.ts.map +1 -1
- package/dist/cjs/shared/auth.js +42 -46
- package/dist/cjs/shared/auth.js.map +1 -1
- package/dist/cjs/shared/metadataUtils.d.ts +1 -1
- package/dist/cjs/shared/metadataUtils.js.map +1 -1
- package/dist/cjs/shared/protocol.d.ts +6 -6
- package/dist/cjs/shared/protocol.d.ts.map +1 -1
- package/dist/cjs/shared/protocol.js +42 -43
- package/dist/cjs/shared/protocol.js.map +1 -1
- package/dist/cjs/shared/stdio.d.ts +1 -1
- package/dist/cjs/shared/stdio.d.ts.map +1 -1
- package/dist/cjs/shared/stdio.js +3 -3
- package/dist/cjs/shared/stdio.js.map +1 -1
- package/dist/cjs/shared/transport.d.ts +1 -1
- package/dist/cjs/shared/transport.d.ts.map +1 -1
- package/dist/cjs/shared/uriTemplate.d.ts.map +1 -1
- package/dist/cjs/shared/uriTemplate.js +69 -71
- package/dist/cjs/shared/uriTemplate.js.map +1 -1
- package/dist/cjs/types.d.ts +9650 -4790
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +199 -234
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/cli.js +45 -47
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/client/auth.d.ts +12 -12
- package/dist/esm/client/auth.d.ts.map +1 -1
- package/dist/esm/client/auth.js +87 -99
- package/dist/esm/client/auth.js.map +1 -1
- package/dist/esm/client/index.d.ts +186 -123
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +43 -44
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/middleware.d.ts +2 -2
- package/dist/esm/client/middleware.d.ts.map +1 -1
- package/dist/esm/client/middleware.js +23 -28
- package/dist/esm/client/middleware.js.map +1 -1
- package/dist/esm/client/sse.d.ts +4 -4
- package/dist/esm/client/sse.d.ts.map +1 -1
- package/dist/esm/client/sse.js +37 -24
- package/dist/esm/client/sse.js.map +1 -1
- package/dist/esm/client/stdio.d.ts +4 -4
- package/dist/esm/client/stdio.d.ts.map +1 -1
- package/dist/esm/client/stdio.js +36 -36
- package/dist/esm/client/stdio.js.map +1 -1
- package/dist/esm/client/streamableHttp.d.ts +7 -6
- package/dist/esm/client/streamableHttp.d.ts.map +1 -1
- package/dist/esm/client/streamableHttp.js +58 -41
- package/dist/esm/client/streamableHttp.js.map +1 -1
- package/dist/esm/client/websocket.d.ts +2 -2
- package/dist/esm/client/websocket.d.ts.map +1 -1
- package/dist/esm/client/websocket.js +6 -8
- package/dist/esm/client/websocket.js.map +1 -1
- package/dist/esm/examples/client/multipleClientsParallel.js +3 -3
- package/dist/esm/examples/client/multipleClientsParallel.js.map +1 -1
- package/dist/esm/examples/client/parallelToolCallsClient.js +7 -6
- package/dist/esm/examples/client/parallelToolCallsClient.js.map +1 -1
- package/dist/esm/examples/client/simpleOAuthClient.js +15 -13
- package/dist/esm/examples/client/simpleOAuthClient.js.map +1 -1
- package/dist/esm/examples/client/simpleStreamableHttp.js +17 -13
- package/dist/esm/examples/client/simpleStreamableHttp.js.map +1 -1
- package/dist/esm/examples/client/streamableHttpWithSseFallbackClient.js +3 -3
- package/dist/esm/examples/client/streamableHttpWithSseFallbackClient.js.map +1 -1
- package/dist/esm/examples/server/demoInMemoryOAuthProvider.d.ts +1 -1
- package/dist/esm/examples/server/demoInMemoryOAuthProvider.d.ts.map +1 -1
- package/dist/esm/examples/server/demoInMemoryOAuthProvider.js +19 -17
- package/dist/esm/examples/server/demoInMemoryOAuthProvider.js.map +1 -1
- package/dist/esm/examples/server/jsonResponseStreamableHttp.js +18 -18
- package/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/mcpServerOutputSchema.js +22 -20
- package/dist/esm/examples/server/mcpServerOutputSchema.js.map +1 -1
- package/dist/esm/examples/server/simpleSseServer.js +8 -8
- package/dist/esm/examples/server/simpleSseServer.js.map +1 -1
- package/dist/esm/examples/server/simpleStatelessStreamableHttp.js +22 -22
- package/dist/esm/examples/server/simpleStatelessStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/simpleStreamableHttp.js +78 -78
- package/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +19 -19
- package/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +8 -8
- package/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/toolWithSampleServer.js +22 -22
- package/dist/esm/examples/server/toolWithSampleServer.js.map +1 -1
- package/dist/esm/examples/shared/inMemoryEventStore.d.ts.map +1 -1
- package/dist/esm/examples/shared/inMemoryEventStore.js.map +1 -1
- package/dist/esm/inMemory.d.ts +3 -3
- package/dist/esm/inMemory.d.ts.map +1 -1
- package/dist/esm/inMemory.js +1 -1
- package/dist/esm/inMemory.js.map +1 -1
- package/dist/esm/server/auth/clients.d.ts +2 -2
- package/dist/esm/server/auth/clients.d.ts.map +1 -1
- package/dist/esm/server/auth/errors.d.ts +1 -1
- package/dist/esm/server/auth/errors.d.ts.map +1 -1
- package/dist/esm/server/auth/errors.js +17 -17
- package/dist/esm/server/auth/errors.js.map +1 -1
- package/dist/esm/server/auth/handlers/authorize.d.ts +3 -3
- package/dist/esm/server/auth/handlers/authorize.d.ts.map +1 -1
- package/dist/esm/server/auth/handlers/authorize.js +26 -23
- package/dist/esm/server/auth/handlers/authorize.js.map +1 -1
- package/dist/esm/server/auth/handlers/metadata.d.ts +2 -2
- package/dist/esm/server/auth/handlers/metadata.js +3 -3
- package/dist/esm/server/auth/handlers/metadata.js.map +1 -1
- package/dist/esm/server/auth/handlers/register.d.ts +4 -4
- package/dist/esm/server/auth/handlers/register.d.ts.map +1 -1
- package/dist/esm/server/auth/handlers/register.js +12 -14
- package/dist/esm/server/auth/handlers/register.js.map +1 -1
- package/dist/esm/server/auth/handlers/revoke.d.ts +4 -4
- package/dist/esm/server/auth/handlers/revoke.d.ts.map +1 -1
- package/dist/esm/server/auth/handlers/revoke.js +16 -16
- package/dist/esm/server/auth/handlers/revoke.js.map +1 -1
- package/dist/esm/server/auth/handlers/token.d.ts +3 -3
- package/dist/esm/server/auth/handlers/token.d.ts.map +1 -1
- package/dist/esm/server/auth/handlers/token.js +22 -22
- package/dist/esm/server/auth/handlers/token.js.map +1 -1
- package/dist/esm/server/auth/middleware/allowedMethods.d.ts +1 -1
- package/dist/esm/server/auth/middleware/allowedMethods.d.ts.map +1 -1
- package/dist/esm/server/auth/middleware/allowedMethods.js +2 -4
- package/dist/esm/server/auth/middleware/allowedMethods.js.map +1 -1
- package/dist/esm/server/auth/middleware/bearerAuth.d.ts +4 -4
- package/dist/esm/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/dist/esm/server/auth/middleware/bearerAuth.js +8 -8
- package/dist/esm/server/auth/middleware/bearerAuth.js.map +1 -1
- package/dist/esm/server/auth/middleware/clientAuth.d.ts +4 -4
- package/dist/esm/server/auth/middleware/clientAuth.d.ts.map +1 -1
- package/dist/esm/server/auth/middleware/clientAuth.js +8 -8
- package/dist/esm/server/auth/middleware/clientAuth.js.map +1 -1
- package/dist/esm/server/auth/provider.d.ts +4 -4
- package/dist/esm/server/auth/provider.d.ts.map +1 -1
- package/dist/esm/server/auth/providers/proxyProvider.d.ts +10 -10
- package/dist/esm/server/auth/providers/proxyProvider.d.ts.map +1 -1
- package/dist/esm/server/auth/providers/proxyProvider.js +36 -36
- package/dist/esm/server/auth/providers/proxyProvider.js.map +1 -1
- package/dist/esm/server/auth/router.d.ts +11 -11
- package/dist/esm/server/auth/router.d.ts.map +1 -1
- package/dist/esm/server/auth/router.js +22 -24
- package/dist/esm/server/auth/router.js.map +1 -1
- package/dist/esm/server/auth/types.d.ts +1 -1
- package/dist/esm/server/auth/types.d.ts.map +1 -1
- package/dist/esm/server/completable.d.ts +5 -5
- package/dist/esm/server/completable.d.ts.map +1 -1
- package/dist/esm/server/completable.js +6 -6
- package/dist/esm/server/completable.js.map +1 -1
- package/dist/esm/server/index.d.ts +9 -9
- package/dist/esm/server/index.d.ts.map +1 -1
- package/dist/esm/server/index.js +41 -45
- package/dist/esm/server/index.js.map +1 -1
- package/dist/esm/server/mcp.d.ts +8 -8
- package/dist/esm/server/mcp.d.ts.map +1 -1
- package/dist/esm/server/mcp.js +93 -88
- package/dist/esm/server/mcp.js.map +1 -1
- package/dist/esm/server/sse.d.ts +4 -4
- package/dist/esm/server/sse.d.ts.map +1 -1
- package/dist/esm/server/sse.js +20 -19
- package/dist/esm/server/sse.js.map +1 -1
- package/dist/esm/server/stdio.d.ts +3 -3
- package/dist/esm/server/stdio.d.ts.map +1 -1
- package/dist/esm/server/stdio.js +9 -9
- package/dist/esm/server/stdio.js.map +1 -1
- package/dist/esm/server/streamableHttp.d.ts +5 -5
- package/dist/esm/server/streamableHttp.d.ts.map +1 -1
- package/dist/esm/server/streamableHttp.js +67 -68
- package/dist/esm/server/streamableHttp.js.map +1 -1
- package/dist/esm/shared/auth-utils.d.ts.map +1 -1
- package/dist/esm/shared/auth-utils.js +3 -3
- package/dist/esm/shared/auth-utils.js.map +1 -1
- package/dist/esm/shared/auth.d.ts +1 -1
- package/dist/esm/shared/auth.d.ts.map +1 -1
- package/dist/esm/shared/auth.js +43 -47
- package/dist/esm/shared/auth.js.map +1 -1
- package/dist/esm/shared/metadataUtils.d.ts +1 -1
- package/dist/esm/shared/metadataUtils.js.map +1 -1
- package/dist/esm/shared/protocol.d.ts +6 -6
- package/dist/esm/shared/protocol.d.ts.map +1 -1
- package/dist/esm/shared/protocol.js +43 -44
- package/dist/esm/shared/protocol.js.map +1 -1
- package/dist/esm/shared/stdio.d.ts +1 -1
- package/dist/esm/shared/stdio.d.ts.map +1 -1
- package/dist/esm/shared/stdio.js +4 -4
- package/dist/esm/shared/stdio.js.map +1 -1
- package/dist/esm/shared/transport.d.ts +1 -1
- package/dist/esm/shared/transport.d.ts.map +1 -1
- package/dist/esm/shared/uriTemplate.d.ts.map +1 -1
- package/dist/esm/shared/uriTemplate.js +69 -71
- package/dist/esm/shared/uriTemplate.js.map +1 -1
- package/dist/esm/types.d.ts +9650 -4790
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +197 -232
- package/dist/esm/types.js.map +1 -1
- package/package.json +101 -98
package/README.md
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
# MCP TypeScript SDK  
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<details>
|
|
4
|
+
<summary>Table of Contents</summary>
|
|
4
5
|
|
|
5
6
|
- [Overview](#overview)
|
|
6
7
|
- [Installation](#installation)
|
|
7
8
|
- [Quickstart](#quick-start)
|
|
8
9
|
- [What is MCP?](#what-is-mcp)
|
|
9
10
|
- [Core Concepts](#core-concepts)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
- [Server](#server)
|
|
12
|
+
- [Resources](#resources)
|
|
13
|
+
- [Tools](#tools)
|
|
14
|
+
- [Prompts](#prompts)
|
|
15
|
+
- [Completions](#completions)
|
|
16
|
+
- [Sampling](#sampling)
|
|
16
17
|
- [Running Your Server](#running-your-server)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
- [stdio](#stdio)
|
|
19
|
+
- [Streamable HTTP](#streamable-http)
|
|
20
|
+
- [Testing and Debugging](#testing-and-debugging)
|
|
20
21
|
- [Examples](#examples)
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
- [Echo Server](#echo-server)
|
|
23
|
+
- [SQLite Explorer](#sqlite-explorer)
|
|
23
24
|
- [Advanced Usage](#advanced-usage)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
- [Dynamic Servers](#dynamic-servers)
|
|
26
|
+
- [Low-Level Server](#low-level-server)
|
|
27
|
+
- [Writing MCP Clients](#writing-mcp-clients)
|
|
28
|
+
- [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream)
|
|
29
|
+
- [Backwards Compatibility](#backwards-compatibility)
|
|
29
30
|
- [Documentation](#documentation)
|
|
30
31
|
- [Contributing](#contributing)
|
|
31
32
|
- [License](#license)
|
|
32
33
|
|
|
34
|
+
</details>
|
|
35
|
+
|
|
33
36
|
## Overview
|
|
34
37
|
|
|
35
|
-
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements
|
|
38
|
+
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements
|
|
39
|
+
[the full MCP specification](https://modelcontextprotocol.io/specification/latest), making it easy to:
|
|
36
40
|
|
|
37
|
-
- Build MCP clients that can connect to any MCP server
|
|
38
41
|
- Create MCP servers that expose resources, prompts and tools
|
|
42
|
+
- Build MCP clients that can connect to any MCP server
|
|
39
43
|
- Use standard transports like stdio and Streamable HTTP
|
|
40
|
-
- Handle all MCP protocol messages and lifecycle events
|
|
41
44
|
|
|
42
45
|
## Installation
|
|
43
46
|
|
|
@@ -45,64 +48,96 @@ The Model Context Protocol allows applications to provide context for LLMs in a
|
|
|
45
48
|
npm install @modelcontextprotocol/sdk
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
> ⚠️ MCP requires Node.js v18.x or higher to work fine.
|
|
49
|
-
|
|
50
51
|
## Quick Start
|
|
51
52
|
|
|
52
|
-
Let's create a simple MCP server that exposes a calculator tool and some data
|
|
53
|
+
Let's create a simple MCP server that exposes a calculator tool and some data. Save the following as `server.ts`:
|
|
53
54
|
|
|
54
55
|
```typescript
|
|
55
|
-
import { McpServer, ResourceTemplate } from
|
|
56
|
-
import {
|
|
57
|
-
import
|
|
56
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
57
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
58
|
+
import express from 'express';
|
|
59
|
+
import { z } from 'zod';
|
|
58
60
|
|
|
59
61
|
// Create an MCP server
|
|
60
62
|
const server = new McpServer({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
name: 'demo-server',
|
|
64
|
+
version: '1.0.0'
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
// Add an addition tool
|
|
66
|
-
server.registerTool(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
server.registerTool(
|
|
69
|
+
'add',
|
|
70
|
+
{
|
|
71
|
+
title: 'Addition Tool',
|
|
72
|
+
description: 'Add two numbers',
|
|
73
|
+
inputSchema: { a: z.number(), b: z.number() },
|
|
74
|
+
outputSchema: { result: z.number() }
|
|
75
|
+
},
|
|
76
|
+
async ({ a, b }) => {
|
|
77
|
+
const output = { result: a + b };
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
80
|
+
structuredContent: output
|
|
81
|
+
};
|
|
82
|
+
}
|
|
75
83
|
);
|
|
76
84
|
|
|
77
85
|
// Add a dynamic greeting resource
|
|
78
86
|
server.registerResource(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
'greeting',
|
|
88
|
+
new ResourceTemplate('greeting://{name}', { list: undefined }),
|
|
89
|
+
{
|
|
90
|
+
title: 'Greeting Resource', // Display name for UI
|
|
91
|
+
description: 'Dynamic greeting generator'
|
|
92
|
+
},
|
|
93
|
+
async (uri, { name }) => ({
|
|
94
|
+
contents: [
|
|
95
|
+
{
|
|
96
|
+
uri: uri.href,
|
|
97
|
+
text: `Hello, ${name}!`
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
})
|
|
91
101
|
);
|
|
92
102
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
|
|
103
|
+
// Set up Express and HTTP transport
|
|
104
|
+
const app = express();
|
|
105
|
+
app.use(express.json());
|
|
106
|
+
|
|
107
|
+
app.post('/mcp', async (req, res) => {
|
|
108
|
+
// Create a new transport for each request to prevent request ID collisions
|
|
109
|
+
const transport = new StreamableHTTPServerTransport({
|
|
110
|
+
sessionIdGenerator: undefined,
|
|
111
|
+
enableJsonResponse: true
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
res.on('close', () => {
|
|
115
|
+
transport.close();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await server.connect(transport);
|
|
119
|
+
await transport.handleRequest(req, res, req.body);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const port = parseInt(process.env.PORT || '3000');
|
|
123
|
+
app.listen(port, () => {
|
|
124
|
+
console.log(`Demo MCP Server running on http://localhost:${port}/mcp`);
|
|
125
|
+
}).on('error', error => {
|
|
126
|
+
console.error('Server error:', error);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
96
129
|
```
|
|
97
130
|
|
|
98
|
-
|
|
131
|
+
Install the deps with `npm install @modelcontextprotocol/sdk express zod@3`, and run with `npx -y tsx server.ts`.
|
|
132
|
+
|
|
133
|
+
You can connect to it using any MCP client that supports streamable http, such as:
|
|
99
134
|
|
|
100
|
-
|
|
135
|
+
- [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector): `npx @modelcontextprotocol/inspector` and connect to the streamable HTTP URL `http://localhost:3000/mcp`
|
|
136
|
+
- [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp): `claude mcp add --transport http my-server http://localhost:3000/mcp`
|
|
137
|
+
- [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers): `code --add-mcp "{\"name\":\"my-server\",\"type\":\"http\",\"url\":\"http://localhost:3000/mcp\"}"`
|
|
138
|
+
- [Cursor](https://cursor.com/docs/context/mcp): Click [this deeplink](cursor://anysphere.cursor-deeplink/mcp/install?name=my-server&config=eyJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvbWNwIn0%3D)
|
|
101
139
|
|
|
102
|
-
|
|
103
|
-
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
|
|
104
|
-
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
|
|
105
|
-
- And more!
|
|
140
|
+
Then try asking your agent to add two numbers using its new tool!
|
|
106
141
|
|
|
107
142
|
## Core Concepts
|
|
108
143
|
|
|
@@ -112,212 +147,249 @@ The McpServer is your core interface to the MCP protocol. It handles connection
|
|
|
112
147
|
|
|
113
148
|
```typescript
|
|
114
149
|
const server = new McpServer({
|
|
115
|
-
|
|
116
|
-
|
|
150
|
+
name: 'my-app',
|
|
151
|
+
version: '1.0.0'
|
|
117
152
|
});
|
|
118
153
|
```
|
|
119
154
|
|
|
120
|
-
###
|
|
155
|
+
### Tools
|
|
121
156
|
|
|
122
|
-
|
|
157
|
+
[Tools](https://modelcontextprotocol.io/specification/latest/server/tools) let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects. Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call,
|
|
158
|
+
and the arguments.
|
|
123
159
|
|
|
124
160
|
```typescript
|
|
125
|
-
//
|
|
126
|
-
server.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
// Simple tool with parameters
|
|
162
|
+
server.registerTool(
|
|
163
|
+
'calculate-bmi',
|
|
164
|
+
{
|
|
165
|
+
title: 'BMI Calculator',
|
|
166
|
+
description: 'Calculate Body Mass Index',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
weightKg: z.number(),
|
|
169
|
+
heightM: z.number()
|
|
170
|
+
},
|
|
171
|
+
outputSchema: { bmi: z.number() }
|
|
172
|
+
},
|
|
173
|
+
async ({ weightKg, heightM }) => {
|
|
174
|
+
const output = { bmi: weightKg / (heightM * heightM) };
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: 'text',
|
|
179
|
+
text: JSON.stringify(output)
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
structuredContent: output
|
|
183
|
+
};
|
|
184
|
+
}
|
|
140
185
|
);
|
|
141
186
|
|
|
142
|
-
//
|
|
143
|
-
server.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
187
|
+
// Async tool with external API call
|
|
188
|
+
server.registerTool(
|
|
189
|
+
'fetch-weather',
|
|
190
|
+
{
|
|
191
|
+
title: 'Weather Fetcher',
|
|
192
|
+
description: 'Get weather data for a city',
|
|
193
|
+
inputSchema: { city: z.string() },
|
|
194
|
+
outputSchema: { temperature: z.number(), conditions: z.string() }
|
|
195
|
+
},
|
|
196
|
+
async ({ city }) => {
|
|
197
|
+
const response = await fetch(`https://api.weather.com/${city}`);
|
|
198
|
+
const data = await response.json();
|
|
199
|
+
const output = { temperature: data.temp, conditions: data.conditions };
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
202
|
+
structuredContent: output
|
|
203
|
+
};
|
|
204
|
+
}
|
|
156
205
|
);
|
|
157
206
|
|
|
158
|
-
//
|
|
159
|
-
server.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
207
|
+
// Tool that returns ResourceLinks
|
|
208
|
+
server.registerTool(
|
|
209
|
+
'list-files',
|
|
210
|
+
{
|
|
211
|
+
title: 'List Files',
|
|
212
|
+
description: 'List project files',
|
|
213
|
+
inputSchema: { pattern: z.string() },
|
|
214
|
+
outputSchema: {
|
|
215
|
+
count: z.number(),
|
|
216
|
+
files: z.array(z.object({ name: z.string(), uri: z.string() }))
|
|
168
217
|
}
|
|
169
|
-
|
|
170
|
-
|
|
218
|
+
},
|
|
219
|
+
async ({ pattern }) => {
|
|
220
|
+
const output = {
|
|
221
|
+
count: 2,
|
|
222
|
+
files: [
|
|
223
|
+
{ name: 'README.md', uri: 'file:///project/README.md' },
|
|
224
|
+
{ name: 'index.ts', uri: 'file:///project/src/index.ts' }
|
|
225
|
+
]
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{ type: 'text', text: JSON.stringify(output) },
|
|
230
|
+
// ResourceLinks let tools return references without file content
|
|
231
|
+
{
|
|
232
|
+
type: 'resource_link',
|
|
233
|
+
uri: 'file:///project/README.md',
|
|
234
|
+
name: 'README.md',
|
|
235
|
+
mimeType: 'text/markdown',
|
|
236
|
+
description: 'A README file'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: 'resource_link',
|
|
240
|
+
uri: 'file:///project/src/index.ts',
|
|
241
|
+
name: 'index.ts',
|
|
242
|
+
mimeType: 'text/typescript',
|
|
243
|
+
description: 'An index file'
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
structuredContent: output
|
|
247
|
+
};
|
|
171
248
|
}
|
|
172
|
-
}),
|
|
173
|
-
{
|
|
174
|
-
title: "GitHub Repository",
|
|
175
|
-
description: "Repository information"
|
|
176
|
-
},
|
|
177
|
-
async (uri, { owner, repo }) => ({
|
|
178
|
-
contents: [{
|
|
179
|
-
uri: uri.href,
|
|
180
|
-
text: `Repository: ${owner}/${repo}`
|
|
181
|
-
}]
|
|
182
|
-
})
|
|
183
249
|
);
|
|
184
250
|
```
|
|
185
251
|
|
|
186
|
-
|
|
252
|
+
#### ResourceLinks
|
|
253
|
+
|
|
254
|
+
Tools can return `ResourceLink` objects to reference resources without embedding their full content. This can be helpful for performance when dealing with large files or many resources - clients can then selectively read only the resources they need using the provided URIs.
|
|
187
255
|
|
|
188
|
-
|
|
256
|
+
### Resources
|
|
257
|
+
|
|
258
|
+
[Resources](https://modelcontextprotocol.io/specification/latest/server/resources) can also expose data to LLMs, but unlike tools shouldn't perform significant computation or have side effects.
|
|
259
|
+
|
|
260
|
+
Resources are designed to be used in an application-driven way, meaning MCP client applications can decide how to expose them. For example, a client could expose a resource picker to the human, or could expose them to the model directly.
|
|
189
261
|
|
|
190
262
|
```typescript
|
|
191
|
-
//
|
|
192
|
-
server.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
263
|
+
// Static resource
|
|
264
|
+
server.registerResource(
|
|
265
|
+
'config',
|
|
266
|
+
'config://app',
|
|
267
|
+
{
|
|
268
|
+
title: 'Application Config',
|
|
269
|
+
description: 'Application configuration data',
|
|
270
|
+
mimeType: 'text/plain'
|
|
271
|
+
},
|
|
272
|
+
async uri => ({
|
|
273
|
+
contents: [
|
|
274
|
+
{
|
|
275
|
+
uri: uri.href,
|
|
276
|
+
text: 'App configuration here'
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
})
|
|
208
280
|
);
|
|
209
281
|
|
|
210
|
-
//
|
|
211
|
-
server.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
// Dynamic resource with parameters
|
|
283
|
+
server.registerResource(
|
|
284
|
+
'user-profile',
|
|
285
|
+
new ResourceTemplate('users://{userId}/profile', { list: undefined }),
|
|
286
|
+
{
|
|
287
|
+
title: 'User Profile',
|
|
288
|
+
description: 'User profile information'
|
|
289
|
+
},
|
|
290
|
+
async (uri, { userId }) => ({
|
|
291
|
+
contents: [
|
|
292
|
+
{
|
|
293
|
+
uri: uri.href,
|
|
294
|
+
text: `Profile data for user ${userId}`
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
})
|
|
225
298
|
);
|
|
226
299
|
|
|
227
|
-
//
|
|
228
|
-
server.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
description: '
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
300
|
+
// Resource with context-aware completion
|
|
301
|
+
server.registerResource(
|
|
302
|
+
'repository',
|
|
303
|
+
new ResourceTemplate('github://repos/{owner}/{repo}', {
|
|
304
|
+
list: undefined,
|
|
305
|
+
complete: {
|
|
306
|
+
// Provide intelligent completions based on previously resolved parameters
|
|
307
|
+
repo: (value, context) => {
|
|
308
|
+
if (context?.arguments?.['owner'] === 'org1') {
|
|
309
|
+
return ['project1', 'project2', 'project3'].filter(r => r.startsWith(value));
|
|
310
|
+
}
|
|
311
|
+
return ['default-repo'].filter(r => r.startsWith(value));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}),
|
|
315
|
+
{
|
|
316
|
+
title: 'GitHub Repository',
|
|
317
|
+
description: 'Repository information'
|
|
318
|
+
},
|
|
319
|
+
async (uri, { owner, repo }) => ({
|
|
320
|
+
contents: [
|
|
321
|
+
{
|
|
322
|
+
uri: uri.href,
|
|
323
|
+
text: `Repository: ${owner}/${repo}`
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
})
|
|
255
327
|
);
|
|
256
328
|
```
|
|
257
329
|
|
|
258
|
-
#### ResourceLinks
|
|
259
|
-
|
|
260
|
-
Tools can return `ResourceLink` objects to reference resources without embedding their full content. This is essential for performance when dealing with large files or many resources - clients can then selectively read only the resources they need using the provided URIs.
|
|
261
|
-
|
|
262
330
|
### Prompts
|
|
263
331
|
|
|
264
|
-
Prompts are reusable templates that help
|
|
332
|
+
[Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) are reusable templates that help humans prompt models to interact with your server. They're designed to be user-driven, and might appear as slash commands in a chat interface.
|
|
265
333
|
|
|
266
334
|
```typescript
|
|
267
|
-
import { completable } from
|
|
335
|
+
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
268
336
|
|
|
269
337
|
server.registerPrompt(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
338
|
+
'review-code',
|
|
339
|
+
{
|
|
340
|
+
title: 'Code Review',
|
|
341
|
+
description: 'Review code for best practices and potential issues',
|
|
342
|
+
argsSchema: { code: z.string() }
|
|
343
|
+
},
|
|
344
|
+
({ code }) => ({
|
|
345
|
+
messages: [
|
|
346
|
+
{
|
|
347
|
+
role: 'user',
|
|
348
|
+
content: {
|
|
349
|
+
type: 'text',
|
|
350
|
+
text: `Please review this code:\n\n${code}`
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
})
|
|
285
355
|
);
|
|
286
356
|
|
|
287
357
|
// Prompt with context-aware completion
|
|
288
358
|
server.registerPrompt(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
359
|
+
'team-greeting',
|
|
360
|
+
{
|
|
361
|
+
title: 'Team Greeting',
|
|
362
|
+
description: 'Generate a greeting for team members',
|
|
363
|
+
argsSchema: {
|
|
364
|
+
department: completable(z.string(), value => {
|
|
365
|
+
// Department suggestions
|
|
366
|
+
return ['engineering', 'sales', 'marketing', 'support'].filter(d => d.startsWith(value));
|
|
367
|
+
}),
|
|
368
|
+
name: completable(z.string(), (value, context) => {
|
|
369
|
+
// Name suggestions based on selected department
|
|
370
|
+
const department = context?.arguments?.['department'];
|
|
371
|
+
if (department === 'engineering') {
|
|
372
|
+
return ['Alice', 'Bob', 'Charlie'].filter(n => n.startsWith(value));
|
|
373
|
+
} else if (department === 'sales') {
|
|
374
|
+
return ['David', 'Eve', 'Frank'].filter(n => n.startsWith(value));
|
|
375
|
+
} else if (department === 'marketing') {
|
|
376
|
+
return ['Grace', 'Henry', 'Iris'].filter(n => n.startsWith(value));
|
|
377
|
+
}
|
|
378
|
+
return ['Guest'].filter(n => n.startsWith(value));
|
|
379
|
+
})
|
|
307
380
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
})
|
|
381
|
+
},
|
|
382
|
+
({ department, name }) => ({
|
|
383
|
+
messages: [
|
|
384
|
+
{
|
|
385
|
+
role: 'assistant',
|
|
386
|
+
content: {
|
|
387
|
+
type: 'text',
|
|
388
|
+
text: `Hello ${name}, welcome to the ${department} team!`
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
})
|
|
321
393
|
);
|
|
322
394
|
```
|
|
323
395
|
|
|
@@ -330,32 +402,33 @@ MCP supports argument completions to help users fill in prompt arguments and res
|
|
|
330
402
|
```typescript
|
|
331
403
|
// Request completions for any argument
|
|
332
404
|
const result = await client.complete({
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
405
|
+
ref: {
|
|
406
|
+
type: 'ref/prompt', // or "ref/resource"
|
|
407
|
+
name: 'example' // or uri: "template://..."
|
|
408
|
+
},
|
|
409
|
+
argument: {
|
|
410
|
+
name: 'argumentName',
|
|
411
|
+
value: 'partial' // What the user has typed so far
|
|
412
|
+
},
|
|
413
|
+
context: {
|
|
414
|
+
// Optional: Include previously resolved arguments
|
|
415
|
+
arguments: {
|
|
416
|
+
previousArg: 'value'
|
|
417
|
+
}
|
|
344
418
|
}
|
|
345
|
-
}
|
|
346
419
|
});
|
|
347
|
-
|
|
348
420
|
```
|
|
349
421
|
|
|
350
422
|
### Display Names and Metadata
|
|
351
423
|
|
|
352
|
-
All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name, while `name` remains the unique identifier.
|
|
424
|
+
All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name (e.g. 'Create a new issue'), while `name` remains the unique identifier (e.g. `create_issue`).
|
|
353
425
|
|
|
354
426
|
**Note:** The `register*` methods (`registerTool`, `registerPrompt`, `registerResource`) are the recommended approach for new code. The older methods (`tool`, `prompt`, `resource`) remain available for backwards compatibility.
|
|
355
427
|
|
|
356
428
|
#### Title Precedence for Tools
|
|
357
429
|
|
|
358
430
|
For tools specifically, there are two ways to specify a title:
|
|
431
|
+
|
|
359
432
|
- `title` field in the tool configuration
|
|
360
433
|
- `annotations.title` field (when using the older `tool()` method with annotations)
|
|
361
434
|
|
|
@@ -363,23 +436,32 @@ The precedence order is: `title` → `annotations.title` → `name`
|
|
|
363
436
|
|
|
364
437
|
```typescript
|
|
365
438
|
// Using registerTool (recommended)
|
|
366
|
-
server.registerTool(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
439
|
+
server.registerTool(
|
|
440
|
+
'my_tool',
|
|
441
|
+
{
|
|
442
|
+
title: 'My Tool', // This title takes precedence
|
|
443
|
+
annotations: {
|
|
444
|
+
title: 'Annotation Title' // This is ignored if title is set
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
handler
|
|
448
|
+
);
|
|
372
449
|
|
|
373
450
|
// Using tool with annotations (older API)
|
|
374
|
-
server.tool(
|
|
375
|
-
|
|
376
|
-
|
|
451
|
+
server.tool(
|
|
452
|
+
'my_tool',
|
|
453
|
+
'description',
|
|
454
|
+
{
|
|
455
|
+
title: 'Annotation Title' // This is used as title
|
|
456
|
+
},
|
|
457
|
+
handler
|
|
458
|
+
);
|
|
377
459
|
```
|
|
378
460
|
|
|
379
461
|
When building clients, use the provided utility to get the appropriate display name:
|
|
380
462
|
|
|
381
463
|
```typescript
|
|
382
|
-
import { getDisplayName } from
|
|
464
|
+
import { getDisplayName } from '@modelcontextprotocol/sdk/shared/metadataUtils.js';
|
|
383
465
|
|
|
384
466
|
// Automatically handles the precedence: title → annotations.title → name
|
|
385
467
|
const displayName = getDisplayName(tool);
|
|
@@ -390,102 +472,174 @@ const displayName = getDisplayName(tool);
|
|
|
390
472
|
MCP servers can request LLM completions from connected clients that support sampling.
|
|
391
473
|
|
|
392
474
|
```typescript
|
|
393
|
-
import { McpServer } from
|
|
394
|
-
import {
|
|
395
|
-
import
|
|
475
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
476
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
477
|
+
import express from 'express';
|
|
478
|
+
import { z } from 'zod';
|
|
396
479
|
|
|
397
480
|
const mcpServer = new McpServer({
|
|
398
|
-
|
|
399
|
-
|
|
481
|
+
name: 'tools-with-sample-server',
|
|
482
|
+
version: '1.0.0'
|
|
400
483
|
});
|
|
401
484
|
|
|
402
485
|
// Tool that uses LLM sampling to summarize any text
|
|
403
486
|
mcpServer.registerTool(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
},
|
|
411
|
-
async ({ text }) => {
|
|
412
|
-
// Call the LLM through MCP sampling
|
|
413
|
-
const response = await mcpServer.server.createMessage({
|
|
414
|
-
messages: [
|
|
415
|
-
{
|
|
416
|
-
role: "user",
|
|
417
|
-
content: {
|
|
418
|
-
type: "text",
|
|
419
|
-
text: `Please summarize the following text concisely:\n\n${text}`,
|
|
420
|
-
},
|
|
487
|
+
'summarize',
|
|
488
|
+
{
|
|
489
|
+
title: 'Text Summarizer',
|
|
490
|
+
description: 'Summarize any text using an LLM',
|
|
491
|
+
inputSchema: {
|
|
492
|
+
text: z.string().describe('Text to summarize')
|
|
421
493
|
},
|
|
422
|
-
|
|
423
|
-
|
|
494
|
+
outputSchema: { summary: z.string() }
|
|
495
|
+
},
|
|
496
|
+
async ({ text }) => {
|
|
497
|
+
// Call the LLM through MCP sampling
|
|
498
|
+
const response = await mcpServer.server.createMessage({
|
|
499
|
+
messages: [
|
|
500
|
+
{
|
|
501
|
+
role: 'user',
|
|
502
|
+
content: {
|
|
503
|
+
type: 'text',
|
|
504
|
+
text: `Please summarize the following text concisely:\n\n${text}`
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
],
|
|
508
|
+
maxTokens: 500
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const summary = response.content.type === 'text' ? response.content.text : 'Unable to generate summary';
|
|
512
|
+
const output = { summary };
|
|
513
|
+
return {
|
|
514
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
515
|
+
structuredContent: output
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const app = express();
|
|
521
|
+
app.use(express.json());
|
|
522
|
+
|
|
523
|
+
app.post('/mcp', async (req, res) => {
|
|
524
|
+
const transport = new StreamableHTTPServerTransport({
|
|
525
|
+
sessionIdGenerator: undefined,
|
|
526
|
+
enableJsonResponse: true
|
|
424
527
|
});
|
|
425
528
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
type: "text",
|
|
430
|
-
text: response.content.type === "text" ? response.content.text : "Unable to generate summary",
|
|
431
|
-
},
|
|
432
|
-
],
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
);
|
|
529
|
+
res.on('close', () => {
|
|
530
|
+
transport.close();
|
|
531
|
+
});
|
|
436
532
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
console.error("MCP server is running...");
|
|
441
|
-
}
|
|
533
|
+
await mcpServer.connect(transport);
|
|
534
|
+
await transport.handleRequest(req, res, req.body);
|
|
535
|
+
});
|
|
442
536
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
537
|
+
const port = parseInt(process.env.PORT || '3000');
|
|
538
|
+
app.listen(port, () => {
|
|
539
|
+
console.log(`MCP Server running on http://localhost:${port}/mcp`);
|
|
540
|
+
}).on('error', error => {
|
|
541
|
+
console.error('Server error:', error);
|
|
542
|
+
process.exit(1);
|
|
446
543
|
});
|
|
447
544
|
```
|
|
448
545
|
|
|
449
|
-
|
|
450
546
|
## Running Your Server
|
|
451
547
|
|
|
452
548
|
MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
|
|
453
549
|
|
|
454
|
-
###
|
|
550
|
+
### Streamable HTTP
|
|
551
|
+
|
|
552
|
+
For remote servers, use the Streamable HTTP transport.
|
|
553
|
+
|
|
554
|
+
#### Without Session Management (Recommended)
|
|
455
555
|
|
|
456
|
-
For
|
|
556
|
+
For most use cases where session management isn't needed:
|
|
457
557
|
|
|
458
558
|
```typescript
|
|
459
|
-
import { McpServer } from
|
|
460
|
-
import {
|
|
559
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
560
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
561
|
+
import express from 'express';
|
|
562
|
+
import { z } from 'zod';
|
|
563
|
+
|
|
564
|
+
const app = express();
|
|
565
|
+
app.use(express.json());
|
|
461
566
|
|
|
567
|
+
// Create the MCP server once (can be reused across requests)
|
|
462
568
|
const server = new McpServer({
|
|
463
|
-
|
|
464
|
-
|
|
569
|
+
name: 'example-server',
|
|
570
|
+
version: '1.0.0'
|
|
465
571
|
});
|
|
466
572
|
|
|
467
|
-
//
|
|
573
|
+
// Set up your tools, resources, and prompts
|
|
574
|
+
server.registerTool(
|
|
575
|
+
'echo',
|
|
576
|
+
{
|
|
577
|
+
title: 'Echo Tool',
|
|
578
|
+
description: 'Echoes back the provided message',
|
|
579
|
+
inputSchema: { message: z.string() },
|
|
580
|
+
outputSchema: { echo: z.string() }
|
|
581
|
+
},
|
|
582
|
+
async ({ message }) => {
|
|
583
|
+
const output = { echo: `Tool echo: ${message}` };
|
|
584
|
+
return {
|
|
585
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
586
|
+
structuredContent: output
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
);
|
|
468
590
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
591
|
+
app.post('/mcp', async (req, res) => {
|
|
592
|
+
// In stateless mode, create a new transport for each request to prevent
|
|
593
|
+
// request ID collisions. Different clients may use the same JSON-RPC request IDs,
|
|
594
|
+
// which would cause responses to be routed to the wrong HTTP connections if
|
|
595
|
+
// the transport state is shared.
|
|
472
596
|
|
|
473
|
-
|
|
597
|
+
try {
|
|
598
|
+
const transport = new StreamableHTTPServerTransport({
|
|
599
|
+
sessionIdGenerator: undefined,
|
|
600
|
+
enableJsonResponse: true
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
res.on('close', () => {
|
|
604
|
+
transport.close();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
await server.connect(transport);
|
|
608
|
+
await transport.handleRequest(req, res, req.body);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.error('Error handling MCP request:', error);
|
|
611
|
+
if (!res.headersSent) {
|
|
612
|
+
res.status(500).json({
|
|
613
|
+
jsonrpc: '2.0',
|
|
614
|
+
error: {
|
|
615
|
+
code: -32603,
|
|
616
|
+
message: 'Internal server error'
|
|
617
|
+
},
|
|
618
|
+
id: null
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
474
623
|
|
|
475
|
-
|
|
624
|
+
const port = parseInt(process.env.PORT || '3000');
|
|
625
|
+
app.listen(port, () => {
|
|
626
|
+
console.log(`MCP Server running on http://localhost:${port}/mcp`);
|
|
627
|
+
}).on('error', error => {
|
|
628
|
+
console.error('Server error:', error);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
});
|
|
631
|
+
```
|
|
476
632
|
|
|
477
633
|
#### With Session Management
|
|
478
634
|
|
|
479
|
-
In some cases, servers need
|
|
635
|
+
In some cases, servers need stateful sessions. This can be achieved by [session management](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management) in the MCP protocol.
|
|
480
636
|
|
|
481
637
|
```typescript
|
|
482
|
-
import express from
|
|
483
|
-
import { randomUUID } from
|
|
484
|
-
import { McpServer } from
|
|
485
|
-
import { StreamableHTTPServerTransport } from
|
|
486
|
-
import { isInitializeRequest } from
|
|
487
|
-
|
|
488
|
-
|
|
638
|
+
import express from 'express';
|
|
639
|
+
import { randomUUID } from 'node:crypto';
|
|
640
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
641
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
642
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
489
643
|
|
|
490
644
|
const app = express();
|
|
491
645
|
app.use(express.json());
|
|
@@ -495,69 +649,69 @@ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
|
|
|
495
649
|
|
|
496
650
|
// Handle POST requests for client-to-server communication
|
|
497
651
|
app.post('/mcp', async (req, res) => {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
652
|
+
// Check for existing session ID
|
|
653
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
654
|
+
let transport: StreamableHTTPServerTransport;
|
|
655
|
+
|
|
656
|
+
if (sessionId && transports[sessionId]) {
|
|
657
|
+
// Reuse existing transport
|
|
658
|
+
transport = transports[sessionId];
|
|
659
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
660
|
+
// New initialization request
|
|
661
|
+
transport = new StreamableHTTPServerTransport({
|
|
662
|
+
sessionIdGenerator: () => randomUUID(),
|
|
663
|
+
onsessioninitialized: sessionId => {
|
|
664
|
+
// Store the transport by session ID
|
|
665
|
+
transports[sessionId] = transport;
|
|
666
|
+
}
|
|
667
|
+
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
|
668
|
+
// locally, make sure to set:
|
|
669
|
+
// enableDnsRebindingProtection: true,
|
|
670
|
+
// allowedHosts: ['127.0.0.1'],
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Clean up transport when closed
|
|
674
|
+
transport.onclose = () => {
|
|
675
|
+
if (transport.sessionId) {
|
|
676
|
+
delete transports[transport.sessionId];
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const server = new McpServer({
|
|
680
|
+
name: 'example-server',
|
|
681
|
+
version: '1.0.0'
|
|
682
|
+
});
|
|
529
683
|
|
|
530
|
-
|
|
684
|
+
// ... set up server resources, tools, and prompts ...
|
|
531
685
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
686
|
+
// Connect to the MCP server
|
|
687
|
+
await server.connect(transport);
|
|
688
|
+
} else {
|
|
689
|
+
// Invalid request
|
|
690
|
+
res.status(400).json({
|
|
691
|
+
jsonrpc: '2.0',
|
|
692
|
+
error: {
|
|
693
|
+
code: -32000,
|
|
694
|
+
message: 'Bad Request: No valid session ID provided'
|
|
695
|
+
},
|
|
696
|
+
id: null
|
|
697
|
+
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
546
700
|
|
|
547
|
-
|
|
548
|
-
|
|
701
|
+
// Handle the request
|
|
702
|
+
await transport.handleRequest(req, res, req.body);
|
|
549
703
|
});
|
|
550
704
|
|
|
551
705
|
// Reusable handler for GET and DELETE requests
|
|
552
706
|
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
707
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
708
|
+
if (!sessionId || !transports[sessionId]) {
|
|
709
|
+
res.status(400).send('Invalid or missing session ID');
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const transport = transports[sessionId];
|
|
714
|
+
await transport.handleRequest(req, res);
|
|
561
715
|
};
|
|
562
716
|
|
|
563
717
|
// Handle GET requests for server-to-client notifications via SSE
|
|
@@ -569,10 +723,6 @@ app.delete('/mcp', handleSessionRequest);
|
|
|
569
723
|
app.listen(3000);
|
|
570
724
|
```
|
|
571
725
|
|
|
572
|
-
> [!TIP]
|
|
573
|
-
> When using this in a remote environment, make sure to allow the header parameter `mcp-session-id` in CORS. Otherwise, it may result in a `Bad Request: No valid session ID provided` error. Read the following section for examples.
|
|
574
|
-
|
|
575
|
-
|
|
576
726
|
#### CORS Configuration for Browser-Based Clients
|
|
577
727
|
|
|
578
728
|
If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it:
|
|
@@ -581,109 +731,22 @@ If you'd like your server to be accessible by browser-based MCP clients, you'll
|
|
|
581
731
|
import cors from 'cors';
|
|
582
732
|
|
|
583
733
|
// Add CORS middleware before your MCP routes
|
|
584
|
-
app.use(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
734
|
+
app.use(
|
|
735
|
+
cors({
|
|
736
|
+
origin: '*', // Configure appropriately for production, for example:
|
|
737
|
+
// origin: ['https://your-remote-domain.com', 'https://your-other-remote-domain.com'],
|
|
738
|
+
exposedHeaders: ['Mcp-Session-Id'],
|
|
739
|
+
allowedHeaders: ['Content-Type', 'mcp-session-id']
|
|
740
|
+
})
|
|
741
|
+
);
|
|
590
742
|
```
|
|
591
743
|
|
|
592
744
|
This configuration is necessary because:
|
|
745
|
+
|
|
593
746
|
- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management
|
|
594
747
|
- Browsers restrict access to response headers unless explicitly exposed via CORS
|
|
595
748
|
- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses
|
|
596
749
|
|
|
597
|
-
#### Without Session Management (Stateless)
|
|
598
|
-
|
|
599
|
-
For simpler use cases where session management isn't needed:
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
const app = express();
|
|
603
|
-
app.use(express.json());
|
|
604
|
-
|
|
605
|
-
app.post('/mcp', async (req: Request, res: Response) => {
|
|
606
|
-
// In stateless mode, create a new instance of transport and server for each request
|
|
607
|
-
// to ensure complete isolation. A single instance would cause request ID collisions
|
|
608
|
-
// when multiple clients connect concurrently.
|
|
609
|
-
|
|
610
|
-
try {
|
|
611
|
-
const server = getServer();
|
|
612
|
-
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
|
|
613
|
-
sessionIdGenerator: undefined,
|
|
614
|
-
});
|
|
615
|
-
res.on('close', () => {
|
|
616
|
-
console.log('Request closed');
|
|
617
|
-
transport.close();
|
|
618
|
-
server.close();
|
|
619
|
-
});
|
|
620
|
-
await server.connect(transport);
|
|
621
|
-
await transport.handleRequest(req, res, req.body);
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error('Error handling MCP request:', error);
|
|
624
|
-
if (!res.headersSent) {
|
|
625
|
-
res.status(500).json({
|
|
626
|
-
jsonrpc: '2.0',
|
|
627
|
-
error: {
|
|
628
|
-
code: -32603,
|
|
629
|
-
message: 'Internal server error',
|
|
630
|
-
},
|
|
631
|
-
id: null,
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
// SSE notifications not supported in stateless mode
|
|
638
|
-
app.get('/mcp', async (req: Request, res: Response) => {
|
|
639
|
-
console.log('Received GET MCP request');
|
|
640
|
-
res.writeHead(405).end(JSON.stringify({
|
|
641
|
-
jsonrpc: "2.0",
|
|
642
|
-
error: {
|
|
643
|
-
code: -32000,
|
|
644
|
-
message: "Method not allowed."
|
|
645
|
-
},
|
|
646
|
-
id: null
|
|
647
|
-
}));
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// Session termination not needed in stateless mode
|
|
651
|
-
app.delete('/mcp', async (req: Request, res: Response) => {
|
|
652
|
-
console.log('Received DELETE MCP request');
|
|
653
|
-
res.writeHead(405).end(JSON.stringify({
|
|
654
|
-
jsonrpc: "2.0",
|
|
655
|
-
error: {
|
|
656
|
-
code: -32000,
|
|
657
|
-
message: "Method not allowed."
|
|
658
|
-
},
|
|
659
|
-
id: null
|
|
660
|
-
}));
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
// Start the server
|
|
665
|
-
const PORT = 3000;
|
|
666
|
-
setupServer().then(() => {
|
|
667
|
-
app.listen(PORT, (error) => {
|
|
668
|
-
if (error) {
|
|
669
|
-
console.error('Failed to start server:', error);
|
|
670
|
-
process.exit(1);
|
|
671
|
-
}
|
|
672
|
-
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
|
|
673
|
-
});
|
|
674
|
-
}).catch(error => {
|
|
675
|
-
console.error('Failed to set up the server:', error);
|
|
676
|
-
process.exit(1);
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
This stateless approach is useful for:
|
|
682
|
-
|
|
683
|
-
- Simple API wrappers
|
|
684
|
-
- RESTful scenarios where each request is independent
|
|
685
|
-
- Horizontally scaled deployments without shared session state
|
|
686
|
-
|
|
687
750
|
#### DNS Rebinding Protection
|
|
688
751
|
|
|
689
752
|
The Streamable HTTP transport includes DNS rebinding protection to prevent security vulnerabilities. By default, this protection is **disabled** for backwards compatibility.
|
|
@@ -700,6 +763,25 @@ const transport = new StreamableHTTPServerTransport({
|
|
|
700
763
|
});
|
|
701
764
|
```
|
|
702
765
|
|
|
766
|
+
### stdio
|
|
767
|
+
|
|
768
|
+
For local integrations spawned by another process, you can use the stdio transport:
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
772
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
773
|
+
|
|
774
|
+
const server = new McpServer({
|
|
775
|
+
name: 'example-server',
|
|
776
|
+
version: '1.0.0'
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// ... set up server resources, tools, and prompts ...
|
|
780
|
+
|
|
781
|
+
const transport = new StdioServerTransport();
|
|
782
|
+
await server.connect(transport);
|
|
783
|
+
```
|
|
784
|
+
|
|
703
785
|
### Testing and Debugging
|
|
704
786
|
|
|
705
787
|
To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
|
|
@@ -711,57 +793,66 @@ To test your server, you can use the [MCP Inspector](https://github.com/modelcon
|
|
|
711
793
|
A simple server demonstrating resources, tools, and prompts:
|
|
712
794
|
|
|
713
795
|
```typescript
|
|
714
|
-
import { McpServer, ResourceTemplate } from
|
|
715
|
-
import { z } from
|
|
796
|
+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
797
|
+
import { z } from 'zod';
|
|
716
798
|
|
|
717
799
|
const server = new McpServer({
|
|
718
|
-
|
|
719
|
-
|
|
800
|
+
name: 'echo-server',
|
|
801
|
+
version: '1.0.0'
|
|
720
802
|
});
|
|
721
803
|
|
|
722
|
-
server.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
804
|
+
server.registerTool(
|
|
805
|
+
'echo',
|
|
806
|
+
{
|
|
807
|
+
title: 'Echo Tool',
|
|
808
|
+
description: 'Echoes back the provided message',
|
|
809
|
+
inputSchema: { message: z.string() },
|
|
810
|
+
outputSchema: { echo: z.string() }
|
|
811
|
+
},
|
|
812
|
+
async ({ message }) => {
|
|
813
|
+
const output = { echo: `Tool echo: ${message}` };
|
|
814
|
+
return {
|
|
815
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
816
|
+
structuredContent: output
|
|
817
|
+
};
|
|
818
|
+
}
|
|
735
819
|
);
|
|
736
820
|
|
|
737
|
-
server.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
821
|
+
server.registerResource(
|
|
822
|
+
'echo',
|
|
823
|
+
new ResourceTemplate('echo://{message}', { list: undefined }),
|
|
824
|
+
{
|
|
825
|
+
title: 'Echo Resource',
|
|
826
|
+
description: 'Echoes back messages as resources'
|
|
827
|
+
},
|
|
828
|
+
async (uri, { message }) => ({
|
|
829
|
+
contents: [
|
|
830
|
+
{
|
|
831
|
+
uri: uri.href,
|
|
832
|
+
text: `Resource echo: ${message}`
|
|
833
|
+
}
|
|
834
|
+
]
|
|
835
|
+
})
|
|
747
836
|
);
|
|
748
837
|
|
|
749
838
|
server.registerPrompt(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
839
|
+
'echo',
|
|
840
|
+
{
|
|
841
|
+
title: 'Echo Prompt',
|
|
842
|
+
description: 'Creates a prompt to process a message',
|
|
843
|
+
argsSchema: { message: z.string() }
|
|
844
|
+
},
|
|
845
|
+
({ message }) => ({
|
|
846
|
+
messages: [
|
|
847
|
+
{
|
|
848
|
+
role: 'user',
|
|
849
|
+
content: {
|
|
850
|
+
type: 'text',
|
|
851
|
+
text: `Please process this message: ${message}`
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
]
|
|
855
|
+
})
|
|
765
856
|
);
|
|
766
857
|
```
|
|
767
858
|
|
|
@@ -770,81 +861,91 @@ server.registerPrompt(
|
|
|
770
861
|
A more complex example showing database integration:
|
|
771
862
|
|
|
772
863
|
```typescript
|
|
773
|
-
import { McpServer } from
|
|
774
|
-
import sqlite3 from
|
|
775
|
-
import { promisify } from
|
|
776
|
-
import { z } from
|
|
864
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
865
|
+
import sqlite3 from 'sqlite3';
|
|
866
|
+
import { promisify } from 'util';
|
|
867
|
+
import { z } from 'zod';
|
|
777
868
|
|
|
778
869
|
const server = new McpServer({
|
|
779
|
-
|
|
780
|
-
|
|
870
|
+
name: 'sqlite-explorer',
|
|
871
|
+
version: '1.0.0'
|
|
781
872
|
});
|
|
782
873
|
|
|
783
874
|
// Helper to create DB connection
|
|
784
875
|
const getDb = () => {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
876
|
+
const db = new sqlite3.Database('database.db');
|
|
877
|
+
return {
|
|
878
|
+
all: promisify<string, any[]>(db.all.bind(db)),
|
|
879
|
+
close: promisify(db.close.bind(db))
|
|
880
|
+
};
|
|
790
881
|
};
|
|
791
882
|
|
|
792
883
|
server.registerResource(
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
884
|
+
'schema',
|
|
885
|
+
'schema://main',
|
|
886
|
+
{
|
|
887
|
+
title: 'Database Schema',
|
|
888
|
+
description: 'SQLite database schema',
|
|
889
|
+
mimeType: 'text/plain'
|
|
890
|
+
},
|
|
891
|
+
async uri => {
|
|
892
|
+
const db = getDb();
|
|
893
|
+
try {
|
|
894
|
+
const tables = await db.all("SELECT sql FROM sqlite_master WHERE type='table'");
|
|
895
|
+
return {
|
|
896
|
+
contents: [
|
|
897
|
+
{
|
|
898
|
+
uri: uri.href,
|
|
899
|
+
text: tables.map((t: { sql: string }) => t.sql).join('\n')
|
|
900
|
+
}
|
|
901
|
+
]
|
|
902
|
+
};
|
|
903
|
+
} finally {
|
|
904
|
+
await db.close();
|
|
905
|
+
}
|
|
814
906
|
}
|
|
815
|
-
}
|
|
816
907
|
);
|
|
817
908
|
|
|
818
909
|
server.registerTool(
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
910
|
+
'query',
|
|
911
|
+
{
|
|
912
|
+
title: 'SQL Query',
|
|
913
|
+
description: 'Execute SQL queries on the database',
|
|
914
|
+
inputSchema: { sql: z.string() },
|
|
915
|
+
outputSchema: {
|
|
916
|
+
rows: z.array(z.record(z.any())),
|
|
917
|
+
rowCount: z.number()
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
async ({ sql }) => {
|
|
921
|
+
const db = getDb();
|
|
922
|
+
try {
|
|
923
|
+
const results = await db.all(sql);
|
|
924
|
+
const output = { rows: results, rowCount: results.length };
|
|
925
|
+
return {
|
|
926
|
+
content: [
|
|
927
|
+
{
|
|
928
|
+
type: 'text',
|
|
929
|
+
text: JSON.stringify(output, null, 2)
|
|
930
|
+
}
|
|
931
|
+
],
|
|
932
|
+
structuredContent: output
|
|
933
|
+
};
|
|
934
|
+
} catch (err: unknown) {
|
|
935
|
+
const error = err as Error;
|
|
936
|
+
return {
|
|
937
|
+
content: [
|
|
938
|
+
{
|
|
939
|
+
type: 'text',
|
|
940
|
+
text: `Error: ${error.message}`
|
|
941
|
+
}
|
|
942
|
+
],
|
|
943
|
+
isError: true
|
|
944
|
+
};
|
|
945
|
+
} finally {
|
|
946
|
+
await db.close();
|
|
947
|
+
}
|
|
846
948
|
}
|
|
847
|
-
}
|
|
848
949
|
);
|
|
849
950
|
```
|
|
850
951
|
|
|
@@ -854,62 +955,122 @@ server.registerTool(
|
|
|
854
955
|
|
|
855
956
|
If you want to offer an initial set of tools/prompts/resources, but later add additional ones based on user action or external state change, you can add/update/remove them _after_ the Server is connected. This will automatically emit the corresponding `listChanged` notifications:
|
|
856
957
|
|
|
857
|
-
```
|
|
858
|
-
import { McpServer } from
|
|
859
|
-
import {
|
|
958
|
+
```typescript
|
|
959
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
960
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
961
|
+
import express from 'express';
|
|
962
|
+
import { z } from 'zod';
|
|
860
963
|
|
|
861
964
|
const server = new McpServer({
|
|
862
|
-
|
|
863
|
-
|
|
965
|
+
name: 'Dynamic Example',
|
|
966
|
+
version: '1.0.0'
|
|
864
967
|
});
|
|
865
968
|
|
|
866
|
-
const listMessageTool = server.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
969
|
+
const listMessageTool = server.registerTool(
|
|
970
|
+
'listMessages',
|
|
971
|
+
{
|
|
972
|
+
title: 'List Messages',
|
|
973
|
+
description: 'List messages in a channel',
|
|
974
|
+
inputSchema: { channel: z.string() },
|
|
975
|
+
outputSchema: { messages: z.array(z.string()) }
|
|
976
|
+
},
|
|
977
|
+
async ({ channel }) => {
|
|
978
|
+
const messages = await listMessages(channel);
|
|
979
|
+
const output = { messages };
|
|
980
|
+
return {
|
|
981
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
982
|
+
structuredContent: output
|
|
983
|
+
};
|
|
984
|
+
}
|
|
872
985
|
);
|
|
873
986
|
|
|
874
|
-
const putMessageTool = server.
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
987
|
+
const putMessageTool = server.registerTool(
|
|
988
|
+
'putMessage',
|
|
989
|
+
{
|
|
990
|
+
title: 'Put Message',
|
|
991
|
+
description: 'Send a message to a channel',
|
|
992
|
+
inputSchema: { channel: z.string(), message: z.string() },
|
|
993
|
+
outputSchema: { success: z.boolean() }
|
|
994
|
+
},
|
|
995
|
+
async ({ channel, message }) => {
|
|
996
|
+
await putMessage(channel, message);
|
|
997
|
+
const output = { success: true };
|
|
998
|
+
return {
|
|
999
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
1000
|
+
structuredContent: output
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
880
1003
|
);
|
|
881
1004
|
// Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
|
|
882
|
-
putMessageTool.disable()
|
|
883
|
-
|
|
884
|
-
const upgradeAuthTool = server.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1005
|
+
putMessageTool.disable();
|
|
1006
|
+
|
|
1007
|
+
const upgradeAuthTool = server.registerTool(
|
|
1008
|
+
'upgradeAuth',
|
|
1009
|
+
{
|
|
1010
|
+
title: 'Upgrade Authorization',
|
|
1011
|
+
description: 'Upgrade user authorization level',
|
|
1012
|
+
inputSchema: { permission: z.enum(['write', 'admin']) },
|
|
1013
|
+
outputSchema: {
|
|
1014
|
+
success: z.boolean(),
|
|
1015
|
+
newPermission: z.string()
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
// Any mutations here will automatically emit `listChanged` notifications
|
|
1019
|
+
async ({ permission }) => {
|
|
1020
|
+
const { ok, err, previous } = await upgradeAuthAndStoreToken(permission);
|
|
1021
|
+
if (!ok) {
|
|
1022
|
+
return {
|
|
1023
|
+
content: [{ type: 'text', text: `Error: ${err}` }],
|
|
1024
|
+
isError: true
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
896
1027
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1028
|
+
// If we previously had read-only access, 'putMessage' is now available
|
|
1029
|
+
if (previous === 'read') {
|
|
1030
|
+
putMessageTool.enable();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (permission === 'write') {
|
|
1034
|
+
// If we've just upgraded to 'write' permissions, we can still call 'upgradeAuth'
|
|
1035
|
+
// but can only upgrade to 'admin'.
|
|
1036
|
+
upgradeAuthTool.update({
|
|
1037
|
+
paramsSchema: { permission: z.enum(['admin']) } // change validation rules
|
|
1038
|
+
});
|
|
1039
|
+
} else {
|
|
1040
|
+
// If we're now an admin, we no longer have anywhere to upgrade to, so fully remove that tool
|
|
1041
|
+
upgradeAuthTool.remove();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const output = { success: true, newPermission: permission };
|
|
1045
|
+
return {
|
|
1046
|
+
content: [{ type: 'text', text: JSON.stringify(output) }],
|
|
1047
|
+
structuredContent: output
|
|
1048
|
+
};
|
|
906
1049
|
}
|
|
907
|
-
|
|
908
|
-
)
|
|
1050
|
+
);
|
|
909
1051
|
|
|
910
|
-
// Connect
|
|
911
|
-
const
|
|
912
|
-
|
|
1052
|
+
// Connect with HTTP transport
|
|
1053
|
+
const app = express();
|
|
1054
|
+
app.use(express.json());
|
|
1055
|
+
|
|
1056
|
+
app.post('/mcp', async (req, res) => {
|
|
1057
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1058
|
+
sessionIdGenerator: undefined,
|
|
1059
|
+
enableJsonResponse: true
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
res.on('close', () => {
|
|
1063
|
+
transport.close();
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
await server.connect(transport);
|
|
1067
|
+
await transport.handleRequest(req, res, req.body);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
const port = parseInt(process.env.PORT || '3000');
|
|
1071
|
+
app.listen(port, () => {
|
|
1072
|
+
console.log(`MCP Server running on http://localhost:${port}/mcp`);
|
|
1073
|
+
});
|
|
913
1074
|
```
|
|
914
1075
|
|
|
915
1076
|
### Improving Network Efficiency with Notification Debouncing
|
|
@@ -918,8 +1079,8 @@ When performing bulk updates that trigger notifications (e.g., enabling or disab
|
|
|
918
1079
|
|
|
919
1080
|
This feature coalesces multiple, rapid calls for the same notification type into a single message. For example, if you disable five tools in a row, only one `notifications/tools/list_changed` message will be sent instead of five.
|
|
920
1081
|
|
|
921
|
-
> [!IMPORTANT]
|
|
922
|
-
>
|
|
1082
|
+
> [!IMPORTANT] This feature is designed for "simple" notifications that do not carry unique data in their parameters. To prevent silent data loss, debouncing is **automatically bypassed** for any notification that contains a `params` object or a `relatedRequestId`. Such
|
|
1083
|
+
> notifications will always be sent immediately.
|
|
923
1084
|
|
|
924
1085
|
This is an opt-in feature configured during server initialization.
|
|
925
1086
|
|
|
@@ -954,53 +1115,56 @@ server.registerTool("tool3", ...).disable();
|
|
|
954
1115
|
For more control, you can use the low-level Server class directly:
|
|
955
1116
|
|
|
956
1117
|
```typescript
|
|
957
|
-
import { Server } from
|
|
958
|
-
import { StdioServerTransport } from
|
|
959
|
-
import {
|
|
960
|
-
ListPromptsRequestSchema,
|
|
961
|
-
GetPromptRequestSchema
|
|
962
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
1118
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
1119
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
1120
|
+
import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
963
1121
|
|
|
964
1122
|
const server = new Server(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1123
|
+
{
|
|
1124
|
+
name: 'example-server',
|
|
1125
|
+
version: '1.0.0'
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
capabilities: {
|
|
1129
|
+
prompts: {}
|
|
1130
|
+
}
|
|
972
1131
|
}
|
|
973
|
-
}
|
|
974
1132
|
);
|
|
975
1133
|
|
|
976
1134
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1135
|
+
return {
|
|
1136
|
+
prompts: [
|
|
1137
|
+
{
|
|
1138
|
+
name: 'example-prompt',
|
|
1139
|
+
description: 'An example prompt template',
|
|
1140
|
+
arguments: [
|
|
1141
|
+
{
|
|
1142
|
+
name: 'arg1',
|
|
1143
|
+
description: 'Example argument',
|
|
1144
|
+
required: true
|
|
1145
|
+
}
|
|
1146
|
+
]
|
|
1147
|
+
}
|
|
1148
|
+
]
|
|
1149
|
+
};
|
|
988
1150
|
});
|
|
989
1151
|
|
|
990
|
-
server.setRequestHandler(GetPromptRequestSchema, async
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1152
|
+
server.setRequestHandler(GetPromptRequestSchema, async request => {
|
|
1153
|
+
if (request.params.name !== 'example-prompt') {
|
|
1154
|
+
throw new Error('Unknown prompt');
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
description: 'Example prompt',
|
|
1158
|
+
messages: [
|
|
1159
|
+
{
|
|
1160
|
+
role: 'user',
|
|
1161
|
+
content: {
|
|
1162
|
+
type: 'text',
|
|
1163
|
+
text: 'Example prompt text'
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
]
|
|
1167
|
+
};
|
|
1004
1168
|
});
|
|
1005
1169
|
|
|
1006
1170
|
const transport = new StdioServerTransport();
|
|
@@ -1013,73 +1177,98 @@ MCP servers can request additional information from users through the elicitatio
|
|
|
1013
1177
|
|
|
1014
1178
|
```typescript
|
|
1015
1179
|
// Server-side: Restaurant booking tool that asks for alternatives
|
|
1016
|
-
server.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1180
|
+
server.registerTool(
|
|
1181
|
+
'book-restaurant',
|
|
1182
|
+
{
|
|
1183
|
+
title: 'Book Restaurant',
|
|
1184
|
+
description: 'Book a table at a restaurant',
|
|
1185
|
+
inputSchema: {
|
|
1186
|
+
restaurant: z.string(),
|
|
1187
|
+
date: z.string(),
|
|
1188
|
+
partySize: z.number()
|
|
1189
|
+
},
|
|
1190
|
+
outputSchema: {
|
|
1191
|
+
success: z.boolean(),
|
|
1192
|
+
booking: z
|
|
1193
|
+
.object({
|
|
1194
|
+
restaurant: z.string(),
|
|
1195
|
+
date: z.string(),
|
|
1196
|
+
partySize: z.number()
|
|
1197
|
+
})
|
|
1198
|
+
.optional(),
|
|
1199
|
+
alternatives: z.array(z.string()).optional()
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
async ({ restaurant, date, partySize }) => {
|
|
1203
|
+
// Check availability
|
|
1204
|
+
const available = await checkAvailability(restaurant, date, partySize);
|
|
1205
|
+
|
|
1206
|
+
if (!available) {
|
|
1207
|
+
// Ask user if they want to try alternative dates
|
|
1208
|
+
const result = await server.server.elicitInput({
|
|
1209
|
+
message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
|
|
1210
|
+
requestedSchema: {
|
|
1211
|
+
type: 'object',
|
|
1212
|
+
properties: {
|
|
1213
|
+
checkAlternatives: {
|
|
1214
|
+
type: 'boolean',
|
|
1215
|
+
title: 'Check alternative dates',
|
|
1216
|
+
description: 'Would you like me to check other dates?'
|
|
1217
|
+
},
|
|
1218
|
+
flexibleDates: {
|
|
1219
|
+
type: 'string',
|
|
1220
|
+
title: 'Date flexibility',
|
|
1221
|
+
description: 'How flexible are your dates?',
|
|
1222
|
+
enum: ['next_day', 'same_week', 'next_week'],
|
|
1223
|
+
enumNames: ['Next day', 'Same week', 'Next week']
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
required: ['checkAlternatives']
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
if (result.action === 'accept' && result.content?.checkAlternatives) {
|
|
1231
|
+
const alternatives = await findAlternatives(restaurant, date, partySize, result.content.flexibleDates as string);
|
|
1232
|
+
const output = { success: false, alternatives };
|
|
1233
|
+
return {
|
|
1234
|
+
content: [
|
|
1235
|
+
{
|
|
1236
|
+
type: 'text',
|
|
1237
|
+
text: JSON.stringify(output)
|
|
1238
|
+
}
|
|
1239
|
+
],
|
|
1240
|
+
structuredContent: output
|
|
1241
|
+
};
|
|
1045
1242
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1243
|
+
|
|
1244
|
+
const output = { success: false };
|
|
1245
|
+
return {
|
|
1246
|
+
content: [
|
|
1247
|
+
{
|
|
1248
|
+
type: 'text',
|
|
1249
|
+
text: JSON.stringify(output)
|
|
1250
|
+
}
|
|
1251
|
+
],
|
|
1252
|
+
structuredContent: output
|
|
1253
|
+
};
|
|
1048
1254
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
result.content.flexibleDates as string
|
|
1057
|
-
);
|
|
1255
|
+
|
|
1256
|
+
// Book the table
|
|
1257
|
+
await makeBooking(restaurant, date, partySize);
|
|
1258
|
+
const output = {
|
|
1259
|
+
success: true,
|
|
1260
|
+
booking: { restaurant, date, partySize }
|
|
1261
|
+
};
|
|
1058
1262
|
return {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1263
|
+
content: [
|
|
1264
|
+
{
|
|
1265
|
+
type: 'text',
|
|
1266
|
+
text: JSON.stringify(output)
|
|
1267
|
+
}
|
|
1268
|
+
],
|
|
1269
|
+
structuredContent: output
|
|
1063
1270
|
};
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
return {
|
|
1067
|
-
content: [{
|
|
1068
|
-
type: "text",
|
|
1069
|
-
text: "No booking made. Original date not available."
|
|
1070
|
-
}]
|
|
1071
|
-
};
|
|
1072
1271
|
}
|
|
1073
|
-
|
|
1074
|
-
// Book the table
|
|
1075
|
-
await makeBooking(restaurant, date, partySize);
|
|
1076
|
-
return {
|
|
1077
|
-
content: [{
|
|
1078
|
-
type: "text",
|
|
1079
|
-
text: `Booked table for ${partySize} at ${restaurant} on ${date}`
|
|
1080
|
-
}]
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
1272
|
);
|
|
1084
1273
|
```
|
|
1085
1274
|
|
|
@@ -1087,24 +1276,24 @@ Client-side: Handle elicitation requests
|
|
|
1087
1276
|
|
|
1088
1277
|
```typescript
|
|
1089
1278
|
// This is a placeholder - implement based on your UI framework
|
|
1090
|
-
async function getInputFromUser(
|
|
1091
|
-
|
|
1092
|
-
|
|
1279
|
+
async function getInputFromUser(
|
|
1280
|
+
message: string,
|
|
1281
|
+
schema: any
|
|
1282
|
+
): Promise<{
|
|
1283
|
+
action: 'accept' | 'decline' | 'cancel';
|
|
1284
|
+
data?: Record<string, any>;
|
|
1093
1285
|
}> {
|
|
1094
|
-
|
|
1095
|
-
|
|
1286
|
+
// This should be implemented depending on the app
|
|
1287
|
+
throw new Error('getInputFromUser must be implemented for your platform');
|
|
1096
1288
|
}
|
|
1097
1289
|
|
|
1098
|
-
client.setRequestHandler(ElicitRequestSchema, async
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
action: userResponse.action,
|
|
1106
|
-
content: userResponse.action === "accept" ? userResponse.data : undefined
|
|
1107
|
-
};
|
|
1290
|
+
client.setRequestHandler(ElicitRequestSchema, async request => {
|
|
1291
|
+
const userResponse = await getInputFromUser(request.params.message, request.params.requestedSchema);
|
|
1292
|
+
|
|
1293
|
+
return {
|
|
1294
|
+
action: userResponse.action,
|
|
1295
|
+
content: userResponse.action === 'accept' ? userResponse.data : undefined
|
|
1296
|
+
};
|
|
1108
1297
|
});
|
|
1109
1298
|
```
|
|
1110
1299
|
|
|
@@ -1115,20 +1304,18 @@ client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
|
1115
1304
|
The SDK provides a high-level client interface:
|
|
1116
1305
|
|
|
1117
1306
|
```typescript
|
|
1118
|
-
import { Client } from
|
|
1119
|
-
import { StdioClientTransport } from
|
|
1307
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
1308
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
1120
1309
|
|
|
1121
1310
|
const transport = new StdioClientTransport({
|
|
1122
|
-
|
|
1123
|
-
|
|
1311
|
+
command: 'node',
|
|
1312
|
+
args: ['server.js']
|
|
1124
1313
|
});
|
|
1125
1314
|
|
|
1126
|
-
const client = new Client(
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1131
|
-
);
|
|
1315
|
+
const client = new Client({
|
|
1316
|
+
name: 'example-client',
|
|
1317
|
+
version: '1.0.0'
|
|
1318
|
+
});
|
|
1132
1319
|
|
|
1133
1320
|
await client.connect(transport);
|
|
1134
1321
|
|
|
@@ -1137,10 +1324,10 @@ const prompts = await client.listPrompts();
|
|
|
1137
1324
|
|
|
1138
1325
|
// Get a prompt
|
|
1139
1326
|
const prompt = await client.getPrompt({
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1327
|
+
name: 'example-prompt',
|
|
1328
|
+
arguments: {
|
|
1329
|
+
arg1: 'value'
|
|
1330
|
+
}
|
|
1144
1331
|
});
|
|
1145
1332
|
|
|
1146
1333
|
// List resources
|
|
@@ -1148,17 +1335,16 @@ const resources = await client.listResources();
|
|
|
1148
1335
|
|
|
1149
1336
|
// Read a resource
|
|
1150
1337
|
const resource = await client.readResource({
|
|
1151
|
-
|
|
1338
|
+
uri: 'file:///example.txt'
|
|
1152
1339
|
});
|
|
1153
1340
|
|
|
1154
1341
|
// Call a tool
|
|
1155
1342
|
const result = await client.callTool({
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1343
|
+
name: 'example-tool',
|
|
1344
|
+
arguments: {
|
|
1345
|
+
arg1: 'value'
|
|
1346
|
+
}
|
|
1160
1347
|
});
|
|
1161
|
-
|
|
1162
1348
|
```
|
|
1163
1349
|
|
|
1164
1350
|
### Proxy Authorization Requests Upstream
|
|
@@ -1174,31 +1360,33 @@ const app = express();
|
|
|
1174
1360
|
|
|
1175
1361
|
const proxyProvider = new ProxyOAuthServerProvider({
|
|
1176
1362
|
endpoints: {
|
|
1177
|
-
authorizationUrl:
|
|
1178
|
-
tokenUrl:
|
|
1179
|
-
revocationUrl:
|
|
1363
|
+
authorizationUrl: 'https://auth.external.com/oauth2/v1/authorize',
|
|
1364
|
+
tokenUrl: 'https://auth.external.com/oauth2/v1/token',
|
|
1365
|
+
revocationUrl: 'https://auth.external.com/oauth2/v1/revoke'
|
|
1180
1366
|
},
|
|
1181
|
-
verifyAccessToken: async
|
|
1367
|
+
verifyAccessToken: async token => {
|
|
1182
1368
|
return {
|
|
1183
1369
|
token,
|
|
1184
|
-
clientId:
|
|
1185
|
-
scopes: [
|
|
1186
|
-
}
|
|
1370
|
+
clientId: '123',
|
|
1371
|
+
scopes: ['openid', 'email', 'profile']
|
|
1372
|
+
};
|
|
1187
1373
|
},
|
|
1188
|
-
getClient: async
|
|
1374
|
+
getClient: async client_id => {
|
|
1189
1375
|
return {
|
|
1190
1376
|
client_id,
|
|
1191
|
-
redirect_uris: [
|
|
1192
|
-
}
|
|
1377
|
+
redirect_uris: ['http://localhost:3000/callback']
|
|
1378
|
+
};
|
|
1193
1379
|
}
|
|
1194
|
-
})
|
|
1195
|
-
|
|
1196
|
-
app.use(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
app.use(
|
|
1383
|
+
mcpAuthRouter({
|
|
1384
|
+
provider: proxyProvider,
|
|
1385
|
+
issuerUrl: new URL('http://auth.external.com'),
|
|
1386
|
+
baseUrl: new URL('http://mcp.example.com'),
|
|
1387
|
+
serviceDocumentationUrl: new URL('https://docs.example.com/')
|
|
1388
|
+
})
|
|
1389
|
+
);
|
|
1202
1390
|
```
|
|
1203
1391
|
|
|
1204
1392
|
This setup allows you to:
|
|
@@ -1218,31 +1406,29 @@ Clients and servers with StreamableHttp transport can maintain [backwards compat
|
|
|
1218
1406
|
For clients that need to work with both Streamable HTTP and older SSE servers:
|
|
1219
1407
|
|
|
1220
1408
|
```typescript
|
|
1221
|
-
import { Client } from
|
|
1222
|
-
import { StreamableHTTPClientTransport } from
|
|
1223
|
-
import { SSEClientTransport } from
|
|
1224
|
-
let client: Client|undefined = undefined
|
|
1409
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
1410
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
1411
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
1412
|
+
let client: Client | undefined = undefined;
|
|
1225
1413
|
const baseUrl = new URL(url);
|
|
1226
1414
|
try {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
await client.connect(transport);
|
|
1235
|
-
console.log("Connected using Streamable HTTP transport");
|
|
1415
|
+
client = new Client({
|
|
1416
|
+
name: 'streamable-http-client',
|
|
1417
|
+
version: '1.0.0'
|
|
1418
|
+
});
|
|
1419
|
+
const transport = new StreamableHTTPClientTransport(new URL(baseUrl));
|
|
1420
|
+
await client.connect(transport);
|
|
1421
|
+
console.log('Connected using Streamable HTTP transport');
|
|
1236
1422
|
} catch (error) {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1423
|
+
// If that fails with a 4xx error, try the older SSE transport
|
|
1424
|
+
console.log('Streamable HTTP connection failed, falling back to SSE transport');
|
|
1425
|
+
client = new Client({
|
|
1426
|
+
name: 'sse-client',
|
|
1427
|
+
version: '1.0.0'
|
|
1428
|
+
});
|
|
1429
|
+
const sseTransport = new SSEClientTransport(baseUrl);
|
|
1430
|
+
await client.connect(sseTransport);
|
|
1431
|
+
console.log('Connected using SSE transport');
|
|
1246
1432
|
}
|
|
1247
1433
|
```
|
|
1248
1434
|
|
|
@@ -1251,14 +1437,14 @@ try {
|
|
|
1251
1437
|
For servers that need to support both Streamable HTTP and older clients:
|
|
1252
1438
|
|
|
1253
1439
|
```typescript
|
|
1254
|
-
import express from
|
|
1255
|
-
import { McpServer } from
|
|
1256
|
-
import { StreamableHTTPServerTransport } from
|
|
1257
|
-
import { SSEServerTransport } from
|
|
1440
|
+
import express from 'express';
|
|
1441
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
1442
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
1443
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
1258
1444
|
|
|
1259
1445
|
const server = new McpServer({
|
|
1260
|
-
|
|
1261
|
-
|
|
1446
|
+
name: 'backwards-compatible-server',
|
|
1447
|
+
version: '1.0.0'
|
|
1262
1448
|
});
|
|
1263
1449
|
|
|
1264
1450
|
// ... set up server resources, tools, and prompts ...
|
|
@@ -1268,39 +1454,39 @@ app.use(express.json());
|
|
|
1268
1454
|
|
|
1269
1455
|
// Store transports for each session type
|
|
1270
1456
|
const transports = {
|
|
1271
|
-
|
|
1272
|
-
|
|
1457
|
+
streamable: {} as Record<string, StreamableHTTPServerTransport>,
|
|
1458
|
+
sse: {} as Record<string, SSEServerTransport>
|
|
1273
1459
|
};
|
|
1274
1460
|
|
|
1275
1461
|
// Modern Streamable HTTP endpoint
|
|
1276
1462
|
app.all('/mcp', async (req, res) => {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1463
|
+
// Handle Streamable HTTP transport for modern clients
|
|
1464
|
+
// Implementation as shown in the "With Session Management" example
|
|
1465
|
+
// ...
|
|
1280
1466
|
});
|
|
1281
1467
|
|
|
1282
1468
|
// Legacy SSE endpoint for older clients
|
|
1283
1469
|
app.get('/sse', async (req, res) => {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1470
|
+
// Create SSE transport for legacy clients
|
|
1471
|
+
const transport = new SSEServerTransport('/messages', res);
|
|
1472
|
+
transports.sse[transport.sessionId] = transport;
|
|
1473
|
+
|
|
1474
|
+
res.on('close', () => {
|
|
1475
|
+
delete transports.sse[transport.sessionId];
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
await server.connect(transport);
|
|
1293
1479
|
});
|
|
1294
1480
|
|
|
1295
1481
|
// Legacy message endpoint for older clients
|
|
1296
1482
|
app.post('/messages', async (req, res) => {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1483
|
+
const sessionId = req.query.sessionId as string;
|
|
1484
|
+
const transport = transports.sse[sessionId];
|
|
1485
|
+
if (transport) {
|
|
1486
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
1487
|
+
} else {
|
|
1488
|
+
res.status(400).send('No transport found for sessionId');
|
|
1489
|
+
}
|
|
1304
1490
|
});
|
|
1305
1491
|
|
|
1306
1492
|
app.listen(3000);
|