nest-graphql-mcp 0.0.0-stage → 0.1.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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chase McManning
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # Nest GraphQL MCP Module
2
+
3
+ This module provides integration between NestJS, GraphQL, and the Model Context Protocol (MCP).
4
+
5
+ If you have a mature GraphQL environment, this module allows you to expose specific queries and mutations to AI agents as MCP tools by using your existing schema types and documentation directly.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install nest-graphql-mcp @modelcontextprotocol/sdk zod
11
+ ```
12
+
13
+ ## Module Registration
14
+
15
+ Import `NestGraphqlMcpModule.forRoot()` in your root `AppModule`:
16
+
17
+ ```ts
18
+ import { Module } from '@nestjs/common';
19
+ import { NestGraphqlMcpModule } from 'nest-graphql-mcp';
20
+
21
+ @Module({
22
+ imports: [
23
+ NestGraphqlMcpModule.forRoot({
24
+ path: '/mcp/stateless', // Endpoint for AI agents
25
+ }),
26
+ // ... GraphQLModule import (see Code-First or Schema-First setup below)
27
+ ],
28
+ })
29
+ export class AppModule {}
30
+ ```
31
+
32
+ ## Code-First Approach
33
+
34
+ Use TypeScript decorators to generate the GraphQL schema and register the MCP directive.
35
+
36
+ Pass `MCP_DIRECTIVE_DEFINITION` to buildSchemaOptions.directives:
37
+
38
+ ```ts
39
+ import { Module } from '@nestjs/common';
40
+ import { GraphQLModule } from '@nestjs/graphql';
41
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
42
+ import { NestGraphqlMcpModule, MCP_DIRECTIVE_DEFINITION } from 'nest-graphql-mcp';
43
+
44
+ @Module({
45
+ imports: [
46
+ GraphQLModule.forRoot<ApolloDriverConfig>({
47
+ driver: ApolloDriver,
48
+ autoSchemaFile: true,
49
+ buildSchemaOptions: {
50
+ directives: [MCP_DIRECTIVE_DEFINITION],
51
+ },
52
+ }),
53
+ NestGraphqlMcpModule.forRoot(),
54
+ ],
55
+ })
56
+ export class AppModule {}
57
+ ```
58
+
59
+ Attach the `@ExposeMcp()` decorator to any GraphQL Query or Mutation you want to expose to AI agents. You can optionally pass agent-specific prompt instructions.
60
+
61
+ ```ts
62
+ import { Resolver, Query, Args } from '@nestjs/graphql';
63
+ import { UseGuards } from '@nestjs/common';
64
+ import { ExposeMcp } from 'nest-graphql-mcp';
65
+ import { AuthGuard } from './auth.guard';
66
+ import { User } from './user.model';
67
+
68
+ @Resolver(() => User)
69
+ export class UserResolver {
70
+ @Query(() => User, {
71
+ description: 'Fetches user details by ID.',
72
+ })
73
+ @ExposeMcp('Prefer looking up users by UUID. Do not call in a loop for bulk queries.')
74
+ @UseGuards(AuthGuard)
75
+ async userById(@Args('id') id: string) {
76
+ return this.userService.findById(id);
77
+ }
78
+
79
+ @Query(() => [User])
80
+ // Standard GQL query hidden from MCP tools because @ExposeMcp() is omitted
81
+ async internalAdminSearch() {
82
+ return this.userService.findAll();
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Schema-First Approach
88
+
89
+ In the Schema-First approach, you define your GraphQL types and directives inside .graphql SDL files. This module automatically reads the compiled GraphQL AST at startup to discover fields annotated with `@mcp`.
90
+
91
+ ```ts
92
+ import { Module } from '@nestjs/common';
93
+ import { GraphQLModule } from '@nestjs/graphql';
94
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
95
+ import { NestGraphqlMcpModule, MCP_DIRECTIVE_DEFINITION } from 'nest-graphql-mcp';
96
+
97
+ @Module({
98
+ imports: [
99
+ GraphQLModule.forRoot<ApolloDriverConfig>({
100
+ driver: ApolloDriver,
101
+ typePaths: ['./**/*.graphql'],
102
+ definitions: {
103
+ path: join(process.cwd(), 'src/graphql.ts'),
104
+ },
105
+ }),
106
+ NestGraphqlMcpModule.forRoot(),
107
+ ],
108
+ })
109
+ export class AppModule {}
110
+ ```
111
+
112
+ Add the `@mcp` directive definition to your .graphql schema file and annotate fields directly:
113
+
114
+ ```graphql
115
+ " Directive to expose operations as MCP Tools to AI agents. "
116
+ directive @mcp(instructions: String) on FIELD_DEFINITION
117
+
118
+ type User {
119
+ id: ID!
120
+ name: String!
121
+ email: String!
122
+ }
123
+
124
+ type Query {
125
+ me: User @mcp
126
+
127
+ # Exposing to MCP with custom agent instructions
128
+ userById(id: ID!): User @mcp(instructions: "Prefer looking up users by UUID. Do not call in a loop for bulk queries.")
129
+
130
+ # Hidden from MCP tools
131
+ internalAdminSearch: [User!]!
132
+ }
133
+ ```
134
+
135
+ Import your `.graphql` files as usual. No special directive configuration is needed in Typescript.
136
+
137
+ ## Authentication & Context Forwarding
138
+
139
+ When an AI agent calls an MCP tool over HTTP, `nest-graphql-mcp` forwards the incoming `Authorization` header directly into GraphQL's execution context:
140
+
141
+ ```
142
+ [AI Agent]
143
+ │
144
+ │ HTTP POST /mcp/stateless
145
+ │ Header: Authorization: Bearer <SERVICE_ACCOUNT_JWT>
146
+ ▼
147
+ [nest-graphql-mcp]
148
+ │
149
+ │ Executes query with contextValue: { req: { headers: { authorization } } }
150
+ ▼
151
+ [NestJS Resolvers / @UseGuards()]
152
+ ```
153
+
154
+ This ensures your existing NestJS Guards, Role Checks, and Field-level permissions continue to execute for AI service accounts.
155
+
156
+ ## Agent Setup & Testing
157
+
158
+ ### Cursor / Claude / Standard MCP Setup
159
+
160
+ Add the endpoint and service account JWT to your mcp.json config:
161
+
162
+ ```ts
163
+ {
164
+ "mcpServers": {
165
+ "nestjs-api": {
166
+ "url": "http://localhost:3000/mcp/stateless",
167
+ "transport": "http",
168
+ "headers": {
169
+ "Authorization": "Bearer YOUR_SERVICE_ACCOUNT_JWT"
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ### Inspectiing Tools via Curl
177
+
178
+ You can verify that exposed tools and JSON Schemas are generated correctly by sending a JSON-RPC request:
179
+
180
+ ```sh
181
+ # List Tools
182
+ curl -X POST http://localhost:3000/api/mcp/stateless \
183
+ -H "Content-Type: application/json" \
184
+ -H "Accept: application/json, text/event-stream" \
185
+ -H "Authorization: Bearer YOUR_SERVICE_ACCOUNT_JWT" \
186
+ -d '{
187
+ "jsonrpc": "2.0",
188
+ "id": 1,
189
+ "method": "tools/list"
190
+ }'
191
+ ```
192
+
193
+ ```sh
194
+ # Execute Tool
195
+ curl -s -X POST http://localhost:3000/api/mcp/stateless \
196
+ -H "Content-Type: application/json" \
197
+ -H "Accept: application/json, text/event-stream" \
198
+ -H "Authorization: Bearer YOUR_SERVICE_ACCOUNT_JWT" \
199
+ -d '{
200
+ "jsonrpc": "2.0",
201
+ "id": 2,
202
+ "method": "tools/call",
203
+ "params": {
204
+ "name": "query_getUser",
205
+ "arguments": {
206
+ "id": "usr_123"
207
+ }
208
+ }
209
+ }'
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT, see the included [LICENSE](LICENSE.md) file.
@@ -0,0 +1,6 @@
1
+ import { GraphQLDirective } from 'graphql';
2
+ export declare const MCP_OPTIONS: unique symbol;
3
+ /**
4
+ * GraphQL directive definition for exposing fields as MCP.
5
+ */
6
+ export declare const MCP_DIRECTIVE_DEFINITION: GraphQLDirective;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MCP_DIRECTIVE_DEFINITION = exports.MCP_OPTIONS = void 0;
4
+ const graphql_1 = require("graphql");
5
+ exports.MCP_OPTIONS = Symbol('MCP_OPTIONS');
6
+ /**
7
+ * GraphQL directive definition for exposing fields as MCP.
8
+ */
9
+ exports.MCP_DIRECTIVE_DEFINITION = new graphql_1.GraphQLDirective({
10
+ name: 'mcp',
11
+ locations: [graphql_1.DirectiveLocation.FIELD_DEFINITION],
12
+ args: {
13
+ instructions: { type: graphql_1.GraphQLString },
14
+ },
15
+ });
@@ -0,0 +1,5 @@
1
+ export * from './constants';
2
+ export * from './mcp.decorator';
3
+ export * from './mcp.service';
4
+ export * from './nest-graphql-mcp.module';
5
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./constants"), exports);
18
+ __exportStar(require("./mcp.decorator"), exports);
19
+ __exportStar(require("./mcp.service"), exports);
20
+ __exportStar(require("./nest-graphql-mcp.module"), exports);
21
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,8 @@
1
+ import { type Request, type Response } from 'express';
2
+ import { McpService } from './mcp.service';
3
+ export declare class McpController {
4
+ private readonly mcpService;
5
+ constructor(mcpService: McpService);
6
+ private transport;
7
+ handleStatelessMcp(req: Request, res: Response): Promise<void>;
8
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.McpController = void 0;
16
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
17
+ const common_1 = require("@nestjs/common");
18
+ const mcp_service_1 = require("./mcp.service");
19
+ let McpController = class McpController {
20
+ mcpService;
21
+ // Store active sessions mapped by their session ID
22
+ // private readonly sessions = new Map<string, StreamableHTTPServerTransport>();
23
+ constructor(mcpService) {
24
+ this.mcpService = mcpService;
25
+ }
26
+ // The single McpServer instance supports one transport at a time, so track the
27
+ // active transport and close it before reconnecting on the next request.
28
+ // ponytail: one in-flight request at a time per endpoint; per-request McpServer
29
+ // instances (or sessions) when concurrent stateless connections are needed.
30
+ transport;
31
+ // @All()
32
+ // async handleMcp(@Req() req: Request, @Res() res: Response) {
33
+ // const authHeader = req.headers.authorization;
34
+ // if (!authHeader) {
35
+ // throw new UnauthorizedException('Missing Authorization header');
36
+ // }
37
+ // const sessionId = req.headers['mcp-session-id'] as string | undefined;
38
+ // // Resume existing session if mcp-session-id header is provided
39
+ // if (sessionId && this.sessions.has(sessionId)) {
40
+ // const transport = this.sessions.get(sessionId)!;
41
+ // await transport.handleRequest(req, res, req.body);
42
+ // return;
43
+ // }
44
+ // const transport = new StreamableHTTPServerTransport({
45
+ // sessionIdGenerator: () => randomUUID(),
46
+ // onsessioninitialized: (id) => this.sessions.set(id, transport),
47
+ // });
48
+ // transport.onclose = () => {
49
+ // if (transport.sessionId) {
50
+ // this.sessions.delete(transport.sessionId);
51
+ // }
52
+ // };
53
+ // // Connect transport to the underlying McpServer and handle request
54
+ // await this.mcpService.getMcpServer().connect(transport);
55
+ // await transport.handleRequest(req, res, req.body);
56
+ // }
57
+ async handleStatelessMcp(req, res) {
58
+ // TODO: This is if we don't need in-memory session tracking acrosst urns
59
+ // (e.g. token is validated on every request). Probably the preferred route.
60
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
61
+ // We don't need to persist state between requests.
62
+ sessionIdGenerator: undefined,
63
+ });
64
+ res.on('close', () => {
65
+ transport.close();
66
+ });
67
+ // Close the previous transport (no-op if already closed) so the shared
68
+ // server can attach to this request's transport.
69
+ if (this.transport) {
70
+ await this.transport.close();
71
+ }
72
+ this.transport = transport;
73
+ await this.mcpService.getMcpServer().connect(transport);
74
+ await transport.handleRequest(req, res, req.body);
75
+ }
76
+ };
77
+ exports.McpController = McpController;
78
+ __decorate([
79
+ (0, common_1.All)('stateless'),
80
+ __param(0, (0, common_1.Req)()),
81
+ __param(1, (0, common_1.Res)()),
82
+ __metadata("design:type", Function),
83
+ __metadata("design:paramtypes", [Object, Object]),
84
+ __metadata("design:returntype", Promise)
85
+ ], McpController.prototype, "handleStatelessMcp", null);
86
+ exports.McpController = McpController = __decorate([
87
+ (0, common_1.Controller)('mcp'),
88
+ __metadata("design:paramtypes", [mcp_service_1.McpService])
89
+ ], McpController);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Whitelists a GraphQL Query or Mutation for MCP and optionally adds custom
3
+ * instructions specifically for AI agents.
4
+ */
5
+ export declare const ExposeMcp: (agentInstructions?: string) => MethodDecorator & PropertyDecorator & ClassDecorator;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExposeMcp = void 0;
4
+ const graphql_1 = require("@nestjs/graphql");
5
+ /**
6
+ * Whitelists a GraphQL Query or Mutation for MCP and optionally adds custom
7
+ * instructions specifically for AI agents.
8
+ */
9
+ const ExposeMcp = (agentInstructions) => {
10
+ if (agentInstructions) {
11
+ // Escape quotes to prevent directive string syntax errors
12
+ const sanitized = agentInstructions.replace(/"/g, '\\"');
13
+ return (0, graphql_1.Directive)(`@mcp(instructions: "${sanitized}")`);
14
+ }
15
+ return (0, graphql_1.Directive)('@mcp');
16
+ };
17
+ exports.ExposeMcp = ExposeMcp;
@@ -0,0 +1,20 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { OnModuleInit } from '@nestjs/common';
3
+ import { ModuleRef } from '@nestjs/core';
4
+ import { type GraphqlMcpModuleOptions } from './types';
5
+ export declare class McpService implements OnModuleInit {
6
+ private readonly moduleRef;
7
+ private readonly options;
8
+ private readonly mcpServer;
9
+ private schemaHost;
10
+ constructor(moduleRef: ModuleRef, options: GraphqlMcpModuleOptions);
11
+ onModuleInit(): void;
12
+ /**
13
+ * Helper to parse the `@mcp` directive and its `instructions` argument from field AST.
14
+ */
15
+ private getMcpDirectiveConfig;
16
+ private mapGraphQLTypeToZod;
17
+ private buildSelectionSet;
18
+ getMcpServer(): McpServer;
19
+ private registerObjectTypeFields;
20
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.McpService = void 0;
16
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@nestjs/core");
19
+ const graphql_1 = require("@nestjs/graphql");
20
+ const graphql_2 = require("graphql");
21
+ const zod_1 = require("zod");
22
+ const constants_1 = require("./constants");
23
+ let McpService = class McpService {
24
+ moduleRef;
25
+ options;
26
+ mcpServer;
27
+ schemaHost;
28
+ constructor(moduleRef, options) {
29
+ this.moduleRef = moduleRef;
30
+ this.options = options;
31
+ this.mcpServer = new mcp_js_1.McpServer({
32
+ name: this.options.serverName || 'nestjs-mcp-server',
33
+ version: this.options.serverVersion || '1.0.0',
34
+ });
35
+ }
36
+ onModuleInit() {
37
+ this.schemaHost = this.moduleRef.get(graphql_1.GraphQLSchemaHost, { strict: false });
38
+ const { schema } = this.schemaHost;
39
+ if (!schema) {
40
+ return;
41
+ }
42
+ const queryType = schema.getQueryType();
43
+ if (queryType) {
44
+ this.registerObjectTypeFields(queryType, 'query');
45
+ }
46
+ const mutationType = schema.getMutationType();
47
+ if (mutationType) {
48
+ this.registerObjectTypeFields(mutationType, 'mutation');
49
+ }
50
+ }
51
+ /**
52
+ * Helper to parse the `@mcp` directive and its `instructions` argument from field AST.
53
+ */
54
+ getMcpDirectiveConfig(field) {
55
+ const directives = field.astNode?.directives;
56
+ if (!directives) {
57
+ return { isExposed: false };
58
+ }
59
+ const mcpDirective = directives.find((d) => d.name.value === 'mcp');
60
+ if (!mcpDirective) {
61
+ return { isExposed: false };
62
+ }
63
+ let instructions;
64
+ const arg = mcpDirective.arguments?.find((a) => a.name.value === 'instructions');
65
+ if (arg && arg.value.kind === 'StringValue') {
66
+ instructions = arg.value.value;
67
+ }
68
+ return { isExposed: true, instructions };
69
+ }
70
+ mapGraphQLTypeToZod(type) {
71
+ const isNonNull = (0, graphql_2.isNonNullType)(type);
72
+ const unwrappedType = isNonNull
73
+ ? type.ofType
74
+ : type;
75
+ let schema;
76
+ if ((0, graphql_2.isScalarType)(unwrappedType)) {
77
+ switch (unwrappedType.name) {
78
+ case 'Int':
79
+ case 'Float':
80
+ schema = zod_1.z.number();
81
+ break;
82
+ case 'Boolean':
83
+ schema = zod_1.z.boolean();
84
+ break;
85
+ default:
86
+ schema = zod_1.z.string();
87
+ }
88
+ }
89
+ else {
90
+ schema = zod_1.z.any();
91
+ }
92
+ return isNonNull ? schema : schema.optional();
93
+ }
94
+ buildSelectionSet(returnType) {
95
+ let namedType = returnType;
96
+ while ('ofType' in namedType) {
97
+ namedType = namedType.ofType;
98
+ }
99
+ if ((0, graphql_2.isObjectType)(namedType)) {
100
+ const fields = namedType.getFields();
101
+ const scalarFields = Object.keys(fields).filter((key) => {
102
+ let fType = fields[key].type;
103
+ while ('ofType' in fType) {
104
+ fType = fType.ofType;
105
+ }
106
+ return (0, graphql_2.isScalarType)(fType);
107
+ });
108
+ return scalarFields.join('\n');
109
+ }
110
+ return '';
111
+ }
112
+ getMcpServer() {
113
+ return this.mcpServer;
114
+ }
115
+ registerObjectTypeFields(objectType, operationType) {
116
+ const fields = objectType.getFields();
117
+ for (const [fieldName, field] of Object.entries(fields)) {
118
+ // Check for @mcp/@ExposeMcp directives and parse instructions
119
+ const mcpConfig = this.getMcpDirectiveConfig(field);
120
+ if (!mcpConfig.isExposed) {
121
+ continue;
122
+ }
123
+ const toolName = `${operationType}_${fieldName}`;
124
+ // Build an additive description (Base GraphQL + Agent Context)
125
+ const baseDescription = field.description || `Executes the ${fieldName} ${operationType}.`;
126
+ const finalDescription = mcpConfig.instructions
127
+ ? `${baseDescription}\n\n[Agent Context]: ${mcpConfig.instructions}`
128
+ : baseDescription;
129
+ // Build input schema
130
+ const zodShape = {};
131
+ for (const arg of field.args) {
132
+ zodShape[arg.name] = this.mapGraphQLTypeToZod(arg.type);
133
+ }
134
+ this.mcpServer.registerTool(toolName, {
135
+ title: fieldName,
136
+ description: finalDescription,
137
+ inputSchema: zodShape,
138
+ }, async (args, extra) => {
139
+ const selectionSet = this.buildSelectionSet(field.type);
140
+ const selectionBlock = selectionSet ? `{ ${selectionSet} }` : '';
141
+ const varDefs = field.args
142
+ .map((a) => `$${a.name}: ${a.type.toString()}`)
143
+ .join(', ');
144
+ const varArgs = field.args
145
+ .map((a) => `${a.name}: $${a.name}`)
146
+ .join(', ');
147
+ const queryString = `
148
+ ${operationType} ${fieldName}McpWrapper${varDefs ? `(${varDefs})` : ''} {
149
+ ${fieldName}${varArgs ? `(${varArgs})` : ''} ${selectionBlock}
150
+ }
151
+ `;
152
+ const req = extra?.request;
153
+ const authHeader = req?.headers?.['authorization'];
154
+ const result = await (0, graphql_2.graphql)({
155
+ schema: this.schemaHost.schema,
156
+ source: queryString,
157
+ variableValues: args,
158
+ contextValue: {
159
+ req: {
160
+ headers: { authorization: authHeader },
161
+ },
162
+ },
163
+ });
164
+ if (result.errors) {
165
+ return {
166
+ isError: true,
167
+ content: [
168
+ { type: 'text', text: JSON.stringify(result.errors, null, 2) },
169
+ ],
170
+ };
171
+ }
172
+ return {
173
+ content: [
174
+ {
175
+ type: 'text',
176
+ text: JSON.stringify(result.data?.[fieldName], null, 2),
177
+ },
178
+ ],
179
+ };
180
+ });
181
+ }
182
+ }
183
+ };
184
+ exports.McpService = McpService;
185
+ exports.McpService = McpService = __decorate([
186
+ (0, common_1.Injectable)(),
187
+ __param(1, (0, common_1.Inject)(constants_1.MCP_OPTIONS)),
188
+ __metadata("design:paramtypes", [core_1.ModuleRef, Object])
189
+ ], McpService);
@@ -0,0 +1,5 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { GraphqlMcpModuleOptions } from './types';
3
+ export declare class NestGraphqlMcpModule {
4
+ static forRoot(options?: GraphqlMcpModuleOptions): DynamicModule;
5
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var NestGraphqlMcpModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.NestGraphqlMcpModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const constants_1 = require("./constants");
13
+ const mcp_controller_1 = require("./mcp.controller");
14
+ const mcp_service_1 = require("./mcp.service");
15
+ let NestGraphqlMcpModule = NestGraphqlMcpModule_1 = class NestGraphqlMcpModule {
16
+ static forRoot(options = {}) {
17
+ return {
18
+ module: NestGraphqlMcpModule_1,
19
+ controllers: [mcp_controller_1.McpController],
20
+ providers: [
21
+ {
22
+ provide: constants_1.MCP_OPTIONS,
23
+ useValue: options,
24
+ },
25
+ mcp_service_1.McpService,
26
+ ],
27
+ exports: [mcp_service_1.McpService, constants_1.MCP_OPTIONS],
28
+ };
29
+ }
30
+ };
31
+ exports.NestGraphqlMcpModule = NestGraphqlMcpModule;
32
+ exports.NestGraphqlMcpModule = NestGraphqlMcpModule = NestGraphqlMcpModule_1 = __decorate([
33
+ (0, common_1.Module)({})
34
+ ], NestGraphqlMcpModule);
@@ -0,0 +1,6 @@
1
+ import { GraphQLDirective } from 'graphql';
2
+ export declare const MCP_OPTIONS: unique symbol;
3
+ /**
4
+ * GraphQL directive definition for exposing fields as MCP.
5
+ */
6
+ export declare const MCP_DIRECTIVE_DEFINITION: GraphQLDirective;