Metadata-Version: 2.4
Name: deepquery-sdk
Version: 1.1.0
Summary: Connector Development Kit for Deep Query — build MCP-emitting connectors with built-in read/action safety classification and provenance.
Project-URL: Homepage, https://github.com/GilbertAshivaka/deepquery-sdk
Project-URL: Repository, https://github.com/GilbertAshivaka/deepquery-sdk
Project-URL: Changelog, https://github.com/GilbertAshivaka/deepquery-sdk/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/GilbertAshivaka/deepquery-sdk/blob/main/README.md
Author-email: Gilbert Ashivaka <gilbertashivaka@gmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: connector,deepquery,mcp,model-context-protocol,rag
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.10
Requires-Dist: mcp<2,>=1.10
Requires-Dist: pydantic<3,>=2
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# DeepQuerySDK — Connector Development Kit

Build connectors for Deep Query. A connector you write with this SDK **emits a
standard [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server**
— so Deep Query consumes it through the exact same interface as any public MCP
server. You never touch JSON-RPC, transport, or schema plumbing.

> **Status: v1.1.0 — connectors can now emit all three MCP primitives.**
> Beyond tools (reads + gated actions), use `@mcp_resource` to expose
> URI-addressable **resources** (static or templated) and `@prompt` to offer
> **prompt** templates. See [CHANGELOG.md](CHANGELOG.md). Built on v1.0.0 —
> all four build phases below remain.
>
> **v1.0.0 — all four build phases complete.** Core contracts (Phase 1),
> the gated **preview → approve → execute / reject** lifecycle + OAuth 2.1 PKCE
> scaffolding + credential injection (Phase 2), the `deepquery` **CLI** and
> **mock-agent harness** with the §5/§6/§13 contracts encoded as `validate`
> checks (Phase 3), and the semver **compatibility contract** + packaging
> (Phase 4). The SDK contract is at **major version 1**: connectors declaring
> `sdk_major_version = 1` are loadable by this runtime.

## What you define

A connector is a subclass of `Connector` that declares three things:

| You define | What it is | Gated? |
|---|---|---|
| **Resources** | read-only data the agent can retrieve and **cite** | no |
| **Actions** | operations that change external state | yes (preview → execute) |
| **Auth** | how the connector authenticates to the external system | n/a |

Every read carries a **provenance envelope** so live data can be cited honestly.
Every action is tagged `dq.mutates: true` in the emitted MCP server so Deep
Query's approval gate knows it must be confirmed by a human.

## Install (development)

```powershell
# from the DeepQuerySDK/ folder, using the bundled venv
venv\Scripts\python.exe -m pip install -e .
```

## 60-second example

```python
from deepquery_sdk import Connector, OAuth2Auth, resource, action

class JiraConnector(Connector):
    name = "jira"
    version = "0.1.0"
    description = "Read and act on Jira issues."

    # Declare OAuth 2.1 with least-privilege scopes. The gateway runs/stores the
    # grant and injects the token; this connector never stores credentials.
    auth = OAuth2Auth(
        authorize_endpoint="https://auth.atlassian.com/authorize",
        token_endpoint="https://auth.atlassian.com/oauth/token",
        scopes=["read:jira-work", "write:jira-work"],
    )
    requires_network = True

    @resource(description="Search Jira issues by text query.",
              input_schema={"type": "object",
                            "properties": {"query": {"type": "string"}},
                            "required": ["query"]})
    def search_issues(self, query: str):
        # ... call the real Jira API here ...
        return [
            self.cite(
                {"key": "DQ-431", "summary": "Login flow broken", "status": "In Review"},
                source_object_id="DQ-431",
                title_or_label="DQ-431 — Login flow broken",
                deep_link="https://example.atlassian.net/browse/DQ-431",
                mutability_note="live status field",
            )
        ]

    @action(description="Create a new Jira issue.",
            input_schema={"type": "object",
                          "properties": {"project": {"type": "string"},
                                         "summary": {"type": "string"}},
                          "required": ["project", "summary"]})
    def create_issue(self, project: str, summary: str):
        # only ever called after the approval gate confirms the preview.
        auth = self.apply_auth()  # headers built from the injected credential
        return {"created": f"{project}-NEW", "summary": summary}

    @create_issue.preview
    def _(self, project: str, summary: str) -> str:
        return f"Will create a new issue in project '{project}' titled '{summary}'."
```

Emit and serve it as an MCP server (the gateway injects the credential):

```python
from deepquery_sdk import Credential, static_credential_provider
from deepquery_sdk.mcp_emit import run_stdio

connector = JiraConnector()
connector.set_credential_provider(static_credential_provider(Credential(token="...")))
run_stdio(connector)
```

## The gated action lifecycle

Calling an action does **not** execute it. It returns a preview and a single-use
approval token; the gateway drives the decision through two control tools:

```
call create_issue(args)        -> { status: "preview", approval_token, preview, arguments }
call dq.execute_action(token)  -> runs execute() for exactly those args  (after human approval)
call dq.reject_action(token)   -> discards the action; it never runs
```

The token binds the previewed arguments, so execute can never drift from what the
human approved, and a rejected/used token can never run.

## The `deepquery` CLI

```bash
deepquery scaffold acme-crm           # generate a new connector from the template
deepquery validate connector.py       # enforce the §5/§6/§13 safety contracts
deepquery manifest connector.py       # print/export the connector manifest
deepquery run-dev connector.py        # drive it interactively with the mock agent
deepquery emit connector.py --out dist/   # produce the deployable MCP server artifact
```

A target can be a `path/to/connector.py`, a directory containing `connector.py`,
or a `module.path:ClassName`. `validate` is the gatekeeper: it statically scans
`@resource` bodies for mutating calls and **fails a resource that writes** (a
misclassified action), plus checks descriptions, manifest, deployment honesty,
and least-privilege scopes.

The dev harness (`deepquery_sdk.harness.MockAgent`) discovers a connector over a
real in-memory MCP session and drives reads (inspecting provenance) and the full
preview → approve / reject action flow — the same sequence the real Agent Layer
uses.

## Try it without the test suite

```powershell
# read path + classification + provenance, over a real stdio MCP server
venv\Scripts\python.exe examples\manual_client.py

# the full preview -> approve -> execute and preview -> reject flow
venv\Scripts\python.exe examples\manual_action_flow.py
```

See [`examples/jira_connector/`](examples/jira_connector/) for the full runnable
connector used in the Phase 1 and Phase 2 tests.

## Versioning & compatibility

The SDK follows [SemVer](https://semver.org); see [CHANGELOG.md](CHANGELOG.md).
Every connector manifest declares the SDK **major** version it targets, and the
gateway calls `assert_compatible(manifest)` before loading — refusing a connector
built against an incompatible major with a clear error. Breaking changes ship
with a guide in [MIGRATIONS.md](MIGRATIONS.md).

## Release / publishing (maintainers)

Distributions are built with `python -m build` into `dist/`. To publish to PyPI:

```bash
python -m build                       # build sdist + wheel into dist/
python -m twine check dist/*          # validate metadata
python -m twine upload dist/*         # publish (requires PyPI credentials)
```

Tag the release `v<version>` and ensure `version` in `pyproject.toml` matches
`SDK_VERSION` (a test enforces this against the installed package).
