Skip to main content
Glama
lebobo88

DeepSeek Pi MCP

by lebobo88
README.md
# DeepSeek Pi MCP

An MCP server that gives local AI agents access to DeepSeek V4 through Pi's coding-agent SDK and provider layer.

## Setup

Install dependencies and build:

```powershell
npm install
npm run build
```

Store a DeepSeek API key using Pi's credential flow (`/login` in Pi, selecting DeepSeek). The server intentionally reads Pi's stored `deepseek` credential and does not accept keys from MCP callers.

Configure approved workspaces as a JSON array of existing directories:

```powershell
$env:DEEPSEEK_MCP_WORKSPACE_ROOTS = '["C:\\work\\projects"]'
```

If `DEEPSEEK_MCP_WORKSPACE_ROOTS` is unset, the server defaults to its own working directory at launch. For a global install (e.g. registered once at user scope in Claude Code), leave it unset — each MCP client spawns the server with `cwd` set to the calling project's directory, so the allowed workspace tracks whichever project invoked it.

Start the stdio server:

```powershell
npm start
```

An MCP client can launch the built entrypoint directly. For example:

```json
{
  "mcpServers": {
    "deepseek-pi": {
      "command": "node",
      "args": ["C:\\path\\to\\deepseek-pi-mcp\\dist\\index.js"],
      "env": {
        "DEEPSEEK_MCP_WORKSPACE_ROOTS": "[\"C:\\\\work\\\\projects\"]"
      }
    }
  }
}
```

For a custom Pi data directory, set `PI_AGENT_DIR` to the directory containing `auth.json` and `models.json`.

### Governance settings

| Variable | Effect |
| --- | --- |
| `DEEPSEEK_MCP_REQUIRE_POLICY` | When truthy, `start_coding_task` refuses any call that omits `policy`. Omitting the field is then not a way back to full authority. |
| `DEEPSEEK_MCP_POLICY_FLOOR` | A JSON policy merged over every caller policy. The result is never weaker than the floor. |
| `DEEPSEEK_MCP_POLICY_FLOOR_FILE` | Path to the same JSON. Takes precedence over the inline variable. |

Both are read once at startup, and the floor's provenance is reported so a caller can tell an absent floor from a configured one.

## Task policies

`start_coding_task` accepts a `policy` describing what the Pi agent may do. The server merges the administrator floor over it and returns the enforced result as `policy` on every snapshot, alongside `toolCalls` — a ledger of every allowed and blocked call with its reason.

```jsonc
{
  "tools": ["read", "write", "edit", "grep", "find", "ls", "bash"],
  "excludeTools": [],
  "allowPaths": ["src/**"],          // optional; a path must match one
  "denyPaths": [".claude/**"],       // always beats allowPaths
  "confineToWorkspace": true,        // every path argument must resolve inside the workspace
  "bash": { "mode": "allowlist", "allow": ["^npm test$"], "deny": [] },
  "appendSystemPrompt": "…caller's contract text…",
  "maxToolCalls": 200,
  "timeoutMs": 600000
}
```

Enforcement happens in a `tool_call` gate that runs after Pi validates the arguments and before the tool executes; a denial reaches the model as a normal tool error carrying the reason, so the agent can react to it rather than silently believing it succeeded. Paths are resolved against the workspace and canonicalized over their longest existing prefix, so `..` traversal, absolute paths, and symlinks — whether the link is the final component or an intermediate directory — are all caught.

Globs use `**` to cross separators and `*` within a segment; `dir/**` also matches `dir` itself. Merging is designed so a caller can only narrow: deny lists union, allow lists apply as a conjunction (a value must satisfy the floor's set *and* the caller's), tool lists intersect, the stricter bash mode wins, and numeric caps take the minimum.

Two defaults are worth knowing. A call that supplies **no** `policy` keeps the server's historical behavior — all seven tools, unrestricted shell — still subject to the floor. A call that supplies a policy is fail-closed: bash is `off` and `confineToWorkspace` is `true` unless asked for otherwise.

## MCP tools

- `deepseek_chat` sends a normalized conversation to either `deepseek-v4-flash` or `deepseek-v4-pro`. The model is required on every call. Tool definitions are forwarded and returned tool calls are not executed by this server.
- `start_coding_task` starts an asynchronous Pi coding session in an approved workspace and returns a task ID.
- `send_coding_task_message` sends a new prompt to an idle task or queues `steer`/`follow_up` input while it is running.
- `get_coding_task_status` returns state, recent events, latest assistant text, and Pi usage/cost statistics.
- `get_coding_task_result` returns the same bounded transcript and final result, with `resultAvailable` indicating whether the task has settled.
- `await_coding_task` blocks until a task settles or the deadline elapses, then returns the final snapshot. A task that hits the deadline is cancelled rather than left running against the workspace.
- `cancel_coding_task` aborts an active task.
- `close_coding_task` disposes of a session and removes it from memory.

Coding sessions are resumable only while this server process is alive. Extensions discovered inside a workspace are not loaded, so a repository cannot register its own tools into a governed session.

## What the policy layer does not do

1. **Bash is the escape hatch.** `confineToWorkspace` constrains the file tools only. A shell command can `cd` elsewhere or use absolute paths, so path confinement is only meaningful when `bash.mode` is `off` or `allowlist`. Declaring `unrestricted` together with `confineToWorkspace: true` is rejected as a self-contradiction, and any merge that still produces that pairing is reported in `policy.advisories`.
2. **An allowlist is not a shell parser.** `^npm test` on its own would also admit `npm test & del .`, so allowlist mode rejects any command containing `; & | < > ` (backtick) `% ^`, `$(`, `${`, or a newline. Legitimate commands needing those characters cannot be allowlisted.
3. **An allowed command still runs arbitrary code.** `npm test` executes whatever the project's test script and its dependencies do. The gate controls *which command runs*, never *what that command then does*. A workspace whose scripts are untrusted defeats the bash allowlist by construction.
4. **`grep` and `find` patterns are confined but not content-filtered.** Their path arguments are checked, but a wildcard sweep inside the workspace can still read a file that `denyPaths` would block a direct `read` from. Omit those tools when read denial has to be airtight.
5. **This is a policy layer, not an OS sandbox.** Pi still runs with the server user's permissions. It stops a cooperative agent from wandering outside its brief; it is not a containment boundary against a hostile one. The floor binds the *calling agent*, not the machine's owner, who can always reconfigure and restart the server.

## Development

```powershell
npm run typecheck
npm run build
npm test
```