@marchward/mcp-server 0.2.2

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/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Marchward MCP Server
2
+
3
+ Govern what an AI coding agent is allowed to **do**, from inside the agent. This is an
4
+ [MCP](https://modelcontextprotocol.io) server that exposes [Marchward](https://marchward.ai) governance
5
+ operations as tools, so an agent (Claude, Cursor, Copilot, Windsurf, etc.) can check authorization
6
+ before it acts, run actions through a credential-mediated gateway, and read its own tamper-evident
7
+ audit trail.
8
+
9
+ Apache-2.0. The agent holds only a Marchward API key; the real downstream credentials are injected
10
+ server-side, never by the agent.
11
+
12
+ ## Tools
13
+
14
+ | Tool | Purpose |
15
+ |------|---------|
16
+ | `marchward_authorize` | Check whether an action is allowed before the agent takes it (ALLOW / BLOCK / ESCALATE / ALLOW_WITH_CONDITIONS). |
17
+ | `marchward_execute` | Run an action through Marchward's credential-mediated gateway. |
18
+ | `marchward_register_agent` | Register a new agent so it is governed from its first action. |
19
+ | `marchward_list_agents` | List registered agents and their bound policies. |
20
+ | `marchward_get_decisions` | Read the governance decision audit trail. |
21
+ | `marchward_check_coverage` | Find governance gaps between declared and actually-called tools. |
22
+ | `marchward_bind_policy` | Attach a governance policy to an agent. |
23
+
24
+ ## Quick start
25
+
26
+ You need a Marchward API key (`mw_...`), free at [marchward.ai](https://marchward.ai).
27
+
28
+ ### stdio (Claude Code, Cursor, local)
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "marchward": {
34
+ "command": "npx",
35
+ "args": ["-y", "@marchward/mcp-server"],
36
+ "env": {
37
+ "MARCHWARD_API_URL": "https://api.marchward.ai",
38
+ "MARCHWARD_API_KEY": "mw_your_key_here"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Streamable HTTP (remote)
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "marchward": {
51
+ "type": "url",
52
+ "url": "https://mcp.marchward.ai/mcp",
53
+ "headers": { "Authorization": "Bearer mw_your_key_here" }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Environment variables
60
+
61
+ | Variable | Required | Default | Description |
62
+ |----------|----------|---------|-------------|
63
+ | `MARCHWARD_API_URL` | yes | — | Marchward API base URL (`https://api.marchward.ai`) |
64
+ | `MARCHWARD_API_KEY` | yes (stdio) | — | Your Marchward API key (`mw_...`) |
65
+ | `TRANSPORT` | no | `stdio` | `stdio` or `http` |
66
+ | `PORT` | no | `3100` | HTTP server port (http transport only) |
67
+
68
+ ## Run from source
69
+
70
+ ```bash
71
+ npm install
72
+ npm run build
73
+ MARCHWARD_API_URL=https://api.marchward.ai MARCHWARD_API_KEY=mw_... npm start
74
+ ```
75
+
76
+ ## How it fits
77
+
78
+ This server is the agent-facing surface of the Marchward runtime authority. The open governance engine
79
+ and proxy live at [github.com/marchward/marchward](https://github.com/marchward/marchward); the hosted
80
+ control plane (credential vault, audit-as-a-service, approval workflow) is at
81
+ [marchward.ai](https://marchward.ai).
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Lightweight Marchward API client for the MCP server.
3
+ *
4
+ * We don't import the full SDK to keep dependencies minimal.
5
+ * This makes HTTP calls directly to the Marchward API.
6
+ */
7
+ export interface MarchwardConfig {
8
+ apiUrl: string;
9
+ apiKey: string;
10
+ timeout?: number;
11
+ }
12
+ export declare class MarchwardAPIClient {
13
+ private apiUrl;
14
+ private apiKey;
15
+ private timeout;
16
+ constructor(config: MarchwardConfig);
17
+ private request;
18
+ authorize(input: {
19
+ toolName: string;
20
+ arguments?: Record<string, unknown>;
21
+ policyBundleId?: string;
22
+ agentId?: string;
23
+ mode?: string;
24
+ context?: Record<string, unknown>;
25
+ signals?: Record<string, unknown>;
26
+ }): Promise<Record<string, unknown>>;
27
+ execute(input: {
28
+ toolName: string;
29
+ arguments?: Record<string, unknown>;
30
+ policyBundleId: string;
31
+ agentId: string;
32
+ service: string;
33
+ downstream: {
34
+ url: string;
35
+ method: string;
36
+ headers?: Record<string, string>;
37
+ body?: unknown;
38
+ };
39
+ mode?: string;
40
+ }): Promise<Record<string, unknown>>;
41
+ createAgent(input: {
42
+ name: string;
43
+ description?: string;
44
+ agentId?: string;
45
+ }): Promise<Record<string, unknown>>;
46
+ listAgents(): Promise<Record<string, unknown>>;
47
+ getAgent(agentId: string): Promise<Record<string, unknown>>;
48
+ bindPolicy(agentId: string, policyBundleId: string): Promise<Record<string, unknown>>;
49
+ listDecisions(query?: {
50
+ agentId?: string;
51
+ toolName?: string;
52
+ result?: string;
53
+ limit?: number;
54
+ offset?: number;
55
+ }): Promise<Record<string, unknown>>;
56
+ checkCoverage(agentId: string): Promise<{
57
+ agentId: string;
58
+ totalDeclaredTools: any;
59
+ totalObservedTools: number;
60
+ coveragePercent: number;
61
+ coveredTools: any;
62
+ uncoveredTools: any;
63
+ undeclaredButObserved: string[];
64
+ totalDecisionsAnalyzed: any;
65
+ summary: string;
66
+ }>;
67
+ health(): Promise<Record<string, unknown>>;
68
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Lightweight Marchward API client for the MCP server.
3
+ *
4
+ * We don't import the full SDK to keep dependencies minimal.
5
+ * This makes HTTP calls directly to the Marchward API.
6
+ */
7
+ export class MarchwardAPIClient {
8
+ apiUrl;
9
+ apiKey;
10
+ timeout;
11
+ constructor(config) {
12
+ this.apiUrl = config.apiUrl.replace(/\/$/, "");
13
+ this.apiKey = config.apiKey;
14
+ this.timeout = config.timeout ?? 10_000;
15
+ }
16
+ async request(method, path, body) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), this.timeout);
19
+ try {
20
+ const res = await fetch(`${this.apiUrl}${path}`, {
21
+ method,
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ Authorization: `Bearer ${this.apiKey}`,
25
+ },
26
+ body: body ? JSON.stringify(body) : undefined,
27
+ signal: controller.signal,
28
+ });
29
+ const data = await res.json();
30
+ if (!res.ok) {
31
+ throw new Error(`Marchward API ${method} ${path} returned ${res.status}: ${JSON.stringify(data)}`);
32
+ }
33
+ return data;
34
+ }
35
+ finally {
36
+ clearTimeout(timer);
37
+ }
38
+ }
39
+ // ─── Core Governance ──────────────────────────────────────────
40
+ async authorize(input) {
41
+ return this.request("POST", "/v1/authorize", {
42
+ toolCall: {
43
+ toolName: input.toolName,
44
+ arguments: input.arguments ?? {},
45
+ },
46
+ // Only include policyBundle if explicitly provided — otherwise let
47
+ // the API auto-resolve from the API key → agent → policy chain.
48
+ policyBundle: input.policyBundleId ? { id: input.policyBundleId } : undefined,
49
+ agent: input.agentId ? { agentId: input.agentId } : undefined,
50
+ mode: input.mode,
51
+ context: input.context,
52
+ signals: input.signals,
53
+ });
54
+ }
55
+ async execute(input) {
56
+ return this.request("POST", "/v1/execute", {
57
+ toolCall: {
58
+ toolName: input.toolName,
59
+ arguments: input.arguments ?? {},
60
+ },
61
+ policyBundle: { id: input.policyBundleId },
62
+ agent: { agentId: input.agentId },
63
+ service: input.service,
64
+ downstream: input.downstream,
65
+ mode: input.mode,
66
+ });
67
+ }
68
+ // ─── Agent Registry ───────────────────────────────────────────
69
+ async createAgent(input) {
70
+ return this.request("POST", "/v1/agents", input);
71
+ }
72
+ async listAgents() {
73
+ return this.request("GET", "/v1/agents");
74
+ }
75
+ async getAgent(agentId) {
76
+ return this.request("GET", `/v1/agents/${agentId}`);
77
+ }
78
+ async bindPolicy(agentId, policyBundleId) {
79
+ return this.request("POST", `/v1/agents/${agentId}/bind-policy`, { policyBundleId });
80
+ }
81
+ // ─── Decisions ────────────────────────────────────────────────
82
+ async listDecisions(query) {
83
+ const params = new URLSearchParams();
84
+ if (query?.agentId)
85
+ params.set("agentId", query.agentId);
86
+ if (query?.toolName)
87
+ params.set("toolName", query.toolName);
88
+ if (query?.result)
89
+ params.set("result", query.result);
90
+ if (query?.limit)
91
+ params.set("limit", String(query.limit));
92
+ if (query?.offset)
93
+ params.set("offset", String(query.offset));
94
+ const qs = params.toString();
95
+ return this.request("GET", `/v1/decisions${qs ? `?${qs}` : ""}`);
96
+ }
97
+ // ─── Coverage Check ───────────────────────────────────────────
98
+ async checkCoverage(agentId) {
99
+ // Get agent's registered tools
100
+ const agent = await this.getAgent(agentId);
101
+ const registeredTools = agent.tools ?? [];
102
+ // Get recent decisions for this agent
103
+ const decisions = await this.listDecisions({
104
+ agentId,
105
+ limit: 200,
106
+ });
107
+ const decisionList = decisions.decisions ?? [];
108
+ // Find unique tool names that have produced decisions
109
+ const observedTools = new Set();
110
+ for (const d of decisionList) {
111
+ if (d.toolName)
112
+ observedTools.add(d.toolName);
113
+ }
114
+ // Compare declared vs observed
115
+ const declaredToolNames = registeredTools.map((t) => t.name ?? t.toolName ?? t);
116
+ const covered = declaredToolNames.filter((t) => observedTools.has(t));
117
+ const uncovered = declaredToolNames.filter((t) => !observedTools.has(t));
118
+ const undeclaredButObserved = [...observedTools].filter((t) => !declaredToolNames.includes(t));
119
+ const totalDeclared = declaredToolNames.length;
120
+ const coveragePercent = totalDeclared > 0
121
+ ? Math.round((covered.length / totalDeclared) * 100)
122
+ : 0;
123
+ return {
124
+ agentId,
125
+ totalDeclaredTools: totalDeclared,
126
+ totalObservedTools: observedTools.size,
127
+ coveragePercent,
128
+ coveredTools: covered,
129
+ uncoveredTools: uncovered,
130
+ undeclaredButObserved,
131
+ totalDecisionsAnalyzed: decisionList.length,
132
+ summary: totalDeclared === 0
133
+ ? `Agent "${agentId}" has no declared tools. Cannot calculate coverage.`
134
+ : coveragePercent === 100
135
+ ? `Agent "${agentId}" has 100% governance coverage (${totalDeclared}/${totalDeclared} tools producing decisions).`
136
+ : `Agent "${agentId}" has ${coveragePercent}% governance coverage (${covered.length}/${totalDeclared} tools). Missing: ${uncovered.join(", ")}`,
137
+ };
138
+ }
139
+ // ─── Health ───────────────────────────────────────────────────
140
+ async health() {
141
+ return this.request("GET", "/health");
142
+ }
143
+ }
144
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Marchward MCP Server
4
+ *
5
+ * Exposes Marchward governance operations as MCP tools for AI coding agents.
6
+ * Supports two transports:
7
+ * - stdio: Single-user, local. API key from MARCHWARD_API_KEY env var.
8
+ * - http: Multi-tenant shared service. Each user passes their own
9
+ * API key via the Authorization header in their MCP config.
10
+ * The server holds NO credentials — it's a stateless proxy.
11
+ *
12
+ * Environment variables (TENET_* names still read as a fallback for back-compat):
13
+ * MARCHWARD_API_URL — Marchward API base URL (required)
14
+ * MARCHWARD_API_KEY — Marchward API key (required for stdio only)
15
+ * PORT — HTTP server port (default: 3100, http transport only)
16
+ * TRANSPORT — "stdio" or "http" (default: "stdio")
17
+ */
18
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Marchward MCP Server
4
+ *
5
+ * Exposes Marchward governance operations as MCP tools for AI coding agents.
6
+ * Supports two transports:
7
+ * - stdio: Single-user, local. API key from MARCHWARD_API_KEY env var.
8
+ * - http: Multi-tenant shared service. Each user passes their own
9
+ * API key via the Authorization header in their MCP config.
10
+ * The server holds NO credentials — it's a stateless proxy.
11
+ *
12
+ * Environment variables (TENET_* names still read as a fallback for back-compat):
13
+ * MARCHWARD_API_URL — Marchward API base URL (required)
14
+ * MARCHWARD_API_KEY — Marchward API key (required for stdio only)
15
+ * PORT — HTTP server port (default: 3100, http transport only)
16
+ * TRANSPORT — "stdio" or "http" (default: "stdio")
17
+ */
18
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
21
+ import express from "express";
22
+ import { randomUUID } from "node:crypto";
23
+ import { MarchwardAPIClient } from "./api-client.js";
24
+ import { TOOL_DEFINITIONS, createToolHandlers } from "./tools.js";
25
+ // ─── Configuration ──────────────────────────────────────────────
26
+ const MARCHWARD_API_URL = (process.env.MARCHWARD_API_URL ?? process.env.TENET_API_URL);
27
+ const MARCHWARD_API_KEY = (process.env.MARCHWARD_API_KEY ?? process.env.TENET_API_KEY);
28
+ const PORT = parseInt(process.env.PORT ?? "3100", 10);
29
+ const TRANSPORT = process.env.TRANSPORT ?? "stdio";
30
+ // ─── Build Server ───────────────────────────────────────────────
31
+ /**
32
+ * Create an MCP server wired to a specific Marchward API client.
33
+ * For stdio: one client per process (key from env).
34
+ * For HTTP: one client per session (key from request headers).
35
+ */
36
+ function createMcpServer(apiKey) {
37
+ if (!MARCHWARD_API_URL) {
38
+ throw new Error("MARCHWARD_API_URL environment variable is required");
39
+ }
40
+ const server = new McpServer({
41
+ name: "marchward",
42
+ version: "0.2.1",
43
+ });
44
+ const client = new MarchwardAPIClient({
45
+ apiUrl: MARCHWARD_API_URL,
46
+ apiKey,
47
+ });
48
+ const handlers = createToolHandlers(client);
49
+ // Register each tool with the MCP server
50
+ for (const [key, def] of Object.entries(TOOL_DEFINITIONS)) {
51
+ const handlerFn = handlers[key];
52
+ if (handlerFn) {
53
+ server.tool(def.name, def.description, def.inputSchema.shape, handlerFn);
54
+ }
55
+ }
56
+ return server;
57
+ }
58
+ // ─── stdio Transport (single-user, local) ───────────────────────
59
+ async function runStdio() {
60
+ if (!MARCHWARD_API_KEY) {
61
+ throw new Error("MARCHWARD_API_KEY environment variable is required for stdio transport");
62
+ }
63
+ const server = createMcpServer(MARCHWARD_API_KEY);
64
+ const transport = new StdioServerTransport();
65
+ await server.connect(transport);
66
+ console.error("[marchward-mcp] Running on stdio transport");
67
+ }
68
+ // ─── HTTP Transport (multi-tenant, shared service) ──────────────
69
+ function extractApiKey(req) {
70
+ // 1. Check Authorization header (standard MCP auth)
71
+ const auth = req.headers["authorization"];
72
+ if (auth) {
73
+ if (auth.startsWith("Bearer ")) {
74
+ return auth.slice(7);
75
+ }
76
+ if (auth.startsWith("mw_") || auth.startsWith("tnt_")) {
77
+ return auth;
78
+ }
79
+ }
80
+ // 2. Check URL query parameter (for clients like Claude's custom connector
81
+ // that don't support static Authorization headers — they use OAuth or authless)
82
+ const queryKey = req.query.key;
83
+ if (queryKey?.startsWith("mw_") || queryKey?.startsWith("tnt_")) {
84
+ return queryKey;
85
+ }
86
+ // 3. Fall back to environment variable (single-tenant deployment mode)
87
+ if (MARCHWARD_API_KEY) {
88
+ return MARCHWARD_API_KEY;
89
+ }
90
+ return null;
91
+ }
92
+ async function runHttp() {
93
+ if (!MARCHWARD_API_URL) {
94
+ throw new Error("MARCHWARD_API_URL environment variable is required");
95
+ }
96
+ const app = express();
97
+ app.use(express.json());
98
+ // Request logging for debugging connector issues
99
+ app.use((req, _res, next) => {
100
+ const hasAuth = !!req.headers["authorization"];
101
+ const hasQueryKey = !!req.query.key;
102
+ const hasEnvKey = !!MARCHWARD_API_KEY;
103
+ console.log(`[marchward-mcp] ${req.method} ${req.path} | auth-header: ${hasAuth} | query-key: ${hasQueryKey} | env-fallback: ${hasEnvKey}`);
104
+ next();
105
+ });
106
+ // Track active sessions (transport + the API key they authenticated with)
107
+ const sessions = new Map();
108
+ app.post("/mcp", async (req, res) => {
109
+ const sessionId = req.headers["mcp-session-id"];
110
+ // ── Existing session ──────────────────────────────────────
111
+ if (sessionId && sessions.has(sessionId)) {
112
+ const entry = sessions.get(sessionId);
113
+ await entry.transport.handleRequest(req, res, req.body);
114
+ return;
115
+ }
116
+ // ── New session — extract user's API key from headers ─────
117
+ const apiKey = extractApiKey(req);
118
+ if (!apiKey) {
119
+ res.status(401).json({
120
+ error: "Missing Authorization header. Include your Marchward API key as: Authorization: Bearer mw_your_key",
121
+ });
122
+ return;
123
+ }
124
+ // Create a server + client scoped to this user's API key
125
+ const server = createMcpServer(apiKey);
126
+ const transport = new StreamableHTTPServerTransport({
127
+ sessionIdGenerator: () => randomUUID(),
128
+ });
129
+ transport.onclose = () => {
130
+ const sid = transport.sessionId;
131
+ if (sid)
132
+ sessions.delete(sid);
133
+ };
134
+ await server.connect(transport);
135
+ // Handle the initial request
136
+ await transport.handleRequest(req, res, req.body);
137
+ // Store session for subsequent requests
138
+ if (transport.sessionId) {
139
+ sessions.set(transport.sessionId, { transport, apiKey });
140
+ }
141
+ });
142
+ // Handle GET for SSE streams
143
+ app.get("/mcp", async (req, res) => {
144
+ const sessionId = req.headers["mcp-session-id"];
145
+ if (!sessionId || !sessions.has(sessionId)) {
146
+ res.status(400).json({ error: "Invalid or missing session ID" });
147
+ return;
148
+ }
149
+ const entry = sessions.get(sessionId);
150
+ await entry.transport.handleRequest(req, res);
151
+ });
152
+ // Handle DELETE for session cleanup
153
+ app.delete("/mcp", async (req, res) => {
154
+ const sessionId = req.headers["mcp-session-id"];
155
+ if (sessionId && sessions.has(sessionId)) {
156
+ const entry = sessions.get(sessionId);
157
+ await entry.transport.close();
158
+ sessions.delete(sessionId);
159
+ }
160
+ res.status(200).json({ ok: true });
161
+ });
162
+ // Health check (no auth required)
163
+ app.get("/health", (_req, res) => {
164
+ res.json({
165
+ status: "ok",
166
+ server: "marchward-mcp-server",
167
+ version: "0.2.1",
168
+ transport: "streamable-http",
169
+ activeSessions: sessions.size,
170
+ marchwardApiUrl: MARCHWARD_API_URL,
171
+ });
172
+ });
173
+ app.listen(PORT, () => {
174
+ console.log(`[marchward-mcp] HTTP server listening on port ${PORT}`);
175
+ console.log(`[marchward-mcp] MCP endpoint: http://localhost:${PORT}/mcp`);
176
+ console.log(`[marchward-mcp] Health check: http://localhost:${PORT}/health`);
177
+ console.log(`[marchward-mcp] Marchward API: ${MARCHWARD_API_URL}`);
178
+ console.log(`[marchward-mcp] Mode: multi-tenant (API key per session from Authorization header)`);
179
+ });
180
+ }
181
+ // ─── Entry Point ────────────────────────────────────────────────
182
+ async function main() {
183
+ try {
184
+ if (TRANSPORT === "http") {
185
+ await runHttp();
186
+ }
187
+ else {
188
+ await runStdio();
189
+ }
190
+ }
191
+ catch (err) {
192
+ console.error("[marchward-mcp] Fatal error:", err);
193
+ process.exit(1);
194
+ }
195
+ }
196
+ main();
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}