From f779c2c1cfabb31f8337cffb76a6ad0806bbd1e8 Mon Sep 17 00:00:00 2001 From: Eugeny Date: Wed, 12 Aug 2026 02:02:23 +0300 Subject: [PATCH 1/8] feat(mcp): add initial mcp server setup --- package.json | 2 ++ src/index.ts | 2 ++ src/integrations/mcp/index.ts | 9 ++++++ src/integrations/mcp/mcp.ts | 54 +++++++++++++++++++++++++++++++++++ yarn.lock | 32 +++++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 src/integrations/mcp/index.ts create mode 100644 src/integrations/mcp/mcp.ts diff --git a/package.json b/package.json index 85f560d3..fdbfa97f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "@graphql-tools/utils": "^8.9.0", "@hawk.so/nodejs": "^3.3.2", "@hawk.so/types": "^0.7.0", + "@modelcontextprotocol/node": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", "@octokit/oauth-methods": "^4.0.0", diff --git a/src/index.ts b/src/index.ts index c60d2bfe..a26c1e65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import RedisHelper from './redisHelper'; import { appendSsoRoutes } from './sso'; import { appendGitHubRoutes } from './integrations/github'; import { appendAiAssistantRoutes } from './services/askAi'; +import { appendMCPRoutes } from './integrations/mcp'; /** * Option to enable playground @@ -277,6 +278,7 @@ class HawkAPI { * Append AI assistant route to Express app */ appendAiAssistantRoutes(this.app); + appendMCPRoutes(this.app, sharedFactories); await this.server.start(); this.app.use(graphqlUploadExpress()); diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts new file mode 100644 index 00000000..2053de3c --- /dev/null +++ b/src/integrations/mcp/index.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import { ContextFactories } from 'src/types/graphql'; +import { createMCPRouter } from "./mcp"; + +export function appendMCPRoutes(app: express.Application, factories: ContextFactories): void { + const router = createMCPRouter(factories); + + app.use('/integration/mcp', router); +} diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts new file mode 100644 index 00000000..378daa27 --- /dev/null +++ b/src/integrations/mcp/mcp.ts @@ -0,0 +1,54 @@ +import express from "express"; +import { McpServer } from '@modelcontextprotocol/server'; +import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; +import { ContextFactories } from 'src/types/graphql'; + + +const server = new McpServer({ + name: "server", + version: "0.0.0", + description: "A test server" +}); + +server.registerTool( + "hello_world", + { + description: "A test tool to greet the user" + }, + async () => { + return { + content: [ + { + type: "text" as const, + text: "Hi" + } + ] + } + } +) + +const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); + +server.connect(transport); + +export const createMCPRouter = (factories: ContextFactories): express.Router => { + const router = express.Router(); + + router.post("/", async (req, res, next) => { + try { + await transport.handleRequest(req, res, req.body); + } catch (error) { + next(error); + } + }); + + router.post("/", async (req, res, next) => { + try { + await transport.handleRequest(req, res, req.body); + } catch (error) { + next(error); + } + }) + + return router; +}; diff --git a/yarn.lock b/yarn.lock index 4b218c76..1caa5422 100644 --- a/yarn.lock +++ b/yarn.lock @@ -531,6 +531,11 @@ dependencies: bson "^7.0.0" +"@hono/node-server@^1.19.9": + version "1.19.17" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.17.tgz#99bd8625cdbae82652a135c71363cd6322fae483" + integrity sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -869,6 +874,28 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@modelcontextprotocol/core@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/core/-/core-2.0.0.tgz#c918a4c6aef22a7c59dfb0aabc2a82c187cce157" + integrity sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA== + dependencies: + zod "^4.2.0" + +"@modelcontextprotocol/node@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/node/-/node-2.0.0.tgz#3851275cb1c08aeb113d5b7cbe6dbe7adab37c65" + integrity sha512-Y4hAC2XdGDUdDOCbLDOCA4+aL3NUldjsOWlDL/YwpAxrPhRm1xHd7lZ+mLacvZ9t3PaH28wgNoaLQGrIk1P2pg== + dependencies: + "@hono/node-server" "^1.19.9" + +"@modelcontextprotocol/server@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/server/-/server-2.0.0.tgz#4fae5ccb8f7925ed9a6bf6595bb3b58617063ffd" + integrity sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw== + dependencies: + "@modelcontextprotocol/core" "2.0.0" + zod "^4.2.0" + "@mongodb-js/saslprep@^1.3.0": version "1.4.4" resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz#34a946ff6ae142e8f2259b87f2935f8284ba874d" @@ -7159,3 +7186,8 @@ zod@^3.25.76: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== + +zod@^4.2.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.4.3.tgz#b680f172885d18bbebf21a834ea25e55a1bbf356" + integrity sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ== From 4db6aa513161f274730aa039df42b39a401c73c2 Mon Sep 17 00:00:00 2001 From: Eugeny Date: Wed, 12 Aug 2026 02:10:11 +0300 Subject: [PATCH 2/8] remove duplication --- src/integrations/mcp/mcp.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index 378daa27..76ff61c2 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -42,13 +42,5 @@ export const createMCPRouter = (factories: ContextFactories): express.Router => } }); - router.post("/", async (req, res, next) => { - try { - await transport.handleRequest(req, res, req.body); - } catch (error) { - next(error); - } - }) - return router; }; From 65c4e9fb3a23dad70690d466f669e6c8643744b1 Mon Sep 17 00:00:00 2001 From: Eugeny Date: Wed, 19 Aug 2026 02:15:36 +0300 Subject: [PATCH 3/8] chore(mcp): add comments, fix transport single instance issue --- src/index.ts | 5 +++ src/integrations/mcp/index.ts | 6 +++ src/integrations/mcp/mcp.ts | 78 ++++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index a26c1e65..188411db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -278,6 +278,11 @@ class HawkAPI { * Append AI assistant route to Express app */ appendAiAssistantRoutes(this.app); + + /** + * Append MCP integration routes to Express app using shared factories + * Note: This must be called after database connections are established + */ appendMCPRoutes(this.app, sharedFactories); await this.server.start(); diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts index 2053de3c..e7bc2da5 100644 --- a/src/integrations/mcp/index.ts +++ b/src/integrations/mcp/index.ts @@ -2,6 +2,12 @@ import express from 'express'; import { ContextFactories } from 'src/types/graphql'; import { createMCPRouter } from "./mcp"; +/** + * Append MCP route to Express App + * + * @param app - Express application instance + * @param factories - context factories for database access + */ export function appendMCPRoutes(app: express.Application, factories: ContextFactories): void { const router = createMCPRouter(factories); diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index 76ff61c2..7fcc5386 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -3,39 +3,28 @@ import { McpServer } from '@modelcontextprotocol/server'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import { ContextFactories } from 'src/types/graphql'; - -const server = new McpServer({ - name: "server", - version: "0.0.0", - description: "A test server" -}); - -server.registerTool( - "hello_world", - { - description: "A test tool to greet the user" - }, - async () => { - return { - content: [ - { - type: "text" as const, - text: "Hi" - } - ] - } - } -) - -const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); - -server.connect(transport); - +/** + * Create MCP router + * + * @param factories - context factories for database access + * @returns Express router with MCP integration endpoints + */ export const createMCPRouter = (factories: ContextFactories): express.Router => { const router = express.Router(); + const server = createMCPServer(factories); + /** + * POST /integration/mcp + * Initiate MCP integration connection + */ router.post("/", async (req, res, next) => { try { + /** + * Each request must use its own transport + */ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); } catch (error) { next(error); @@ -44,3 +33,36 @@ export const createMCPRouter = (factories: ContextFactories): express.Router => return router; }; + +/** + * Create MCP server + * + * @param factories - context factories for database access + * @returns configured MCP server + */ +const createMCPServer = (factories: ContextFactories) => { + const server = new McpServer({ + name: "server", + version: "0.0.0", + description: "A test server" + }); + + server.registerTool( + "hello_world", + { + description: "A test tool to greet the user" + }, + async () => { + return { + content: [ + { + type: "text" as const, + text: "Hi" + } + ] + } + } + ); + + return server; +}; From 5dbc591b4591c9ac52c0b7f28cf96c7f5b27df70 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:40:43 +0000 Subject: [PATCH 4/8] Bump version up to 1.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdbfa97f..8faac0a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.6.0", + "version": "1.6.1", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From 25b4fbdc2a1180816e5d6419e1a872e0d672aca1 Mon Sep 17 00:00:00 2001 From: KoshaevEugeny <103786108+akulistus@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:40:30 +0300 Subject: [PATCH 5/8] Update src/integrations/mcp/mcp.ts Co-authored-by: Reversean <43142829+Reversean@users.noreply.github.com> --- src/integrations/mcp/mcp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index 7fcc5386..72c0407f 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -17,7 +17,7 @@ export const createMCPRouter = (factories: ContextFactories): express.Router => * POST /integration/mcp * Initiate MCP integration connection */ - router.post("/", async (req, res, next) => { + router.all("/", async (req, res, next) => { try { /** * Each request must use its own transport From 9cd7c86a95681b6d70fef702323fed63d9a571bf Mon Sep 17 00:00:00 2001 From: Eugeny Koshaev Date: Thu, 22 Oct 2026 22:48:38 +0300 Subject: [PATCH 6/8] fix lint --- src/integrations/mcp/index.ts | 4 +- src/integrations/mcp/mcp.ts | 73 ++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts index e7bc2da5..0f32b8ba 100644 --- a/src/integrations/mcp/index.ts +++ b/src/integrations/mcp/index.ts @@ -1,10 +1,10 @@ import express from 'express'; import { ContextFactories } from 'src/types/graphql'; -import { createMCPRouter } from "./mcp"; +import { createMCPRouter } from './mcp'; /** * Append MCP route to Express App - * + * * @param app - Express application instance * @param factories - context factories for database access */ diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index 72c0407f..c62cc653 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -1,15 +1,48 @@ -import express from "express"; +import express from 'express'; import { McpServer } from '@modelcontextprotocol/server'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import { ContextFactories } from 'src/types/graphql'; +/** + * Create MCP server + * + * @param factories - context factories for database access + * @returns configured MCP server + */ +function createMCPServer(factories: ContextFactories) { + const server = new McpServer({ + name: 'server', + version: '0.0.0', + description: 'A test server', + }); + + server.registerTool( + 'hello_world', + { + description: 'A test tool to greet the user', + }, + async () => { + return { + content: [ + { + type: 'text' as const, + text: 'Hi', + }, + ], + }; + } + ); + + return server; +}; + /** * Create MCP router * * @param factories - context factories for database access * @returns Express router with MCP integration endpoints */ -export const createMCPRouter = (factories: ContextFactories): express.Router => { +export function createMCPRouter(factories: ContextFactories): express.Router { const router = express.Router(); const server = createMCPServer(factories); @@ -17,12 +50,13 @@ export const createMCPRouter = (factories: ContextFactories): express.Router => * POST /integration/mcp * Initiate MCP integration connection */ - router.all("/", async (req, res, next) => { + router.all('/', async (req, res, next) => { try { /** * Each request must use its own transport */ const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); + await server.connect(transport); await transport.handleRequest(req, res, req.body); @@ -33,36 +67,3 @@ export const createMCPRouter = (factories: ContextFactories): express.Router => return router; }; - -/** - * Create MCP server - * - * @param factories - context factories for database access - * @returns configured MCP server - */ -const createMCPServer = (factories: ContextFactories) => { - const server = new McpServer({ - name: "server", - version: "0.0.0", - description: "A test server" - }); - - server.registerTool( - "hello_world", - { - description: "A test tool to greet the user" - }, - async () => { - return { - content: [ - { - type: "text" as const, - text: "Hi" - } - ] - } - } - ); - - return server; -}; From f149a514a3af94981eb5c6f5de0b1f6e1d3c6a1e Mon Sep 17 00:00:00 2001 From: Eugeny Koshaev Date: Sat, 24 Oct 2026 00:00:18 +0300 Subject: [PATCH 7/8] chore(MCP): update MPC request handler to support protocol revision 2026-07-28 --- package.json | 4 ++-- src/integrations/mcp/index.ts | 8 ++++---- src/integrations/mcp/mcp.ts | 35 +++++++++-------------------------- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 8faac0a5..4edbeae5 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "@graphql-tools/utils": "^8.9.0", "@hawk.so/nodejs": "^3.3.2", "@hawk.so/types": "^0.7.0", - "@modelcontextprotocol/node": "^2.0.0", - "@modelcontextprotocol/server": "^2.0.0", + "@modelcontextprotocol/node": "^2.1.2", + "@modelcontextprotocol/server": "^2.4.0", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", "@octokit/oauth-methods": "^4.0.0", diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts index 0f32b8ba..95cacf29 100644 --- a/src/integrations/mcp/index.ts +++ b/src/integrations/mcp/index.ts @@ -1,15 +1,15 @@ import express from 'express'; import { ContextFactories } from 'src/types/graphql'; -import { createMCPRouter } from './mcp'; +import { createMCPHandler } from './mcp'; /** - * Append MCP route to Express App + * Appends MCP route to Express App * * @param app - Express application instance * @param factories - context factories for database access */ export function appendMCPRoutes(app: express.Application, factories: ContextFactories): void { - const router = createMCPRouter(factories); + const handler = createMCPHandler(factories); - app.use('/integration/mcp', router); + app.use('/integration/mcp', (req, res) => void handler(req, res, req.body)); } diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index c62cc653..6e5ca541 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -1,8 +1,9 @@ -import express from 'express'; -import { McpServer } from '@modelcontextprotocol/server'; -import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; +import { createMcpHandler, McpServer } from '@modelcontextprotocol/server'; +import { toNodeHandler } from '@modelcontextprotocol/node'; import { ContextFactories } from 'src/types/graphql'; +import type { NodeMcpRequestHandler } from "@modelcontextprotocol/node"; + /** * Create MCP server * @@ -37,33 +38,15 @@ function createMCPServer(factories: ContextFactories) { }; /** - * Create MCP router + * Creates MCP handler * * @param factories - context factories for database access - * @returns Express router with MCP integration endpoints + * @returns MCP RequestHandler */ -export function createMCPRouter(factories: ContextFactories): express.Router { - const router = express.Router(); +export function createMCPHandler(factories: ContextFactories): NodeMcpRequestHandler { const server = createMCPServer(factories); - /** - * POST /integration/mcp - * Initiate MCP integration connection - */ - router.all('/', async (req, res, next) => { - try { - /** - * Each request must use its own transport - */ - const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); - - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - } catch (error) { - next(error); - } - }); + const handler = createMcpHandler(() => server); - return router; + return toNodeHandler(handler); }; From 1e7520cd3c7ba3ac76fdc41dd6d03e38e0fca6f7 Mon Sep 17 00:00:00 2001 From: Eugeny Koshaev Date: Sat, 24 Oct 2026 00:06:33 +0300 Subject: [PATCH 8/8] fix lint --- src/integrations/mcp/index.ts | 2 +- src/integrations/mcp/mcp.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts index 95cacf29..4a6953bf 100644 --- a/src/integrations/mcp/index.ts +++ b/src/integrations/mcp/index.ts @@ -11,5 +11,5 @@ import { createMCPHandler } from './mcp'; export function appendMCPRoutes(app: express.Application, factories: ContextFactories): void { const handler = createMCPHandler(factories); - app.use('/integration/mcp', (req, res) => void handler(req, res, req.body)); + app.use('/integration/mcp', (req, res) => handler(req, res, req.body)); } diff --git a/src/integrations/mcp/mcp.ts b/src/integrations/mcp/mcp.ts index 6e5ca541..1e68a115 100644 --- a/src/integrations/mcp/mcp.ts +++ b/src/integrations/mcp/mcp.ts @@ -2,10 +2,10 @@ import { createMcpHandler, McpServer } from '@modelcontextprotocol/server'; import { toNodeHandler } from '@modelcontextprotocol/node'; import { ContextFactories } from 'src/types/graphql'; -import type { NodeMcpRequestHandler } from "@modelcontextprotocol/node"; +import type { NodeMcpRequestHandler } from '@modelcontextprotocol/node'; /** - * Create MCP server + * Creates MCP server * * @param factories - context factories for database access * @returns configured MCP server @@ -38,7 +38,7 @@ function createMCPServer(factories: ContextFactories) { }; /** - * Creates MCP handler + * Creates MCP request handler * * @param factories - context factories for database access * @returns MCP RequestHandler