mcp-gov 1.0.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/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "mcp-gov",
3
+ "version": "1.0.0",
4
+ "description": "MCP Governance System - Permission control and audit logging for Model Context Protocol servers",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "mcp-gov-proxy": "bin/mcp-gov-proxy.js",
9
+ "mcp-gov-wrap": "bin/mcp-gov-wrap.js",
10
+ "mcp-gov-unwrap": "bin/mcp-gov-unwrap.js"
11
+ },
12
+ "scripts": {
13
+ "example:github": "node examples/github/server.js",
14
+ "test": "node test/proxy.test.js && node test/wrapper.test.js && node test/unwrap.test.js && node test/platform.test.js && node test/integration.test.js && node test/multi-service.test.js && node test/performance.test.js && node test/service-param.test.js",
15
+ "test:all": "npm test",
16
+ "test:proxy": "node test/proxy.test.js",
17
+ "test:wrapper": "node test/wrapper.test.js",
18
+ "test:unwrap": "node test/unwrap.test.js",
19
+ "test:platform": "node test/platform.test.js",
20
+ "test:integration": "node test/integration.test.js",
21
+ "test:multi-service": "node test/multi-service.test.js",
22
+ "test:performance": "node test/performance.test.js",
23
+ "test:service-param": "node test/service-param.test.js",
24
+ "postinstall": "node postinstall.js",
25
+ "publish:npm": "./scripts/publish.sh"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "src/",
30
+ "postinstall.js",
31
+ "README.md"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/amrhas82/mcp-gov.git"
36
+ },
37
+ "keywords": [
38
+ "mcp",
39
+ "governance",
40
+ "permissions",
41
+ "audit"
42
+ ],
43
+ "author": "amrhas82",
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^0.5.0",
47
+ "axios": "^1.6.0",
48
+ "dotenv": "^16.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.0.10"
52
+ }
53
+ }
package/postinstall.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ const colors = {
4
+ bright: '\x1b[1m',
5
+ cyan: '\x1b[36m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ reset: '\x1b[0m'
9
+ };
10
+
11
+ console.log('');
12
+ console.log(`${colors.green}==========================================`);
13
+ console.log(` mcp-gov installed successfully`);
14
+ console.log(`==========================================${colors.reset}`);
15
+ console.log('');
16
+ console.log(`${colors.bright}${colors.yellow}Quick Start:${colors.reset}`);
17
+ console.log('');
18
+ console.log(` ${colors.cyan}mcp-gov-wrap${colors.reset} Add governance to claude_desktop_config.json`);
19
+ console.log(` ${colors.cyan}mcp-gov-unwrap${colors.reset} Remove governance`);
20
+ console.log('');
21
+ console.log(`${colors.bright}Docs:${colors.reset} https://github.com/amrhas82/mcp-gov`);
22
+ console.log('');
23
+ console.log(`${colors.green}==========================================${colors.reset}`);
24
+ console.log('');
package/src/index.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * GovernedMCPServer - MCP Server with permission control and audit logging.
3
+ * Middleware wrapper that intercepts tool registration to inject governance.
4
+ */
5
+
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { parseToolName } from './operation-detector.js';
10
+
11
+ /**
12
+ * @typedef {Object} ServerConfig
13
+ * @property {string} name - Server name
14
+ * @property {string} version - Server version
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object.<string, Object.<string, 'allow'|'deny'>>} PermissionRules
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} ToolDefinition
23
+ * @property {string} name - Tool name
24
+ * @property {string} description - Tool description
25
+ * @property {Object} inputSchema - JSON Schema for input
26
+ */
27
+
28
+ /**
29
+ * @typedef {function(any): Promise<Object>} ToolHandler
30
+ */
31
+
32
+ /**
33
+ * MCP Server with governance layer for permission control and audit logging.
34
+ */
35
+ export class GovernedMCPServer {
36
+ /**
37
+ * @param {ServerConfig} config - Server configuration {name, version}
38
+ * @param {PermissionRules} rules - Permission rules {serviceName: {operation: 'allow'|'deny'}}
39
+ */
40
+ constructor(config, rules = {}) {
41
+ this.config = config;
42
+ this.rules = rules;
43
+ this.tools = new Map(); // Store tool definitions and handlers
44
+ this.server = new Server(
45
+ {
46
+ name: config.name,
47
+ version: config.version
48
+ },
49
+ {
50
+ capabilities: {
51
+ tools: {}
52
+ }
53
+ }
54
+ );
55
+
56
+ // Set up tool handlers
57
+ this._setupHandlers();
58
+ }
59
+
60
+ /**
61
+ * Set up MCP protocol handlers for listing and calling tools.
62
+ * @private
63
+ */
64
+ _setupHandlers() {
65
+ // Handle tools/list request
66
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
67
+ return {
68
+ tools: Array.from(this.tools.values()).map(t => ({
69
+ name: t.definition.name,
70
+ description: t.definition.description,
71
+ inputSchema: t.definition.inputSchema
72
+ }))
73
+ };
74
+ });
75
+
76
+ // Handle tools/call request
77
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
78
+ const toolName = request.params.name;
79
+ const args = request.params.arguments || {};
80
+
81
+ const tool = this.tools.get(toolName);
82
+ if (!tool) {
83
+ return {
84
+ content: [
85
+ {
86
+ type: 'text',
87
+ text: `Unknown tool: ${toolName}`
88
+ }
89
+ ],
90
+ isError: true
91
+ };
92
+ }
93
+
94
+ // Check permission
95
+ const allowed = this.checkPermission(toolName);
96
+
97
+ if (!allowed) {
98
+ this.logOperation(toolName, args, 'denied', 'Permission denied by governance rules');
99
+
100
+ return {
101
+ content: [
102
+ {
103
+ type: 'text',
104
+ text: `Permission denied: ${toolName} is not allowed by governance policy`
105
+ }
106
+ ],
107
+ isError: true
108
+ };
109
+ }
110
+
111
+ this.logOperation(toolName, args, 'allowed');
112
+
113
+ try {
114
+ const result = await tool.handler(args);
115
+ this.logOperation(toolName, args, 'success');
116
+ return result;
117
+ } catch (error) {
118
+ const errorMessage = error instanceof Error ? error.message : String(error);
119
+ this.logOperation(toolName, args, 'error', errorMessage);
120
+ return {
121
+ content: [
122
+ {
123
+ type: 'text',
124
+ text: `Error: ${errorMessage}`
125
+ }
126
+ ],
127
+ isError: true
128
+ };
129
+ }
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Check if a tool operation is permitted by rules.
135
+ * @param {string} toolName - Tool name to check
136
+ * @returns {boolean} True if allowed, false if denied
137
+ */
138
+ checkPermission(toolName) {
139
+ const { service, operation } = parseToolName(toolName);
140
+
141
+ // Check if service has rules
142
+ if (!this.rules[service]) {
143
+ // Default to 'allow' if no rule exists (permissive for POC)
144
+ return true;
145
+ }
146
+
147
+ // Check operation permission
148
+ const permission = this.rules[service][operation];
149
+ if (permission === 'deny') {
150
+ return false;
151
+ }
152
+
153
+ // Default to allow
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Log operation to stderr as structured JSON.
159
+ * @param {string} tool - Tool name
160
+ * @param {Object|string} args - Arguments (will be truncated to 200 chars)
161
+ * @param {string} status - Status: 'allowed', 'denied', 'success', 'error'
162
+ * @param {string} detail - Optional detail message
163
+ */
164
+ logOperation(tool, args, status, detail = '') {
165
+ const argsStr = typeof args === 'string' ? args : JSON.stringify(args);
166
+ const truncatedArgs = argsStr.length > 200 ? argsStr.substring(0, 200) + '...' : argsStr;
167
+
168
+ const logEntry = {
169
+ timestamp: new Date().toISOString(),
170
+ tool,
171
+ args: truncatedArgs,
172
+ status,
173
+ detail
174
+ };
175
+
176
+ console.error(JSON.stringify(logEntry));
177
+ }
178
+
179
+ /**
180
+ * Register a tool with governance wrapper.
181
+ * @param {ToolDefinition} toolDef - Tool definition {name, description, inputSchema}
182
+ * @param {ToolHandler} handler - Async handler function
183
+ */
184
+ registerTool(toolDef, handler) {
185
+ this.tools.set(toolDef.name, {
186
+ definition: toolDef,
187
+ handler
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Start the MCP server with stdio transport.
193
+ */
194
+ async start() {
195
+ const transport = new StdioServerTransport();
196
+ await this.server.connect(transport);
197
+ console.error('MCP Server started:', this.config.name, 'v' + this.config.version);
198
+ }
199
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Operation detection logic for MCP tool names.
3
+ * Analyzes tool names to determine operation type (admin/delete/execute/write/read).
4
+ */
5
+
6
+ import { OPERATION_KEYWORDS } from './operation-keywords.js';
7
+
8
+ /**
9
+ * @typedef {'admin'|'delete'|'execute'|'write'|'read'} OperationType
10
+ */
11
+
12
+ /**
13
+ * Extract service name from tool name prefix (e.g., "github_list_repos" → "github")
14
+ * @param {string} toolName - Full tool name
15
+ * @returns {string} Service name or "unknown"
16
+ */
17
+ export function extractService(toolName) {
18
+ if (!toolName || typeof toolName !== 'string') {
19
+ return 'unknown';
20
+ }
21
+
22
+ // Split by underscore and take first segment as service name
23
+ const parts = toolName.split('_');
24
+ return parts.length > 1 ? parts[0] : 'unknown';
25
+ }
26
+
27
+ /**
28
+ * Detect operation type from tool name using priority-based keyword matching.
29
+ * Priority order: admin → delete → execute → write → read
30
+ * @param {string} toolName - Tool name to analyze
31
+ * @returns {OperationType} Operation type: 'admin', 'delete', 'execute', 'write', 'read', or 'write' (default)
32
+ */
33
+ export function detectOperation(toolName) {
34
+ if (!toolName || typeof toolName !== 'string') {
35
+ return 'write'; // Conservative default
36
+ }
37
+
38
+ const lowerName = toolName.toLowerCase();
39
+
40
+ // Check keywords in priority order
41
+ /** @type {OperationType[]} */
42
+ const operationTypes = ['admin', 'delete', 'execute', 'write', 'read'];
43
+
44
+ for (const opType of operationTypes) {
45
+ const keywords = /** @type {Record<OperationType, string[]>} */ (OPERATION_KEYWORDS)[opType];
46
+ for (const keyword of keywords) {
47
+ if (lowerName.includes(keyword)) {
48
+ return opType;
49
+ }
50
+ }
51
+ }
52
+
53
+ // Default to 'write' if no match (conservative)
54
+ return 'write';
55
+ }
56
+
57
+ /**
58
+ * Parse tool name into service and operation components.
59
+ * @param {string} toolName - Full tool name
60
+ * @returns {{service: string, operation: OperationType}} Parsed components
61
+ */
62
+ export function parseToolName(toolName) {
63
+ return {
64
+ service: extractService(toolName),
65
+ operation: detectOperation(toolName)
66
+ };
67
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Exhaustive keyword mappings for operation detection.
3
+ * Priority order: admin → delete → execute → write → read
4
+ */
5
+
6
+ export const OPERATION_KEYWORDS = {
7
+ // ADMIN operations - highest priority (system control)
8
+ admin: [
9
+ 'admin', 'superuser', 'root', 'sudo', 'elevate', 'privilege',
10
+ 'grant', 'revoke', 'permission', 'role', 'access', 'policy',
11
+ 'configure', 'config', 'setting', 'preference', 'setup',
12
+ 'initialize', 'init', 'bootstrap', 'install', 'uninstall',
13
+ 'enable', 'disable', 'activate', 'deactivate', 'toggle',
14
+ 'migrate', 'migration', 'backup', 'restore', 'export', 'import',
15
+ 'deploy', 'deployment', 'provision', 'manage', 'management'
16
+ ],
17
+
18
+ // DELETE operations - second priority (destructive)
19
+ delete: [
20
+ 'delete', 'remove', 'destroy', 'erase', 'clear', 'purge',
21
+ 'drop', 'truncate', 'unlink', 'discard', 'revoke', 'cancel',
22
+ 'terminate', 'kill', 'stop', 'abort', 'close', 'shutdown',
23
+ 'deactivate', 'disable', 'detach', 'disconnect', 'unpublish',
24
+ 'archive', 'trash', 'clean', 'wipe', 'reset', 'revert'
25
+ ],
26
+
27
+ // EXECUTE operations - third priority (action/mutation)
28
+ execute: [
29
+ 'execute', 'run', 'invoke', 'call', 'trigger', 'fire',
30
+ 'launch', 'start', 'begin', 'process', 'perform', 'apply',
31
+ 'send', 'submit', 'post', 'publish', 'deploy', 'release',
32
+ 'build', 'compile', 'generate', 'compute', 'calculate',
33
+ 'merge', 'rebase', 'commit', 'push', 'pull', 'sync',
34
+ 'approve', 'reject', 'accept', 'decline', 'confirm',
35
+ 'schedule', 'queue', 'enqueue', 'dispatch', 'broadcast',
36
+ 'notify', 'alert', 'trigger', 'activate', 'deactivate',
37
+ 'lock', 'unlock', 'freeze', 'unfreeze', 'suspend', 'resume'
38
+ ],
39
+
40
+ // WRITE operations - fourth priority (creation/modification)
41
+ write: [
42
+ 'create', 'add', 'insert', 'new', 'make', 'build',
43
+ 'write', 'save', 'store', 'persist', 'record',
44
+ 'update', 'modify', 'edit', 'change', 'alter', 'set',
45
+ 'put', 'patch', 'replace', 'overwrite', 'append',
46
+ 'upload', 'push', 'commit', 'submit', 'publish',
47
+ 'move', 'rename', 'copy', 'duplicate', 'clone',
48
+ 'attach', 'link', 'associate', 'bind', 'connect',
49
+ 'assign', 'allocate', 'register', 'enroll', 'subscribe',
50
+ 'comment', 'reply', 'respond', 'post', 'share',
51
+ 'transfer', 'migrate', 'convert', 'transform',
52
+ 'fork', 'branch', 'tag', 'label', 'mark',
53
+ 'star', 'favorite', 'like', 'follow', 'watch',
54
+ 'open', 'reopen', 'draft', 'issue', 'pr', 'pull_request'
55
+ ],
56
+
57
+ // READ operations - lowest priority (safe/non-mutating)
58
+ read: [
59
+ 'get', 'list', 'fetch', 'retrieve', 'query', 'find',
60
+ 'search', 'lookup', 'check', 'verify', 'validate',
61
+ 'read', 'view', 'show', 'display', 'print', 'render',
62
+ 'describe', 'info', 'detail', 'summary', 'status',
63
+ 'count', 'total', 'aggregate', 'stats', 'statistics',
64
+ 'download', 'export', 'extract', 'parse', 'decode',
65
+ 'inspect', 'audit', 'log', 'trace', 'monitor',
66
+ 'compare', 'diff', 'match', 'filter', 'sort',
67
+ 'scan', 'analyze', 'review', 'preview', 'browse',
68
+ 'watch', 'observe', 'listen', 'subscribe', 'poll',
69
+ 'test', 'ping', 'health', 'heartbeat', 'check'
70
+ ]
71
+ };
72
+
73
+ // Total keyword count for reference
74
+ export const TOTAL_KEYWORDS = Object.values(OPERATION_KEYWORDS)
75
+ .reduce((sum, keywords) => sum + keywords.length, 0);