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/LICENSE +21 -0
- package/README.md +1247 -0
- package/bin/mcp-gov-proxy.js +362 -0
- package/bin/mcp-gov-unwrap.js +442 -0
- package/bin/mcp-gov-wrap.js +766 -0
- package/package.json +53 -0
- package/postinstall.js +24 -0
- package/src/index.js +199 -0
- package/src/operation-detector.js +67 -0
- package/src/operation-keywords.js +75 -0
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);
|