diff --git a/package.json b/package.json index 85f560d3..4edbeae5 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": { @@ -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.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/index.ts b/src/index.ts index c60d2bfe..188411db 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 @@ -278,6 +279,12 @@ class HawkAPI { */ 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(); this.app.use(graphqlUploadExpress()); this.server.applyMiddleware({ app: this.app }); diff --git a/src/integrations/mcp/index.ts b/src/integrations/mcp/index.ts new file mode 100644 index 00000000..4a6953bf --- /dev/null +++ b/src/integrations/mcp/index.ts @@ -0,0 +1,15 @@ +import express from 'express'; +import { ContextFactories } from 'src/types/graphql'; +import { createMCPHandler } from './mcp'; + +/** + * 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 handler = createMCPHandler(factories); + + 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 new file mode 100644 index 00000000..1e68a115 --- /dev/null +++ b/src/integrations/mcp/mcp.ts @@ -0,0 +1,52 @@ +import { createMcpHandler, McpServer } from '@modelcontextprotocol/server'; +import { toNodeHandler } from '@modelcontextprotocol/node'; +import { ContextFactories } from 'src/types/graphql'; + +import type { NodeMcpRequestHandler } from '@modelcontextprotocol/node'; + +/** + * Creates 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; +}; + +/** + * Creates MCP request handler + * + * @param factories - context factories for database access + * @returns MCP RequestHandler + */ +export function createMCPHandler(factories: ContextFactories): NodeMcpRequestHandler { + const server = createMCPServer(factories); + + const handler = createMcpHandler(() => server); + + return toNodeHandler(handler); +}; 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==