Reconcile assistant MCP with current schema and runner contracts

Regenerate migration 0294 and runner inventories after rebasing. Keep the new migration idempotent and document direct client setup. Workspace build, typechecks, focused MCP tests, and eval unit tests pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-01 10:12:09 -05:00
1 parent 8140626706
commit 114ff6ebdd
11 files changed
+1789 -53

No files matched your search

+32
View File
@@ -28,6 +28,38 @@ token again; normal API endpoints do not accept these OAuth tokens. Public
installation requires a reachable HTTPS deployment. Private self-hosted
instances require a directly reachable endpoint; no managed relay is included.
## Connect an assistant directly
After the instance is enabled and reachable, an existing team member can connect
without a store listing. For Codex CLI:
```sh
codex mcp add paperclip --url https://YOUR-PAPERCLIP-HOST/mcp/paperclip
codex mcp login paperclip --scopes paperclip:read,paperclip:write,offline_access
```
For Claude Code:
```sh
claude mcp add --transport http paperclip https://YOUR-PAPERCLIP-HOST/mcp/paperclip
```
Open Claude Code's `/mcp` menu and authenticate the Paperclip server. Both flows
open browser sign-in and consent: select a team and explicitly enable task and
comment writes when wanted. Read-only consent cannot delegate work.
Ask the assistant to identify the connected team and list its agents, then ask
it to delegate a small task to an available agent. The returned task link is the
durable reference. In a later conversation, ask for that task's progress and
report. Configure the agent's provider credentials, execution environment and
budget in Paperclip before expecting it to run; the assistant connection does
not supply them. Revocation is available at `/assistant-connections`.
The optional [workflow packages](../integrations/assistant-plugins/README.md)
teach team review, delegation and result retrieval. Build them for the same
endpoint before installing locally. Public ChatGPT/Codex and Claude directory
installation requires the separate deployment and submission work below.
## Identity and consent
- Protected-resource discovery: `/.well-known/oauth-protected-resource/mcp/paperclip`.
@@ -0,0 +1,110 @@
CREATE TABLE IF NOT EXISTS "mcp_mutation_receipts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"user_id" text NOT NULL,
"grant_id" uuid NOT NULL,
"operation" text NOT NULL,
"request_id" uuid NOT NULL,
"arguments_hash" text NOT NULL,
"status" text DEFAULT 'reserved' NOT NULL,
"result" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mcp_oauth_clients" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"redirect_uris" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mcp_oauth_grants" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"user_id" text NOT NULL,
"client_id" text NOT NULL,
"resource" text NOT NULL,
"scopes" jsonb NOT NULL,
"revoked_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mcp_oauth_requests" (
"id" text PRIMARY KEY NOT NULL,
"client_id" text NOT NULL,
"redirect_uri" text NOT NULL,
"resource" text NOT NULL,
"scopes" jsonb NOT NULL,
"state" text,
"challenge" text NOT NULL,
"grant_id" uuid,
"code_hash" text,
"decided_at" timestamp with time zone,
"consumed_at" timestamp with time zone,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "mcp_oauth_tokens" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"grant_id" uuid NOT NULL,
"token_hash" text NOT NULL,
"kind" text NOT NULL,
"used_at" timestamp with time zone,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_mutation_receipts_company_id_companies_id_fk' AND conrelid = 'public.mcp_mutation_receipts'::regclass) THEN
ALTER TABLE "mcp_mutation_receipts" ADD CONSTRAINT "mcp_mutation_receipts_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_mutation_receipts_user_id_user_id_fk' AND conrelid = 'public.mcp_mutation_receipts'::regclass) THEN
ALTER TABLE "mcp_mutation_receipts" ADD CONSTRAINT "mcp_mutation_receipts_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk' AND conrelid = 'public.mcp_mutation_receipts'::regclass) THEN
ALTER TABLE "mcp_mutation_receipts" ADD CONSTRAINT "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk" FOREIGN KEY ("grant_id") REFERENCES "public"."mcp_oauth_grants"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_grants_company_id_companies_id_fk' AND conrelid = 'public.mcp_oauth_grants'::regclass) THEN
ALTER TABLE "mcp_oauth_grants" ADD CONSTRAINT "mcp_oauth_grants_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_grants_user_id_user_id_fk' AND conrelid = 'public.mcp_oauth_grants'::regclass) THEN
ALTER TABLE "mcp_oauth_grants" ADD CONSTRAINT "mcp_oauth_grants_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk' AND conrelid = 'public.mcp_oauth_grants'::regclass) THEN
ALTER TABLE "mcp_oauth_grants" ADD CONSTRAINT "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."mcp_oauth_clients"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk' AND conrelid = 'public.mcp_oauth_requests'::regclass) THEN
ALTER TABLE "mcp_oauth_requests" ADD CONSTRAINT "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."mcp_oauth_clients"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk' AND conrelid = 'public.mcp_oauth_requests'::regclass) THEN
ALTER TABLE "mcp_oauth_requests" ADD CONSTRAINT "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk" FOREIGN KEY ("grant_id") REFERENCES "public"."mcp_oauth_grants"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk' AND conrelid = 'public.mcp_oauth_tokens'::regclass) THEN
ALTER TABLE "mcp_oauth_tokens" ADD CONSTRAINT "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk" FOREIGN KEY ("grant_id") REFERENCES "public"."mcp_oauth_grants"("id") ON DELETE cascade ON UPDATE no action;
END IF;
END $$;--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "mcp_mutation_receipts_request_uq" ON "mcp_mutation_receipts" USING btree ("company_id","user_id","operation","request_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "mcp_mutation_receipts_company_idx" ON "mcp_mutation_receipts" USING btree ("company_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "mcp_oauth_grants_user_company_idx" ON "mcp_oauth_grants" USING btree ("user_id","company_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "mcp_oauth_requests_code_uq" ON "mcp_oauth_requests" USING btree ("code_hash");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "mcp_oauth_requests_expiry_idx" ON "mcp_oauth_requests" USING btree ("expires_at");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "mcp_oauth_tokens_hash_uq" ON "mcp_oauth_tokens" USING btree ("token_hash");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "mcp_oauth_tokens_grant_idx" ON "mcp_oauth_tokens" USING btree ("grant_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "mcp_oauth_tokens_expiry_idx" ON "mcp_oauth_tokens" USING btree ("expires_at");
+7
View File
@@ -2045,6 +2045,13 @@
"when": 1790796608171,
"tag": "0293_broad_rattler",
"breakpoints": true
},
{
"idx": 294,
"version": "7",
"when": 1790866454887,
"tag": "0294_nervous_caretaker",
"breakpoints": true
}
]
}
@@ -8,9 +8,9 @@ The skill/reference inventory and eval cases are the only normative behavior sou
## Baseline Counts
- Skill/reference headings: 157
- Skill/reference headings: 158
- Eval cases: 106 across 16 groups
- Total normative rows: 263
- Total normative rows: 264
- Legacy MCP aliases folded into normative rows: 42
| Eval group | Cases |
@@ -67,11 +67,12 @@ The skill/reference inventory and eval cases are the only normative behavior sou
| skill:skills/paperclip/SKILL.md:critical-rules:585 | optional_agent_tool | skills/paperclip/SKILL.md:585 |
| skill:skills/paperclip/SKILL.md:comment-style-required:606 | always_agent_tool | skills/paperclip/SKILL.md:606 |
| skill:skills/paperclip/SKILL.md:update:638 | optional_agent_tool | skills/paperclip/SKILL.md:638 |
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:648 | optional_agent_tool | skills/paperclip/SKILL.md:648 |
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:681 | optional_agent_tool | skills/paperclip/SKILL.md:681 |
| skill:skills/paperclip/SKILL.md:searching-issues:710 | optional_agent_tool | skills/paperclip/SKILL.md:710 |
| skill:skills/paperclip/SKILL.md:full-reference:720 | optional_agent_tool | skills/paperclip/SKILL.md:720 |
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:724 | always_agent_tool | skills/paperclip/SKILL.md:724 |
| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:648 | always_agent_tool | skills/paperclip/SKILL.md:648 |
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:662 | optional_agent_tool | skills/paperclip/SKILL.md:662 |
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:695 | optional_agent_tool | skills/paperclip/SKILL.md:695 |
| skill:skills/paperclip/SKILL.md:searching-issues:724 | optional_agent_tool | skills/paperclip/SKILL.md:724 |
| skill:skills/paperclip/SKILL.md:full-reference:734 | optional_agent_tool | skills/paperclip/SKILL.md:734 |
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:738 | always_agent_tool | skills/paperclip/SKILL.md:738 |
| skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 |
| skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 |
| skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 |
@@ -238,43 +238,52 @@
{
"id": "skill:skills/paperclip/SKILL.md:648",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L648:planning-required-when-planning-requested",
"sourceAnchor": "skills/paperclip/SKILL.md#L648:task-documents-and-deliverables",
"heading": "Task Documents and Deliverables",
"primaryDisposition": "always_agent_tool",
"semanticOperation": "write_document",
"expectedMockState": "operation_result"
},
{
"id": "skill:skills/paperclip/SKILL.md:662",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L662:planning-required-when-planning-requested",
"heading": "Planning (Required when planning requested)",
"primaryDisposition": "always_agent_tool",
"semanticOperation": "write_document",
"expectedMockState": "operation_result"
},
{
"id": "skill:skills/paperclip/SKILL.md:681",
"id": "skill:skills/paperclip/SKILL.md:695",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L681:key-endpoints-hot-routes",
"sourceAnchor": "skills/paperclip/SKILL.md#L695:key-endpoints-hot-routes",
"heading": "Key Endpoints (Hot Routes)",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
},
{
"id": "skill:skills/paperclip/SKILL.md:710",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L710:searching-issues",
"heading": "Searching Issues",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
},
{
"id": "skill:skills/paperclip/SKILL.md:720",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L720:full-reference",
"heading": "Full Reference",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
},
{
"id": "skill:skills/paperclip/SKILL.md:724",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L724:conversational-confirmation-answers",
"sourceAnchor": "skills/paperclip/SKILL.md#L724:searching-issues",
"heading": "Searching Issues",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
},
{
"id": "skill:skills/paperclip/SKILL.md:734",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L734:full-reference",
"heading": "Full Reference",
"primaryDisposition": "control_plane_owned",
"semanticOperation": "runtime_reconciliation",
"expectedMockState": "runtime_decision_record"
},
{
"id": "skill:skills/paperclip/SKILL.md:738",
"kind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md#L738:conversational-confirmation-answers",
"heading": "Conversational confirmation answers",
"primaryDisposition": "always_agent_tool",
"semanticOperation": "call_api",
@@ -2,9 +2,9 @@
Generated by `scripts/generate-capability-contract.mjs`; do not edit generated files.
- Skill/reference headings: 158
- Skill/reference headings: 159
- Legacy MCP tools: 42
- Eval cases: 106 across 16 groups
- Deterministic content SHA-256: `1868c1fc065aa9c16a4ee97ab33fc75abb5d94a7bc49b95f768e1490c9cd0344`
- Deterministic content SHA-256: `eb214bdf3465ef2d1521eefedf0488212b7bcf1d7584152db1b3f1ed1ab231b7`
Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation.
@@ -42,7 +42,7 @@ function validInventories() {
schemaVersion: 2,
inventoryRole: "normative",
generatedFrom: ["skills/paperclip/SKILL.md"],
rows: Array.from({ length: 157 }, (_, index) => row(`capability-${index}`)),
rows: Array.from({ length: 158 }, (_, index) => row(`capability-${index}`)),
},
evaluations: {
schemaVersion: 2,
@@ -247,7 +247,7 @@ export async function buildMcpInventory(repoRoot) {
export function validateInventories(inventories) {
const errors = [];
const expectedCounts = { capabilities: 157, evaluations: 106, legacyMcpAliases: 42 };
const expectedCounts = { capabilities: 158, evaluations: 106, legacyMcpAliases: 42 };
const normativeNames = ["capabilities", "evaluations"];
const normativeRows = new Map();
const globalNormativeIds = new Set();
@@ -404,12 +404,12 @@
]
},
{
"id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:648",
"id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:648",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:648",
"title": "Planning (Required when planning requested)",
"expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.",
"primaryDisposition": "optional_agent_tool",
"title": "Task Documents and Deliverables",
"expectedSemantics": "Skill guidance headed “Task Documents and Deliverables”.",
"primaryDisposition": "always_agent_tool",
"requiredGrants": [],
"assertionClasses": [
"control_plane_invariant"
@@ -419,9 +419,24 @@
]
},
{
"id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:681",
"id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:662",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:681",
"sourceAnchor": "skills/paperclip/SKILL.md:662",
"title": "Planning (Required when planning requested)",
"expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.",
"primaryDisposition": "optional_agent_tool",
"requiredGrants": [],
"assertionClasses": [
"control_plane_invariant"
],
"evidenceIds": [
"skill:skills/paperclip/SKILL.md:662"
]
},
{
"id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:695",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:695",
"title": "Key Endpoints (Hot Routes)",
"expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.",
"primaryDisposition": "optional_agent_tool",
@@ -430,13 +445,13 @@
"control_plane_invariant"
],
"evidenceIds": [
"skill:skills/paperclip/SKILL.md:681"
"skill:skills/paperclip/SKILL.md:695"
]
},
{
"id": "skill:skills/paperclip/SKILL.md:searching-issues:710",
"id": "skill:skills/paperclip/SKILL.md:searching-issues:724",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:710",
"sourceAnchor": "skills/paperclip/SKILL.md:724",
"title": "Searching Issues",
"expectedSemantics": "Skill guidance headed “Searching Issues”.",
"primaryDisposition": "optional_agent_tool",
@@ -445,13 +460,13 @@
"control_plane_invariant"
],
"evidenceIds": [
"skill:skills/paperclip/SKILL.md:710"
"skill:skills/paperclip/SKILL.md:724"
]
},
{
"id": "skill:skills/paperclip/SKILL.md:full-reference:720",
"id": "skill:skills/paperclip/SKILL.md:full-reference:734",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:720",
"sourceAnchor": "skills/paperclip/SKILL.md:734",
"title": "Full Reference",
"expectedSemantics": "Skill guidance headed “Full Reference”.",
"primaryDisposition": "optional_agent_tool",
@@ -460,13 +475,13 @@
"control_plane_invariant"
],
"evidenceIds": [
"skill:skills/paperclip/SKILL.md:720"
"skill:skills/paperclip/SKILL.md:734"
]
},
{
"id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:724",
"id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:738",
"sourceKind": "skill_heading",
"sourceAnchor": "skills/paperclip/SKILL.md:724",
"sourceAnchor": "skills/paperclip/SKILL.md:738",
"title": "Conversational confirmation answers",
"expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.",
"primaryDisposition": "always_agent_tool",
@@ -475,7 +490,7 @@
"control_plane_invariant"
],
"evidenceIds": [
"skill:skills/paperclip/SKILL.md:724"
"skill:skills/paperclip/SKILL.md:738"
]
},
{
@@ -6,9 +6,9 @@ export type CapabilityPrimaryDisposition =
| "optional_agent_tool";
export const capabilityInventoryCounts = {
"skillReferenceCapabilities": 157,
"skillReferenceCapabilities": 158,
"evalCases": 106,
"normativeRows": 263,
"normativeRows": 264,
"legacyMcpAliases": 42
} as const;